1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-10-15 14:07:38 +00:00

Compare commits

...

62 Commits

Author SHA1 Message Date
Jonathan Coates
9cf0f85fcb Merge branch 'mc-1.20.x' into mc-1.21.x 2025-07-12 19:00:00 +01:00
Jonathan Coates
018ce7c8a5 Bump CC:T to 1.116.1 2025-07-12 18:57:05 +01:00
Jonathan Coates
44726827b4 Sync translations from Crowdin 2025-07-12 09:49:14 +01:00
Jonathan Coates
2bf0aba455 Don't use SpeakerPosition to get speaker's level
We use getLevel() specifically for reading the current registries
(and/or server). We don't need the exact position of the speaker to
query this, so add a dedicated method for it.

We actually had a similar method on 1.21.7 already for upgrades. This
just moves it to SpeakerPeripheral.

Fixes #2236.
2025-07-10 01:07:51 +01:00
Jonathan Coates
3cf914cb4c Fix NPE when loading mcfunctions
CommandSourceStack.getServer can be null, despite being marked as
non-nullable. Mojang!!! *shakes fist*

Fixes #2235.
2025-07-10 00:47:42 +01:00
Batári Balázs László
180156ff1c Add lang hu_hu (#2232) 2025-07-06 21:47:22 +00:00
Jonathan Coates
c6ba753568 Update allowed Minecraft versions 2025-07-06 22:34:43 +01:00
Jonathan Coates
9f45c91925 Update to latest TeaVM 2025-07-02 09:21:43 +01:00
Jonathan Coates
5fa724ed24 Move Create integration setup to its proper place
Merge conflict gone wrong, I assume.
2025-06-30 22:53:15 +01:00
Jonathan Coates
fbf994e803 Merge branch 'mc-1.20.x' into mc-1.21.x 2025-06-28 11:21:22 +01:00
Jonathan Coates
8344c0a5c2 Bump CC:T to 1.116.0 2025-06-28 11:03:52 +01:00
Jonathan Coates
a292d33830 Syntax highlighting for multiline tokens in edit
I don't love the implementation of this (see discussion in #2220), but
it's better than nothing. Wow, the editor really needs a bit of a
rewrite, the code is kinda messy.

Fixes #1396.
2025-06-25 22:50:23 +01:00
Jonathan Coates
341d1c7bc2 Move paint/edit menu bar into common module
I want to add a menu bar to the edit runner too, so let's make this code
a little more reusable first.
2025-06-25 22:14:09 +01:00
Jonathan Coates
531eacfac7 Merge pull request #2224 from matematikaadit/patch-1
Miniscule typo fix in the shell.path() doc comment
2025-06-21 16:59:01 +01:00
Adit Cahya Ramadhan
1f3da5205c Miniscule typo fix in the shell.path() doc comment
Noticed this when reading the shell API page in the wiki.
2025-06-21 22:29:04 +07:00
Jonathan Coates
798ceefafe Add test for crafting of disks
See #2221.
2025-06-18 21:00:32 +01:00
Jonathan Coates
7c0f79fc3c Move edit_runner into its own module
I've a few more features I'd like to add to it. Moving it out makes it
slightly easier to maintain.
2025-06-17 17:55:24 +01:00
Jonathan Coates
b35cefc5dd Switch from path to parentPath
I've been putting this off for a while, as I had issues in the past with
people using old Node versions (e.g. #1806), but it no long works on my
machine, so time to make the switch.

Also do a bit of a package update. Hit a rollup bug while doing this
(https://github.com/rollup/plugins/issues/1877), so holding that update
back for now.
2025-06-17 17:46:10 +01:00
Jonathan Coates
89dd521930 Merge branch 'mc-1.20.x' into mc-1.21.x 2025-06-15 16:31:51 +01:00
Jonathan Coates
9272e2efcd Use vanilla's nine-slice sprites for border rendering
- Move remaining sprites to the vanilla GUI atlas.

 - Convert our computer border/sidebar sprites to use vanilla's
   nine-sliced mcmeta files. I thought I'd have to do something custom
   here for the sidebar, as that has no right border, but vanilla
   supports that natively!

 - Use the normal GuiGraphics.blitSprite for rendering computer
   border/sidebar.

 - Obey nine-slice scaling within the pocket computer renderer.
2025-06-15 16:25:54 +01:00
Jonathan Coates
69353a4fcf Use lexer for edit's syntax highlighting
This is slightly more accurate for long strings and comments. Note that
we still work a line at a time (and in a non-incremental manner), so
doesn't actaully support multi-line strings (#1396).

We do now treat goto as a keyword (fixes #1653). We don't currently
support labels — those *technically* aren't a token (`:: foo --[[ a
comment ]] ::` is a valid label!), but maybe we could special-case the
short `::foo::` form.
2025-06-15 13:25:21 +01:00
Jonathan Coates
ff363dca5a Move sidebar_advanced.png down by one pixel
Apparently this has been broken since the file was created in
53546b9f57d9acaa4cdca14ae00eaf68ce8c50bd!? I'm sure I fixed this before,
but maybe that was a different but similar issue >_>.
2025-06-14 18:44:16 +01:00
LorneHyde
1c51282426 Fix syntax highlighting for strings ending in an escaped backslash (#2194) 2025-06-08 19:55:14 +00:00
Jonathan Coates
4a3a1c9275 Merge pull request #2214 from Wojbie/patch-1
Update motd path in startup.lua
2025-06-03 08:08:30 +01:00
Wojbie
2557dd0af9 Update motd path in startup.lua
Removes situations where shell resolution caused arbitrary program called `motd` at root get executed instead of expected /rom one.
2025-06-03 01:03:02 +02:00
Jonathan Coates
b5c0c6e104 Fix out-of-bounds when pasting too-long text
Used a `<=` instead of a `<`! How did I mess this up!?

Fixes #2209
2025-06-02 08:58:43 +01:00
SpartanSoftware
876fd8ddb8 Fix 0 being treated as a valid colour (#2211) 2025-05-31 07:46:03 +00:00
Jonathan Coates
ee3b1343b5 Handle keyboard layouts for our computer shortcuts
Convert GLFW's key codes back to their actual key, and then use that
when checking keyboard shortcuts. We *don't* do this for the paste key,
just to be consistent with vanilla's behaviour.

Fixes #2207.
2025-05-25 22:32:39 +01:00
JackMacWindows
b440b964b7 Add notes about minor changed file handle behavior in 1.109.0 (#2203) 2025-05-25 20:24:26 +00:00
Jonathan Coates
5dfc401b45 Update build plugin versions 2025-05-18 10:05:27 +01:00
Jonathan Coates
418c9be7ac Allow changing terminal size with components
We add a new computercraft:term_size component that allows changing the
terminal size of computers and pocket computers.
2025-05-16 18:22:20 +01:00
Jonathan Coates
b491f6b11f Merge branch 'mc-1.20.x' into mc-1.21.x 2025-05-16 17:56:51 +01:00
Jonathan Coates
4344c3072f Add a test for turtle upgrade crafting
Every few years I get confused about which side turtle upgrades go on
when crafting. The fact that it's flipped always throws me! Let's add a
comment to the recipe, and add some tests to reassure myself.
2025-05-10 14:39:51 +01:00
Jonathan Coates
947001104d Update Cobalt to 0.9.6
- Allow heterogenous __lt/__le.
2025-04-21 14:36:44 +01:00
Jonathan Coates
8711512769 Remove allocation tracking for computers
Reverts 76968f2f28. We'd originally added
this to gather some numbers for #1580, with the hope that it would also
be useful for server admins. Sadly, it's not as accurate as I originally
hoped — the number sometimes goes down for unclear reasons (something to
do with the TLAB maybe??).

Closes #1739.
2025-04-21 08:32:03 +01:00
Jonathan Coates
fdae94b3c1 Merge branch 'mc-1.20.x' into mc-1.21.x 2025-03-25 08:44:27 +00:00
Jonathan Coates
9c0ce27ce6 Switch a few more places to use Java 17 features
New ErrorProne hint, and one which is actually pretty useful!
2025-03-22 09:39:47 +00:00
Jonathan Coates
c458360b18 Bump versions of build tooling
The main thing of note is Spotless, which also bumps the version of
Ktlint. I've been putting this off for a while[^1], as this changed a
bunch of formatting, and Spotless's (broken) caching was making it hard
to test. Ended up downloading ktlint and running it localy.

[^1]: 8204944b5f
2025-03-21 14:28:31 +00:00
Jonathan Coates
09ad6c1905 Add tags for disks and floppies
Fixes #2158
2025-03-20 19:16:16 +00:00
Jonathan Coates
0e1e8a72d3 Fix syncing of colour between PocketBrain and item
- Actually set colour when constructing the brain.
 - Sync it back after crafting, much like we do for upgrades (see
   dcc74e15c7) for more details.

We should take a proper look at this on 1.21.4 and make these methods
main-thread only, so we can sync immediately.

Fixes #2157
2025-03-20 18:54:06 +00:00
Jonathan Coates
ffa6eadc26 Register our BEs as entities
Fixes #2141. Hah, I wrote some tests for this in
b03546a158, but they pass because hoppers
still support vanilla inventories, but turtles don't.

Wish NeoForge registered a fallback for any inventory, like Fabric does,
but there we go.
2025-03-16 16:37:29 +00:00
Jonathan Coates
7c1e8e1951 Fix usages of javax's Nullable annotation 2025-03-16 16:29:19 +00:00
Jonathan Coates
b805a34c2d Merge branch 'mc-1.20.x' into mc-1.21.x 2025-03-16 16:28:53 +00:00
Jonathan Coates
b03546a158 Add game tests to check our blocks are inventories
See #2141
2025-03-16 15:59:43 +00:00
Jonathan Coates
582713467f Remove air blocks from test structures
This is a bit nasty, but makes the structure files *significtly* smaller
(1/4 the size), so feels worth doing.
2025-03-16 15:59:43 +00:00
Jonathan Coates
b6f41a0df5 Fix several issues with char/paste event validation
- Fix isValidClipboard always returning true.
 - Fix characters >=128 being rejected. We changed the signature from a
   byte to an int in 0f123b5efd, but
   didn't update all call sites.

   Valhalla cannot come soon enough. I would love to be able to have
   (cheap) wrapper classes for some of these types.

See Zeus-guy's comments in #860.
2025-03-16 14:07:15 +00:00
Jonathan Coates
594738a022 Standardised item details docs a little
Sort of closes #2125. I've really struggled to find a way to make it
clear that the information returned here is a snapshot of the current
item, and not a live view and/or proxy. Most wordings I've tried end up
feeling really clunky — given that this is a relatively rare
misunderstanding, let's not stress about this too much.
2025-03-16 10:25:57 +00:00
Jonathan Coates
27f2ab364c Clean up disk <-> drive right clicking
Oh dear. I'd originally set out to *remove* logic from DiskItem — we're
so close to being able to remove this item in 1.21! However, while
looking at this code, I realised I could remove the whole Forge-specific
doesSneakBypassUse.

We now remove the use hook on the block, and override useOn on the item.
Obvious in retrospect!
2025-03-15 12:28:29 +00:00
Jonathan Coates
5a43273757 docs: specify valid types for settings.define (#2140) 2025-03-13 07:10:30 +00:00
Drew Edwards
97e28516fb docs: specify valid types for settings.define 2025-03-13 01:40:08 +00:00
Jonathan Coates
676fb5fb53 Remove usages of onNeighborChange
Oh. This is from ye olde days (it's one of the first PRs to CC[^1]!). In
pre-1.13 days, furnaces changing their lit state would replace the block
(creating a new BE) and then set back the old BE. CC wouldn't pick up
the second event, and so would continue to use the peripheral from the
first.

We don't really need this any more, for a couple of reasons:
 a) Furnaces don't do this any more.

 b) Peripherals are now refreshed on the next tick rather than
    immediately.

 c) Forge's capabilities have an explicit invalidate() hook already. This
    technically only detects *removing* block entities, but I'm not sure
    there's any cases where you add a block entity without also
    triggering a block state change.

Ironically, the place we probably need this more is Fabric, where the
lookup API doesn't have a public invalidate hook (it's hidden away in
the BlockApiCache). I'm mostly relying on c) here, in that we just won't
see this happen in practice.

[^1]: https://github.com/dan200/ComputerCraft/pull/180
2025-03-09 15:22:49 +00:00
Jonathan Coates
08dc08b5a3 Replace appendHoverText with component-based tooltips
We have several items (e.g. ComputerItem), which only exist for their
custom tooltip implementation. We remove these, and replace them
vanilla-style component-based tooltips (TooltipProvider).

The implementation here is a little janky — as the vanilla list of
components is hard-coded, and neither mod loader offers a way to extend
it. For now we just use the generic mod-loader tooltip hook — this
probably would be easier with a mixin, but let's do things Properly.

It would be nice to fully remove DiskItem (we only keep this around for
doesSneakBypassUse), but that can be a future task.
2025-03-09 13:39:45 +00:00
Jonathan Coates
8f4d4038f6 Merge branch 'mc-1.20.x' into mc-1.21.x 2025-03-09 12:35:44 +00:00
Jonathan Coates
63ba3fe274 Fix printout crafting
Introduced by the previous commit — I'd made one of the checks too lax.
Add some tests for this, so it doesn't happen again, though this code
does get a complete rewrite in 1.21 anyway >_>.
2025-03-09 12:19:59 +00:00
Jonathan Coates
749b3df227 Remove PrintoutItem.getType
Kinda surprised this is still around! Not sure why I kept it post
the-flattening really, it's been redundant for a while.
2025-03-09 11:46:36 +00:00
Jonathan Coates
b97634b717 Flesh out LuaTable a bit
Add a whole buncha helper methods for parsing values, much like
IArguments. This allows us to remove TableHelper. Gosh, that dates back
to 2018!
2025-03-08 23:39:11 +00:00
Jonathan Coates
1b8344d0a3 Ignore shader loading errors
Another go at fixing #2127.

In a892739f8e we set the precision on the
Tbo uniform. However, this is stripped in the shader pre-processing
Pojav/gl4es does, and so has no effect. As a (terrible) workaround, we
now just ignore shader loading errors. This probably does leak memory
(we'll never clean up the program), but there's not much we can do about
that.
2025-03-05 19:01:32 +00:00
Jonathan Coates
b42bc0a01a Bump Loom and vanilla-extract versions 2025-03-05 18:49:03 +00:00
Jonathan Coates
70a7478529 Ignore some components when sending item to client
We send the item-form of the current computer in the computer menu data.
However, this leaks the current LockCode, as we include all components.
We now only gather a safe subset of components when constructing the
item.
2025-03-05 18:20:05 +00:00
Jonathan Coates
0cff73e2fc Add turtle.getEquipped{Left,Right}
These just return details about the currently equipped *item*. This
allows us to expose information about the currently equipped upgrade,
without having to invent a whole new format.

Docs are a bit consise, but didn't really know how to flesh them out any
further.

Fixes #964, fixes #1613, closes #1692.
2025-03-03 21:30:19 +00:00
Jonathan Coates
a892739f8e Specify precision in monitor fragment shader
Some people run Minecraft on OpenGL ES GPUs via the gl4es translation
bridge. This sets the default precision for floats and ints, but not
usamplerBuffer.

Using lowp should be fine here (we don't need to encode much info!), but
we use mediump just in case. Have run this through the Mali Offline
compiler, and it seems fine with it.

Fixes #2127.
2025-03-03 10:13:26 +00:00
Jonathan Coates
f8785a092f Fix particle texture for turtle colour model
This is never actually used in practice, but let's avoid any missing
texture reference warnings! Embarrassing that I hadn't noticed this
before!
2025-03-02 21:14:27 +00:00
283 changed files with 3680 additions and 11344 deletions

View File

@@ -27,7 +27,7 @@ indent_size = 2
[*.yml]
indent_size = 2
[{*.kt,*.kts}]
[*.{kt,kts}]
ij_kotlin_code_style_defaults = KOTLIN_OFFICIAL
ij_kotlin_continuation_indent_size = 4
ij_kotlin_spaces_around_equality_operators = true
@@ -39,3 +39,14 @@ ij_kotlin_allow_trailing_comma_on_call_site = true
ij_kotlin_method_parameters_wrap = off
ij_kotlin_call_parameters_wrap = off
ij_kotlin_extends_list_wrap = off
ktlint_code_style = intellij_idea
ktlint_standard_class-naming = disabled
ktlint_standard_class-signature = disabled
ktlint_standard_function-naming = disabled
ktlint_standard_no-wildcard-imports = disabled
# FIXME: These two are disable right now as they're over-eager in putting things
# on the same line. We should set max_line_length and handle this properly.
ktlint_standard_function-signature = disabled
ktlint_standard_function-expression-body = disabled

View File

@@ -11,7 +11,8 @@ body:
What version of Minecraft are you using? If your version is not listed, please try to reproduce on one of the supported versions.
options:
- 1.20.1
- 1.21.x
- 1.21.1
- 1.21.7
validations:
required: true
- type: input

View File

@@ -88,8 +88,8 @@ You'll first need to [set up a development environment as above](#setting-up-a-d
Once this is set up, you can now run `./gradlew docWebsite`. This generates documentation from our Lua and Java code,
writing the resulting HTML into `./projects/web/build/site`, which can then be opened in a browser. When iterating on
documentation, you can instead run `./gradlew docWebsite -t`, which will rebuild documentation every time you change a
file.
documentation, you can instead run `./gradlew :web:assemble -x :web:compileTeaVM -t`, which will rebuild documentation
every time you change a file.
Documentation is built using [illuaminate] which, while not currently documented (somewhat ironic), is largely the same
as [ldoc][ldoc]. Documentation comments are written in Markdown, though note that we do not support many GitHub-specific

View File

@@ -6,7 +6,6 @@
import cc.tweaked.gradle.CCTweakedExtension
import cc.tweaked.gradle.CCTweakedPlugin
import cc.tweaked.gradle.IdeaRunConfigurations
import cc.tweaked.gradle.MinecraftConfigurations
plugins {

View File

@@ -29,7 +29,7 @@ base.archivesName.convention("cc-tweaked-$mcVersion-${project.name}")
java {
toolchain {
languageVersion= CCTweakedPlugin.JAVA_VERSION
languageVersion = CCTweakedPlugin.JAVA_VERSION
}
withSourcesJar()
@@ -93,6 +93,7 @@ sourceSets.all {
check("InvalidBlockTag", CheckSeverity.OFF) // Broken by @cc.xyz
check("InlineMeSuggester", CheckSeverity.OFF) // Minecraft uses @Deprecated liberally
// Too many false positives right now. Maybe we need an indirection for it later on.
check("AssignmentExpression", CheckSeverity.OFF) // I'm a bad person.
check("ReferenceEquality", CheckSeverity.OFF)
check("EnumOrdinal", CheckSeverity.OFF) // For now. We could replace most of these with EnumMap.
check("OperatorPrecedence", CheckSeverity.OFF) // For now.
@@ -121,7 +122,6 @@ tasks.compileTestJava {
}
}
tasks.withType(JavaCompile::class.java).configureEach {
options.encoding = "UTF-8"
}
@@ -170,7 +170,7 @@ tasks.test {
tasks.withType(JacocoReport::class.java).configureEach {
reports.xml.required = true
reports.html.required =true
reports.html.required = true
}
project.plugins.withType(CCTweakedPlugin::class.java) {
@@ -194,30 +194,23 @@ spotless {
fun FormatExtension.defaults() {
endWithNewline()
trimTrailingWhitespace()
indentWithSpaces(4)
leadingTabsToSpaces(4)
}
java {
defaults()
importOrder("", "javax|java", "\\#")
removeUnusedImports()
}
val ktlintConfig = mapOf(
"ktlint_standard_no-wildcard-imports" to "disabled",
"ktlint_standard_class-naming" to "disabled",
"ktlint_standard_function-naming" to "disabled",
"ij_kotlin_allow_trailing_comma" to "true",
"ij_kotlin_allow_trailing_comma_on_call_site" to "true",
)
kotlinGradle {
defaults()
ktlint().editorConfigOverride(ktlintConfig)
ktlint()
}
kotlin {
defaults()
ktlint().editorConfigOverride(ktlintConfig)
ktlint()
}
}

View File

@@ -5,10 +5,8 @@
package cc.tweaked.gradle
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.AbstractExecTask
import org.gradle.api.tasks.OutputDirectory
import java.io.File
abstract class ExecToDir : AbstractExecTask<ExecToDir>(ExecToDir::class.java) {
@get:OutputDirectory

View File

@@ -25,7 +25,6 @@ import javax.xml.xpath.XPathFactory
* Would be good to PR some (or all) of these changes upstream at some point.
*
* @see net.fabricmc.loom.configuration.ide.idea.IdeaSyncTask
* @see net.minecraftforge.gradle.common.util.runs.IntellijRunGenerator
*/
internal class IdeaRunConfigurations(project: Project) {
private val rootProject = project.rootProject
@@ -35,22 +34,6 @@ internal class IdeaRunConfigurations(project: Project) {
private val writer = TransformerFactory.newInstance().newTransformer()
private val ideaDir = rootProject.file(".idea/")
private val buildDir: Lazy<String?> = lazy {
val ideaMisc = ideaDir.resolve("misc.xml")
try {
val doc = Files.newBufferedReader(ideaMisc.toPath()).use {
documentBuilder.parse(InputSource(it))
}
val node =
xpath.evaluate("//component[@name=\"ProjectRootManager\"]/output", doc, XPathConstants.NODE) as Node
val attr = node.attributes.getNamedItem("url") as Attr
attr.value.removePrefix("file://")
} catch (e: Exception) {
LOGGER.error("Failed to find root directory", e)
null
}
}
fun patch() = synchronized(LOCK) {
val runConfigDir = ideaDir.resolve("runConfigurations")
@@ -58,10 +41,9 @@ internal class IdeaRunConfigurations(project: Project) {
Files.list(runConfigDir.toPath()).use {
for (configuration in it) {
val filename = configuration.fileName.toString();
val filename = configuration.fileName.toString()
when {
filename.endsWith("_fabric.xml") -> patchFabric(configuration)
filename.startsWith("forge_") && filename.endsWith(".xml") -> patchForge(configuration)
else -> {}
}
}
@@ -72,65 +54,6 @@ internal class IdeaRunConfigurations(project: Project) {
setXml("//configuration", "folderName") { "Fabric" }
}
private fun patchForge(path: Path) = withXml(path) {
val configId = path.fileName.toString().removePrefix("forge_").removeSuffix(".xml")
val sourceSet = forgeConfigs[configId]
if (sourceSet == null) {
LOGGER.error("[{}] Cannot map run configuration to a known source set", path)
return@withXml
}
setXml("//configuration", "folderName") { "Forge" }
setXml("//configuration/module", "name") { "${rootProject.name}.forge.$sourceSet" }
if (buildDir.value == null) return@withXml
setXml("//configuration/envs/env[@name=\"MOD_CLASSES\"]", "value") { classpath ->
val classes = classpath!!.split(':')
val newClasses = mutableListOf<String>()
fun appendUnique(x: String) {
if (!newClasses.contains(x)) newClasses.add(x)
}
for (entry in classes) {
if (!entry.contains("/out/")) {
appendUnique(entry)
continue
}
val match = CLASSPATH_ENTRY.matchEntire(entry)
if (match != null) {
val modId = match.groups["modId"]!!.value
val proj = match.groups["proj"]!!.value
var component = match.groups["component"]!!.value
if (component == "production") component = "main"
appendUnique(forgeModEntry(modId, proj, component))
} else {
LOGGER.warn("[{}] Unknown classpath entry {}", path, entry)
appendUnique(entry)
}
}
// Ensure common code is on the classpath
for (proj in listOf("common", "common-api")) {
for (component in listOf("main", "client")) {
appendUnique(forgeModEntry("computercraft", proj, component))
}
}
if (newClasses.any { it.startsWith("cctest%%") }) {
appendUnique(forgeModEntry("cctest", "core", "testFixtures"))
appendUnique(forgeModEntry("cctest", "common", "testFixtures"))
appendUnique(forgeModEntry("cctest", "common", "testMod"))
}
newClasses.joinToString(":")
}
}
private fun forgeModEntry(mod: String, project: String, component: String) =
"$mod%%${buildDir.value}/production/${rootProject.name}.$project.$component"
private fun LocatedDocument.setXml(xpath: String, attribute: String, value: (String?) -> String) {
val node = this@IdeaRunConfigurations.xpath.evaluate(xpath, document, XPathConstants.NODE) as Node?
if (node == null) {
@@ -159,16 +82,5 @@ internal class IdeaRunConfigurations(project: Project) {
companion object {
private val LOGGER = Logging.getLogger(IdeaRunConfigurations::class.java)
private val LOCK = Any()
private val CLASSPATH_ENTRY =
Regex("(?<modId>[a-z]+)%%\\\$PROJECT_DIR\\\$/projects/(?<proj>[a-z-]+)/out/(?<component>\\w+)/(?<type>[a-z]+)\$")
private val forgeConfigs = mapOf(
"runClient" to "client",
"runData" to "main",
"runGameTestServer" to "testMod",
"runServer" to "main",
"runTestClient" to "testMod",
)
}
}

View File

@@ -1,16 +0,0 @@
// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package cc.tweaked.gradle
import groovy.util.Node
import groovy.util.NodeList
object XmlUtil {
fun findChild(node: Node, name: String): Node? = when (val child = node.get(name)) {
is Node -> child
is NodeList -> child.singleOrNull() as Node?
else -> null
}
}

View File

@@ -12,6 +12,7 @@ files:
de: de_de # German
es-ES: es_es # Spanish
fr: fr_fr # French
hu: hu_hu # Hungarian
it: it_it # Italian
ja: ja_jp # Japanese
ko: ko_kr # Korean

View File

@@ -12,7 +12,7 @@ neogradle.subsystems.conventions.runs.enabled=false
# Mod properties
isUnstable=true
modVersion=1.115.1
modVersion=1.116.1
# Minecraft properties: We want to configure this here so we can read it in settings.gradle
mcVersion=1.21.1

View File

@@ -10,7 +10,7 @@
fabric-api = "0.102.1+1.21.1"
fabric-loader = "0.15.11"
neoForge = "21.1.9"
neoForgeSpi = "8.0.1"
neoMergeTool = "2.0.0"
mixin = "0.8.5"
parchment = "2024.07.28"
parchmentMc = "1.21"
@@ -26,7 +26,7 @@ slf4j = "2.0.9"
asm = "9.6"
autoService = "1.1.1"
checkerFramework = "3.42.0"
cobalt = { strictly = "0.9.5" }
cobalt = { strictly = "0.9.6" }
commonsCli = "1.6.0"
jetbrainsAnnotations = "24.1.0"
jspecify = "1.0.0"
@@ -58,24 +58,24 @@ junitPlatform = "1.11.4"
jmh = "1.37"
# Build tools
cctJavadoc = "1.8.3"
checkstyle = "10.21.2"
errorProne-core = "2.36.0"
cctJavadoc = "1.8.4"
checkstyle = "10.23.1"
errorProne-core = "2.38.0"
errorProne-plugin = "4.1.0"
fabric-loom = "1.9.2"
fabric-loom = "1.10.4"
githubRelease = "2.5.2"
gradleVersions = "0.50.0"
ideaExt = "1.1.7"
illuaminate = "0.1.0-74-gf1551d5"
illuaminate = "0.1.0-83-g1131f68"
lwjgl = "3.3.3"
minotaur = "2.8.7"
modDevGradle = "2.0.74"
nullAway = "0.12.3"
modDevGradle = "2.0.95"
nullAway = "0.12.7"
shadow = "8.3.1"
spotless = "6.23.3"
spotless = "7.0.2"
taskTree = "2.1.1"
teavm = "0.11.0-SQUID.1"
vanillaExtract = "0.2.0"
teavm = "0.13.0-SQUID.1"
vanillaExtract = "0.2.1"
versionCatalogUpdate = "0.8.1"
[libraries]
@@ -87,7 +87,7 @@ checkerFramework = { module = "org.checkerframework:checker-qual", version.ref =
cobalt = { module = "cc.tweaked:cobalt", version.ref = "cobalt" }
commonsCli = { module = "commons-cli:commons-cli", version.ref = "commonsCli" }
fastutil = { module = "it.unimi.dsi:fastutil", version.ref = "fastutil" }
neoForgeSpi = { module = "net.neoforged:neoforgespi", version.ref = "neoForgeSpi" }
neoMergeTool = { module = "net.neoforged:mergetool", version.ref = "neoMergeTool" }
guava = { module = "com.google.guava:guava", version.ref = "guava" }
jetbrainsAnnotations = { module = "org.jetbrains:annotations", version.ref = "jetbrainsAnnotations" }
jspecify = { module = "org.jspecify:jspecify", version.ref = "jspecify" }

960
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -12,11 +12,11 @@
"tslib": "^2.0.3"
},
"devDependencies": {
"@rollup/plugin-node-resolve": "^15.2.1",
"@rollup/plugin-node-resolve": "^16.0.0",
"@rollup/plugin-typescript": "^12.0.0",
"@rollup/plugin-url": "^8.0.1",
"@swc/core": "^1.3.92",
"@types/node": "^22.0.0",
"@types/node": "^24.0.0",
"lightningcss": "^1.22.0",
"preact-render-to-string": "^6.2.1",
"rehype": "^13.0.0",

View File

@@ -28,6 +28,20 @@ public class ComputerCraftTags {
public static final TagKey<Item> WIRED_MODEM = make("wired_modem");
public static final TagKey<Item> MONITOR = make("monitor");
/**
* Floppy disks. Both the read/write version, and treasure disks.
*
* @since 1.116.0
*/
public static final TagKey<Item> DISKS = make("disks");
/**
* All pocket computers.
*
* @since 1.116.0
*/
public static final TagKey<Item> POCKET_COMPUTERS = make("pocket_computers");
/**
* Items which can be {@linkplain Item#use(Level, Player, InteractionHand) used} when calling
* {@code turtle.place()}.

View File

@@ -51,10 +51,10 @@ public abstract class ComponentDetailProvider<T> implements DetailProvider<DataC
* This method is always called on the server thread, so it is safe to interact with the world here, but you should
* take care to avoid long blocking operations as this will stall the server and other computers.
*
* @param data The full details to be returned for this item stack. New properties should be added to this map.
* @param item The component to provide details for.
* @param data The full details to be returned for this item stack. New properties should be added to this map.
* @param component The component to provide details for.
*/
public abstract void provideComponentDetails(Map<? super String, Object> data, T item);
public abstract void provideComponentDetails(Map<? super String, Object> data, T component);
@Override
public final void provideDetails(Map<? super String, Object> data, DataComponentHolder holder) {

View File

@@ -25,7 +25,6 @@ import dan200.computercraft.shared.command.CommandComputerCraft;
import dan200.computercraft.shared.computer.core.ComputerState;
import dan200.computercraft.shared.computer.core.ServerContext;
import dan200.computercraft.shared.computer.inventory.AbstractComputerMenu;
import dan200.computercraft.shared.media.items.DiskItem;
import dan200.computercraft.shared.turtle.TurtleOverlay;
import net.minecraft.Util;
import net.minecraft.client.Minecraft;
@@ -41,7 +40,6 @@ import net.minecraft.client.renderer.item.ClampedItemPropertyFunction;
import net.minecraft.client.renderer.item.ItemProperties;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.PreparableReloadListener;
import net.minecraft.server.packs.resources.ResourceProvider;
import net.minecraft.util.FastColor;
import net.minecraft.world.entity.LivingEntity;
@@ -52,6 +50,8 @@ import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.component.DyedItemColor;
import net.minecraft.world.level.ItemLike;
import org.jspecify.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
@@ -69,6 +69,8 @@ import java.util.function.Supplier;
* @see ModRegistry The common registry for actual game objects.
*/
public final class ClientRegistry {
private static final Logger LOG = LoggerFactory.getLogger(ClientRegistry.class);
private ClientRegistry() {
}
@@ -143,10 +145,6 @@ public final class ClientRegistry {
void register(Item item, ResourceLocation name, ClampedItemPropertyFunction property);
}
public static void registerReloadListeners(Consumer<PreparableReloadListener> register, Minecraft minecraft) {
register.accept(GuiSprites.initialise(minecraft.getTextureManager()));
}
private static final ResourceLocation[] EXTRA_MODELS = {
TurtleOverlay.ELF_MODEL,
TurtleBlockEntityRenderer.COLOUR_TURTLE_MODEL,
@@ -160,7 +158,7 @@ public final class ClientRegistry {
public static void registerItemColours(BiConsumer<ItemColor, ItemLike> register) {
register.accept(
(stack, layer) -> layer == 1 ? DiskItem.getColour(stack) : -1,
(stack, layer) -> layer == 1 ? DyedItemColor.getOrDefault(stack, Colour.WHITE.getARGB()) : -1,
ModRegistry.Items.DISK.get()
);
@@ -192,7 +190,18 @@ public final class ClientRegistry {
}
public static void registerShaders(ResourceProvider resources, BiConsumer<ShaderInstance, Consumer<ShaderInstance>> load) throws IOException {
RenderTypes.registerShaders(resources, load);
RenderTypes.registerShaders(resources, (name, create, onLoaded) -> {
ShaderInstance shader;
try {
shader = create.get();
} catch (Exception e) {
LOG.error("Failed to load {}", name, e);
onLoaded.accept(null);
return;
}
load.accept(shader, onLoaded);
});
}
private record UnclampedPropertyFunction(

View File

@@ -6,9 +6,7 @@ package dan200.computercraft.client.gui;
import dan200.computercraft.client.gui.widgets.ComputerSidebar;
import dan200.computercraft.client.gui.widgets.TerminalWidget;
import dan200.computercraft.client.render.ComputerBorderRenderer;
import dan200.computercraft.client.render.RenderTypes;
import dan200.computercraft.client.render.SpriteRenderer;
import dan200.computercraft.core.util.Nullability;
import dan200.computercraft.shared.computer.inventory.AbstractComputerMenu;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.network.chat.Component;
@@ -40,14 +38,15 @@ public final class ComputerScreen<T extends AbstractComputerMenu> extends Abstra
public void renderBg(GuiGraphics graphics, float partialTicks, int mouseX, int mouseY) {
// Draw a border around the terminal
var terminal = getTerminal();
var spriteRenderer = SpriteRenderer.createForGui(graphics, RenderTypes.GUI_SPRITES);
var computerTextures = GuiSprites.getComputerTextures(family);
ComputerBorderRenderer.render(
spriteRenderer, computerTextures,
terminal.getX(), terminal.getY(), terminal.getWidth(), terminal.getHeight(), false
graphics.blitSprite(
computerTextures.border(),
terminal.getX() - BORDER, terminal.getY() - BORDER, terminal.getWidth() + BORDER * 2, terminal.getHeight() + BORDER * 2
);
graphics.blitSprite(
Nullability.assertNonNull(computerTextures.sidebar()),
leftPos, topPos + sidebarYOffset, AbstractComputerMenu.SIDEBAR_WIDTH, ComputerSidebar.HEIGHT
);
ComputerSidebar.renderBackground(spriteRenderer, computerTextures, leftPos, topPos + sidebarYOffset);
graphics.flush(); // Flush to ensure background textures are drawn before foreground.
}
}

View File

@@ -7,9 +7,6 @@ package dan200.computercraft.client.gui;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.client.render.ComputerBorderRenderer;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.renderer.texture.TextureManager;
import net.minecraft.client.resources.TextureAtlasHolder;
import net.minecraft.resources.ResourceLocation;
import org.jspecify.annotations.Nullable;
@@ -19,10 +16,7 @@ import java.util.stream.Stream;
/**
* Sprite sheet for all GUI texutres in the mod.
*/
public final class GuiSprites extends TextureAtlasHolder {
public static final ResourceLocation SPRITE_SHEET = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "gui");
public static final ResourceLocation TEXTURE = SPRITE_SHEET.withPath(x -> "textures/atlas/" + x + ".png");
public final class GuiSprites {
public static final ButtonTextures TURNED_OFF = button("turned_off");
public static final ButtonTextures TURNED_ON = button("turned_on");
public static final ButtonTextures TERMINATE = button("terminate");
@@ -32,6 +26,9 @@ public final class GuiSprites extends TextureAtlasHolder {
public static final ComputerTextures COMPUTER_COMMAND = computer("command", false, true);
public static final ComputerTextures COMPUTER_COLOUR = computer("colour", true, false);
private GuiSprites() {
}
private static ButtonTextures button(String name) {
return new ButtonTextures(
ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "buttons/" + name),
@@ -47,34 +44,6 @@ public final class GuiSprites extends TextureAtlasHolder {
);
}
private static @Nullable GuiSprites instance;
private GuiSprites(TextureManager textureManager) {
super(textureManager, TEXTURE, SPRITE_SHEET);
}
/**
* Initialise the singleton {@link GuiSprites} instance.
*
* @param textureManager The current texture manager.
* @return The singleton {@link GuiSprites} instance, to register as resource reload listener.
*/
public static GuiSprites initialise(TextureManager textureManager) {
if (instance != null) throw new IllegalStateException("GuiSprites has already been initialised");
return instance = new GuiSprites(textureManager);
}
/**
* Lookup a texture on the atlas.
*
* @param texture The texture to find.
* @return The sprite on the atlas.
*/
public static TextureAtlasSprite get(ResourceLocation texture) {
if (instance == null) throw new IllegalStateException("GuiSprites has not been initialised");
return instance.getSprite(texture);
}
/**
* Get the appropriate textures to use for a particular computer family.
*

View File

@@ -0,0 +1,40 @@
// SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.client.gui;
import org.lwjgl.glfw.GLFW;
/**
* Supports for converting/translating key codes.
*/
public class KeyConverter {
/**
* GLFW's key events refer to the physical key code, rather than the "actual" key code (with keyboard layout
* applied).
* <p>
* This makes sense for WASD-style input, but is a right pain for keyboard shortcuts this function attempts to
* translate those keys back to their "actual" key code. See also
* <a href="https://github.com/glfw/glfw/issues/1502"> this discussion on GLFW's GitHub.</a>
*
* @param key The current key code.
* @param scanCode The current scan code.
* @return The translated key code.
*/
public static int physicalToActual(int key, int scanCode) {
var name = GLFW.glfwGetKeyName(key, scanCode);
if (name == null || name.length() != 1) return key;
// If we've got a single character as the key name, treat that as the ASCII value of the key,
// and map that back to a key code.
var character = name.charAt(0);
// 0-9 and A-Z map directly to their GLFW key (they're the same ASCII code).
if ((character >= '0' && character <= '9') || (character >= 'A' && character <= 'Z')) return character;
// a-z map to GLFW_KEY_{A,Z}
if (character >= 'a' && character <= 'z') return GLFW.GLFW_KEY_A + (character - 'a');
return key;
}
}

View File

@@ -7,8 +7,7 @@ package dan200.computercraft.client.gui;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.client.gui.widgets.ComputerSidebar;
import dan200.computercraft.client.gui.widgets.TerminalWidget;
import dan200.computercraft.client.render.RenderTypes;
import dan200.computercraft.client.render.SpriteRenderer;
import dan200.computercraft.core.util.Nullability;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.computer.inventory.AbstractComputerMenu;
import dan200.computercraft.shared.turtle.inventory.TurtleMenu;
@@ -64,8 +63,9 @@ public class TurtleScreen extends AbstractComputerScreen<TurtleMenu> {
}
// Render sidebar
var spriteRenderer = SpriteRenderer.createForGui(graphics, RenderTypes.GUI_SPRITES);
ComputerSidebar.renderBackground(spriteRenderer, GuiSprites.getComputerTextures(family), leftPos, topPos + sidebarYOffset);
graphics.flush(); // Flush to ensure background textures are drawn before foreground.
graphics.blitSprite(
Nullability.assertNonNull(GuiSprites.getComputerTextures(family).sidebar()),
leftPos, topPos + sidebarYOffset, AbstractComputerMenu.SIDEBAR_WIDTH, ComputerSidebar.HEIGHT
);
}
}

View File

@@ -6,9 +6,7 @@ package dan200.computercraft.client.gui.widgets;
import dan200.computercraft.client.gui.GuiSprites;
import dan200.computercraft.client.gui.widgets.DynamicImageButton.HintedMessage;
import dan200.computercraft.client.render.SpriteRenderer;
import dan200.computercraft.shared.computer.core.InputHandler;
import dan200.computercraft.shared.computer.inventory.AbstractComputerMenu;
import net.minecraft.client.gui.components.AbstractWidget;
import net.minecraft.network.chat.Component;
@@ -24,12 +22,9 @@ public final class ComputerSidebar {
private static final int ICON_MARGIN = 2;
private static final int CORNERS_BORDER = 3;
private static final int FULL_BORDER = CORNERS_BORDER + ICON_MARGIN;
private static final int BUTTONS = 2;
private static final int HEIGHT = (ICON_HEIGHT + ICON_MARGIN * 2) * BUTTONS + CORNERS_BORDER * 2;
private static final int TEX_HEIGHT = 14;
public static final int HEIGHT = (ICON_HEIGHT + ICON_MARGIN * 2) * BUTTONS + CORNERS_BORDER * 2;
private ComputerSidebar() {
}
@@ -63,14 +58,6 @@ public final class ComputerSidebar {
));
}
public static void renderBackground(SpriteRenderer renderer, GuiSprites.ComputerTextures textures, int x, int y) {
var texture = textures.sidebar();
if (texture == null) throw new NullPointerException(textures + " has no sidebar texture");
var sprite = GuiSprites.get(texture);
renderer.blitVerticalSliced(sprite, x, y, AbstractComputerMenu.SIDEBAR_WIDTH, HEIGHT, FULL_BORDER, FULL_BORDER, TEX_HEIGHT);
}
private static void toggleComputer(BooleanSupplier isOn, InputHandler input) {
if (isOn.getAsBoolean()) {
input.shutdown();

View File

@@ -4,6 +4,7 @@
package dan200.computercraft.client.gui.widgets;
import dan200.computercraft.client.gui.KeyConverter;
import dan200.computercraft.client.render.RenderTypes;
import dan200.computercraft.client.render.text.FixedWidthFontRenderer;
import dan200.computercraft.core.terminal.Terminal;
@@ -83,7 +84,7 @@ public class TerminalWidget extends AbstractWidget {
}
if ((modifiers & GLFW.GLFW_MOD_CONTROL) != 0) {
switch (key) {
switch (KeyConverter.physicalToActual(key, scancode)) {
case GLFW.GLFW_KEY_T -> {
if (terminateTimer < 0) terminateTimer = 0;
}
@@ -119,7 +120,7 @@ public class TerminalWidget extends AbstractWidget {
computer.keyUp(key);
}
switch (key) {
switch (KeyConverter.physicalToActual(key, scancode)) {
case GLFW.GLFW_KEY_T -> terminateTimer = -1;
case GLFW.GLFW_KEY_R -> rebootTimer = -1;
case GLFW.GLFW_KEY_S -> shutdownTimer = -1;

View File

@@ -8,7 +8,6 @@ import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.integration.RecipeModHelpers;
import dan200.computercraft.shared.media.items.DiskItem;
import dan200.computercraft.shared.pocket.items.PocketComputerItem;
import dan200.computercraft.shared.turtle.items.TurtleItem;
import mezz.jei.api.IModPlugin;
@@ -23,6 +22,7 @@ import net.minecraft.client.Minecraft;
import net.minecraft.core.RegistryAccess;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.component.DyedItemColor;
import java.util.List;
@@ -100,7 +100,7 @@ public class JEIComputerCraft implements IModPlugin {
/**
* Distinguishes disks by colour.
*/
private static final IIngredientSubtypeInterpreter<ItemStack> diskSubtype = (stack, ctx) -> Integer.toString(DiskItem.getColour(stack));
private static final IIngredientSubtypeInterpreter<ItemStack> diskSubtype = (stack, ctx) -> Integer.toString(DyedItemColor.getOrDefault(stack, -1));
private static RegistryAccess getRegistryAccess() {
return Minecraft.getInstance().level.registryAccess();

View File

@@ -1,17 +1,14 @@
// Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
// SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers
//
// SPDX-License-Identifier: LicenseRef-CCPL
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.client.render;
import dan200.computercraft.client.gui.GuiSprites;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import static dan200.computercraft.client.render.SpriteRenderer.u;
import static dan200.computercraft.client.render.SpriteRenderer.v;
import dan200.computercraft.client.gui.ComputerScreen;
import net.minecraft.client.resources.metadata.gui.GuiSpriteScaling;
/**
* Renders the borders of computers, either for a GUI ({@link dan200.computercraft.client.gui.ComputerScreen}) or
* Constants for the borders of computers, either for a {@linkplain ComputerScreen GUI} or
* {@linkplain PocketItemRenderer in-hand pocket computers}.
*/
public final class ComputerBorderRenderer {
@@ -21,55 +18,13 @@ public final class ComputerBorderRenderer {
public static final int MARGIN = 2;
/**
* The width of the terminal border.
* The size of the terminal border.
* <p>
* This is only used for layout of elements within UI. When rendering, the size of the computer's border is
* determined by its {@link GuiSpriteScaling}.
*/
public static final int BORDER = 12;
public static final int LIGHT_HEIGHT = 8;
private static final int TEX_SIZE = 36;
private ComputerBorderRenderer() {
}
public static void render(SpriteRenderer renderer, GuiSprites.ComputerTextures textures, int x, int y, int width, int height, boolean withLight) {
var endX = x + width;
var endY = y + height;
var border = GuiSprites.get(textures.border());
// Top bar
blitBorder(renderer, border, x - BORDER, y - BORDER, 0, 0, BORDER, BORDER);
blitBorder(renderer, border, x, y - BORDER, BORDER, 0, width, BORDER);
blitBorder(renderer, border, endX, y - BORDER, BORDER * 2, 0, BORDER, BORDER);
// Vertical bars
blitBorder(renderer, border, x - BORDER, y, 0, BORDER, BORDER, height);
blitBorder(renderer, border, endX, y, BORDER * 2, BORDER, BORDER, height);
// Bottom bar. We allow for drawing a stretched version, which allows for additional elements (such as the
// pocket computer's lights).
if (withLight) {
var pocketBottomTexture = textures.pocketBottom();
if (pocketBottomTexture == null) throw new NullPointerException(textures + " has no pocket texture");
var pocketBottom = GuiSprites.get(pocketBottomTexture);
renderer.blitHorizontalSliced(
pocketBottom, x - BORDER, endY, width + BORDER * 2, BORDER + LIGHT_HEIGHT,
BORDER, BORDER, BORDER * 3
);
} else {
blitBorder(renderer, border, x - BORDER, endY, 0, BORDER * 2, BORDER, BORDER);
blitBorder(renderer, border, x, endY, BORDER, BORDER * 2, width, BORDER);
blitBorder(renderer, border, endX, endY, BORDER * 2, BORDER * 2, BORDER, BORDER);
}
}
private static void blitBorder(SpriteRenderer renderer, TextureAtlasSprite sprite, int x, int y, int u, int v, int width, int height) {
renderer.blit(
x, y, width, height,
u(sprite, u, TEX_SIZE), v(sprite, v, TEX_SIZE),
u(sprite, u + BORDER, TEX_SIZE), v(sprite, v + BORDER, TEX_SIZE)
);
}
}

View File

@@ -12,6 +12,7 @@ import dan200.computercraft.client.pocket.ClientPocketComputers;
import dan200.computercraft.client.render.text.FixedWidthFontRenderer;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.core.util.Colour;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.lectern.CustomLecternBlockEntity;
import dan200.computercraft.shared.media.items.PrintoutData;
import dan200.computercraft.shared.media.items.PrintoutItem;
@@ -59,9 +60,9 @@ public class CustomLecternRenderer implements BlockEntityRenderer<CustomLecternB
poseStack.translate(0, -0.125f, 0);
var item = lectern.getItem();
if (item.getItem() instanceof PrintoutItem printout) {
if (item.getItem() instanceof PrintoutItem) {
var vertexConsumer = LecternPrintoutModel.MATERIAL.buffer(buffer, RenderType::entitySolid);
if (printout.getType() == PrintoutItem.Type.BOOK) {
if (item.is(ModRegistry.Items.PRINTED_BOOK.get())) {
printoutModel.renderBook(poseStack, vertexConsumer, packedLight, packedOverlay);
} else {
printoutModel.renderPages(poseStack, vertexConsumer, packedLight, packedOverlay, PrintoutData.getOrEmpty(item).pages());

View File

@@ -13,13 +13,16 @@ import dan200.computercraft.core.util.Colour;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.config.Config;
import dan200.computercraft.shared.pocket.items.PocketComputerItem;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.resources.metadata.gui.GuiSpriteScaling;
import net.minecraft.util.FastColor;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.component.DyedItemColor;
import org.joml.Matrix4f;
import static dan200.computercraft.client.render.ComputerBorderRenderer.*;
import static dan200.computercraft.client.render.ComputerBorderRenderer.BORDER;
import static dan200.computercraft.client.render.ComputerBorderRenderer.MARGIN;
import static dan200.computercraft.client.render.text.FixedWidthFontRenderer.FONT_HEIGHT;
import static dan200.computercraft.client.render.text.FixedWidthFontRenderer.FONT_WIDTH;
@@ -29,6 +32,11 @@ import static dan200.computercraft.client.render.text.FixedWidthFontRenderer.FON
public final class PocketItemRenderer extends ItemMapLikeRenderer {
public static final PocketItemRenderer INSTANCE = new PocketItemRenderer();
/**
* The height of the pocket computer's light.
*/
private static final int LIGHT_HEIGHT = 8;
private PocketItemRenderer() {
}
@@ -83,14 +91,69 @@ public final class PocketItemRenderer extends ItemMapLikeRenderer {
}
private static void renderFrame(Matrix4f transform, MultiBufferSource render, ComputerFamily family, int colour, int light, int width, int height) {
var texture = colour != -1 ? GuiSprites.COMPUTER_COLOUR : GuiSprites.getComputerTextures(family);
var textures = colour != -1 ? GuiSprites.COMPUTER_COLOUR : GuiSprites.getComputerTextures(family);
var spriteRenderer = new SpriteRenderer(transform, render, 0, light, colour);
renderBorder(spriteRenderer, textures, width, height);
}
var r = (colour >>> 16) & 0xFF;
var g = (colour >>> 8) & 0xFF;
var b = colour & 0xFF;
private static void renderBorder(SpriteRenderer renderer, GuiSprites.ComputerTextures textures, int width, int height) {
var sprites = Minecraft.getInstance().getGuiSprites();
var spriteRenderer = new SpriteRenderer(transform, render.getBuffer(RenderTypes.GUI_SPRITES), 0, light, r, g, b);
ComputerBorderRenderer.render(spriteRenderer, texture, 0, 0, width, height, true);
// Find our border, forcing it to be a nine-sliced texture.
var borderSprite = sprites.getSprite(textures.border());
var borderSlice = getSlice(sprites.getSpriteScaling(borderSprite), DEFAULT_BORDER);
var borderBounds = borderSlice.border();
// And take the separate bottom bit of the pocket computer.
var bottomTexture = textures.pocketBottom();
if (bottomTexture == null) throw new NullPointerException(textures + " has no pocket texture");
var bottomSprite = sprites.getSprite(bottomTexture);
var bottomSlice = getSlice(sprites.getSpriteScaling(bottomSprite), DEFAULT_BOTTOM);
var bottomBounds = bottomSlice.border();
// Now draw a nine-sliced texture, by stitching together the top parts of the border with the pocket bottom.
// Top bar
renderer.blit(
borderSprite, -borderBounds.left(), -borderBounds.top(), borderBounds.left(), borderBounds.top(),
0, 0, borderSlice.width(), borderSlice.height()
);
renderer.blitTiled(
borderSprite, 0, -borderBounds.top(), width, borderBounds.top(),
borderBounds.left(), 0, borderSlice.width() - borderBounds.left() - borderBounds.right(), borderBounds.top(),
borderSlice.width(), borderSlice.height()
);
renderer.blit(
borderSprite, width, -borderBounds.top(), borderBounds.right(), borderBounds.top(),
borderSlice.width() - borderBounds.right(), 0, borderSlice.width(), borderSlice.height()
);
// Vertical bars
renderer.blitTiled(
borderSprite, -borderBounds.left(), 0, borderBounds.left(), height,
0, borderBounds.top(), borderBounds.left(), borderSlice.height() - borderBounds.top() - borderBounds.bottom(),
borderSlice.width(), borderSlice.height()
);
renderer.blitTiled(
borderSprite, width, 0, borderBounds.right(), height,
borderSlice.width() - borderBounds.right(), borderBounds.top(), borderBounds.right(), borderSlice.height() - borderBounds.top() - borderBounds.bottom(),
borderSlice.width(), borderSlice.height()
);
// Bottom
renderer.blit(
bottomSprite, -bottomBounds.left(), height, bottomBounds.left(), bottomSlice.height(),
0, 0, bottomSlice.width(), bottomSlice.height()
);
renderer.blitTiled(
bottomSprite, 0, height, width, bottomSlice.height(),
bottomBounds.left(), 0, bottomSlice.width() - bottomBounds.left() - bottomBounds.right(), bottomSlice.height(),
bottomSlice.width(), bottomSlice.height()
);
renderer.blit(
bottomSprite, width, height, bottomBounds.right(), bottomSlice.height(),
bottomSlice.width() - bottomBounds.right(), 0, bottomSlice.width(), bottomSlice.height()
);
}
private static void renderLight(PoseStack transform, MultiBufferSource render, int colour, int width, int height) {
@@ -101,4 +164,16 @@ public final class PocketItemRenderer extends ItemMapLikeRenderer {
FastColor.ARGB32.opaque(colour), RenderTypes.FULL_BRIGHT_LIGHTMAP
);
}
private static final GuiSpriteScaling.NineSlice DEFAULT_BORDER = new GuiSpriteScaling.NineSlice(
36, 36, new GuiSpriteScaling.NineSlice.Border(12, 12, 12, 12)
);
private static final GuiSpriteScaling.NineSlice DEFAULT_BOTTOM = new GuiSpriteScaling.NineSlice(
36, 20, new GuiSpriteScaling.NineSlice.Border(12, 0, 12, 0)
);
private static GuiSpriteScaling.NineSlice getSlice(GuiSpriteScaling scaling, GuiSpriteScaling.NineSlice fallback) {
return scaling instanceof GuiSpriteScaling.NineSlice slice ? slice : fallback;
}
}

View File

@@ -8,7 +8,6 @@ import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.math.Axis;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.media.items.PrintoutData;
import dan200.computercraft.shared.media.items.PrintoutItem;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.decoration.ItemFrame;
@@ -53,7 +52,7 @@ public final class PrintoutItemRenderer extends ItemMapLikeRenderer {
var pageData = stack.getOrDefault(ModRegistry.DataComponents.PRINTOUT.get(), PrintoutData.EMPTY);
var pages = pageData.pages();
var book = ((PrintoutItem) stack.getItem()).getType() == PrintoutItem.Type.BOOK;
var book = stack.is(ModRegistry.Items.PRINTED_BOOK.get());
double width = LINE_LENGTH * FONT_WIDTH + X_TEXT_MARGIN * 2;
double height = LINES_PER_PAGE * FONT_HEIGHT + Y_TEXT_MARGIN * 2;

View File

@@ -7,7 +7,6 @@ package dan200.computercraft.client.render;
import com.mojang.blaze3d.vertex.DefaultVertexFormat;
import com.mojang.blaze3d.vertex.VertexFormat;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.client.gui.GuiSprites;
import dan200.computercraft.client.render.monitor.MonitorTextureBufferShader;
import dan200.computercraft.client.render.text.FixedWidthFontRenderer;
import net.minecraft.client.renderer.GameRenderer;
@@ -16,11 +15,11 @@ import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.ShaderInstance;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.ResourceProvider;
import org.apache.commons.io.function.IOSupplier;
import org.jspecify.annotations.Nullable;
import java.io.IOException;
import java.util.Objects;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
/**
@@ -54,11 +53,6 @@ public class RenderTypes {
*/
public static final RenderType PRINTOUT_BACKGROUND = RenderType.text(ResourceLocation.fromNamespaceAndPath("computercraft", "textures/gui/printout.png"));
/**
* Render type for {@linkplain GuiSprites GUI sprites}.
*/
public static final RenderType GUI_SPRITES = RenderType.text(GuiSprites.TEXTURE);
public static MonitorTextureBufferShader getMonitorTextureBufferShader() {
if (monitorTboShader == null) throw new NullPointerException("MonitorTboShader has not been registered");
return monitorTboShader;
@@ -68,9 +62,12 @@ public class RenderTypes {
return Objects.requireNonNull(GameRenderer.getRendertypeTextShader(), "Text shader has not been registered");
}
public static void registerShaders(ResourceProvider resources, BiConsumer<ShaderInstance, Consumer<ShaderInstance>> load) throws IOException {
load.accept(
new MonitorTextureBufferShader(
public interface ShaderLoader {
void tryLoad(String name, IOSupplier<ShaderInstance> create, Consumer<@Nullable ShaderInstance> accept) throws IOException;
}
public static void registerShaders(ResourceProvider resources, ShaderLoader load) throws IOException {
load.tryLoad("monitor shader", () -> new MonitorTextureBufferShader(
resources,
ComputerCraftAPI.MOD_ID + "/monitor_tbo",
MONITOR_TBO.format()

View File

@@ -6,129 +6,69 @@ package dan200.computercraft.client.render;
import com.mojang.blaze3d.vertex.VertexConsumer;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.resources.ResourceLocation;
import org.joml.Matrix4f;
/**
* A {@link GuiGraphics}-equivalent which is suitable for both rendering in to a GUI and in-world (as part of an entity
* renderer).
* A {@link GuiGraphics}-equivalent renders to a {@link VertexConsumer}. This is suitable for rendering outside of a
* GUI, such as part of an entity renderer.
* <p>
* This batches all render calls together, though requires that all {@link TextureAtlasSprite}s are on the same sprite
* sheet.
*/
public class SpriteRenderer {
public static final ResourceLocation TEXTURE = ResourceLocation.withDefaultNamespace("textures/atlas/gui.png");
private final Matrix4f transform;
private final VertexConsumer builder;
private final MultiBufferSource buffers;
private final int light;
private final int z;
private final int r, g, b;
private final int colour;
public SpriteRenderer(Matrix4f transform, VertexConsumer builder, int z, int light, int r, int g, int b) {
public SpriteRenderer(Matrix4f transform, MultiBufferSource buffers, int z, int light, int colour) {
this.transform = transform;
this.builder = builder;
this.buffers = buffers;
this.z = z;
this.light = light;
this.r = r;
this.g = g;
this.b = b;
this.colour = colour;
}
public static SpriteRenderer createForGui(GuiGraphics graphics, RenderType renderType) {
return new SpriteRenderer(
graphics.pose().last().pose(), graphics.bufferSource().getBuffer(renderType),
0, RenderTypes.FULL_BRIGHT_LIGHTMAP, 255, 255, 255
);
public void blit(TextureAtlasSprite sprite, int x0, int y0, int width, int height, int spriteX, int spriteY, int spriteWidth, int spriteHeight) {
if (width == 0 || height == 0) return;
var x1 = x0 + width;
var y1 = y0 + height;
var u0 = sprite.getU((float) spriteX / spriteWidth);
var u1 = sprite.getU((float) (spriteX + width) / spriteWidth);
var v0 = sprite.getV((float) spriteY / spriteHeight);
var v1 = sprite.getV((float) (spriteY + height) / spriteHeight);
var vertices = buffers.getBuffer(RenderType.text(sprite.atlasLocation()));
vertices.addVertex(transform, x0, y1, z).setColor(colour).setUv(u0, v1).setLight(light);
vertices.addVertex(transform, x1, y1, z).setColor(colour).setUv(u1, v1).setLight(light);
vertices.addVertex(transform, x1, y0, z).setColor(colour).setUv(u1, v0).setLight(light);
vertices.addVertex(transform, x0, y0, z).setColor(colour).setUv(u0, v0).setLight(light);
}
/**
* Render a single sprite.
*
* @param sprite The texture to draw.
* @param x The x position of the rectangle we'll draw.
* @param y The x position of the rectangle we'll draw.
* @param width The width of the rectangle we'll draw.
* @param height The height of the rectangle we'll draw.
*/
public void blit(TextureAtlasSprite sprite, int x, int y, int width, int height) {
blit(x, y, width, height, sprite.getU0(), sprite.getV0(), sprite.getU1(), sprite.getV1());
}
public void blitTiled(
TextureAtlasSprite sprite,
int x, int y, int width, int height,
int tileX, int tileY, int tileWidth, int tileHeight, int spriteWidth, int spriteHeight
) {
if (width <= 0 || height <= 0) return;
if (tileWidth <= 0 || tileHeight <= 0) {
throw new IllegalArgumentException("Tiled sprite texture size must be positive, got " + tileWidth + "x" + tileHeight);
}
/**
* Render a horizontal 3-sliced texture (i.e. split into left, middle and right). Unlike {@link GuiGraphics#blitNineSliced},
* the middle texture is stretched rather than repeated.
*
* @param sprite The texture to draw.
* @param x The x position of the rectangle we'll draw.
* @param y The x position of the rectangle we'll draw.
* @param width The width of the rectangle we'll draw.
* @param height The height of the rectangle we'll draw.
* @param leftBorder The width of the left border.
* @param rightBorder The width of the right border.
* @param textureWidth The width of the whole texture.
*/
public void blitHorizontalSliced(TextureAtlasSprite sprite, int x, int y, int width, int height, int leftBorder, int rightBorder, int textureWidth) {
// TODO(1.20.2)/TODO(1.21.0): Drive this from mcmeta files, like vanilla does.
if (width < leftBorder + rightBorder) throw new IllegalArgumentException("width is less than two borders");
var centerStart = SpriteRenderer.u(sprite, leftBorder, textureWidth);
var centerEnd = SpriteRenderer.u(sprite, textureWidth - rightBorder, textureWidth);
blit(x, y, leftBorder, height, sprite.getU0(), sprite.getV0(), centerStart, sprite.getV1());
blit(x + leftBorder, y, width - leftBorder - rightBorder, height, centerStart, sprite.getV0(), centerEnd, sprite.getV1());
blit(x + width - rightBorder, y, rightBorder, height, centerEnd, sprite.getV0(), sprite.getU1(), sprite.getV1());
}
/**
* Render a vertical 3-sliced texture (i.e. split into top, middle and bottom). Unlike {@link GuiGraphics#blitNineSliced},
* the middle texture is stretched rather than repeated.
*
* @param sprite The texture to draw.
* @param x The x position of the rectangle we'll draw.
* @param y The x position of the rectangle we'll draw.
* @param width The width of the rectangle we'll draw.
* @param height The height of the rectangle we'll draw.
* @param topBorder The height of the top border.
* @param bottomBorder The height of the bottom border.
* @param textureHeight The height of the whole texture.
*/
public void blitVerticalSliced(TextureAtlasSprite sprite, int x, int y, int width, int height, int topBorder, int bottomBorder, int textureHeight) {
// TODO(1.20.2)/TODO(1.21.0): Drive this from mcmeta files, like vanilla does.
if (width < topBorder + bottomBorder) throw new IllegalArgumentException("height is less than two borders");
var centerStart = SpriteRenderer.v(sprite, topBorder, textureHeight);
var centerEnd = SpriteRenderer.v(sprite, textureHeight - bottomBorder, textureHeight);
blit(x, y, width, topBorder, sprite.getU0(), sprite.getV0(), sprite.getU1(), centerStart);
blit(x, y + topBorder, width, height - topBorder - bottomBorder, sprite.getU0(), centerStart, sprite.getU1(), centerEnd);
blit(x, y + height - bottomBorder, width, bottomBorder, sprite.getU0(), centerEnd, sprite.getU1(), sprite.getV1());
}
/**
* The low-level blit function, used to render a portion of the sprite sheet. Unlike other functions, this takes uvs rather than a single sprite.
*
* @param x The x position of the rectangle we'll draw.
* @param y The x position of the rectangle we'll draw.
* @param width The width of the rectangle we'll draw.
* @param height The height of the rectangle we'll draw.
* @param u0 The first U coordinate.
* @param v0 The first V coordinate.
* @param u1 The second U coordinate.
* @param v1 The second V coordinate.
*/
public void blit(
int x, int y, int width, int height, float u0, float v0, float u1, float v1) {
builder.addVertex(transform, x, y + height, z).setColor(r, g, b, 255).setUv(u0, v1).setLight(light);
builder.addVertex(transform, x + width, y + height, z).setColor(r, g, b, 255).setUv(u1, v1).setLight(light);
builder.addVertex(transform, x + width, y, z).setColor(r, g, b, 255).setUv(u1, v0).setLight(light);
builder.addVertex(transform, x, y, z).setColor(r, g, b, 255).setUv(u0, v0).setLight(light);
}
public static float u(TextureAtlasSprite sprite, int x, int width) {
return sprite.getU((float) x / width);
}
public static float v(TextureAtlasSprite sprite, int y, int height) {
return sprite.getV((float) y / height);
for (var xOffset = 0; xOffset < width; xOffset += tileWidth) {
var sliceWidth = Math.min(tileWidth, width - xOffset);
for (var yOffset = 0; yOffset < height; yOffset += tileHeight) {
var sliceHeight = Math.min(tileHeight, height - yOffset);
blit(sprite, x + xOffset, y + yOffset, sliceWidth, sliceHeight, tileX, tileY, spriteWidth, spriteHeight);
}
}
}
}

View File

@@ -62,8 +62,8 @@ public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBloc
var matrix = transform.last().pose();
var opacity = (int) (mc.options.getBackgroundOpacity(0.25f) * 255) << 24;
var width = -font.width(label) / 2.0f;
font.drawInBatch(label, width, (float) 0, 0x20ffffff, false, matrix, buffers, Font.DisplayMode.SEE_THROUGH, opacity, lightmapCoord);
font.drawInBatch(label, width, (float) 0, CommonColors.WHITE, false, matrix, buffers, Font.DisplayMode.NORMAL, 0, lightmapCoord);
font.drawInBatch(label, width, 0, 0x20ffffff, false, matrix, buffers, Font.DisplayMode.SEE_THROUGH, opacity, lightmapCoord);
font.drawInBatch(label, width, 0, CommonColors.WHITE, false, matrix, buffers, Font.DisplayMode.NORMAL, 0, lightmapCoord);
transform.popPose();
}

View File

@@ -76,7 +76,7 @@ public final class DataProviders {
LecternPocketModel.TEXTURE_NORMAL, LecternPocketModel.TEXTURE_ADVANCED,
LecternPocketModel.TEXTURE_COLOUR, LecternPocketModel.TEXTURE_FRAME, LecternPocketModel.TEXTURE_LIGHT
)));
out.accept(GuiSprites.SPRITE_SHEET, makeSprites(
out.accept(ResourceLocation.withDefaultNamespace("gui"), makeSprites(
// Computers
GuiSprites.COMPUTER_NORMAL.textures(),
GuiSprites.COMPUTER_ADVANCED.textures(),
@@ -85,6 +85,8 @@ public final class DataProviders {
));
});
generator.add(ResourceMetadataProvider::new);
generator.add(pack -> new ExtraModelsProvider(pack, fullRegistries) {
@Override
public Stream<ResourceLocation> getModels(HolderLookup.Provider registries) {

View File

@@ -108,6 +108,8 @@ public final class LanguageProvider implements DataProvider {
add(ComputerCraftTags.Items.TURTLE, "Turtles");
add(ComputerCraftTags.Items.WIRED_MODEM, "Wired modems");
add(ComputerCraftTags.Items.MONITOR, "Monitors");
add(ComputerCraftTags.Items.DISKS, "Disks");
add(ComputerCraftTags.Items.POCKET_COMPUTERS, "Pocket Computers");
add(ComputerCraftTags.Items.DYEABLE, "Dyable items");
add(ComputerCraftTags.Items.TURTLE_CAN_PLACE, "Turtle-placeable items");
@@ -187,7 +189,6 @@ public final class LanguageProvider implements DataProvider {
// Metrics
add(Metrics.COMPUTER_TASKS, "Tasks");
add(Metrics.SERVER_TASKS, "Server tasks");
add(Metrics.JAVA_ALLOCATION, "Java Allocations");
add(Metrics.PERIPHERAL_OPS, "Peripheral calls");
add(Metrics.FS_OPS, "Filesystem operations");
add(Metrics.HTTP_REQUESTS, "HTTP requests");

View File

@@ -0,0 +1,110 @@
// SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.data;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import dan200.computercraft.client.gui.GuiSprites;
import net.minecraft.client.resources.metadata.gui.GuiMetadataSection;
import net.minecraft.client.resources.metadata.gui.GuiSpriteScaling;
import net.minecraft.data.CachedOutput;
import net.minecraft.data.DataProvider;
import net.minecraft.data.PackOutput;
import net.minecraft.data.metadata.PackMetadataGenerator;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.metadata.MetadataSectionType;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.function.Supplier;
/**
* Similar to {@link PackMetadataGenerator}, but for individual resources.
*/
final class ResourceMetadataProvider implements DataProvider {
private final PackOutput output;
ResourceMetadataProvider(PackOutput output) {
this.output = output;
}
private void register(Builder builder) {
for (var computerTextures : List.of(
GuiSprites.COMPUTER_ADVANCED,
GuiSprites.COMPUTER_COLOUR,
GuiSprites.COMPUTER_COMMAND,
GuiSprites.COMPUTER_NORMAL
)) {
builder.texture(computerTextures.border()).add(GuiMetadataSection.TYPE, new GuiMetadataSection(
new GuiSpriteScaling.NineSlice(36, 36, simpleNineSlicedBorder(12))
));
var sidebar = computerTextures.sidebar();
if (sidebar != null) {
builder.texture(sidebar).add(GuiMetadataSection.TYPE, new GuiMetadataSection(
new GuiSpriteScaling.NineSlice(17, 14, new GuiSpriteScaling.NineSlice.Border(3, 4, 0, 3))
));
}
var pocketBottom = computerTextures.pocketBottom();
if (pocketBottom != null) {
builder.texture(pocketBottom).add(GuiMetadataSection.TYPE, new GuiMetadataSection(
new GuiSpriteScaling.NineSlice(36, 20, new GuiSpriteScaling.NineSlice.Border(12, 0, 12, 0))
));
}
}
}
private static GuiSpriteScaling.NineSlice.Border simpleNineSlicedBorder(int size) {
return new GuiSpriteScaling.NineSlice.Border(size, size, size, size);
}
@Override
public CompletableFuture<?> run(CachedOutput cachedOutput) {
var builder = new Builder();
register(builder);
var outputPath = output.getOutputFolder(PackOutput.Target.RESOURCE_PACK);
return CompletableFuture.allOf(builder.metadata.entrySet().stream().map(entry -> {
var json = new JsonObject();
entry.getValue().elements.forEach((name, element) -> json.add(name, element.get()));
return DataProvider.saveStable(cachedOutput, json, outputPath.resolve(entry.getKey().getNamespace()).resolve(entry.getKey().getPath() + ".mcmeta"));
}).toArray(CompletableFuture[]::new));
}
@Override
public String getName() {
return "Resource Metadata";
}
/**
* A builder for a set of {@code mcmeta} files.
*/
private static final class Builder {
private final Map<ResourceLocation, FileMetadata> metadata = new HashMap<>();
FileMetadata texture(ResourceLocation texture) {
return file(texture.withPrefix("textures/").withSuffix(".png"));
}
FileMetadata file(ResourceLocation path) {
return metadata.computeIfAbsent(path, p -> new FileMetadata());
}
}
/**
* A builder for a given file's {@code mcmeta} file.
*/
private static final class FileMetadata {
private final Map<String, Supplier<JsonElement>> elements = new HashMap<>();
<T> FileMetadata add(MetadataSectionType<T> type, T value) {
elements.put(type.getMetadataSectionName(), () -> type.toJson(value));
return this;
}
}
}

View File

@@ -96,6 +96,8 @@ class TagProvider {
tags.copy(ComputerCraftTags.Blocks.TURTLE, ComputerCraftTags.Items.TURTLE);
tags.tag(ComputerCraftTags.Items.WIRED_MODEM).add(ModRegistry.Items.WIRED_MODEM.get(), ModRegistry.Items.WIRED_MODEM_FULL.get());
tags.copy(ComputerCraftTags.Blocks.MONITOR, ComputerCraftTags.Items.MONITOR);
tags.tag(ComputerCraftTags.Items.DISKS).add(ModRegistry.Items.DISK.get(), ModRegistry.Items.TREASURE_DISK.get());
tags.tag(ComputerCraftTags.Items.POCKET_COMPUTERS).add(ModRegistry.Items.POCKET_COMPUTER_NORMAL.get(), ModRegistry.Items.POCKET_COMPUTER_ADVANCED.get());
tags.tag(ComputerCraftTags.Items.DYEABLE)
.addTag(ComputerCraftTags.Items.TURTLE)

View File

@@ -205,8 +205,10 @@
"item.computercraft.treasure_disk": "Floppy Disk",
"itemGroup.computercraft": "ComputerCraft",
"tag.item.computercraft.computer": "Computers",
"tag.item.computercraft.disks": "Disks",
"tag.item.computercraft.dyeable": "Dyable items",
"tag.item.computercraft.monitor": "Monitors",
"tag.item.computercraft.pocket_computers": "Pocket Computers",
"tag.item.computercraft.turtle": "Turtles",
"tag.item.computercraft.turtle_can_place": "Turtle-placeable items",
"tag.item.computercraft.wired_modem": "Wired modems",
@@ -217,7 +219,6 @@
"tracking_field.computercraft.http_download.name": "HTTP download",
"tracking_field.computercraft.http_requests.name": "HTTP requests",
"tracking_field.computercraft.http_upload.name": "HTTP upload",
"tracking_field.computercraft.java_allocation.name": "Java Allocations",
"tracking_field.computercraft.max": "%s (max)",
"tracking_field.computercraft.peripheral.name": "Peripheral calls",
"tracking_field.computercraft.server_tasks.name": "Server tasks",

View File

@@ -0,0 +1,10 @@
{
"gui": {
"scaling": {
"type": "nine_slice",
"border": 12,
"height": 36,
"width": 36
}
}
}

View File

@@ -0,0 +1,10 @@
{
"gui": {
"scaling": {
"type": "nine_slice",
"border": 12,
"height": 36,
"width": 36
}
}
}

View File

@@ -0,0 +1,10 @@
{
"gui": {
"scaling": {
"type": "nine_slice",
"border": 12,
"height": 36,
"width": 36
}
}
}

View File

@@ -0,0 +1,10 @@
{
"gui": {
"scaling": {
"type": "nine_slice",
"border": 12,
"height": 36,
"width": 36
}
}
}

View File

@@ -0,0 +1,15 @@
{
"gui": {
"scaling": {
"type": "nine_slice",
"border": {
"bottom": 0,
"left": 12,
"right": 12,
"top": 0
},
"height": 20,
"width": 36
}
}
}

View File

@@ -0,0 +1,15 @@
{
"gui": {
"scaling": {
"type": "nine_slice",
"border": {
"bottom": 0,
"left": 12,
"right": 12,
"top": 0
},
"height": 20,
"width": 36
}
}
}

View File

@@ -0,0 +1,15 @@
{
"gui": {
"scaling": {
"type": "nine_slice",
"border": {
"bottom": 0,
"left": 12,
"right": 12,
"top": 0
},
"height": 20,
"width": 36
}
}
}

View File

@@ -0,0 +1,15 @@
{
"gui": {
"scaling": {
"type": "nine_slice",
"border": {
"bottom": 3,
"left": 3,
"right": 0,
"top": 4
},
"height": 14,
"width": 17
}
}
}

View File

@@ -0,0 +1,15 @@
{
"gui": {
"scaling": {
"type": "nine_slice",
"border": {
"bottom": 3,
"left": 3,
"right": 0,
"top": 4
},
"height": 14,
"width": 17
}
}
}

View File

@@ -0,0 +1,15 @@
{
"gui": {
"scaling": {
"type": "nine_slice",
"border": {
"bottom": 3,
"left": 3,
"right": 0,
"top": 4
},
"height": 14,
"width": 17
}
}
}

View File

@@ -0,0 +1 @@
{"values": ["computercraft:disk", "computercraft:treasure_disk"]}

View File

@@ -0,0 +1 @@
{"values": ["computercraft:pocket_computer_normal", "computercraft:pocket_computer_advanced"]}

View File

@@ -432,8 +432,8 @@ final class WiredNetworkImpl {
}
private static WiredNodeImpl checkNode(WiredNode node) {
if (node instanceof WiredNodeImpl) {
return (WiredNodeImpl) node;
if (node instanceof WiredNodeImpl n) {
return n;
} else {
throw new IllegalArgumentException("Unknown implementation of IWiredNode: " + node);
}

View File

@@ -13,7 +13,10 @@ import dan200.computercraft.shared.lectern.CustomLecternBlock;
import dan200.computercraft.shared.peripheral.monitor.MonitorWatcher;
import dan200.computercraft.shared.util.DropConsumer;
import dan200.computercraft.shared.util.TickScheduler;
import net.minecraft.core.component.DataComponentType;
import net.minecraft.core.component.DataComponents;
import net.minecraft.core.registries.Registries;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MinecraftServer;
@@ -25,9 +28,8 @@ import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.CreativeModeTab;
import net.minecraft.world.item.CreativeModeTabs;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.*;
import net.minecraft.world.item.component.TooltipProvider;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.LecternBlock;
@@ -40,8 +42,10 @@ import net.minecraft.world.level.storage.loot.providers.number.ConstantValue;
import net.minecraft.world.phys.BlockHitResult;
import org.jspecify.annotations.Nullable;
import java.util.List;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
/**
* Event listeners for server/common code.
@@ -163,4 +167,42 @@ public final class CommonHooks {
out.accept(ModRegistry.Items.COMPUTER_COMMAND.get());
}
}
public static void onItemTooltip(ItemStack stack, Item.TooltipContext context, TooltipFlag flags, List<Component> out) {
var appender = new TooltipAppender(out);
addToTooltip(stack, ModRegistry.DataComponents.PRINTOUT.get(), context, appender, flags);
addToTooltip(stack, ModRegistry.DataComponents.TREASURE_DISK.get(), context, appender, flags);
// Disk and computer IDs require some conditional logic, so we don't bother using TooltipProvider.
var diskId = stack.get(ModRegistry.DataComponents.DISK_ID.get());
if (diskId != null && flags.isAdvanced()) diskId.addToTooltip("gui.computercraft.tooltip.disk_id", appender);
var computerId = stack.get(ModRegistry.DataComponents.COMPUTER_ID.get());
if (computerId != null && (flags.isAdvanced() || !stack.has(DataComponents.CUSTOM_NAME))) {
computerId.addToTooltip("gui.computercraft.tooltip.computer_id", appender);
}
}
/**
* Inserts additional tooltip items directly after the custom name, rather than at the very end.
*/
private static final class TooltipAppender implements Consumer<Component> {
private final List<Component> out;
private int index = 1;
private TooltipAppender(List<Component> out) {
this.out = out;
}
@Override
public void accept(Component component) {
out.add(index++, component);
}
}
private static <T extends TooltipProvider> void addToTooltip(ItemStack stack, DataComponentType<T> component, Item.TooltipContext context, Consumer<Component> out, TooltipFlag flags) {
var provider = stack.get(component);
if (provider != null) provider.addToTooltip(context, out, flags);
}
}

View File

@@ -34,9 +34,8 @@ import dan200.computercraft.shared.computer.blocks.ComputerBlock;
import dan200.computercraft.shared.computer.blocks.ComputerBlockEntity;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.computer.core.ServerComputer;
import dan200.computercraft.shared.computer.core.TerminalSize;
import dan200.computercraft.shared.computer.inventory.ComputerMenuWithoutInventory;
import dan200.computercraft.shared.computer.items.ComputerItem;
import dan200.computercraft.shared.computer.items.CommandComputerItem;
import dan200.computercraft.shared.computer.items.ServerComputerReference;
import dan200.computercraft.shared.config.Config;
import dan200.computercraft.shared.data.BlockNamedEntityLootCondition;
@@ -112,10 +111,7 @@ import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.world.flag.FeatureFlags;
import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.item.BlockItem;
import net.minecraft.world.item.CreativeModeTab;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.*;
import net.minecraft.world.item.component.DyedItemColor;
import net.minecraft.world.item.crafting.CustomRecipe;
import net.minecraft.world.item.crafting.Recipe;
@@ -262,9 +258,9 @@ public final class ModRegistry {
return REGISTRY.register(parent.id().getPath(), () -> supplier.apply(parent.get(), properties()));
}
public static final RegistryEntry<ComputerItem> COMPUTER_NORMAL = ofBlock(Blocks.COMPUTER_NORMAL, ComputerItem::new);
public static final RegistryEntry<ComputerItem> COMPUTER_ADVANCED = ofBlock(Blocks.COMPUTER_ADVANCED, ComputerItem::new);
public static final RegistryEntry<ComputerItem> COMPUTER_COMMAND = ofBlock(Blocks.COMPUTER_COMMAND, CommandComputerItem::new);
public static final RegistryEntry<BlockItem> COMPUTER_NORMAL = ofBlock(Blocks.COMPUTER_NORMAL, BlockItem::new);
public static final RegistryEntry<BlockItem> COMPUTER_ADVANCED = ofBlock(Blocks.COMPUTER_ADVANCED, BlockItem::new);
public static final RegistryEntry<GameMasterBlockItem> COMPUTER_COMMAND = ofBlock(Blocks.COMPUTER_COMMAND, GameMasterBlockItem::new);
public static final RegistryEntry<PocketComputerItem> POCKET_COMPUTER_NORMAL = REGISTRY.register("pocket_computer_normal",
() -> new PocketComputerItem(properties().stacksTo(1), ComputerFamily.NORMAL));
@@ -276,19 +272,19 @@ public final class ModRegistry {
public static final RegistryEntry<DiskItem> DISK =
REGISTRY.register("disk", () -> new DiskItem(properties().stacksTo(1)));
public static final RegistryEntry<TreasureDiskItem> TREASURE_DISK =
REGISTRY.register("treasure_disk", () -> new TreasureDiskItem(properties().stacksTo(1)));
public static final RegistryEntry<DiskItem> TREASURE_DISK =
REGISTRY.register("treasure_disk", () -> new DiskItem(properties().stacksTo(1)));
private static Item.Properties printoutProperties() {
return properties().stacksTo(1).component(DataComponents.PRINTOUT.get(), PrintoutData.EMPTY);
}
public static final RegistryEntry<PrintoutItem> PRINTED_PAGE = REGISTRY.register("printed_page",
() -> new PrintoutItem(printoutProperties(), PrintoutItem.Type.PAGE));
() -> new PrintoutItem(printoutProperties()));
public static final RegistryEntry<PrintoutItem> PRINTED_PAGES = REGISTRY.register("printed_pages",
() -> new PrintoutItem(printoutProperties(), PrintoutItem.Type.PAGES));
() -> new PrintoutItem(printoutProperties()));
public static final RegistryEntry<PrintoutItem> PRINTED_BOOK = REGISTRY.register("printed_book",
() -> new PrintoutItem(printoutProperties(), PrintoutItem.Type.BOOK));
() -> new PrintoutItem(printoutProperties()));
public static final RegistryEntry<BlockItem> SPEAKER = ofBlock(Blocks.SPEAKER, BlockItem::new);
public static final RegistryEntry<BlockItem> DISK_DRIVE = ofBlock(Blocks.DISK_DRIVE, BlockItem::new);
@@ -315,9 +311,6 @@ public final class ModRegistry {
/**
* The id of a computer.
*
* @see ComputerItem
* @see PocketComputerItem
*/
public static final RegistryEntry<DataComponentType<NonNegativeId>> COMPUTER_ID = register("computer_id", b -> b
.persistent(NonNegativeId.CODEC).networkSynchronized(NonNegativeId.STREAM_CODEC)
@@ -325,15 +318,18 @@ public final class ModRegistry {
/**
* The storage capacity of a computer or disk.
*
* @see ComputerItem
* @see PocketComputerItem
* @see DiskItem
*/
public static final RegistryEntry<DataComponentType<StorageCapacity>> STORAGE_CAPACITY = register("storage_capacity", b -> b
.persistent(StorageCapacity.CODEC).networkSynchronized(StorageCapacity.STREAM_CODEC)
);
/**
* The terminal size of a computer.
*/
public static final RegistryEntry<DataComponentType<TerminalSize>> TERMINAL_SIZE = register("terminal_size", b -> b
.persistent(TerminalSize.CODEC).networkSynchronized(TerminalSize.STREAM_CODEC)
);
/**
* The left upgrade of a turtle.
*
@@ -397,9 +393,6 @@ public final class ModRegistry {
/**
* Information about a treasure disk's mount.
*
* @see TreasureDiskItem
* @see TreasureDisk
*/
public static final RegistryEntry<DataComponentType<TreasureDisk>> TREASURE_DISK = register("treasure_disk", b -> b
.persistent(TreasureDisk.CODEC).networkSynchronized(TreasureDisk.STREAM_CODEC)
@@ -407,8 +400,6 @@ public final class ModRegistry {
/**
* The id of a disk.
*
* @see DiskItem
*/
public static final RegistryEntry<DataComponentType<NonNegativeId>> DISK_ID = register("disk_id", b -> b
.persistent(NonNegativeId.CODEC).networkSynchronized(NonNegativeId.STREAM_CODEC)

View File

@@ -50,6 +50,12 @@ public enum UserLevel implements Predicate<CommandSourceStack> {
public static boolean isOwner(CommandSourceStack source) {
var server = source.getServer();
// While CommandSourceStack.getServer is non-nullable, that's a lie for permission checks. When loading
// .mcfunction files, ServerFunctionLibrary constructs an instance with an empty server. In that case, return
// false we don't want to treat functions as an owner!
if (server == null) return false;
var player = source.getPlayer();
return server.isDedicatedServer()
? source.getEntity() == null && source.hasPermission(4) && source.getTextName().equals("Server")

View File

@@ -66,8 +66,8 @@ public final class HelpingArgumentBuilder extends LiteralArgumentBuilder<Command
public LiteralArgumentBuilder<CommandSourceStack> then(final ArgumentBuilder<CommandSourceStack, ?> argument) {
if (getRedirect() != null) throw new IllegalStateException("Cannot add children to a redirected node");
if (argument instanceof HelpingArgumentBuilder) {
children.add((HelpingArgumentBuilder) argument);
if (argument instanceof HelpingArgumentBuilder child) {
children.add(child);
} else if (argument instanceof LiteralArgumentBuilder) {
super.then(argument);
} else {

View File

@@ -4,14 +4,15 @@
package dan200.computercraft.shared.computer.blocks;
import dan200.computercraft.annotations.ForgeOverride;
import dan200.computercraft.shared.common.IBundledRedstoneBlock;
import dan200.computercraft.shared.network.container.ComputerContainerData;
import dan200.computercraft.shared.platform.PlatformHelper;
import dan200.computercraft.shared.platform.RegistryEntry;
import dan200.computercraft.shared.util.BlockEntityHelpers;
import net.minecraft.Util;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.component.DataComponentMap;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
@@ -64,12 +65,6 @@ public abstract class AbstractComputerBlock<T extends AbstractComputerBlockEntit
return computer.getRedstoneOutput(localSide);
}
private ItemStack getItem(AbstractComputerBlockEntity tile) {
var stack = new ItemStack(this);
stack.applyComponents(tile.collectComponents());
return stack;
}
@Override
protected int getSignal(BlockState state, BlockGetter world, BlockPos pos, Direction incomingSide) {
return getDirectSignal(state, world, pos, incomingSide);
@@ -89,13 +84,11 @@ public abstract class AbstractComputerBlock<T extends AbstractComputerBlockEntit
@Override
public ItemStack getCloneItemStack(LevelReader world, BlockPos pos, BlockState state) {
var tile = world.getBlockEntity(pos);
if (tile instanceof AbstractComputerBlockEntity computer) {
var result = getItem(computer);
if (!result.isEmpty()) return result;
var stack = super.getCloneItemStack(world, pos, state);
if (world.getBlockEntity(pos) instanceof AbstractComputerBlockEntity computer) {
stack.applyComponents(computer.collectComponents());
}
return super.getCloneItemStack(world, pos, state);
return stack;
}
@Override
@@ -121,7 +114,10 @@ public abstract class AbstractComputerBlock<T extends AbstractComputerBlockEntit
var serverComputer = computer.createServerComputer();
serverComputer.turnOn();
PlatformHelper.get().openMenu(player, computer.getName(), computer, new ComputerContainerData(serverComputer, getItem(computer)));
var stack = new ItemStack(this);
stack.applyComponents(Util.make(DataComponentMap.builder(), computer::collectSafeComponents).build());
PlatformHelper.get().openMenu(player, computer.getName(), computer, new ComputerContainerData(serverComputer, stack));
}
return InteractionResult.sidedSuccess(level.isClientSide);
}
@@ -135,12 +131,6 @@ public abstract class AbstractComputerBlock<T extends AbstractComputerBlockEntit
if (be instanceof AbstractComputerBlockEntity computer) computer.neighborChanged(neighbourPos);
}
@ForgeOverride
public final void onNeighborChange(BlockState state, LevelReader world, BlockPos pos, BlockPos neighbour) {
var be = world.getBlockEntity(pos);
if (be instanceof AbstractComputerBlockEntity computer) computer.neighborChanged(neighbour);
}
@Override
protected BlockState updateShape(BlockState state, Direction direction, BlockState neighborState, LevelAccessor level, BlockPos pos, BlockPos neighborPos) {
var be = level.getBlockEntity(pos);

View File

@@ -5,6 +5,7 @@
package dan200.computercraft.shared.computer.blocks;
import com.google.common.base.Strings;
import com.google.errorprone.annotations.OverridingMethodsMustInvokeSuper;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.core.computer.ComputerSide;
@@ -187,10 +188,20 @@ public abstract class AbstractComputerBlockEntity extends BlockEntity implements
@Override
protected void collectImplicitComponents(DataComponentMap.Builder builder) {
super.collectImplicitComponents(builder);
collectSafeComponents(builder);
if (lockCode != LockCode.NO_LOCK) builder.set(DataComponents.LOCK, lockCode);
}
/**
* Collect components that are safe to share with the client.
*
* @param builder The component builder.
*/
@OverridingMethodsMustInvokeSuper
protected void collectSafeComponents(DataComponentMap.Builder builder) {
builder.set(ModRegistry.DataComponents.COMPUTER_ID.get(), NonNegativeId.of(computerID));
builder.set(DataComponents.CUSTOM_NAME, label == null ? null : Component.literal(label));
builder.set(ModRegistry.DataComponents.STORAGE_CAPACITY.get(), storageCapacity > 0 ? new StorageCapacity(storageCapacity) : null);
if (lockCode != LockCode.NO_LOCK) builder.set(DataComponents.LOCK, lockCode);
}
@Override

View File

@@ -16,7 +16,6 @@ import net.minecraft.world.level.block.entity.BlockEntityType;
* permission.
*
* @param <T> The type of the computer block entity.
* @see dan200.computercraft.shared.computer.items.CommandComputerItem
*/
public class CommandComputerBlock<T extends ComputerBlockEntity> extends ComputerBlock<T> implements GameMasterBlock {
private static final MapCodec<CommandComputerBlock<?>> CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group(

View File

@@ -10,10 +10,15 @@ import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.computer.core.ComputerState;
import dan200.computercraft.shared.computer.core.ServerComputer;
import dan200.computercraft.shared.computer.core.TerminalSize;
import dan200.computercraft.shared.computer.inventory.ComputerMenuWithoutInventory;
import dan200.computercraft.shared.config.ConfigSpec;
import dan200.computercraft.shared.util.NBTUtil;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.HolderLookup;
import net.minecraft.core.component.DataComponentMap;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.entity.player.Player;
@@ -23,17 +28,66 @@ import net.minecraft.world.level.block.state.BlockState;
import org.jspecify.annotations.Nullable;
public class ComputerBlockEntity extends AbstractComputerBlockEntity {
private static final String NBT_TERMINAL_SIZE = "TerminalSize";
private @Nullable TerminalSize terminalSize;
private @Nullable IPeripheral peripheral;
public ComputerBlockEntity(BlockEntityType<? extends ComputerBlockEntity> type, BlockPos pos, BlockState state, ComputerFamily family) {
super(type, pos, state, family);
}
@Override
protected void loadServer(CompoundTag nbt, HolderLookup.Provider registries) {
super.loadServer(nbt, registries);
terminalSize = NBTUtil.decodeFrom(TerminalSize.CODEC, registries, nbt, NBT_TERMINAL_SIZE);
}
@Override
public void saveAdditional(CompoundTag nbt, HolderLookup.Provider registries) {
super.saveAdditional(nbt, registries);
NBTUtil.encodeTo(TerminalSize.CODEC, registries, nbt, NBT_TERMINAL_SIZE, terminalSize);
}
@Override
protected void loadClient(CompoundTag nbt, HolderLookup.Provider registries) {
super.loadClient(nbt, registries);
terminalSize = NBTUtil.decodeFrom(TerminalSize.CODEC, registries, nbt, NBT_TERMINAL_SIZE);
}
@Override
public CompoundTag getUpdateTag(HolderLookup.Provider registries) {
var tag = super.getUpdateTag(registries);
NBTUtil.encodeTo(TerminalSize.CODEC, registries, tag, NBT_TERMINAL_SIZE, terminalSize);
return tag;
}
@Override
protected void applyImplicitComponents(DataComponentInput component) {
super.applyImplicitComponents(component);
terminalSize = component.get(ModRegistry.DataComponents.TERMINAL_SIZE.get());
}
@Override
protected void collectSafeComponents(DataComponentMap.Builder builder) {
super.collectSafeComponents(builder);
builder.set(ModRegistry.DataComponents.TERMINAL_SIZE.get(), terminalSize);
}
@Override
@Deprecated
public void removeComponentsFromTag(CompoundTag tag) {
super.removeComponentsFromTag(tag);
tag.remove(NBT_TERMINAL_SIZE);
}
@Override
protected ServerComputer createComputer(int id) {
return new ServerComputer((ServerLevel) getLevel(), getBlockPos(), ServerComputer.properties(id, getFamily())
.label(getLabel())
.terminalSize(ConfigSpec.computerTermWidth.get(), ConfigSpec.computerTermHeight.get())
.terminalSize(terminalSize != null ? terminalSize : new TerminalSize(ConfigSpec.computerTermWidth.get(), ConfigSpec.computerTermHeight.get()))
.storageCapacity(storageCapacity)
);
}

View File

@@ -295,10 +295,9 @@ public class ServerComputer implements ComputerEnvironment, ComputerEvents.Recei
return this;
}
public Properties terminalSize(int width, int height) {
if (width <= 0 || height <= 0) throw new IllegalArgumentException("Terminal size must be positive");
this.terminalWidth = width;
this.terminalHeight = height;
public Properties terminalSize(TerminalSize size) {
this.terminalWidth = size.width();
this.terminalHeight = size.height();
return this;
}

View File

@@ -0,0 +1,43 @@
// SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.shared.computer.core;
import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import io.netty.buffer.ByteBuf;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.util.ExtraCodecs;
/**
* The size of a computer terminal.
*
* @param width The terminal's width.
* @param height The terminal's height.
*/
public record TerminalSize(int width, int height) {
public static final int MAX_SIZE = 255;
public static final Codec<TerminalSize> CODEC = RecordCodecBuilder.create(instance -> instance.group(
ExtraCodecs.intRange(1, MAX_SIZE).fieldOf("width").forGetter(TerminalSize::width),
ExtraCodecs.intRange(1, MAX_SIZE).fieldOf("height").forGetter(TerminalSize::height)
).apply(instance, TerminalSize::new));
public static final StreamCodec<ByteBuf, TerminalSize> STREAM_CODEC = StreamCodec.composite(
ByteBufCodecs.VAR_INT, TerminalSize::width,
ByteBufCodecs.VAR_INT, TerminalSize::height,
TerminalSize::new
);
public TerminalSize {
checkBounds("width", width);
checkBounds("height", height);
}
private static void checkBounds(String name, int value) {
if (value < 1 || value > MAX_SIZE) {
throw new IllegalArgumentException(name + " must be between 1 and " + MAX_SIZE);
}
}
}

View File

@@ -1,29 +0,0 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.shared.computer.items;
import dan200.computercraft.shared.computer.blocks.ComputerBlock;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.block.state.BlockState;
import org.jspecify.annotations.Nullable;
/**
* A {@link ComputerItem} which prevents players placing it without permission.
*
* @see net.minecraft.world.item.GameMasterBlockItem
* @see dan200.computercraft.shared.computer.blocks.CommandComputerBlock
*/
public class CommandComputerItem extends ComputerItem {
public CommandComputerItem(ComputerBlock<?> block, Properties settings) {
super(block, settings);
}
@Override
protected @Nullable BlockState getPlacementState(BlockPlaceContext context) {
// Prohibit players placing this block in survival or when not opped.
var player = context.getPlayer();
return player != null && !player.canUseGameMasterBlocks() ? null : super.getPlacementState(context);
}
}

View File

@@ -1,33 +0,0 @@
// Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
//
// SPDX-License-Identifier: LicenseRef-CCPL
package dan200.computercraft.shared.computer.items;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.computer.blocks.AbstractComputerBlock;
import net.minecraft.ChatFormatting;
import net.minecraft.core.component.DataComponents;
import net.minecraft.network.chat.Component;
import net.minecraft.world.item.BlockItem;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.TooltipFlag;
import java.util.List;
public class ComputerItem extends BlockItem {
public ComputerItem(AbstractComputerBlock<?> block, Properties settings) {
super(block, settings);
}
@Override
public void appendHoverText(ItemStack stack, TooltipContext context, List<Component> list, TooltipFlag options) {
if (options.isAdvanced() || !stack.has(DataComponents.CUSTOM_NAME)) {
var id = stack.get(ModRegistry.DataComponents.COMPUTER_ID.get());
if (id != null) {
list.add(Component.translatable("gui.computercraft.tooltip.computer_id", id.id())
.withStyle(ChatFormatting.GRAY));
}
}
}
}

View File

@@ -74,7 +74,7 @@ public class ServerInputState<T extends AbstractContainerMenu & ComputerMenu> im
}
private static boolean isValidClipboard(ByteBuffer buffer) {
for (int i = buffer.remaining(), max = buffer.limit(); i < max; i++) {
for (int i = buffer.position(), max = buffer.limit(); i < max; i++) {
if (!StringUtil.isTypableChar(buffer.get(i))) return false;
}
return true;

View File

@@ -11,6 +11,7 @@ import dan200.computercraft.core.Logging;
import dan200.computercraft.core.apis.http.NetworkUtils;
import dan200.computercraft.core.apis.http.options.ProxyType;
import dan200.computercraft.core.computer.mainthread.MainThreadConfig;
import dan200.computercraft.shared.computer.core.TerminalSize;
import dan200.computercraft.shared.peripheral.monitor.MonitorRenderer;
import dan200.computercraft.shared.platform.PlatformHelper;
import org.apache.logging.log4j.LogManager;
@@ -344,13 +345,13 @@ public final class ConfigSpec {
.push("term_sizes");
builder.comment("Terminal size of computers.").push("computer");
computerTermWidth = builder.comment("Width of computer terminal").defineInRange("width", Config.DEFAULT_COMPUTER_TERM_WIDTH, 1, 255);
computerTermHeight = builder.comment("Height of computer terminal").defineInRange("height", Config.DEFAULT_COMPUTER_TERM_HEIGHT, 1, 255);
computerTermWidth = builder.comment("Width of computer terminal").defineInRange("width", Config.DEFAULT_COMPUTER_TERM_WIDTH, 1, TerminalSize.MAX_SIZE);
computerTermHeight = builder.comment("Height of computer terminal").defineInRange("height", Config.DEFAULT_COMPUTER_TERM_HEIGHT, 1, TerminalSize.MAX_SIZE);
builder.pop();
builder.comment("Terminal size of pocket computers.").push("pocket_computer");
pocketTermWidth = builder.comment("Width of pocket computer terminal").defineInRange("width", Config.DEFAULT_POCKET_TERM_WIDTH, 1, 255);
pocketTermHeight = builder.comment("Height of pocket computer terminal").defineInRange("height", Config.DEFAULT_POCKET_TERM_HEIGHT, 1, 255);
pocketTermWidth = builder.comment("Width of pocket computer terminal").defineInRange("width", Config.DEFAULT_POCKET_TERM_WIDTH, 1, TerminalSize.MAX_SIZE);
pocketTermHeight = builder.comment("Height of pocket computer terminal").defineInRange("height", Config.DEFAULT_POCKET_TERM_HEIGHT, 1, TerminalSize.MAX_SIZE);
builder.pop();
builder.comment("Maximum size of monitors (in blocks).").push("monitor");

View File

@@ -8,9 +8,7 @@ import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.filesystem.Mount;
import dan200.computercraft.api.media.IMedia;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.computer.items.ComputerItem;
import dan200.computercraft.shared.config.ConfigSpec;
import dan200.computercraft.shared.media.items.DiskItem;
import dan200.computercraft.shared.util.DataComponentUtil;
import dan200.computercraft.shared.util.NonNegativeId;
import dan200.computercraft.shared.util.StorageCapacity;
@@ -27,12 +25,12 @@ import java.util.function.Supplier;
*/
public final class MountMedia implements IMedia {
/**
* A {@link MountMedia} implementation for {@linkplain ComputerItem computers}.
* A {@link MountMedia} implementation for {@linkplain ModRegistry.DataComponents#COMPUTER_ID computers}.
*/
public static final IMedia COMPUTER = new MountMedia("computer", ModRegistry.DataComponents.COMPUTER_ID, false, ConfigSpec.computerSpaceLimit);
/**
* A {@link MountMedia} implementation for {@linkplain DiskItem disks}.
* A {@link MountMedia} implementation for {@linkplain ModRegistry.Items#DISK disks}.
*/
public static final IMedia DISK = new MountMedia("disk", ModRegistry.DataComponents.DISK_ID, true, ConfigSpec.floppySpaceLimit);

View File

@@ -1,51 +1,24 @@
// Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
// SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers
//
// SPDX-License-Identifier: LicenseRef-CCPL
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.shared.media.items;
import dan200.computercraft.annotations.ForgeOverride;
import dan200.computercraft.core.util.Colour;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.util.NonNegativeId;
import net.minecraft.ChatFormatting;
import net.minecraft.core.BlockPos;
import net.minecraft.network.chat.Component;
import net.minecraft.world.entity.player.Player;
import dan200.computercraft.shared.peripheral.diskdrive.DiskDriveBlock;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.item.component.DyedItemColor;
import net.minecraft.world.level.LevelReader;
import java.util.List;
import net.minecraft.world.item.context.UseOnContext;
/**
* An item that can be shift-right-clicked into a {@link DiskDriveBlock}.
*/
public class DiskItem extends Item {
public DiskItem(Properties settings) {
super(settings);
}
@Override
public void appendHoverText(ItemStack stack, TooltipContext context, List<Component> list, TooltipFlag options) {
if (options.isAdvanced()) {
var id = stack.get(ModRegistry.DataComponents.DISK_ID.get());
if (id != null) {
list.add(Component.translatable("gui.computercraft.tooltip.disk_id", id.id())
.withStyle(ChatFormatting.GRAY));
}
}
}
@ForgeOverride
public boolean doesSneakBypassUse(ItemStack stack, LevelReader world, BlockPos pos, Player player) {
return true;
}
public static int getDiskID(ItemStack stack) {
return NonNegativeId.getId(stack.get(ModRegistry.DataComponents.DISK_ID.get()));
}
public static int getColour(ItemStack stack) {
return DyedItemColor.getOrDefault(stack, Colour.WHITE.getARGB());
public InteractionResult useOn(UseOnContext context) {
return DiskDriveBlock.defaultUseItemOn(context);
}
}

View File

@@ -12,11 +12,16 @@ import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.shared.ModRegistry;
import io.netty.buffer.ByteBuf;
import net.minecraft.core.component.DataComponentHolder;
import net.minecraft.network.chat.Component;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.item.component.TooltipProvider;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Stream;
/**
@@ -27,7 +32,7 @@ import java.util.stream.Stream;
* @see PrintoutItem
* @see dan200.computercraft.shared.ModRegistry.DataComponents#PRINTOUT
*/
public record PrintoutData(String title, List<Line> lines) implements PrintoutContents {
public record PrintoutData(String title, List<Line> lines) implements PrintoutContents, TooltipProvider {
public static final int LINE_LENGTH = 25;
public static final int LINES_PER_PAGE = 21;
public static final int MAX_PAGES = 16;
@@ -71,6 +76,11 @@ public record PrintoutData(String title, List<Line> lines) implements PrintoutCo
PrintoutData::new
);
@Override
public void addToTooltip(Item.TooltipContext context, Consumer<Component> out, TooltipFlag flag) {
if (!title().isEmpty()) out.accept(Component.literal(title()));
}
/**
* A single line on our printed pages.
*

View File

@@ -14,29 +14,11 @@ import net.minecraft.world.SimpleMenuProvider;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.level.Level;
import java.util.List;
public class PrintoutItem extends Item {
public enum Type {
PAGE,
PAGES,
BOOK
}
private final Type type;
public PrintoutItem(Properties settings, Type type) {
public PrintoutItem(Properties settings) {
super(settings);
this.type = type;
}
@Override
public void appendHoverText(ItemStack stack, TooltipContext context, List<Component> list, TooltipFlag options) {
var title = PrintoutData.getOrEmpty(stack).title();
if (!title.isEmpty()) list.add(Component.literal(title));
}
@Override
@@ -49,8 +31,4 @@ public class PrintoutItem extends Item {
}
return new InteractionResultHolder<>(InteractionResult.sidedSuccess(world.isClientSide), stack);
}
public Type getType() {
return type;
}
}

View File

@@ -9,17 +9,25 @@ import com.mojang.serialization.codecs.RecordCodecBuilder;
import dan200.computercraft.shared.ModRegistry;
import net.minecraft.core.component.DataComponentHolder;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.chat.Component;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.item.component.TooltipProvider;
import java.util.function.Consumer;
/**
* Stores information about a {@linkplain TreasureDiskItem treasure disk's} mount.
* Stores information about a {@linkplain ModRegistry.Items#TREASURE_DISK treasure disk's} mount.
*
* @param name The name/title of the disk.
* @param path The subpath to the resource
* @see ModRegistry.DataComponents#TREASURE_DISK
*/
public record TreasureDisk(String name, String path) {
public record TreasureDisk(String name, String path) implements TooltipProvider {
public static final TreasureDisk UNDEFINED = new TreasureDisk("'missingno' by how did you get this anyway?", "undefined");
public static final Codec<TreasureDisk> CODEC = RecordCodecBuilder.create(i -> i.group(
Codec.STRING.fieldOf("name").forGetter(TreasureDisk::name),
Codec.STRING.fieldOf("path").forGetter(TreasureDisk::path)
@@ -32,7 +40,11 @@ public record TreasureDisk(String name, String path) {
);
public static String getTitle(DataComponentHolder holder) {
var nbt = holder.get(ModRegistry.DataComponents.TREASURE_DISK.get());
return nbt != null ? nbt.name() : "'missingno' by how did you get this anyway?";
return holder.getOrDefault(ModRegistry.DataComponents.TREASURE_DISK.get(), UNDEFINED).name();
}
@Override
public void addToTooltip(Item.TooltipContext context, Consumer<Component> out, TooltipFlag flags) {
out.accept(Component.literal(name()));
}
}

View File

@@ -1,32 +0,0 @@
// Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
//
// SPDX-License-Identifier: LicenseRef-CCPL
package dan200.computercraft.shared.media.items;
import dan200.computercraft.annotations.ForgeOverride;
import net.minecraft.core.BlockPos;
import net.minecraft.network.chat.Component;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.level.LevelReader;
import java.util.List;
public class TreasureDiskItem extends Item {
public TreasureDiskItem(Properties settings) {
super(settings);
}
@Override
public void appendHoverText(ItemStack stack, TooltipContext context, List<Component> list, TooltipFlag tooltipOptions) {
list.add(Component.literal(TreasureDisk.getTitle(stack)));
}
@ForgeOverride
public boolean doesSneakBypassUse(ItemStack stack, LevelReader world, BlockPos pos, Player player) {
return true;
}
}

View File

@@ -17,7 +17,7 @@ import org.jspecify.annotations.Nullable;
import java.io.IOException;
/**
* An {@link IMedia} instance for {@linkplain TreasureDiskItem treasure disks}.
* An {@link IMedia} instance for {@linkplain ModRegistry.Items#TREASURE_DISK treasure disks}.
*/
public final class TreasureDiskMedia implements IMedia {
public static final IMedia INSTANCE = new TreasureDiskMedia();

View File

@@ -7,13 +7,11 @@ package dan200.computercraft.shared.peripheral.diskdrive;
import com.mojang.serialization.MapCodec;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.common.HorizontalContainerBlock;
import dan200.computercraft.shared.platform.PlatformHelper;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.ItemInteractionResult;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.context.UseOnContext;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.BaseEntityBlock;
import net.minecraft.world.level.block.Block;
@@ -23,7 +21,6 @@ import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.EnumProperty;
import net.minecraft.world.phys.BlockHitResult;
import org.jspecify.annotations.Nullable;
public class DiskDriveBlock extends HorizontalContainerBlock {
@@ -50,19 +47,24 @@ public class DiskDriveBlock extends HorizontalContainerBlock {
return CODEC;
}
@Override
protected ItemInteractionResult useItemOn(ItemStack stack, BlockState state, Level level, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) {
if (player.isCrouching() && level.getBlockEntity(pos) instanceof DiskDriveBlockEntity drive) {
// Try to put a disk into the drive
if (stack.isEmpty()) return ItemInteractionResult.SKIP_DEFAULT_BLOCK_INTERACTION;
if (!level.isClientSide && drive.getDiskStack().isEmpty() && PlatformHelper.get().getMedia(stack) != null) {
drive.setDiskStack(stack.split(1));
/**
* A default implementation of {@link Item#useOn(UseOnContext)} for items that can be placed into a drive.
*
* @param context The context of this item usage action.
* @return Whether the item was placed or not.
*/
public static InteractionResult defaultUseItemOn(UseOnContext context) {
var level = context.getLevel();
var blockPos = context.getClickedPos();
var blockState = level.getBlockState(blockPos);
if (blockState.is(ModRegistry.Blocks.DISK_DRIVE.get()) && blockState.getValue(STATE) == DiskDriveState.EMPTY) {
if (!level.isClientSide && level.getBlockEntity(blockPos) instanceof DiskDriveBlockEntity drive && drive.getDiskStack().isEmpty()) {
drive.setDiskStack(context.getItemInHand().split(1));
}
return ItemInteractionResult.sidedSuccess(level.isClientSide);
return InteractionResult.sidedSuccess(level.isClientSide);
}
return super.useItemOn(stack, state, level, pos, player, hand, hit);
return InteractionResult.PASS;
}
@Nullable

View File

@@ -9,7 +9,7 @@ import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.core.util.StringUtil;
import dan200.computercraft.shared.media.items.DiskItem;
import dan200.computercraft.shared.ModRegistry;
import org.jspecify.annotations.Nullable;
import java.util.Optional;
@@ -170,8 +170,8 @@ public class DiskDrivePeripheral implements IPeripheral {
*/
@LuaFunction
public final @Nullable Object @Nullable [] getDiskID() {
var disk = diskDrive.getMedia().stack();
return disk.getItem() instanceof DiskItem ? new Object[]{ DiskItem.getDiskID(disk) } : null;
var id = diskDrive.getMedia().stack().get(ModRegistry.DataComponents.DISK_ID.get());
return id != null ? new Object[]{ id.id() } : null;
}
@Override

View File

@@ -41,9 +41,9 @@ public abstract class AbstractFluidMethods<T> implements GenericPeripheral {
* The returned table is sparse, and so empty tanks will be `nil` - it is recommended to loop over using [`pairs`]
* rather than [`ipairs`].
*
* @param fluids The current fluid handler.
* @param fluids The current fluid storage.
* @return All tanks.
* @cc.treturn { (table|nil)... } All tanks in this fluid storage.
* @cc.treturn { (table|nil)... } Basic information about all fluids in this fluid storage.
*/
@LuaFunction(mainThread = true)
public abstract Map<Integer, Map<String, ?>> tanks(T fluids);

View File

@@ -56,8 +56,8 @@ public abstract class AbstractInventoryMethods<T> implements GenericPeripheral {
* rather than [`ipairs`].
*
* @param inventory The current inventory.
* @return All items in this inventory.
* @cc.treturn { (table|nil)... } All items in this inventory.
* @return Basic information about all items in this inventory.
* @cc.treturn { (table|nil)... } Basic information about all items in this inventory.
* @cc.usage Find an adjacent chest and print all items in it.
*
* <pre>{@code
@@ -86,9 +86,8 @@ public abstract class AbstractInventoryMethods<T> implements GenericPeripheral {
*
* @param inventory The current inventory.
* @param slot The slot to get information about.
* @return Information about the item in this slot, or {@code nil} if not present.
* @return Information about the item in this slot, or {@code nil} if it is empty.
* @throws LuaException If the slot is out of range.
* @cc.treturn table Information about the item in this slot, or {@code nil} if not present.
* @cc.usage Print some information about the first in a chest.
*
* <pre>{@code
@@ -106,7 +105,7 @@ public abstract class AbstractInventoryMethods<T> implements GenericPeripheral {
*/
@Nullable
@LuaFunction(mainThread = true)
public abstract Map<String, ?> getItemDetail(T inventory, int slot) throws LuaException;
public abstract Map<?, ?> getItemDetail(T inventory, int slot) throws LuaException;
/**
* Get the maximum number of items which can be stored in this slot.

View File

@@ -252,11 +252,6 @@ public class CableBlock extends Block implements SimpleWaterloggedBlock, EntityB
if (world.getBlockEntity(pos) instanceof CableBlockEntity modem) modem.neighborChanged(neighbourPos);
}
@ForgeOverride
public final void onNeighborChange(BlockState state, LevelReader world, BlockPos pos, BlockPos neighbour) {
if (world.getBlockEntity(pos) instanceof CableBlockEntity modem) modem.neighborChanged(neighbour);
}
@Override
protected void tick(BlockState state, ServerLevel world, BlockPos pos, RandomSource rand) {
if (world.getBlockEntity(pos) instanceof CableBlockEntity modem) modem.blockTick();

View File

@@ -4,7 +4,6 @@
package dan200.computercraft.shared.peripheral.modem.wired;
import dan200.computercraft.annotations.ForgeOverride;
import dan200.computercraft.shared.ModRegistry;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
@@ -14,7 +13,6 @@ import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.EntityBlock;
import net.minecraft.world.level.block.entity.BlockEntity;
@@ -62,13 +60,6 @@ public class WiredModemFullBlock extends Block implements EntityBlock {
}
}
@ForgeOverride
public final void onNeighborChange(BlockState state, LevelReader level, BlockPos pos, BlockPos neighbour) {
if (state.getValue(PERIPHERAL_ON) && level.getBlockEntity(pos) instanceof WiredModemFullBlockEntity modem) {
modem.neighborChanged(neighbour);
}
}
@Override
protected void tick(BlockState state, ServerLevel world, BlockPos pos, RandomSource rand) {
if (world.getBlockEntity(pos) instanceof WiredModemFullBlockEntity modem) modem.blockTick();

View File

@@ -39,7 +39,7 @@ public class WirelessModemBlockEntity extends BlockEntity {
@Override
public boolean equals(@Nullable IPeripheral other) {
return this == other || (other instanceof Peripheral && entity == ((Peripheral) other).entity);
return this == other || (other instanceof Peripheral o && entity == o.entity);
}
@Override

View File

@@ -40,7 +40,6 @@ import org.jspecify.annotations.Nullable;
* monitor.setCursorPos(1, 1)
* monitor.write("Hello, world!")
* }</pre>
*
* @cc.see monitor_resize Queued when a monitor is resized.
* @cc.see monitor_touch Queued when an advanced monitor is clicked.
*/
@@ -95,7 +94,7 @@ public class MonitorPeripheral extends TermMethods implements IPeripheral {
@Override
public boolean equals(@Nullable IPeripheral other) {
return other instanceof MonitorPeripheral && monitor == ((MonitorPeripheral) other).monitor;
return other instanceof MonitorPeripheral o && monitor == o.monitor;
}
private ServerMonitor getMonitor() throws LuaException {

View File

@@ -58,11 +58,10 @@ public final class MonitorWatcher {
if (monitor == null) continue;
var pos = tile.getBlockPos();
var world = tile.getLevel();
if (!(world instanceof ServerLevel)) continue;
if (!(tile.getLevel() instanceof ServerLevel level)) continue;
var chunk = world.getChunkAt(pos);
if (((ServerLevel) world).getChunkSource().chunkMap.getPlayers(chunk.getPos(), false).isEmpty()) {
var chunk = level.getChunkAt(pos);
if (level.getChunkSource().chunkMap.getPlayers(chunk.getPos(), false).isEmpty()) {
continue;
}

View File

@@ -170,8 +170,7 @@ public final class PrinterBlockEntity extends AbstractContainerBlockEntity imple
}
static boolean isPaper(ItemStack stack) {
var item = stack.getItem();
return item == Items.PAPER || item == ModRegistry.Items.PRINTED_PAGE.get();
return stack.is(Items.PAPER) || stack.is(ModRegistry.Items.PRINTED_PAGE.get());
}
private boolean canInputPage() {

View File

@@ -9,6 +9,7 @@ import dan200.computercraft.core.util.Nullability;
import dan200.computercraft.shared.network.client.SpeakerStopClientMessage;
import dan200.computercraft.shared.network.server.ServerNetworking;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
@@ -47,13 +48,18 @@ public class SpeakerBlockEntity extends BlockEntity {
}
@Override
public SpeakerPosition getPosition() {
protected ServerLevel getLevel() {
return (ServerLevel) speaker.getLevel();
}
@Override
protected SpeakerPosition getPosition() {
return SpeakerPosition.of(speaker.getLevel(), Vec3.atCenterOf(speaker.getBlockPos()));
}
@Override
public boolean equals(@Nullable IPeripheral other) {
return this == other || (other instanceof Peripheral && speaker == ((Peripheral) other).speaker);
return this == other || (other instanceof Peripheral o && speaker == o.speaker);
}
}
}

View File

@@ -32,7 +32,10 @@ import net.minecraft.util.Mth;
import net.minecraft.world.level.block.state.properties.NoteBlockInstrument;
import org.jspecify.annotations.Nullable;
import java.util.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import static dan200.computercraft.api.lua.LuaValues.checkFinite;
@@ -155,7 +158,9 @@ public abstract class SpeakerPeripheral implements IPeripheral {
}
}
public abstract SpeakerPosition getPosition();
protected abstract ServerLevel getLevel();
protected abstract SpeakerPosition getPosition();
public UUID getSource() {
return source;
@@ -255,8 +260,8 @@ public abstract class SpeakerPeripheral implements IPeripheral {
// Prevent playing music discs.
var soundEvent = BuiltInRegistries.SOUND_EVENT.get(identifier);
// TODO: Build a set of sound events at server startup, and cache this.
var level = Objects.requireNonNull(getPosition().level());
if (soundEvent != null && level.registryAccess().registry(Registries.JUKEBOX_SONG).orElseThrow().stream().anyMatch(x -> x.soundEvent().value() == soundEvent)) {
var level = getLevel();
if (soundEvent != null && level.registryAccess().registryOrThrow(Registries.JUKEBOX_SONG).stream().anyMatch(x -> x.soundEvent().value() == soundEvent)) {
return false;
}

View File

@@ -20,10 +20,8 @@ public abstract class UpgradeSpeakerPeripheral extends SpeakerPeripheral {
super.detach(computer);
// We could be in the process of shutting down the server, so we can't send packets in this case.
var level = getPosition().level();
if (level == null) return;
var server = level.getServer();
if (server == null || server.isStopped()) return;
var server = getLevel().getServer();
if (server.isStopped()) return;
ServerNetworking.sendToAllPlayers(new SpeakerStopClientMessage(getSource()), server);
}

View File

@@ -41,11 +41,12 @@ public final class PocketBrain implements IPocketAccess {
private int colour = -1;
private int lightColour = -1;
public PocketBrain(PocketHolder holder, @Nullable UpgradeData<IPocketUpgrade> upgrade, ServerComputer.Properties properties) {
public PocketBrain(PocketHolder holder, @Nullable UpgradeData<IPocketUpgrade> upgrade, int colour, ServerComputer.Properties properties) {
this.computer = new PocketServerComputer(this, holder, properties);
this.holder = holder;
this.position = holder.pos();
this.upgrade = upgrade;
this.colour = colour;
invalidatePeripheral();
}

View File

@@ -7,7 +7,6 @@ package dan200.computercraft.shared.pocket.core;
import dan200.computercraft.api.component.ComputerComponents;
import dan200.computercraft.shared.computer.core.ComputerState;
import dan200.computercraft.shared.computer.core.ServerComputer;
import dan200.computercraft.shared.config.ConfigSpec;
import dan200.computercraft.shared.network.client.PocketComputerDataMessage;
import dan200.computercraft.shared.network.client.PocketComputerDeletedClientMessage;
import dan200.computercraft.shared.network.server.ServerNetworking;
@@ -40,10 +39,7 @@ public final class PocketServerComputer extends ServerComputer {
private Set<ServerPlayer> tracking = Set.of();
PocketServerComputer(PocketBrain brain, PocketHolder holder, ServerComputer.Properties properties) {
super(holder.level(), holder.blockPos(), properties
.terminalSize(ConfigSpec.pocketTermWidth.get(), ConfigSpec.pocketTermHeight.get())
.addComponent(ComputerComponents.POCKET, brain)
);
super(holder.level(), holder.blockPos(), properties.addComponent(ComputerComponents.POCKET, brain));
this.brain = brain;
}

View File

@@ -11,19 +11,16 @@ import dan200.computercraft.api.upgrades.UpgradeData;
import dan200.computercraft.core.computer.ComputerSide;
import dan200.computercraft.impl.PocketUpgrades;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.computer.core.ServerComputer;
import dan200.computercraft.shared.computer.core.ServerComputerRegistry;
import dan200.computercraft.shared.computer.core.ServerContext;
import dan200.computercraft.shared.computer.core.*;
import dan200.computercraft.shared.computer.inventory.ComputerMenuWithoutInventory;
import dan200.computercraft.shared.computer.items.ServerComputerReference;
import dan200.computercraft.shared.config.ConfigSpec;
import dan200.computercraft.shared.network.container.ComputerContainerData;
import dan200.computercraft.shared.platform.PlatformHelper;
import dan200.computercraft.shared.pocket.core.PocketBrain;
import dan200.computercraft.shared.pocket.core.PocketHolder;
import dan200.computercraft.shared.pocket.core.PocketServerComputer;
import dan200.computercraft.shared.util.*;
import net.minecraft.ChatFormatting;
import net.minecraft.network.chat.Component;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
@@ -36,11 +33,10 @@ import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.item.component.DyedItemColor;
import net.minecraft.world.level.Level;
import org.jspecify.annotations.Nullable;
import java.util.List;
import java.util.Objects;
public class PocketComputerItem extends Item {
@@ -184,18 +180,6 @@ public class PocketComputerItem extends Item {
}
}
@Override
public void appendHoverText(ItemStack stack, TooltipContext context, List<Component> list, TooltipFlag flag) {
if (flag.isAdvanced() || getLabel(stack) == null) {
var id = stack.get(ModRegistry.DataComponents.COMPUTER_ID.get());
if (id != null) {
list.add(Component.translatable("gui.computercraft.tooltip.computer_id", id.id())
.withStyle(ChatFormatting.GRAY));
}
}
}
@Nullable
@ForgeOverride
public String getCreatorModId(ItemStack stack) {
@@ -217,10 +201,14 @@ public class PocketComputerItem extends Item {
var computerID = NonNegativeId.getOrCreate(level.getServer(), stack, ModRegistry.DataComponents.COMPUTER_ID.get(), IDAssigner.COMPUTER);
var brain = new PocketBrain(
holder, getUpgradeWithData(stack),
holder, getUpgradeWithData(stack), DyedItemColor.getOrDefault(stack, -1),
ServerComputer.properties(computerID, getFamily())
.label(getLabel(stack))
.storageCapacity(StorageCapacity.getOrDefault(stack.get(ModRegistry.DataComponents.STORAGE_CAPACITY.get()), -1))
.terminalSize(stack.getOrDefault(
ModRegistry.DataComponents.TERMINAL_SIZE.get(),
new TerminalSize(ConfigSpec.pocketTermWidth.get(), ConfigSpec.pocketTermHeight.get())
))
);
var computer = brain.computer();
@@ -256,10 +244,14 @@ public class PocketComputerItem extends Item {
// item. However, if we've just crafted the computer with an upgrade, we should sync the other way, and update
// the computer.
var server = level.getServer();
if (server != null) {
var computer = getServerComputer(server, stack);
if (computer != null) computer.getBrain().setUpgrade(getUpgradeWithData(stack));
}
if (server == null) return;
var computer = getServerComputer(server, stack);
if (computer == null) return;
var brain = computer.getBrain();
brain.setUpgrade(getUpgradeWithData(stack));
brain.setColour(DyedItemColor.getOrDefault(stack, -1));
}
public ComputerFamily getFamily() {

View File

@@ -26,8 +26,7 @@ public class PocketSpeaker extends AbstractPocketUpgrade {
@Override
public void update(IPocketAccess access, @Nullable IPeripheral peripheral) {
if (!(peripheral instanceof PocketSpeakerPeripheral)) return;
((PocketSpeakerPeripheral) peripheral).update();
if (peripheral instanceof PocketSpeakerPeripheral speaker) speaker.update();
}
@Override

View File

@@ -8,17 +8,23 @@ import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.pocket.IPocketAccess;
import dan200.computercraft.shared.peripheral.speaker.SpeakerPosition;
import dan200.computercraft.shared.peripheral.speaker.UpgradeSpeakerPeripheral;
import net.minecraft.server.level.ServerLevel;
import org.jspecify.annotations.Nullable;
public class PocketSpeakerPeripheral extends UpgradeSpeakerPeripheral {
final class PocketSpeakerPeripheral extends UpgradeSpeakerPeripheral {
private final IPocketAccess access;
public PocketSpeakerPeripheral(IPocketAccess access) {
PocketSpeakerPeripheral(IPocketAccess access) {
this.access = access;
}
@Override
public SpeakerPosition getPosition() {
protected ServerLevel getLevel() {
return access.getLevel();
}
@Override
protected SpeakerPosition getPosition() {
var entity = access.getEntity();
return entity == null ? SpeakerPosition.of(access.getLevel(), access.getPosition()) : SpeakerPosition.of(entity);
}

View File

@@ -17,8 +17,8 @@ import net.minecraft.world.item.crafting.CraftingInput;
import net.minecraft.world.item.crafting.Ingredient;
import net.minecraft.world.level.ItemLike;
import net.minecraft.world.level.storage.loot.functions.CopyComponentsFunction;
import org.jspecify.annotations.Nullable;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

View File

@@ -13,7 +13,9 @@ import dan200.computercraft.core.metrics.Metrics;
import dan200.computercraft.core.metrics.MetricsObserver;
import dan200.computercraft.shared.peripheral.generic.methods.AbstractInventoryMethods;
import dan200.computercraft.shared.turtle.core.*;
import org.jspecify.annotations.Nullable;
import java.util.Map;
import java.util.Optional;
/**
@@ -659,6 +661,7 @@ public class TurtleAPI implements ILuaAPI {
* @cc.treturn [2] string The reason equipping this item failed.
* @cc.since 1.6
* @see #equipRight()
* @see #getEquippedLeft()
*/
@LuaFunction
public final MethodResult equipLeft() {
@@ -678,12 +681,45 @@ public class TurtleAPI implements ILuaAPI {
* @cc.treturn [2] string The reason equipping this item failed.
* @cc.since 1.6
* @see #equipLeft()
* @see #getEquippedRight()
*/
@LuaFunction
public final MethodResult equipRight() {
return trackCommand(new TurtleEquipCommand(TurtleSide.RIGHT));
}
/**
* Get the upgrade currently equipped on the left of the turtle.
* <p>
* This returns information about the currently equipped item, in the same form as
* {@link #getItemDetail(ILuaContext, Optional, Optional)}.
*
* @return Information about the currently equipped item, or {@code nil} if no upgrade is equipped.
* @see #equipLeft()
* @cc.since 1.116.0
*/
@LuaFunction(mainThread = true)
public final @Nullable Map<?, ?> getEquippedLeft() {
var upgrade = turtle.getUpgradeWithData(TurtleSide.LEFT);
return upgrade == null ? null : VanillaDetailRegistries.ITEM_STACK.getDetails(upgrade.getUpgradeItem());
}
/**
* Get the upgrade currently equipped on the right of the turtle.
* <p>
* This returns information about the currently equipped item, in the same form as
* {@link #getItemDetail(ILuaContext, Optional, Optional)}.
*
* @return Information about the currently equipped item, or {@code nil} if no upgrade is equipped.
* @see #equipRight()
* @cc.since 1.116.0
*/
@LuaFunction(mainThread = true)
public final @Nullable Map<?, ?> getEquippedRight() {
var upgrade = turtle.getUpgradeWithData(TurtleSide.RIGHT);
return upgrade == null ? null : VanillaDetailRegistries.ITEM_STACK.getDetails(upgrade.getUpgradeItem());
}
/**
* Get information about the block in front of the turtle.
*
@@ -745,7 +781,7 @@ public class TurtleAPI implements ILuaAPI {
* more information about the item at the cost of taking longer to run.
* @return The command result.
* @throws LuaException If the slot is out of range.
* @cc.treturn nil|table Information about the given slot, or {@code nil} if it is empty.
* @cc.treturn nil|table Information about the item in this slot, or {@code nil} if it is empty.
* @cc.since 1.64
* @cc.changed 1.90.0 Added detailed parameter.
* @cc.usage Print the current slot, assuming it contains 13 dirt.

View File

@@ -18,6 +18,7 @@ import dan200.computercraft.shared.computer.blocks.ComputerPeripheral;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.computer.core.ComputerState;
import dan200.computercraft.shared.computer.core.ServerComputer;
import dan200.computercraft.shared.computer.core.TerminalSize;
import dan200.computercraft.shared.config.Config;
import dan200.computercraft.shared.container.BasicContainer;
import dan200.computercraft.shared.platform.PlatformHelper;
@@ -81,7 +82,7 @@ public class TurtleBlockEntity extends AbstractComputerBlockEntity implements Ba
protected ServerComputer createComputer(int id) {
var computer = new ServerComputer((ServerLevel) getLevel(), getBlockPos(), ServerComputer.properties(id, getFamily())
.label(getLabel())
.terminalSize(Config.TURTLE_TERM_WIDTH, Config.TURTLE_TERM_HEIGHT)
.terminalSize(new TerminalSize(Config.TURTLE_TERM_WIDTH, Config.TURTLE_TERM_HEIGHT))
.storageCapacity(storageCapacity)
.addComponent(ComputerComponents.TURTLE, brain)
);
@@ -169,8 +170,8 @@ public class TurtleBlockEntity extends AbstractComputerBlockEntity implements Ba
}
@Override
protected void collectImplicitComponents(DataComponentMap.Builder builder) {
super.collectImplicitComponents(builder);
protected void collectSafeComponents(DataComponentMap.Builder builder) {
super.collectSafeComponents(builder);
builder.set(DataComponents.DYED_COLOR, brain.getColour() == -1 ? null : new DyedItemColor(brain.getColour(), false));
builder.set(ModRegistry.DataComponents.OVERLAY.get(), brain.getOverlay());

View File

@@ -62,14 +62,13 @@ public class TurtleDropCommand implements TurtleCommand {
}
}
switch (transferred) {
case ContainerTransfer.NO_SPACE:
return TurtleCommandResult.failure("No space for items");
case ContainerTransfer.NO_ITEMS:
return TurtleCommandResult.failure("No items to drop");
default:
return switch (transferred) {
case ContainerTransfer.NO_SPACE -> TurtleCommandResult.failure("No space for items");
case ContainerTransfer.NO_ITEMS -> TurtleCommandResult.failure("No items to drop");
default -> {
turtle.playAnimation(TurtleAnimation.WAIT);
return TurtleCommandResult.success();
}
yield TurtleCommandResult.success();
}
};
}
}

View File

@@ -50,15 +50,14 @@ public class TurtleSuckCommand implements TurtleCommand {
if (inventory != null) {
// Take from inventory of thing in front
var transferred = inventory.moveTo(TurtleUtil.getOffsetInventory(turtle), quantity);
switch (transferred) {
case ContainerTransfer.NO_SPACE:
return TurtleCommandResult.failure("No space for items");
case ContainerTransfer.NO_ITEMS:
return TurtleCommandResult.failure("No items to take");
default:
return switch (transferred) {
case ContainerTransfer.NO_SPACE -> TurtleCommandResult.failure("No space for items");
case ContainerTransfer.NO_ITEMS -> TurtleCommandResult.failure("No items to take");
default -> {
turtle.playAnimation(TurtleAnimation.WAIT);
return TurtleCommandResult.success();
}
yield TurtleCommandResult.success();
}
};
} else {
// Suck up loose items off the ground
var aabb = new AABB(

View File

@@ -11,18 +11,18 @@ import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.api.upgrades.UpgradeData;
import dan200.computercraft.impl.TurtleUpgrades;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.computer.items.ComputerItem;
import dan200.computercraft.shared.turtle.TurtleOverlay;
import dan200.computercraft.shared.turtle.blocks.TurtleBlock;
import net.minecraft.core.cauldron.CauldronInteraction;
import net.minecraft.core.component.DataComponents;
import net.minecraft.network.chat.Component;
import net.minecraft.world.ItemInteractionResult;
import net.minecraft.world.item.BlockItem;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.LayeredCauldronBlock;
import org.jspecify.annotations.Nullable;
public class TurtleItem extends ComputerItem {
public class TurtleItem extends BlockItem {
public TurtleItem(TurtleBlock block, Properties settings) {
super(block, settings);
}

View File

@@ -109,7 +109,9 @@ public final class TurtleUpgradeRecipe extends CustomRecipe {
TurtleItem.getUpgradeWithData(turtle, TurtleSide.RIGHT),
};
// Get the upgrades for the new items
// Get the upgrades for the new items.
// Note: because the turtle is facing towards us, the directions are flipped. Items placed to the left
// of the turtle, are equipped on its right (and vice versa).
var items = new ItemStack[]{ rightItem, leftItem };
for (var i = 0; i < 2; i++) {
if (!items[i].isEmpty()) {

View File

@@ -13,6 +13,7 @@ import dan200.computercraft.api.upgrades.UpgradeType;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.peripheral.speaker.SpeakerPosition;
import dan200.computercraft.shared.peripheral.speaker.UpgradeSpeakerPeripheral;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.phys.Vec3;
import org.jspecify.annotations.Nullable;
@@ -26,7 +27,12 @@ public class TurtleSpeaker extends AbstractTurtleUpgrade {
}
@Override
public SpeakerPosition getPosition() {
protected ServerLevel getLevel() {
return (ServerLevel) turtle.getLevel();
}
@Override
protected SpeakerPosition getPosition() {
return SpeakerPosition.of(turtle.getLevel(), Vec3.atCenterOf(turtle.getPosition()));
}

View File

@@ -256,8 +256,8 @@ public class TurtleTool extends AbstractTurtleUpgrade {
EnchantmentHelper.doPostAttackEffects(player.serverLevel(), entity, source);
// Damage the original item stack.
if (!tool.isEmpty() && entity instanceof LivingEntity && didHurt) {
tool.postHurtEnemy((LivingEntity) entity, player);
if (!tool.isEmpty() && entity instanceof LivingEntity living && didHurt) {
tool.postHurtEnemy(living, player);
}
return true;

View File

@@ -70,9 +70,9 @@ public final class DropConsumer {
public static boolean onEntitySpawn(Entity entity) {
// Capture any nearby item spawns
if (dropWorld == entity.level() && entity instanceof ItemEntity
if (dropWorld == entity.level() && entity instanceof ItemEntity item
&& assertNonNull(dropBounds).contains(entity.position())) {
handleDrops(((ItemEntity) entity).getItem());
handleDrops(item.getItem());
return true;
}

View File

@@ -7,7 +7,9 @@ package dan200.computercraft.shared.util;
import com.mojang.serialization.Codec;
import dan200.computercraft.api.ComputerCraftAPI;
import io.netty.buffer.ByteBuf;
import net.minecraft.ChatFormatting;
import net.minecraft.core.component.DataComponentType;
import net.minecraft.network.chat.Component;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.server.MinecraftServer;
@@ -15,6 +17,8 @@ import net.minecraft.util.ExtraCodecs;
import net.minecraft.world.item.ItemStack;
import org.jspecify.annotations.Nullable;
import java.util.function.Consumer;
/**
* A non-negative integer id, used for computer and disk ids.
*
@@ -47,4 +51,8 @@ public record NonNegativeId(int id) {
stack.set(component, new NonNegativeId(diskID));
return diskID;
}
public void addToTooltip(String translation, Consumer<Component> out) {
out.accept(Component.translatable(translation, id()).withStyle(ChatFormatting.GRAY));
}
}

View File

@@ -79,8 +79,8 @@ public class PrettyJsonWriter extends JsonWriter {
// Otherwise we either need to push to our list or finish a record pair.
var head = stack.getLast();
if (head instanceof DocList) {
((DocList) head).add(object);
if (head instanceof DocList headList) {
headList.add(object);
} else {
stack.removeLast();
((DocList) stack.getLast()).add(new Pair((String) head, object));

Some files were not shown because too many files have changed in this diff Show More