diff --git a/.github/workflows/main-ci.yml b/.github/workflows/main-ci.yml index 497a3556a..8a10d43ea 100644 --- a/.github/workflows/main-ci.yml +++ b/.github/workflows/main-ci.yml @@ -30,6 +30,18 @@ jobs: - name: ⚒️ Build run: ./gradlew assemble || ./gradlew assemble + - name: 📦 Prepare Jars + run: | + # Find the main jar and append the git hash onto it. + mkdir -p jars + find projects/forge/build/libs projects/fabric/build/libs -type f -regex '.*[0-9.]+\(-SNAPSHOT\)?\.jar$' -exec bash -c 'cp {} "jars/$(basename {} .jar)-$(git rev-parse HEAD).jar"' \; + + - name: 📤 Upload Jar + uses: actions/upload-artifact@v4 + with: + name: CC-Tweaked + path: ./jars + - name: Cache pre-commit uses: actions/cache@v4 with: @@ -54,18 +66,6 @@ jobs: run: ./tools/parse-reports.py if: ${{ failure() }} - - name: 📦 Prepare Jars - run: | - # Find the main jar and append the git hash onto it. - mkdir -p jars - find projects/forge/build/libs projects/fabric/build/libs -type f -regex '.*[0-9.]+\(-SNAPSHOT\)?\.jar$' -exec bash -c 'cp {} "jars/$(basename {} .jar)-$(git rev-parse HEAD).jar"' \; - - - name: 📤 Upload Jar - uses: actions/upload-artifact@v4 - with: - name: CC-Tweaked - path: ./jars - build-core: strategy: fail-fast: false diff --git a/REUSE.toml b/REUSE.toml index 82da03ae1..273ab494d 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -40,8 +40,8 @@ path = [ "projects/core/src/main/resources/data/computercraft/lua/rom/modules/main/.ignoreme", "projects/core/src/main/resources/data/computercraft/lua/rom/modules/turtle/.ignoreme", "projects/core/src/main/resources/data/computercraft/lua/rom/motd.txt", - "projects/fabric-api/src/main/modJson/fabric.mod.json", "projects/fabric/src/client/resources/computercraft-client.fabric.mixins.json", + "projects/fabric/src/datagen/resources/fabric.mod.json", "projects/fabric/src/main/resources/computercraft.fabric.mixins.json", "projects/fabric/src/main/resources/fabric.mod.json", "projects/fabric/src/testMod/resources/computercraft-gametest.fabric.mixins.json", diff --git a/buildSrc/src/main/kotlin/cc-tweaked.gametest.gradle.kts b/buildSrc/src/main/kotlin/cc-tweaked.mod.gradle.kts similarity index 67% rename from buildSrc/src/main/kotlin/cc-tweaked.gametest.gradle.kts rename to buildSrc/src/main/kotlin/cc-tweaked.mod.gradle.kts index 712b5e857..fb12f8120 100644 --- a/buildSrc/src/main/kotlin/cc-tweaked.gametest.gradle.kts +++ b/buildSrc/src/main/kotlin/cc-tweaked.mod.gradle.kts @@ -19,26 +19,32 @@ plugins { val main = sourceSets["main"] val client = sourceSets["client"] -// Both testMod and testFixtures inherit from the main and client classpath, just so we have access to Minecraft classes. +// datagen and testMod inherit from the main and client classpath, just so we have access to Minecraft classes. +val datagen by sourceSets.creating { + compileClasspath += main.compileClasspath + client.compileClasspath + runtimeClasspath += main.runtimeClasspath + client.runtimeClasspath +} + val testMod by sourceSets.creating { compileClasspath += main.compileClasspath + client.compileClasspath runtimeClasspath += main.runtimeClasspath + client.runtimeClasspath } -configurations { - named(testMod.compileClasspathConfigurationName) { - shouldResolveConsistentlyWith(compileClasspath.get()) - } +val extraConfigurations = listOf(datagen, testMod) - named(testMod.runtimeClasspathConfigurationName) { - shouldResolveConsistentlyWith(runtimeClasspath.get()) +configurations { + for (config in extraConfigurations) { + named(config.compileClasspathConfigurationName) { shouldResolveConsistentlyWith(compileClasspath.get()) } + named(config.runtimeClasspathConfigurationName) { shouldResolveConsistentlyWith(runtimeClasspath.get()) } } } // Like the main test configurations, we're safe to depend on source set outputs. dependencies { - add(testMod.implementationConfigurationName, main.output) - add(testMod.implementationConfigurationName, client.output) + for (config in extraConfigurations) { + add(config.implementationConfigurationName, main.output) + add(config.implementationConfigurationName, client.output) + } } // Similar to java-test-fixtures, but tries to avoid putting the obfuscated jar on the classpath. diff --git a/buildSrc/src/main/kotlin/cc/tweaked/gradle/CCTweakedExtension.kt b/buildSrc/src/main/kotlin/cc/tweaked/gradle/CCTweakedExtension.kt index 70b3d1f3e..0124fee5c 100644 --- a/buildSrc/src/main/kotlin/cc/tweaked/gradle/CCTweakedExtension.kt +++ b/buildSrc/src/main/kotlin/cc/tweaked/gradle/CCTweakedExtension.kt @@ -109,14 +109,13 @@ abstract class CCTweakedExtension( val otherJava = otherProject.extensions.getByType(JavaPluginExtension::class.java) val main = otherJava.sourceSets.getByName("main") val client = otherJava.sourceSets.getByName("client") - val testMod = otherJava.sourceSets.findByName("testMod") - val testFixtures = otherJava.sourceSets.findByName("testFixtures") // Pull in sources from the other project. extendSourceSet(otherProject, main) extendSourceSet(otherProject, client) - if (testMod != null) extendSourceSet(otherProject, testMod) - if (testFixtures != null) extendSourceSet(otherProject, testFixtures) + for (sourceSet in listOf("datagen", "testMod", "testFixtures")) { + otherJava.sourceSets.findByName(sourceSet)?.let { extendSourceSet(otherProject, it) } + } // The extra source-processing tasks should include these files too. project.tasks.named(main.javadocTaskName, Javadoc::class.java) { source(main.allJava, client.allJava) } diff --git a/doc/events/monitor_resize.md b/doc/events/monitor_resize.md index 0bd4ef98e..bbc72be08 100644 --- a/doc/events/monitor_resize.md +++ b/doc/events/monitor_resize.md @@ -8,7 +8,7 @@ SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers SPDX-License-Identifier: MPL-2.0 --> -The [`monitor_resize`] event is fired when an adjacent or networked monitor's size is changed. +The [`monitor_resize`] event is fired when an adjacent or networked [monitor's][`monitor`] size is changed. ## Return Values 1. [`string`]: The event name. diff --git a/doc/events/monitor_touch.md b/doc/events/monitor_touch.md index 400b57881..099ffd719 100644 --- a/doc/events/monitor_touch.md +++ b/doc/events/monitor_touch.md @@ -8,7 +8,7 @@ SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers SPDX-License-Identifier: MPL-2.0 --> -The [`monitor_touch`] event is fired when an adjacent or networked Advanced Monitor is right-clicked. +The [`monitor_touch`] event is fired when an adjacent or networked [Advanced Monitor][`monitor`] is right-clicked. ## Return Values 1. [`string`]: The event name. diff --git a/projects/common/build.gradle.kts b/projects/common/build.gradle.kts index 952639cbf..a90db51a2 100644 --- a/projects/common/build.gradle.kts +++ b/projects/common/build.gradle.kts @@ -6,8 +6,8 @@ import cc.tweaked.gradle.* plugins { id("cc-tweaked.vanilla") - id("cc-tweaked.gametest") id("cc-tweaked.illuaminate") + id("cc-tweaked.mod") id("cc-tweaked.publishing") } diff --git a/projects/common/src/client/java/dan200/computercraft/client/gui/GuiSprites.java b/projects/common/src/client/java/dan200/computercraft/client/gui/GuiSprites.java index e6025c221..7428196c9 100644 --- a/projects/common/src/client/java/dan200/computercraft/client/gui/GuiSprites.java +++ b/projects/common/src/client/java/dan200/computercraft/client/gui/GuiSprites.java @@ -6,7 +6,6 @@ package dan200.computercraft.client.gui; import dan200.computercraft.api.ComputerCraftAPI; import dan200.computercraft.client.render.ComputerBorderRenderer; -import dan200.computercraft.data.client.ClientDataProviders; import dan200.computercraft.shared.computer.core.ComputerFamily; import net.minecraft.client.renderer.texture.TextureAtlasSprite; import net.minecraft.client.renderer.texture.TextureManager; @@ -113,7 +112,6 @@ public final class GuiSprites extends TextureAtlasHolder { * @param pocketBottom The texture for the bottom of a pocket computer. * @param sidebar The texture for the computer sidebar. * @see ComputerBorderRenderer - * @see ClientDataProviders */ public record ComputerTextures( ResourceLocation border, diff --git a/projects/common/src/client/java/dan200/computercraft/data/client/ClientDataProviders.java b/projects/common/src/client/java/dan200/computercraft/data/client/ClientDataProviders.java deleted file mode 100644 index 5d7b1a084..000000000 --- a/projects/common/src/client/java/dan200/computercraft/data/client/ClientDataProviders.java +++ /dev/null @@ -1,60 +0,0 @@ -// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers -// -// SPDX-License-Identifier: MPL-2.0 - -package dan200.computercraft.data.client; - -import dan200.computercraft.client.gui.GuiSprites; -import dan200.computercraft.client.model.LecternPrintoutModel; -import dan200.computercraft.data.DataProviders; -import dan200.computercraft.shared.turtle.TurtleOverlay; -import dan200.computercraft.shared.turtle.inventory.UpgradeSlot; -import net.minecraft.client.renderer.texture.atlas.SpriteSource; -import net.minecraft.client.renderer.texture.atlas.SpriteSources; -import net.minecraft.client.renderer.texture.atlas.sources.SingleFile; -import net.minecraft.core.HolderLookup; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.server.packs.PackType; - -import java.util.List; -import java.util.Optional; -import java.util.concurrent.CompletableFuture; -import java.util.stream.Stream; - -/** - * A version of {@link DataProviders} which relies on client-side classes. - *

- * This is called from {@link DataProviders#add(DataProviders.GeneratorSink)}. - */ -public final class ClientDataProviders { - private ClientDataProviders() { - } - - public static void add(DataProviders.GeneratorSink generator, CompletableFuture registries) { - generator.addFromCodec("Block atlases", PackType.CLIENT_RESOURCES, "atlases", SpriteSources.FILE_CODEC, out -> { - out.accept(ResourceLocation.withDefaultNamespace("blocks"), List.of( - new SingleFile(UpgradeSlot.LEFT_UPGRADE, Optional.empty()), - new SingleFile(UpgradeSlot.RIGHT_UPGRADE, Optional.empty()), - new SingleFile(LecternPrintoutModel.TEXTURE, Optional.empty()) - )); - out.accept(GuiSprites.SPRITE_SHEET, Stream.of( - // Buttons - GuiSprites.TURNED_OFF.textures(), - GuiSprites.TURNED_ON.textures(), - GuiSprites.TERMINATE.textures(), - // Computers - GuiSprites.COMPUTER_NORMAL.textures(), - GuiSprites.COMPUTER_ADVANCED.textures(), - GuiSprites.COMPUTER_COMMAND.textures(), - GuiSprites.COMPUTER_COLOUR.textures() - ).flatMap(x -> x).map(x -> new SingleFile(x, Optional.empty())).toList()); - }); - - generator.add(pack -> new ExtraModelsProvider(pack, registries) { - @Override - public Stream getModels(HolderLookup.Provider registries) { - return registries.lookupOrThrow(TurtleOverlay.REGISTRY).listElements().map(x -> x.value().model()); - } - }); - } -} diff --git a/projects/common/src/main/java/dan200/computercraft/data/BlockModelProvider.java b/projects/common/src/datagen/java/dan200/computercraft/data/BlockModelProvider.java similarity index 100% rename from projects/common/src/main/java/dan200/computercraft/data/BlockModelProvider.java rename to projects/common/src/datagen/java/dan200/computercraft/data/BlockModelProvider.java diff --git a/projects/common/src/main/java/dan200/computercraft/data/DataProviders.java b/projects/common/src/datagen/java/dan200/computercraft/data/DataProviders.java similarity index 60% rename from projects/common/src/main/java/dan200/computercraft/data/DataProviders.java rename to projects/common/src/datagen/java/dan200/computercraft/data/DataProviders.java index dd68cf5b7..55c86db8d 100644 --- a/projects/common/src/main/java/dan200/computercraft/data/DataProviders.java +++ b/projects/common/src/datagen/java/dan200/computercraft/data/DataProviders.java @@ -7,8 +7,15 @@ package dan200.computercraft.data; import com.mojang.serialization.Codec; import dan200.computercraft.api.pocket.IPocketUpgrade; import dan200.computercraft.api.turtle.ITurtleUpgrade; +import dan200.computercraft.client.gui.GuiSprites; +import dan200.computercraft.client.model.LecternPrintoutModel; +import dan200.computercraft.data.client.ExtraModelsProvider; import dan200.computercraft.shared.turtle.TurtleOverlay; +import dan200.computercraft.shared.turtle.inventory.UpgradeSlot; import net.minecraft.Util; +import net.minecraft.client.renderer.texture.atlas.SpriteSource; +import net.minecraft.client.renderer.texture.atlas.SpriteSources; +import net.minecraft.client.renderer.texture.atlas.sources.SingleFile; import net.minecraft.core.HolderLookup; import net.minecraft.core.RegistrySetBuilder; import net.minecraft.data.DataProvider; @@ -20,10 +27,15 @@ import net.minecraft.server.packs.PackType; import net.minecraft.world.item.Item; import net.minecraft.world.level.block.Block; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.function.BiConsumer; import java.util.function.Consumer; +import java.util.function.Function; +import java.util.stream.Stream; /** * All data providers for ComputerCraft. We require a mod-loader abstraction {@link GeneratorSink} (instead of @@ -55,15 +67,37 @@ public final class DataProviders { generator.add(out -> new LanguageProvider(out, fullRegistries)); - // Unfortunately we rely on some client-side classes in this code. We just load in the client side data provider - // and invoke that. - try { - Class.forName("dan200.computercraft.data.client.ClientDataProviders") - .getMethod("add", GeneratorSink.class, CompletableFuture.class) - .invoke(null, generator, fullRegistries); - } catch (ReflectiveOperationException e) { - throw new RuntimeException(e); - } + generator.addFromCodec("Block atlases", PackType.CLIENT_RESOURCES, "atlases", SpriteSources.FILE_CODEC, out -> { + out.accept(ResourceLocation.withDefaultNamespace("blocks"), makeSprites(Stream.of( + UpgradeSlot.LEFT_UPGRADE, + UpgradeSlot.RIGHT_UPGRADE, + LecternPrintoutModel.TEXTURE + ))); + out.accept(GuiSprites.SPRITE_SHEET, makeSprites( + // Buttons + GuiSprites.TURNED_OFF.textures(), + GuiSprites.TURNED_ON.textures(), + GuiSprites.TERMINATE.textures(), + // Computers + GuiSprites.COMPUTER_NORMAL.textures(), + GuiSprites.COMPUTER_ADVANCED.textures(), + GuiSprites.COMPUTER_COMMAND.textures(), + GuiSprites.COMPUTER_COLOUR.textures() + )); + }); + + generator.add(pack -> new ExtraModelsProvider(pack, fullRegistries) { + @Override + public Stream getModels(HolderLookup.Provider registries) { + return registries.lookupOrThrow(TurtleOverlay.REGISTRY).listElements().map(x -> x.value().model()); + } + }); + } + + @SafeVarargs + @SuppressWarnings("varargs") + private static List makeSprites(final Stream... files) { + return Arrays.stream(files).flatMap(Function.identity()).map(x -> new SingleFile(x, Optional.empty())).toList(); } public interface GeneratorSink { diff --git a/projects/common/src/main/java/dan200/computercraft/data/ItemModelProvider.java b/projects/common/src/datagen/java/dan200/computercraft/data/ItemModelProvider.java similarity index 100% rename from projects/common/src/main/java/dan200/computercraft/data/ItemModelProvider.java rename to projects/common/src/datagen/java/dan200/computercraft/data/ItemModelProvider.java diff --git a/projects/common/src/main/java/dan200/computercraft/data/LanguageProvider.java b/projects/common/src/datagen/java/dan200/computercraft/data/LanguageProvider.java similarity index 100% rename from projects/common/src/main/java/dan200/computercraft/data/LanguageProvider.java rename to projects/common/src/datagen/java/dan200/computercraft/data/LanguageProvider.java diff --git a/projects/common/src/main/java/dan200/computercraft/data/LootTableProvider.java b/projects/common/src/datagen/java/dan200/computercraft/data/LootTableProvider.java similarity index 100% rename from projects/common/src/main/java/dan200/computercraft/data/LootTableProvider.java rename to projects/common/src/datagen/java/dan200/computercraft/data/LootTableProvider.java diff --git a/projects/common/src/main/java/dan200/computercraft/data/ModelProvider.java b/projects/common/src/datagen/java/dan200/computercraft/data/ModelProvider.java similarity index 100% rename from projects/common/src/main/java/dan200/computercraft/data/ModelProvider.java rename to projects/common/src/datagen/java/dan200/computercraft/data/ModelProvider.java diff --git a/projects/common/src/main/java/dan200/computercraft/data/PocketUpgradeProvider.java b/projects/common/src/datagen/java/dan200/computercraft/data/PocketUpgradeProvider.java similarity index 100% rename from projects/common/src/main/java/dan200/computercraft/data/PocketUpgradeProvider.java rename to projects/common/src/datagen/java/dan200/computercraft/data/PocketUpgradeProvider.java diff --git a/projects/common/src/main/java/dan200/computercraft/data/PrettyDataProvider.java b/projects/common/src/datagen/java/dan200/computercraft/data/PrettyDataProvider.java similarity index 96% rename from projects/common/src/main/java/dan200/computercraft/data/PrettyDataProvider.java rename to projects/common/src/datagen/java/dan200/computercraft/data/PrettyDataProvider.java index 9c78e245c..fc2ef47e3 100644 --- a/projects/common/src/main/java/dan200/computercraft/data/PrettyDataProvider.java +++ b/projects/common/src/datagen/java/dan200/computercraft/data/PrettyDataProvider.java @@ -7,6 +7,7 @@ package dan200.computercraft.data; import com.google.common.hash.HashCode; import com.google.common.hash.HashFunction; import com.google.common.hash.Hashing; +import dan200.computercraft.shared.util.PrettyJsonWriter; import net.minecraft.data.CachedOutput; import net.minecraft.data.DataProvider; diff --git a/projects/common/src/main/java/dan200/computercraft/data/RecipeProvider.java b/projects/common/src/datagen/java/dan200/computercraft/data/RecipeProvider.java similarity index 100% rename from projects/common/src/main/java/dan200/computercraft/data/RecipeProvider.java rename to projects/common/src/datagen/java/dan200/computercraft/data/RecipeProvider.java diff --git a/projects/common/src/main/java/dan200/computercraft/data/TagProvider.java b/projects/common/src/datagen/java/dan200/computercraft/data/TagProvider.java similarity index 98% rename from projects/common/src/main/java/dan200/computercraft/data/TagProvider.java rename to projects/common/src/datagen/java/dan200/computercraft/data/TagProvider.java index a184a6cc9..e0e60f990 100644 --- a/projects/common/src/main/java/dan200/computercraft/data/TagProvider.java +++ b/projects/common/src/datagen/java/dan200/computercraft/data/TagProvider.java @@ -148,7 +148,7 @@ class TagProvider { /** * A wrapper over {@link ItemTagsProvider}. */ - interface ItemTagConsumer extends TagConsumer { + public interface ItemTagConsumer extends TagConsumer { void copy(TagKey block, TagKey item); } } diff --git a/projects/common/src/main/java/dan200/computercraft/data/TurtleOverlays.java b/projects/common/src/datagen/java/dan200/computercraft/data/TurtleOverlays.java similarity index 100% rename from projects/common/src/main/java/dan200/computercraft/data/TurtleOverlays.java rename to projects/common/src/datagen/java/dan200/computercraft/data/TurtleOverlays.java diff --git a/projects/common/src/main/java/dan200/computercraft/data/TurtleUpgradeProvider.java b/projects/common/src/datagen/java/dan200/computercraft/data/TurtleUpgradeProvider.java similarity index 100% rename from projects/common/src/main/java/dan200/computercraft/data/TurtleUpgradeProvider.java rename to projects/common/src/datagen/java/dan200/computercraft/data/TurtleUpgradeProvider.java diff --git a/projects/common/src/client/java/dan200/computercraft/data/client/ExtraModelsProvider.java b/projects/common/src/datagen/java/dan200/computercraft/data/client/ExtraModelsProvider.java similarity index 90% rename from projects/common/src/client/java/dan200/computercraft/data/client/ExtraModelsProvider.java rename to projects/common/src/datagen/java/dan200/computercraft/data/client/ExtraModelsProvider.java index 1c6ce5476..6fdcae771 100644 --- a/projects/common/src/client/java/dan200/computercraft/data/client/ExtraModelsProvider.java +++ b/projects/common/src/datagen/java/dan200/computercraft/data/client/ExtraModelsProvider.java @@ -19,11 +19,11 @@ import java.util.stream.Stream; /** * A data provider to generate {@link ExtraModels}. */ -abstract class ExtraModelsProvider implements DataProvider { +public abstract class ExtraModelsProvider implements DataProvider { private final Path path; private final CompletableFuture registries; - ExtraModelsProvider(PackOutput output, CompletableFuture registries) { + public ExtraModelsProvider(PackOutput output, CompletableFuture registries) { path = output.getOutputFolder(PackOutput.Target.RESOURCE_PACK).resolve(ExtraModels.PATH.getNamespace()).resolve(ExtraModels.PATH.getPath()); this.registries = registries; } diff --git a/projects/common/src/main/java/dan200/computercraft/data/recipe/AbstractRecipeBuilder.java b/projects/common/src/datagen/java/dan200/computercraft/data/recipe/AbstractRecipeBuilder.java similarity index 100% rename from projects/common/src/main/java/dan200/computercraft/data/recipe/AbstractRecipeBuilder.java rename to projects/common/src/datagen/java/dan200/computercraft/data/recipe/AbstractRecipeBuilder.java diff --git a/projects/common/src/main/java/dan200/computercraft/data/recipe/ShapedSpecBuilder.java b/projects/common/src/datagen/java/dan200/computercraft/data/recipe/ShapedSpecBuilder.java similarity index 100% rename from projects/common/src/main/java/dan200/computercraft/data/recipe/ShapedSpecBuilder.java rename to projects/common/src/datagen/java/dan200/computercraft/data/recipe/ShapedSpecBuilder.java diff --git a/projects/common/src/main/java/dan200/computercraft/data/recipe/ShapelessSpecBuilder.java b/projects/common/src/datagen/java/dan200/computercraft/data/recipe/ShapelessSpecBuilder.java similarity index 100% rename from projects/common/src/main/java/dan200/computercraft/data/recipe/ShapelessSpecBuilder.java rename to projects/common/src/datagen/java/dan200/computercraft/data/recipe/ShapelessSpecBuilder.java diff --git a/projects/common/src/generated/resources/assets/computercraft/lang/en_us.json b/projects/common/src/generated/resources/assets/computercraft/lang/en_us.json index dd334cb3c..a650ab69f 100644 --- a/projects/common/src/generated/resources/assets/computercraft/lang/en_us.json +++ b/projects/common/src/generated/resources/assets/computercraft/lang/en_us.json @@ -83,35 +83,35 @@ "gui.computercraft.config.disabled_generic_methods.tooltip": "A list of generic methods or method sources to disable. Generic methods are\nmethods added to a block/block entity when there is no explicit peripheral\nprovider. This includes inventory methods (i.e. inventory.getItemDetail,\ninventory.pushItems), and (if on Forge), the fluid_storage and energy_storage\nmethods.\nMethods in this list can either be a whole group of methods (computercraft:inventory)\nor a single method (computercraft:inventory#pushItems).\n", "gui.computercraft.config.execution": "Execution", "gui.computercraft.config.execution.computer_threads": "Computer threads", - "gui.computercraft.config.execution.computer_threads.tooltip": "Set the number of threads computers can run on. A higher number means more\ncomputers can run at once, but may induce lag. Please note that some mods may\nnot work with a thread count higher than 1. Use with caution.\nRange: > 1", + "gui.computercraft.config.execution.computer_threads.tooltip": "Set the number of threads computers can run on. A higher number means more\ncomputers can run at once, but may induce lag. Please note that some mods may\nnot work with a thread count higher than 1. Use with caution.", "gui.computercraft.config.execution.max_main_computer_time": "Server tick computer time limit", - "gui.computercraft.config.execution.max_main_computer_time.tooltip": "The ideal maximum time a computer can execute for in a tick, in milliseconds.\nNote, we will quite possibly go over this limit, as there's no way to tell how\nlong a will take - this aims to be the upper bound of the average time.\nRange: > 1", + "gui.computercraft.config.execution.max_main_computer_time.tooltip": "The ideal maximum time a computer can execute for in a tick, in milliseconds.\nNote, we will quite possibly go over this limit, as there's no way to tell how\nlong a will take - this aims to be the upper bound of the average time.", "gui.computercraft.config.execution.max_main_global_time": "Server tick global time limit", - "gui.computercraft.config.execution.max_main_global_time.tooltip": "The maximum time that can be spent executing tasks in a single tick, in\nmilliseconds.\nNote, we will quite possibly go over this limit, as there's no way to tell how\nlong a will take - this aims to be the upper bound of the average time.\nRange: > 1", + "gui.computercraft.config.execution.max_main_global_time.tooltip": "The maximum time that can be spent executing tasks in a single tick, in\nmilliseconds.\nNote, we will quite possibly go over this limit, as there's no way to tell how\nlong a will take - this aims to be the upper bound of the average time.", "gui.computercraft.config.execution.tooltip": "Controls execution behaviour of computers. This is largely intended for\nfine-tuning servers, and generally shouldn't need to be touched.", "gui.computercraft.config.floppy_space_limit": "Floppy Disk space limit (bytes)", "gui.computercraft.config.floppy_space_limit.tooltip": "The disk space limit for floppy disks, in bytes.", "gui.computercraft.config.http": "HTTP", "gui.computercraft.config.http.bandwidth": "Bandwidth", "gui.computercraft.config.http.bandwidth.global_download": "Global download limit", - "gui.computercraft.config.http.bandwidth.global_download.tooltip": "The number of bytes which can be downloaded in a second. This is shared across all computers. (bytes/s).\nRange: > 1", + "gui.computercraft.config.http.bandwidth.global_download.tooltip": "The number of bytes which can be downloaded in a second. This is shared across all computers. (bytes/s).", "gui.computercraft.config.http.bandwidth.global_upload": "Global upload limit", - "gui.computercraft.config.http.bandwidth.global_upload.tooltip": "The number of bytes which can be uploaded in a second. This is shared across all computers. (bytes/s).\nRange: > 1", + "gui.computercraft.config.http.bandwidth.global_upload.tooltip": "The number of bytes which can be uploaded in a second. This is shared across all computers. (bytes/s).", "gui.computercraft.config.http.bandwidth.tooltip": "Limits bandwidth used by computers.", "gui.computercraft.config.http.enabled": "Enable the HTTP API", "gui.computercraft.config.http.enabled.tooltip": "Enable the \"http\" API on Computers. Disabling this also disables the \"pastebin\" and\n\"wget\" programs, that many users rely on. It's recommended to leave this on and use\nthe \"rules\" config option to impose more fine-grained control.", "gui.computercraft.config.http.max_requests": "Maximum concurrent requests", - "gui.computercraft.config.http.max_requests.tooltip": "The number of http requests a computer can make at one time. Additional requests\nwill be queued, and sent when the running requests have finished. Set to 0 for\nunlimited.\nRange: > 0", + "gui.computercraft.config.http.max_requests.tooltip": "The number of http requests a computer can make at one time. Additional requests\nwill be queued, and sent when the running requests have finished. Set to 0 for\nunlimited.", "gui.computercraft.config.http.max_websockets": "Maximum concurrent websockets", - "gui.computercraft.config.http.max_websockets.tooltip": "The number of websockets a computer can have open at one time.\nRange: > 1", + "gui.computercraft.config.http.max_websockets.tooltip": "The number of websockets a computer can have open at one time.", "gui.computercraft.config.http.proxy": "Proxy", "gui.computercraft.config.http.proxy.host": "Host name", "gui.computercraft.config.http.proxy.host.tooltip": "The hostname or IP address of the proxy server.", "gui.computercraft.config.http.proxy.port": "Port", - "gui.computercraft.config.http.proxy.port.tooltip": "The port of the proxy server.\nRange: 1 ~ 65536", + "gui.computercraft.config.http.proxy.port.tooltip": "The port of the proxy server.", "gui.computercraft.config.http.proxy.tooltip": "Tunnels HTTP and websocket requests through a proxy server. Only affects HTTP\nrules with \"use_proxy\" set to true (off by default).\nIf authentication is required for the proxy, create a \"computercraft-proxy.pw\"\nfile in the same directory as \"computercraft-server.toml\", containing the\nusername and password separated by a colon, e.g. \"myuser:mypassword\". For\nSOCKS4 proxies only the username is required.", "gui.computercraft.config.http.proxy.type": "Proxy type", - "gui.computercraft.config.http.proxy.type.tooltip": "The type of proxy to use.\nAllowed Values: HTTP, HTTPS, SOCKS4, SOCKS5", + "gui.computercraft.config.http.proxy.type.tooltip": "The type of proxy to use.", "gui.computercraft.config.http.rules": "Allow/deny rules", "gui.computercraft.config.http.rules.tooltip": "A list of rules which control behaviour of the \"http\" API for specific domains or\nIPs. Each rule matches against a hostname and an optional port, and then sets several\nproperties for the request. Rules are evaluated in order, meaning earlier rules override\nlater ones.\n\nValid properties:\n - \"host\" (required): The domain or IP address this rule matches. This may be a domain name\n (\"pastebin.com\"), wildcard (\"*.pastebin.com\") or CIDR notation (\"127.0.0.0/8\").\n - \"port\" (optional): Only match requests for a specific port, such as 80 or 443.\n\n - \"action\" (optional): Whether to allow or deny this request.\n - \"max_download\" (optional): The maximum size (in bytes) that a computer can download in this\n request.\n - \"max_upload\" (optional): The maximum size (in bytes) that a computer can upload in a this request.\n - \"max_websocket_message\" (optional): The maximum size (in bytes) that a computer can send or\n receive in one websocket packet.\n - \"use_proxy\" (optional): Enable use of the HTTP/SOCKS proxy if it is configured.", "gui.computercraft.config.http.tooltip": "Controls the HTTP API", @@ -120,61 +120,61 @@ "gui.computercraft.config.log_computer_errors": "Log computer errors", "gui.computercraft.config.log_computer_errors.tooltip": "Log exceptions thrown by peripherals and other Lua objects. This makes it easier\nfor mod authors to debug problems, but may result in log spam should people use\nbuggy methods.", "gui.computercraft.config.maximum_open_files": "Maximum files open per computer", - "gui.computercraft.config.maximum_open_files.tooltip": "Set how many files a computer can have open at the same time. Set to 0 for unlimited.\nRange: > 0", + "gui.computercraft.config.maximum_open_files.tooltip": "Set how many files a computer can have open at the same time. Set to 0 for unlimited.", "gui.computercraft.config.monitor_distance": "Monitor distance", - "gui.computercraft.config.monitor_distance.tooltip": "The maximum distance monitors will render at. This defaults to the standard tile\nentity limit, but may be extended if you wish to build larger monitors.\nRange: 16 ~ 1024", + "gui.computercraft.config.monitor_distance.tooltip": "The maximum distance monitors will render at. This defaults to the standard tile\nentity limit, but may be extended if you wish to build larger monitors.", "gui.computercraft.config.monitor_renderer": "Monitor renderer", - "gui.computercraft.config.monitor_renderer.tooltip": "The renderer to use for monitors. Generally this should be kept at \"best\" - if\nmonitors have performance issues, you may wish to experiment with alternative\nrenderers.\nAllowed Values: BEST, TBO, VBO", + "gui.computercraft.config.monitor_renderer.tooltip": "The renderer to use for monitors. Generally this should be kept at \"best\" - if\nmonitors have performance issues, you may wish to experiment with alternative\nrenderers.", "gui.computercraft.config.peripheral": "Peripherals", "gui.computercraft.config.peripheral.command_block_enabled": "Enable command block peripheral", "gui.computercraft.config.peripheral.command_block_enabled.tooltip": "Enable Command Block peripheral support", "gui.computercraft.config.peripheral.max_notes_per_tick": "Maximum notes that a computer can play at once", - "gui.computercraft.config.peripheral.max_notes_per_tick.tooltip": "Maximum amount of notes a speaker can play at once.\nRange: > 1", + "gui.computercraft.config.peripheral.max_notes_per_tick.tooltip": "Maximum amount of notes a speaker can play at once.", "gui.computercraft.config.peripheral.modem_high_altitude_range": "Modem range (high-altitude)", - "gui.computercraft.config.peripheral.modem_high_altitude_range.tooltip": "The range of Wireless Modems at maximum altitude in clear weather, in meters.\nRange: 0 ~ 100000", + "gui.computercraft.config.peripheral.modem_high_altitude_range.tooltip": "The range of Wireless Modems at maximum altitude in clear weather, in meters.", "gui.computercraft.config.peripheral.modem_high_altitude_range_during_storm": "Modem range (high-altitude, bad weather)", - "gui.computercraft.config.peripheral.modem_high_altitude_range_during_storm.tooltip": "The range of Wireless Modems at maximum altitude in stormy weather, in meters.\nRange: 0 ~ 100000", + "gui.computercraft.config.peripheral.modem_high_altitude_range_during_storm.tooltip": "The range of Wireless Modems at maximum altitude in stormy weather, in meters.", "gui.computercraft.config.peripheral.modem_range": "Modem range (default)", - "gui.computercraft.config.peripheral.modem_range.tooltip": "The range of Wireless Modems at low altitude in clear weather, in meters.\nRange: 0 ~ 100000", + "gui.computercraft.config.peripheral.modem_range.tooltip": "The range of Wireless Modems at low altitude in clear weather, in meters.", "gui.computercraft.config.peripheral.modem_range_during_storm": "Modem range (bad weather)", - "gui.computercraft.config.peripheral.modem_range_during_storm.tooltip": "The range of Wireless Modems at low altitude in stormy weather, in meters.\nRange: 0 ~ 100000", + "gui.computercraft.config.peripheral.modem_range_during_storm.tooltip": "The range of Wireless Modems at low altitude in stormy weather, in meters.", "gui.computercraft.config.peripheral.monitor_bandwidth": "Monitor bandwidth", - "gui.computercraft.config.peripheral.monitor_bandwidth.tooltip": "The limit to how much monitor data can be sent *per tick*. Note:\n - Bandwidth is measured before compression, so the data sent to the client is\n smaller.\n - This ignores the number of players a packet is sent to. Updating a monitor for\n one player consumes the same bandwidth limit as sending to 20.\n - A full sized monitor sends ~25kb of data. So the default (1MB) allows for ~40\n monitors to be updated in a single tick.\nSet to 0 to disable.\nRange: > 0", + "gui.computercraft.config.peripheral.monitor_bandwidth.tooltip": "The limit to how much monitor data can be sent *per tick*. Note:\n - Bandwidth is measured before compression, so the data sent to the client is\n smaller.\n - This ignores the number of players a packet is sent to. Updating a monitor for\n one player consumes the same bandwidth limit as sending to 20.\n - A full sized monitor sends ~25kb of data. So the default (1MB) allows for ~40\n monitors to be updated in a single tick.\nSet to 0 to disable.", "gui.computercraft.config.peripheral.tooltip": "Various options relating to peripherals.", "gui.computercraft.config.term_sizes": "Terminal sizes", "gui.computercraft.config.term_sizes.computer": "Computer", "gui.computercraft.config.term_sizes.computer.height": "Terminal height", - "gui.computercraft.config.term_sizes.computer.height.tooltip": "Range: 1 ~ 255", + "gui.computercraft.config.term_sizes.computer.height.tooltip": "Height of computer terminal", "gui.computercraft.config.term_sizes.computer.tooltip": "Terminal size of computers.", "gui.computercraft.config.term_sizes.computer.width": "Terminal width", - "gui.computercraft.config.term_sizes.computer.width.tooltip": "Range: 1 ~ 255", + "gui.computercraft.config.term_sizes.computer.width.tooltip": "Width of computer terminal", "gui.computercraft.config.term_sizes.monitor": "Monitor", "gui.computercraft.config.term_sizes.monitor.height": "Max monitor height", - "gui.computercraft.config.term_sizes.monitor.height.tooltip": "Range: 1 ~ 32", + "gui.computercraft.config.term_sizes.monitor.height.tooltip": "Maximum height of monitors", "gui.computercraft.config.term_sizes.monitor.tooltip": "Maximum size of monitors (in blocks).", "gui.computercraft.config.term_sizes.monitor.width": "Max monitor width", - "gui.computercraft.config.term_sizes.monitor.width.tooltip": "Range: 1 ~ 32", + "gui.computercraft.config.term_sizes.monitor.width.tooltip": "Maximum width of monitors", "gui.computercraft.config.term_sizes.pocket_computer": "Pocket Computer", "gui.computercraft.config.term_sizes.pocket_computer.height": "Terminal height", - "gui.computercraft.config.term_sizes.pocket_computer.height.tooltip": "Range: 1 ~ 255", + "gui.computercraft.config.term_sizes.pocket_computer.height.tooltip": "Height of pocket computer terminal", "gui.computercraft.config.term_sizes.pocket_computer.tooltip": "Terminal size of pocket computers.", "gui.computercraft.config.term_sizes.pocket_computer.width": "Terminal width", - "gui.computercraft.config.term_sizes.pocket_computer.width.tooltip": "Range: 1 ~ 255", + "gui.computercraft.config.term_sizes.pocket_computer.width.tooltip": "Width of pocket computer terminal", "gui.computercraft.config.term_sizes.tooltip": "Configure the size of various computer's terminals.\nLarger terminals require more bandwidth, so use with care.", "gui.computercraft.config.turtle": "Turtles", "gui.computercraft.config.turtle.advanced_fuel_limit": "Advanced Turtle fuel limit", - "gui.computercraft.config.turtle.advanced_fuel_limit.tooltip": "The fuel limit for Advanced Turtles.\nRange: > 0", + "gui.computercraft.config.turtle.advanced_fuel_limit.tooltip": "The fuel limit for Advanced Turtles.", "gui.computercraft.config.turtle.can_push": "Turtles can push entities", "gui.computercraft.config.turtle.can_push.tooltip": "If set to true, Turtles will push entities out of the way instead of stopping if\nthere is space to do so.", "gui.computercraft.config.turtle.need_fuel": "Enable fuel", "gui.computercraft.config.turtle.need_fuel.tooltip": "Set whether Turtles require fuel to move.", "gui.computercraft.config.turtle.normal_fuel_limit": "Turtle fuel limit", - "gui.computercraft.config.turtle.normal_fuel_limit.tooltip": "The fuel limit for Turtles.\nRange: > 0", + "gui.computercraft.config.turtle.normal_fuel_limit.tooltip": "The fuel limit for Turtles.", "gui.computercraft.config.turtle.tooltip": "Various options relating to turtles.", "gui.computercraft.config.upload_max_size": "File upload size limit (bytes)", - "gui.computercraft.config.upload_max_size.tooltip": "The file upload size limit, in bytes. Must be in range of 1 KiB and 16 MiB.\nKeep in mind that uploads are processed in a single tick - large files or\npoor network performance can stall the networking thread. And mind the disk space!\nRange: 1024 ~ 16777216", + "gui.computercraft.config.upload_max_size.tooltip": "The file upload size limit, in bytes. Must be in range of 1 KiB and 16 MiB.\nKeep in mind that uploads are processed in a single tick - large files or\npoor network performance can stall the networking thread. And mind the disk space!", "gui.computercraft.config.upload_nag_delay": "Upload nag delay", - "gui.computercraft.config.upload_nag_delay.tooltip": "The delay in seconds after which we'll notify about unhandled imports. Set to 0 to disable.\nRange: 0 ~ 60", + "gui.computercraft.config.upload_nag_delay.tooltip": "The delay in seconds after which we'll notify about unhandled imports. Set to 0 to disable.", "gui.computercraft.pocket_computer_overlay": "Pocket computer open. Press ESC to close.", "gui.computercraft.terminal": "Computer terminal", "gui.computercraft.tooltip.computer_id": "Computer ID: %s", diff --git a/projects/common/src/main/java/dan200/computercraft/shared/config/ConfigFile.java b/projects/common/src/main/java/dan200/computercraft/shared/config/ConfigFile.java index 5fe5f01d2..6f4feed74 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/config/ConfigFile.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/config/ConfigFile.java @@ -9,9 +9,7 @@ import com.google.common.base.Splitter; import javax.annotation.Nullable; import javax.annotation.OverridingMethodsMustInvokeSuper; import java.nio.file.Path; -import java.util.ArrayDeque; -import java.util.Deque; -import java.util.List; +import java.util.*; import java.util.function.Predicate; import java.util.function.Supplier; import java.util.stream.Stream; @@ -19,27 +17,47 @@ import java.util.stream.Stream; /** * A config file which the user can modify. */ -public interface ConfigFile { - String TRANSLATION_PREFIX = "gui.computercraft.config."; - Splitter SPLITTER = Splitter.on('.'); +public abstract class ConfigFile { + public static final String TRANSLATION_PREFIX = "gui.computercraft.config."; + public static final Splitter SPLITTER = Splitter.on('.'); /** * An entry in the config file, either a {@link Value} or {@linkplain Group group of other entries}. */ - sealed interface Entry permits Group, Value { + public abstract static sealed class Entry permits Group, Value { + protected final String path; + private final String translationKey; + private final String comment; + + protected Entry(String path, String comment) { + this.path = path; + this.translationKey = TRANSLATION_PREFIX + path; + this.comment = comment; + } + + public final String path() { + return path; + } + /** * Get the translation key of this config entry. * * @return This entry's translation key. */ - String translationKey(); + public final String translationKey() { + return translationKey; + } /** * Get the comment about this config entry. * * @return The comment for this config entry. */ - String comment(); + public final String comment() { + return comment; + } + + abstract Stream entries(); } /** @@ -47,13 +65,38 @@ public interface ConfigFile { * * @param The type of the stored value. */ - non-sealed interface Value extends Entry, Supplier { + public abstract static non-sealed class Value extends Entry implements Supplier { + protected Value(String translationKey, String comment) { + super(translationKey, comment); + } + + @Override + Stream entries() { + return Stream.of(this); + } } /** * A group of config entries. */ - non-sealed interface Group extends Entry { + public static final class Group extends Entry { + private final Map children; + + public Group(String translationKey, String comment, Map children) { + super(translationKey, comment); + this.children = children; + } + + @Override + Stream entries() { + return Stream.concat(Stream.of(this), children.values().stream().flatMap(Entry::entries)); + } + } + + protected final Map entries; + + protected ConfigFile(Map entries) { + this.entries = entries; } /** @@ -61,16 +104,46 @@ public interface ConfigFile { * * @return All config keys. */ - Stream entries(); + public final Stream entries() { + return entries.values().stream().flatMap(Entry::entries); + } - @Nullable - Entry getEntry(String path); + public final @Nullable Entry getEntry(String path) { + var iterator = SPLITTER.split(path).iterator(); + + var entry = entries.get(iterator.next()); + while (iterator.hasNext()) { + if (!(entry instanceof Group group)) return null; + entry = group.children.get(iterator.next()); + } + + return entry; + } /** * A builder which can be used to generate a config object. */ - abstract class Builder { - protected final Deque groupStack = new ArrayDeque<>(); + public abstract static class Builder { + protected record RootGroup(String path, Map children) { + public RootGroup { + } + } + + protected final Deque groupStack = new ArrayDeque<>(); + private @Nullable String pendingComment; + + protected Builder() { + groupStack.addLast(new RootGroup("", new HashMap<>())); + } + + protected final String getPath() { + return groupStack.getLast().path(); + } + + protected final String getPath(String name) { + var path = groupStack.getLast().path(); + return path.isEmpty() ? name : path + "." + name; + } protected String getTranslation(String name) { var key = new StringBuilder(TRANSLATION_PREFIX); @@ -86,7 +159,19 @@ public interface ConfigFile { * @param comment The comment. * @return The current object, for chaining. */ - public abstract Builder comment(String comment); + @OverridingMethodsMustInvokeSuper + public ConfigFile.Builder comment(String comment) { + if (pendingComment != null) throw new IllegalStateException("Already have a comment"); + pendingComment = comment; + return this; + } + + protected String takeComment() { + var comment = pendingComment; + if (comment == null) throw new IllegalStateException("No comment specified"); + pendingComment = null; + return comment; + } /** * Push a new config group. @@ -95,7 +180,10 @@ public interface ConfigFile { */ @OverridingMethodsMustInvokeSuper public void push(String name) { - groupStack.addLast(name); + var path = getPath(name); + Map children = new HashMap<>(); + groupStack.getLast().children().put(name, new Group(path, takeComment(), children)); + groupStack.addLast(new RootGroup(path, children)); } /** @@ -113,22 +201,22 @@ public interface ConfigFile { */ public abstract Builder worldRestart(); - public abstract ConfigFile.Value define(String path, T defaultValue); + public abstract ConfigFile.Value define(String name, T defaultValue); /** * A boolean-specific override of the above {@link #define(String, Object)} method. * - * @param path The path to the value we're defining. + * @param name The name of the value we're defining. * @param defaultValue The default value. * @return The accessor for this config option. */ - public abstract ConfigFile.Value define(String path, boolean defaultValue); + public abstract ConfigFile.Value define(String name, boolean defaultValue); - public abstract ConfigFile.Value defineInRange(String path, int defaultValue, int min, int max); + public abstract ConfigFile.Value defineInRange(String name, int defaultValue, int min, int max); - public abstract ConfigFile.Value> defineList(String path, List defaultValue, Supplier newValue, Predicate elementValidator); + public abstract ConfigFile.Value> defineList(String name, List defaultValue, Supplier newValue, Predicate elementValidator); - public abstract > ConfigFile.Value defineEnum(String path, V defaultValue); + public abstract > ConfigFile.Value defineEnum(String name, V defaultValue); /** * Finalise this config file. @@ -140,7 +228,7 @@ public interface ConfigFile { } @FunctionalInterface - interface ConfigListener { + public interface ConfigListener { /** * The function called then a config file is changed. * diff --git a/projects/common/src/main/java/dan200/computercraft/shared/config/ConfigSpec.java b/projects/common/src/main/java/dan200/computercraft/shared/config/ConfigSpec.java index 1d28130e4..13ee5e3eb 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/config/ConfigSpec.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/config/ConfigSpec.java @@ -344,18 +344,18 @@ public final class ConfigSpec { .push("term_sizes"); builder.comment("Terminal size of computers.").push("computer"); - computerTermWidth = builder.defineInRange("width", Config.computerTermWidth, 1, 255); - computerTermHeight = builder.defineInRange("height", Config.computerTermHeight, 1, 255); + computerTermWidth = builder.comment("Width of computer terminal").defineInRange("width", Config.computerTermWidth, 1, 255); + computerTermHeight = builder.comment("Height of computer terminal").defineInRange("height", Config.computerTermHeight, 1, 255); builder.pop(); builder.comment("Terminal size of pocket computers.").push("pocket_computer"); - pocketTermWidth = builder.defineInRange("width", Config.pocketTermWidth, 1, 255); - pocketTermHeight = builder.defineInRange("height", Config.pocketTermHeight, 1, 255); + pocketTermWidth = builder.comment("Width of pocket computer terminal").defineInRange("width", Config.pocketTermWidth, 1, 255); + pocketTermHeight = builder.comment("Height of pocket computer terminal").defineInRange("height", Config.pocketTermHeight, 1, 255); builder.pop(); builder.comment("Maximum size of monitors (in blocks).").push("monitor"); - monitorWidth = builder.defineInRange("width", Config.monitorWidth, 1, 32); - monitorHeight = builder.defineInRange("height", Config.monitorHeight, 1, 32); + monitorWidth = builder.comment("Maximum width of monitors").defineInRange("width", Config.monitorWidth, 1, 32); + monitorHeight = builder.comment("Maximum height of monitors").defineInRange("height", Config.monitorHeight, 1, 32); builder.pop(); builder.pop(); diff --git a/projects/common/src/main/java/dan200/computercraft/shared/peripheral/monitor/MonitorPeripheral.java b/projects/common/src/main/java/dan200/computercraft/shared/peripheral/monitor/MonitorPeripheral.java index 8e44aa0e0..c07bfb41e 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/peripheral/monitor/MonitorPeripheral.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/peripheral/monitor/MonitorPeripheral.java @@ -21,7 +21,11 @@ import javax.annotation.Nullable; * Monitors act as [terminal redirects][`term.Redirect`] and so expose the same methods, as well as several additional * ones, which are documented below. *

- * Like computers, monitors come in both normal (no colour) and advanced (colour) varieties. + * If the monitor is resized (by adding new blocks to the monitor, or by calling {@link setTextScale}), then a + * [`monitor_resize`] event will be queued. + *

+ * Like computers, monitors come in both normal (no colour) and advanced (colour) varieties. Advanced monitors be right + * clicked, which will trigger a [`monitor_touch`] event. *

* ## Recipes *

@@ -37,6 +41,9 @@ import javax.annotation.Nullable; * monitor.setCursorPos(1, 1) * monitor.write("Hello, world!") * } + * + * @cc.see monitor_resize Queued when a monitor is resized. + * @cc.see monitor_touch Queued when an advanced monitor is clicked. */ public class MonitorPeripheral extends TermMethods implements IPeripheral { private final MonitorBlockEntity monitor; diff --git a/projects/common/src/main/java/dan200/computercraft/data/PrettyJsonWriter.java b/projects/common/src/main/java/dan200/computercraft/shared/util/PrettyJsonWriter.java similarity index 99% rename from projects/common/src/main/java/dan200/computercraft/data/PrettyJsonWriter.java rename to projects/common/src/main/java/dan200/computercraft/shared/util/PrettyJsonWriter.java index 38ee71e85..d52459acc 100644 --- a/projects/common/src/main/java/dan200/computercraft/data/PrettyJsonWriter.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/util/PrettyJsonWriter.java @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: MPL-2.0 -package dan200.computercraft.data; +package dan200.computercraft.shared.util; import com.google.gson.Gson; import com.google.gson.GsonBuilder; @@ -23,7 +23,7 @@ import java.util.List; /** * Alternative version of {@link JsonWriter} which attempts to lay out the JSON in a more compact format. * - * @see PrettyDataProvider + * @see dan200.computercraft.data.PrettyDataProvider */ public class PrettyJsonWriter extends JsonWriter { private static final Gson GSON = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create(); diff --git a/projects/common/src/main/java/dan200/computercraft/shared/util/Trie.java b/projects/common/src/main/java/dan200/computercraft/shared/util/Trie.java deleted file mode 100644 index 70037585b..000000000 --- a/projects/common/src/main/java/dan200/computercraft/shared/util/Trie.java +++ /dev/null @@ -1,46 +0,0 @@ -// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers -// -// SPDX-License-Identifier: MPL-2.0 - -package dan200.computercraft.shared.util; - -import javax.annotation.Nullable; -import java.util.HashMap; -import java.util.Map; -import java.util.stream.Stream; - -/** - * A key-value map, where the key is a list of values. - * - * @param The type of keys in this trie. - * @param The values in this map. - */ -public class Trie { - private @Nullable V current; - private @Nullable Map> children; - - public Trie getChild(Iterable key) { - var self = this; - for (var keyElement : key) { - if (self.children == null) self.children = new HashMap<>(1); - self = self.children.computeIfAbsent(keyElement, x -> new Trie<>()); - } - - return self; - } - - public @Nullable V getValue(Iterable key) { - return getChild(key).current; - } - - public void setValue(Iterable key, V value) { - getChild(key).current = value; - } - - public Stream stream() { - return Stream.concat( - current == null ? Stream.empty() : Stream.of(current), - children == null ? Stream.empty() : children.values().stream().flatMap(Trie::stream) - ); - } -} diff --git a/projects/common/src/testMod/java/dan200/computercraft/export/Exporter.java b/projects/common/src/testMod/java/dan200/computercraft/export/Exporter.java index f42a6da40..c9e537a0c 100644 --- a/projects/common/src/testMod/java/dan200/computercraft/export/Exporter.java +++ b/projects/common/src/testMod/java/dan200/computercraft/export/Exporter.java @@ -14,9 +14,9 @@ import com.mojang.brigadier.arguments.StringArgumentType; import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.builder.RequiredArgumentBuilder; import dan200.computercraft.api.ComputerCraftAPI; -import dan200.computercraft.data.PrettyJsonWriter; import dan200.computercraft.gametest.core.TestHooks; import dan200.computercraft.shared.util.RegistryHelper; +import dan200.computercraft.shared.util.PrettyJsonWriter; import net.minecraft.client.Minecraft; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.client.gui.GuiGraphics; diff --git a/projects/fabric/build.gradle.kts b/projects/fabric/build.gradle.kts index 21ce836b3..2fd654fb4 100644 --- a/projects/fabric/build.gradle.kts +++ b/projects/fabric/build.gradle.kts @@ -8,7 +8,7 @@ import java.util.* plugins { id("cc-tweaked.fabric") - id("cc-tweaked.gametest") + id("cc-tweaked.mod") id("cc-tweaked.mod-publishing") } @@ -147,6 +147,8 @@ loom { configName = "Datagen" client() + source(sourceSets.datagen.get()) + runDir("run/dataGen") property("fabric-api.datagen") property("fabric-api.datagen.output-dir", layout.buildDirectory.dir("generatedResources").getAbsolutePath()) diff --git a/projects/fabric/src/main/java/dan200/computercraft/data/FabricDataGenerators.java b/projects/fabric/src/datagen/java/dan200/computercraft/data/FabricDataProviders.java similarity index 98% rename from projects/fabric/src/main/java/dan200/computercraft/data/FabricDataGenerators.java rename to projects/fabric/src/datagen/java/dan200/computercraft/data/FabricDataProviders.java index ff1014890..2e7dd4eb0 100644 --- a/projects/fabric/src/main/java/dan200/computercraft/data/FabricDataGenerators.java +++ b/projects/fabric/src/datagen/java/dan200/computercraft/data/FabricDataProviders.java @@ -29,7 +29,7 @@ import java.util.concurrent.CompletableFuture; import java.util.function.BiConsumer; import java.util.function.Consumer; -public class FabricDataGenerators implements DataGeneratorEntrypoint { +public class FabricDataProviders implements DataGeneratorEntrypoint { @Override public void onInitializeDataGenerator(FabricDataGenerator generator) { var pack = new PlatformGeneratorsImpl(generator.createPack(), generator.getRegistries()); diff --git a/projects/fabric/src/datagen/resources/fabric.mod.json b/projects/fabric/src/datagen/resources/fabric.mod.json new file mode 100644 index 000000000..1daa9ec5f --- /dev/null +++ b/projects/fabric/src/datagen/resources/fabric.mod.json @@ -0,0 +1,13 @@ +{ + "schemaVersion": 1, + "id": "cc-datagen", + "version": "1.0.0", + "entrypoints": { + "fabric-datagen": [ + "dan200.computercraft.data.FabricDataProviders" + ] + }, + "depends": { + "computercraft": "*" + } +} diff --git a/projects/fabric/src/main/java/dan200/computercraft/shared/platform/FabricConfigFile.java b/projects/fabric/src/main/java/dan200/computercraft/shared/platform/FabricConfigFile.java index 5dc569fa4..69549bf9c 100644 --- a/projects/fabric/src/main/java/dan200/computercraft/shared/platform/FabricConfigFile.java +++ b/projects/fabric/src/main/java/dan200/computercraft/shared/platform/FabricConfigFile.java @@ -11,7 +11,6 @@ import com.electronwill.nightconfig.core.file.FileWatcher; import com.electronwill.nightconfig.core.io.WritingMode; import com.google.errorprone.annotations.concurrent.GuardedBy; import dan200.computercraft.shared.config.ConfigFile; -import dan200.computercraft.shared.util.Trie; import org.apache.commons.lang3.function.TriFunction; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -22,6 +21,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.Arrays; import java.util.List; +import java.util.Map; import java.util.function.Predicate; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -30,18 +30,17 @@ import java.util.stream.Stream; /** * A {@link ConfigFile} which sits directly on top of NightConfig. */ -public class FabricConfigFile implements ConfigFile { +public final class FabricConfigFile extends ConfigFile { private static final Logger LOG = LoggerFactory.getLogger(FabricConfigFile.class); private final ConfigSpec spec; - private final Trie entries; private final ConfigListener onChange; private @Nullable CommentedFileConfig config; - public FabricConfigFile(ConfigSpec spec, Trie entries, ConfigListener onChange) { + private FabricConfigFile(ConfigSpec spec, Map entries, ConfigListener onChange) { + super(entries); this.spec = spec; - this.entries = entries; this.onChange = onChange; } @@ -76,7 +75,7 @@ public class FabricConfigFile implements ConfigFile { @SuppressWarnings("unchecked") private Stream> values() { - return (Stream>) (Stream) entries.stream().filter(ValueImpl.class::isInstance); + return (Stream>) (Stream) entries().filter(ValueImpl.class::isInstance); } public synchronized void unload() { @@ -104,7 +103,7 @@ public class FabricConfigFile implements ConfigFile { // Ensure the config file matches the spec var isNewFile = config.isEmpty(); - entries.stream().forEach(x -> config.setComment(x.path, x.comment)); + entries().forEach(x -> config.setComment(x.path(), x instanceof ValueImpl v ? v.fullComment : x.comment())); var corrected = isNewFile ? spec.correct(config) : spec.correct(config, (action, entryPath, oldValue, newValue) -> { LOG.warn("Incorrect key {} was corrected from {} to {}", String.join(".", entryPath), oldValue, newValue); }); @@ -116,148 +115,77 @@ public class FabricConfigFile implements ConfigFile { return corrected > 0; } - @Override - public Stream entries() { - return entries.stream().map(x -> (ConfigFile.Entry) x); - } - - @Nullable - @Override - public ConfigFile.Entry getEntry(String path) { - return (ConfigFile.Entry) entries.getValue(SPLITTER.split(path)); - } - static class Builder extends ConfigFile.Builder { private final ConfigSpec spec = new ConfigSpec(); - private final Trie entries = new Trie<>(); - - private @Nullable String pendingComment; - - private String getFullPath(String path) { - var key = new StringBuilder(); - for (var group : groupStack) key.append(group).append('.'); - key.append(path); - return key.toString(); - } - - @Override - public ConfigFile.Builder comment(String comment) { - if (pendingComment != null) throw new IllegalStateException("Already have a comment"); - pendingComment = comment; - return this; - } - - private String takeComment() { - var comment = pendingComment; - if (comment == null) throw new IllegalStateException("No comment specified"); - pendingComment = null; - return comment; - } - - private String takeComment(String suffix) { - var comment = pendingComment == null ? "" : pendingComment + "\n"; - pendingComment = null; - return comment + suffix; - } - - @Override - public void push(String name) { - var path = getFullPath(name); - var splitPath = SPLITTER.split(path); - entries.setValue(splitPath, new GroupImpl(path, takeComment())); - - super.push(name); - } @Override public ConfigFile.Builder worldRestart() { return this; } - private Value defineValue(String fullPath, String comment, T defaultValue, TriFunction getter) { - var value = new ValueImpl(fullPath, comment, defaultValue, getter); - entries.setValue(SPLITTER.split(fullPath), value); + private Value defineValue(String name, String comment, @Nullable String suffix, T defaultValue, TriFunction getter) { + var fullComment = suffix == null ? comment : comment + "\n" + suffix; + var value = new ValueImpl(getPath(name), comment, fullComment, defaultValue, getter); + groupStack.getLast().children().put(name, value); return value; } @Override - public Value define(String path, T defaultValue) { - var fullPath = getFullPath(path); - spec.define(fullPath, defaultValue); - return defineValue(fullPath, takeComment(), defaultValue, Config::getOrElse); + public Value define(String name, T defaultValue) { + var path = getPath(name); + spec.define(path, defaultValue); + return defineValue(name, takeComment(), null, defaultValue, Config::getOrElse); } @Override - public Value define(String path, boolean defaultValue) { - var fullPath = getFullPath(path); - spec.define(fullPath, defaultValue, x -> x instanceof Boolean); - return defineValue(fullPath, takeComment(), defaultValue, UnmodifiableConfig::getOrElse); + public Value define(String name, boolean defaultValue) { + var path = getPath(name); + spec.define(path, defaultValue, x -> x instanceof Boolean); + return defineValue(name, takeComment(), null, defaultValue, UnmodifiableConfig::getOrElse); } @Override - public Value defineInRange(String path, int defaultValue, int min, int max) { - var fullPath = getFullPath(path); - spec.defineInRange(fullPath, defaultValue, min, max); + public Value defineInRange(String name, int defaultValue, int min, int max) { + var path = getPath(name); + spec.defineInRange(path, defaultValue, min, max); var suffix = max == Integer.MAX_VALUE ? "Range: > " + min : "Range: " + min + " ~ " + max; - return defineValue(fullPath, takeComment(suffix), defaultValue, UnmodifiableConfig::getIntOrElse); + return defineValue(name, takeComment(), suffix, defaultValue, UnmodifiableConfig::getIntOrElse); } @Override - public Value> defineList(String path, List defaultValue, Supplier newValue, Predicate elementValidator) { - var fullPath = getFullPath(path); - spec.defineList(fullPath, defaultValue, elementValidator); - return defineValue(fullPath, takeComment(), defaultValue, Config::getOrElse); + public Value> defineList(String name, List defaultValue, Supplier newValue, Predicate elementValidator) { + var path = getPath(name); + spec.defineList(path, defaultValue, elementValidator); + return defineValue(name, takeComment(), null, defaultValue, Config::getOrElse); } @Override - public > Value defineEnum(String path, V defaultValue) { - var fullPath = getFullPath(path); - spec.define(fullPath, defaultValue, o -> o != null && o != NullObject.NULL_OBJECT && EnumGetMethod.NAME_IGNORECASE.validate(o, defaultValue.getDeclaringClass())); + public > Value defineEnum(String name, V defaultValue) { + var path = getPath(name); + spec.define(path, defaultValue, o -> o != null && o != NullObject.NULL_OBJECT && EnumGetMethod.NAME_IGNORECASE.validate(o, defaultValue.getDeclaringClass())); var suffix = "Allowed Values: " + Arrays.stream(defaultValue.getDeclaringClass().getEnumConstants()).map(Enum::name).collect(Collectors.joining(", ")); - return defineValue(fullPath, takeComment(suffix), defaultValue, (c, p, d) -> c.getEnumOrElse(p, d, EnumGetMethod.NAME_IGNORECASE)); + return defineValue(name, takeComment(), suffix, defaultValue, (c, p, d) -> c.getEnumOrElse(p, d, EnumGetMethod.NAME_IGNORECASE)); } @Override public ConfigFile build(ConfigListener onChange) { - return new FabricConfigFile(spec, entries, onChange); + var children = groupStack.removeLast().children(); + if (!groupStack.isEmpty()) throw new IllegalStateException("Mismatched config push/pop"); + return new FabricConfigFile(spec, children, onChange); } } - private static class Entry { - final String path; - final String comment; - - Entry(String path, String comment) { - this.path = path; - this.comment = comment; - } - - @SuppressWarnings("UnusedMethod") - public final String translationKey() { - return TRANSLATION_PREFIX + path; - } - - @SuppressWarnings("UnusedMethod") - public final String comment() { - return comment; - } - } - - private static final class GroupImpl extends Entry implements Group { - private GroupImpl(String path, String comment) { - super(path, comment); - } - } - - private static final class ValueImpl extends Entry implements Value { + private static final class ValueImpl extends Value { private @Nullable T value; private final T defaultValue; private final TriFunction get; + private final String fullComment; - private ValueImpl(String path, String comment, T defaultValue, TriFunction get) { + private ValueImpl(String path, String comment, String fullComment, T defaultValue, TriFunction get) { super(path, comment); + this.fullComment = fullComment; this.defaultValue = defaultValue; this.get = get; } diff --git a/projects/fabric/src/main/resources/fabric.mod.json b/projects/fabric/src/main/resources/fabric.mod.json index 0e3f19a7e..27f252e48 100644 --- a/projects/fabric/src/main/resources/fabric.mod.json +++ b/projects/fabric/src/main/resources/fabric.mod.json @@ -23,9 +23,6 @@ "client": [ "dan200.computercraft.client.ComputerCraftClient::init" ], - "fabric-datagen": [ - "dan200.computercraft.data.FabricDataGenerators" - ], "jei_mod_plugin": [ "dan200.computercraft.client.integration.jei.JEIComputerCraft" ], diff --git a/projects/fabric/src/testMod/resources/fabric.mod.json b/projects/fabric/src/testMod/resources/fabric.mod.json index 9b8648888..ffc442bec 100644 --- a/projects/fabric/src/testMod/resources/fabric.mod.json +++ b/projects/fabric/src/testMod/resources/fabric.mod.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "id": "cctest", + "id": "cc-datagen", "version": "1.0.0", "entrypoints": { "main": [ diff --git a/projects/forge/build.gradle.kts b/projects/forge/build.gradle.kts index 343de48e2..02115859d 100644 --- a/projects/forge/build.gradle.kts +++ b/projects/forge/build.gradle.kts @@ -7,7 +7,7 @@ import net.neoforged.gradle.dsl.common.runs.run.Run plugins { id("cc-tweaked.forge") - id("cc-tweaked.gametest") + id("cc-tweaked.mod") id("cc-tweaked.mod-publishing") } @@ -65,6 +65,8 @@ runs { "--existing", project(":common").file("src/main/resources/").absolutePath, "--existing", file("src/main/resources/").absolutePath, ) + + modSources.add("computercraft", sourceSets.datagen.get()) } fun Run.configureForGameTest() { diff --git a/projects/forge/src/main/java/dan200/computercraft/data/Generators.java b/projects/forge/src/datagen/java/dan200/computercraft/data/ForgeDataProviders.java similarity index 99% rename from projects/forge/src/main/java/dan200/computercraft/data/Generators.java rename to projects/forge/src/datagen/java/dan200/computercraft/data/ForgeDataProviders.java index ea6f0b744..f2370246f 100644 --- a/projects/forge/src/main/java/dan200/computercraft/data/Generators.java +++ b/projects/forge/src/datagen/java/dan200/computercraft/data/ForgeDataProviders.java @@ -32,7 +32,7 @@ import java.util.function.BiConsumer; import java.util.function.Consumer; @EventBusSubscriber(bus = EventBusSubscriber.Bus.MOD) -public class Generators { +public class ForgeDataProviders { @SubscribeEvent public static void gather(GatherDataEvent event) { var generator = event.getGenerator(); diff --git a/projects/forge/src/main/java/dan200/computercraft/shared/platform/ForgeConfigFile.java b/projects/forge/src/main/java/dan200/computercraft/shared/platform/ForgeConfigFile.java index 05788eb61..f2f7e497e 100644 --- a/projects/forge/src/main/java/dan200/computercraft/shared/platform/ForgeConfigFile.java +++ b/projects/forge/src/main/java/dan200/computercraft/shared/platform/ForgeConfigFile.java @@ -5,50 +5,33 @@ package dan200.computercraft.shared.platform; import dan200.computercraft.shared.config.ConfigFile; -import dan200.computercraft.shared.util.Trie; import net.neoforged.neoforge.common.ModConfigSpec; -import javax.annotation.Nullable; -import java.util.ArrayList; import java.util.List; -import java.util.Objects; +import java.util.Map; import java.util.function.Predicate; import java.util.function.Supplier; -import java.util.stream.Stream; /** * A {@link ConfigFile} which wraps Forge's config implementation. */ -public final class ForgeConfigFile implements ConfigFile { +public final class ForgeConfigFile extends ConfigFile { private final ModConfigSpec spec; - private final Trie entries; - public ForgeConfigFile(ModConfigSpec spec, Trie entries) { + private ForgeConfigFile(ModConfigSpec spec, Map entries) { + super(entries); this.spec = spec; - this.entries = entries; } public ModConfigSpec spec() { return spec; } - @Override - public Stream entries() { - return entries.stream(); - } - - @Nullable - @Override - public Entry getEntry(String path) { - return entries.getValue(SPLITTER.split(path)); - } - /** * Wraps {@link ModConfigSpec.Builder} into our own config builder abstraction. */ static class Builder extends ConfigFile.Builder { private final ModConfigSpec.Builder builder = new ModConfigSpec.Builder(); - private final Trie entries = new Trie<>(); private void translation(String name) { builder.translation(getTranslation(name)); @@ -56,24 +39,23 @@ public final class ForgeConfigFile implements ConfigFile { @Override public ConfigFile.Builder comment(String comment) { + super.comment(comment); builder.comment(comment); return this; } @Override public void push(String name) { + super.push(name); + translation(name); builder.push(name); - super.push(name); } @Override public void pop() { - var path = new ArrayList<>(groupStack); - entries.setValue(path, new GroupImpl(path)); - - builder.pop(); super.pop(); + builder.pop(); } @Override @@ -82,100 +64,63 @@ public final class ForgeConfigFile implements ConfigFile { return this; } - private ConfigFile.Value defineValue(ModConfigSpec.ConfigValue value) { - var wrapped = new ValueImpl<>(value); - entries.setValue(value.getPath(), wrapped); + private ConfigFile.Value defineValue(String name, ModConfigSpec.ConfigValue value) { + var wrapped = new ValueImpl<>(getPath(name), takeComment(), value); + groupStack.getLast().children().put(name, wrapped); return wrapped; } @Override - public ConfigFile.Value define(String path, T defaultValue) { - translation(path); - return defineValue(builder.define(path, defaultValue)); + public ConfigFile.Value define(String name, T defaultValue) { + translation(name); + return defineValue(name, builder.define(name, defaultValue)); } @Override - public ConfigFile.Value define(String path, boolean defaultValue) { - translation(path); - return defineValue(builder.define(path, defaultValue)); + public ConfigFile.Value define(String name, boolean defaultValue) { + translation(name); + return defineValue(name, builder.define(name, defaultValue)); } @Override - public ConfigFile.Value defineInRange(String path, int defaultValue, int min, int max) { - translation(path); - return defineValue(builder.defineInRange(path, defaultValue, min, max)); + public ConfigFile.Value defineInRange(String name, int defaultValue, int min, int max) { + translation(name); + return defineValue(name, builder.defineInRange(name, defaultValue, min, max)); } @Override - public ConfigFile.Value> defineList(String path, List defaultValue, Supplier newValue, Predicate elementValidator) { - translation(path); - return defineValue(builder.defineList(path, defaultValue, newValue, elementValidator)); + public ConfigFile.Value> defineList(String name, List defaultValue, Supplier newValue, Predicate elementValidator) { + translation(name); + return defineValue(name, builder.defineList(name, defaultValue, newValue, elementValidator)); } @Override - public > ConfigFile.Value defineEnum(String path, V defaultValue) { - translation(path); - return defineValue(builder.defineEnum(path, defaultValue)); + public > ConfigFile.Value defineEnum(String name, V defaultValue) { + translation(name); + return defineValue(name, builder.defineEnum(name, defaultValue)); } @Override public ConfigFile build(ConfigListener onChange) { + var children = groupStack.removeLast().children(); + if (!groupStack.isEmpty()) throw new IllegalStateException("Mismatched config push/pop"); + var spec = builder.build(); - entries.stream().forEach(x -> { - if (x instanceof ValueImpl value) value.owner = spec; - if (x instanceof GroupImpl value) value.owner = spec; - }); - return new ForgeConfigFile(spec, entries); + return new ForgeConfigFile(spec, children); } } - private static final class GroupImpl implements ConfigFile.Group { - private final List path; - private @Nullable ModConfigSpec owner; - - private GroupImpl(List path) { - this.path = path; - } - - @Override - public String translationKey() { - if (owner == null) throw new IllegalStateException("Config has not been built yet"); - return owner.getLevelTranslationKey(path); - } - - @Override - public String comment() { - if (owner == null) throw new IllegalStateException("Config has not been built yet"); - return owner.getLevelComment(path); - } - } - - private static final class ValueImpl implements ConfigFile.Value { + private static final class ValueImpl extends Value { private final ModConfigSpec.ConfigValue value; - private @Nullable ModConfigSpec owner; - private ValueImpl(ModConfigSpec.ConfigValue value) { + private ValueImpl(String path, String comment, ModConfigSpec.ConfigValue value) { + super(path, comment); this.value = value; } - private ModConfigSpec.ValueSpec spec() { - if (owner == null) throw new IllegalStateException("Config has not been built yet"); - return owner.getSpec().get(value.getPath()); - } - @Override public T get() { return value.get(); } - - @Override - public String translationKey() { - return Objects.requireNonNull(spec().getTranslationKey(), "No comment for value"); - } - - @Override - public String comment() { - return Objects.requireNonNull(spec().getComment(), "No comment for value"); - } } }