diff --git a/projects/common/src/client/java/dan200/computercraft/client/ClientRegistry.java b/projects/common/src/client/java/dan200/computercraft/client/ClientRegistry.java index fa84f3c86..7f83021cb 100644 --- a/projects/common/src/client/java/dan200/computercraft/client/ClientRegistry.java +++ b/projects/common/src/client/java/dan200/computercraft/client/ClientRegistry.java @@ -25,6 +25,7 @@ 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; import net.minecraft.client.color.item.ItemColor; @@ -53,6 +54,7 @@ import net.minecraft.world.level.ItemLike; import javax.annotation.Nullable; import java.io.File; import java.io.IOException; +import java.util.Collection; import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.Supplier; @@ -143,15 +145,14 @@ public final class ClientRegistry { register.accept(GuiSprites.initialise(minecraft.getTextureManager())); } - private static final String[] EXTRA_MODELS = new String[]{ - "block/turtle_colour", - "block/turtle_elf_overlay", - "block/turtle_rainbow_overlay", - "block/turtle_trans_overlay", + private static final ResourceLocation[] EXTRA_MODELS = { + TurtleOverlay.ELF_MODEL, + TurtleBlockEntityRenderer.COLOUR_TURTLE_MODEL, }; - public static void registerExtraModels(Consumer register) { - for (var model : EXTRA_MODELS) register.accept(ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, model)); + public static void registerExtraModels(Consumer register, Collection extraModels) { + for (var model : EXTRA_MODELS) register.accept(model); + extraModels.forEach(register); TurtleUpgradeModellers.getDependencies().forEach(register); } diff --git a/projects/common/src/client/java/dan200/computercraft/client/model/ExtraModels.java b/projects/common/src/client/java/dan200/computercraft/client/model/ExtraModels.java new file mode 100644 index 000000000..e7b8838ca --- /dev/null +++ b/projects/common/src/client/java/dan200/computercraft/client/model/ExtraModels.java @@ -0,0 +1,68 @@ +// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers +// +// SPDX-License-Identifier: MPL-2.0 + +package dan200.computercraft.client.model; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; +import com.mojang.serialization.Codec; +import com.mojang.serialization.JsonOps; +import dan200.computercraft.api.ComputerCraftAPI; +import dan200.computercraft.shared.turtle.TurtleOverlay; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.packs.resources.ResourceManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.*; + +/** + * A list of extra models to load on the client. + *

+ * This is largely intended for use with {@linkplain TurtleOverlay turtle overlays}. As overlays are stored in a dynamic + * registry, they are not available when resources are loaded, and so we need a way to request the overlays' models be + * loaded. + * + * @param models The models to load. + */ +public record ExtraModels(List models) { + private static final Logger LOG = LoggerFactory.getLogger(ExtraModels.class); + private static final Gson GSON = new Gson(); + + /** + * The path where the extra models are listed. + */ + public static final ResourceLocation PATH = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "extra_models.json"); + + /** + * The coded used to store the extra model file. + */ + public static final Codec CODEC = ResourceLocation.CODEC.listOf().xmap(ExtraModels::new, ExtraModels::models); + + /** + * Get the list of all extra models to load. + * + * @param resources The current resource manager. + * @return A set of all resources to load. + */ + public static Collection loadAll(ResourceManager resources) { + Set out = new HashSet<>(); + + for (var path : resources.getResourceStack(PATH)) { + ExtraModels models; + try (var stream = path.openAsReader()) { + models = ExtraModels.CODEC.parse(JsonOps.INSTANCE, GSON.fromJson(stream, JsonElement.class)).getOrThrow(JsonParseException::new); + } catch (IOException | RuntimeException e) { + LOG.error("Failed to load extra models from {}", path.sourcePackId()); + continue; + } + + out.addAll(models.models()); + } + + return Collections.unmodifiableCollection(out); + } +} diff --git a/projects/common/src/client/java/dan200/computercraft/client/model/turtle/TurtleModelParts.java b/projects/common/src/client/java/dan200/computercraft/client/model/turtle/TurtleModelParts.java index f714a64f2..a6ef119ca 100644 --- a/projects/common/src/client/java/dan200/computercraft/client/model/turtle/TurtleModelParts.java +++ b/projects/common/src/client/java/dan200/computercraft/client/model/turtle/TurtleModelParts.java @@ -12,13 +12,14 @@ import dan200.computercraft.api.turtle.ITurtleUpgrade; import dan200.computercraft.api.turtle.TurtleSide; import dan200.computercraft.api.upgrades.UpgradeData; import dan200.computercraft.client.platform.ClientPlatformHelper; -import dan200.computercraft.client.render.TurtleBlockEntityRenderer; import dan200.computercraft.client.turtle.TurtleUpgradeModellers; +import dan200.computercraft.shared.turtle.TurtleOverlay; import dan200.computercraft.shared.turtle.items.TurtleItem; import dan200.computercraft.shared.util.DataComponentUtil; import dan200.computercraft.shared.util.Holiday; import net.minecraft.client.Minecraft; import net.minecraft.client.resources.model.BakedModel; +import net.minecraft.client.resources.model.ModelManager; import net.minecraft.core.component.DataComponents; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.item.ItemStack; @@ -52,7 +53,7 @@ public final class TurtleModelParts { boolean colour, @Nullable UpgradeData leftUpgrade, @Nullable UpgradeData rightUpgrade, - @Nullable ResourceLocation overlay, + @Nullable TurtleOverlay overlay, boolean christmas, boolean flip ) { @@ -113,10 +114,10 @@ public final class TurtleModelParts { var parts = new ArrayList(4); parts.add(transform(combo.colour() ? colourModel : familyModel, transformation)); - var overlayModelLocation = TurtleBlockEntityRenderer.getTurtleOverlayModel(combo.overlay(), combo.christmas()); - if (overlayModelLocation != null) { - parts.add(transform(ClientPlatformHelper.get().getModel(modelManager, overlayModelLocation), transformation)); - } + if (combo.overlay() != null) addPart(parts, modelManager, transformation, combo.overlay().model()); + + var showChristmas = TurtleOverlay.showElfOverlay(combo.overlay(), combo.christmas()); + if (showChristmas) addPart(parts, modelManager, transformation, TurtleOverlay.ELF_MODEL); addUpgrade(parts, transformation, TurtleSide.LEFT, combo.leftUpgrade()); addUpgrade(parts, transformation, TurtleSide.RIGHT, combo.rightUpgrade()); @@ -124,6 +125,10 @@ public final class TurtleModelParts { return parts; } + private void addPart(List parts, ModelManager modelManager, Transformation transformation, ResourceLocation model) { + parts.add(transform(ClientPlatformHelper.get().getModel(modelManager, model), transformation)); + } + private void addUpgrade(List parts, Transformation transformation, TurtleSide side, @Nullable UpgradeData upgrade) { if (upgrade == null) return; var model = TurtleUpgradeModellers.getModel(upgrade.upgrade(), upgrade.data(), side); diff --git a/projects/common/src/client/java/dan200/computercraft/client/render/TurtleBlockEntityRenderer.java b/projects/common/src/client/java/dan200/computercraft/client/render/TurtleBlockEntityRenderer.java index e5d292cde..5d4bb12c0 100644 --- a/projects/common/src/client/java/dan200/computercraft/client/render/TurtleBlockEntityRenderer.java +++ b/projects/common/src/client/java/dan200/computercraft/client/render/TurtleBlockEntityRenderer.java @@ -11,6 +11,7 @@ import dan200.computercraft.api.ComputerCraftAPI; import dan200.computercraft.api.turtle.TurtleSide; import dan200.computercraft.client.platform.ClientPlatformHelper; import dan200.computercraft.client.turtle.TurtleUpgradeModellers; +import dan200.computercraft.shared.turtle.TurtleOverlay; import dan200.computercraft.shared.turtle.blocks.TurtleBlockEntity; import dan200.computercraft.shared.util.Holiday; import net.minecraft.client.Minecraft; @@ -28,8 +29,7 @@ import net.minecraft.world.phys.HitResult; import javax.annotation.Nullable; public class TurtleBlockEntityRenderer implements BlockEntityRenderer { - private static final ResourceLocation COLOUR_TURTLE_MODEL = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_colour"); - private static final ResourceLocation ELF_OVERLAY_MODEL = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_elf_overlay"); + public static final ResourceLocation COLOUR_TURTLE_MODEL = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_colour"); private final BlockEntityRenderDispatcher renderer; private final Font font; @@ -39,12 +39,6 @@ public class TurtleBlockEntityRenderer implements BlockEntityRenderer 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()), @@ -44,5 +47,12 @@ public final class ClientDataProviders { 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/client/java/dan200/computercraft/data/client/ExtraModelsProvider.java b/projects/common/src/client/java/dan200/computercraft/data/client/ExtraModelsProvider.java new file mode 100644 index 000000000..1c6ce5476 --- /dev/null +++ b/projects/common/src/client/java/dan200/computercraft/data/client/ExtraModelsProvider.java @@ -0,0 +1,52 @@ +// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers +// +// SPDX-License-Identifier: MPL-2.0 + +package dan200.computercraft.data.client; + +import com.mojang.serialization.JsonOps; +import dan200.computercraft.client.model.ExtraModels; +import net.minecraft.core.HolderLookup; +import net.minecraft.data.CachedOutput; +import net.minecraft.data.DataProvider; +import net.minecraft.data.PackOutput; +import net.minecraft.resources.ResourceLocation; + +import java.nio.file.Path; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Stream; + +/** + * A data provider to generate {@link ExtraModels}. + */ +abstract class ExtraModelsProvider implements DataProvider { + private final Path path; + private final CompletableFuture registries; + + ExtraModelsProvider(PackOutput output, CompletableFuture registries) { + path = output.getOutputFolder(PackOutput.Target.RESOURCE_PACK).resolve(ExtraModels.PATH.getNamespace()).resolve(ExtraModels.PATH.getPath()); + this.registries = registries; + } + + /** + * Return a stream of models to load. + * + * @param registries The current registries. + * @return The collection of extra models to load. + */ + public abstract Stream getModels(HolderLookup.Provider registries); + + @Override + public final CompletableFuture run(CachedOutput output) { + return registries.thenCompose(registries -> { + var models = new ExtraModels(getModels(registries).sorted().toList()); + var json = ExtraModels.CODEC.encodeStart(JsonOps.INSTANCE, models).getOrThrow(IllegalStateException::new); + return DataProvider.saveStable(output, json, path); + }); + } + + @Override + public final String getName() { + return "Extra Models"; + } +} diff --git a/projects/common/src/generated/resources/assets/computercraft/extra_models.json b/projects/common/src/generated/resources/assets/computercraft/extra_models.json new file mode 100644 index 000000000..4e592f53d --- /dev/null +++ b/projects/common/src/generated/resources/assets/computercraft/extra_models.json @@ -0,0 +1 @@ +["computercraft:block/turtle_rainbow_overlay", "computercraft:block/turtle_trans_overlay"] diff --git a/projects/common/src/generated/resources/data/computercraft/advancement/recipes/redstone/turtle_advanced_overlays/turtle_trans_overlay.json b/projects/common/src/generated/resources/data/computercraft/advancement/recipes/redstone/turtle_advanced_overlays/rainbow_flag.json similarity index 91% rename from projects/common/src/generated/resources/data/computercraft/advancement/recipes/redstone/turtle_advanced_overlays/turtle_trans_overlay.json rename to projects/common/src/generated/resources/data/computercraft/advancement/recipes/redstone/turtle_advanced_overlays/rainbow_flag.json index 45978b7fd..f9c5c65c2 100644 --- a/projects/common/src/generated/resources/data/computercraft/advancement/recipes/redstone/turtle_advanced_overlays/turtle_trans_overlay.json +++ b/projects/common/src/generated/resources/data/computercraft/advancement/recipes/redstone/turtle_advanced_overlays/rainbow_flag.json @@ -3,7 +3,7 @@ "criteria": { "has_dye": {"conditions": {"items": [{"items": "#c:dyes"}]}, "trigger": "minecraft:inventory_changed"}, "has_the_recipe": { - "conditions": {"recipe": "computercraft:turtle_advanced_overlays/turtle_trans_overlay"}, + "conditions": {"recipe": "computercraft:turtle_advanced_overlays/rainbow_flag"}, "trigger": "minecraft:recipe_unlocked" }, "has_turtle": { @@ -12,5 +12,5 @@ } }, "requirements": [["has_the_recipe", "has_turtle", "has_dye"]], - "rewards": {"recipes": ["computercraft:turtle_advanced_overlays/turtle_trans_overlay"]} + "rewards": {"recipes": ["computercraft:turtle_advanced_overlays/rainbow_flag"]} } diff --git a/projects/common/src/generated/resources/data/computercraft/advancement/recipes/redstone/turtle_advanced_overlays/turtle_rainbow_overlay.json b/projects/common/src/generated/resources/data/computercraft/advancement/recipes/redstone/turtle_advanced_overlays/trans_flag.json similarity index 90% rename from projects/common/src/generated/resources/data/computercraft/advancement/recipes/redstone/turtle_advanced_overlays/turtle_rainbow_overlay.json rename to projects/common/src/generated/resources/data/computercraft/advancement/recipes/redstone/turtle_advanced_overlays/trans_flag.json index 1022665f5..3c7f72954 100644 --- a/projects/common/src/generated/resources/data/computercraft/advancement/recipes/redstone/turtle_advanced_overlays/turtle_rainbow_overlay.json +++ b/projects/common/src/generated/resources/data/computercraft/advancement/recipes/redstone/turtle_advanced_overlays/trans_flag.json @@ -3,7 +3,7 @@ "criteria": { "has_dye": {"conditions": {"items": [{"items": "#c:dyes"}]}, "trigger": "minecraft:inventory_changed"}, "has_the_recipe": { - "conditions": {"recipe": "computercraft:turtle_advanced_overlays/turtle_rainbow_overlay"}, + "conditions": {"recipe": "computercraft:turtle_advanced_overlays/trans_flag"}, "trigger": "minecraft:recipe_unlocked" }, "has_turtle": { @@ -12,5 +12,5 @@ } }, "requirements": [["has_the_recipe", "has_turtle", "has_dye"]], - "rewards": {"recipes": ["computercraft:turtle_advanced_overlays/turtle_rainbow_overlay"]} + "rewards": {"recipes": ["computercraft:turtle_advanced_overlays/trans_flag"]} } diff --git a/projects/common/src/generated/resources/data/computercraft/advancement/recipes/redstone/turtle_normal_overlays/turtle_trans_overlay.json b/projects/common/src/generated/resources/data/computercraft/advancement/recipes/redstone/turtle_normal_overlays/rainbow_flag.json similarity index 91% rename from projects/common/src/generated/resources/data/computercraft/advancement/recipes/redstone/turtle_normal_overlays/turtle_trans_overlay.json rename to projects/common/src/generated/resources/data/computercraft/advancement/recipes/redstone/turtle_normal_overlays/rainbow_flag.json index 5ab930860..7db35e7b1 100644 --- a/projects/common/src/generated/resources/data/computercraft/advancement/recipes/redstone/turtle_normal_overlays/turtle_trans_overlay.json +++ b/projects/common/src/generated/resources/data/computercraft/advancement/recipes/redstone/turtle_normal_overlays/rainbow_flag.json @@ -3,7 +3,7 @@ "criteria": { "has_dye": {"conditions": {"items": [{"items": "#c:dyes"}]}, "trigger": "minecraft:inventory_changed"}, "has_the_recipe": { - "conditions": {"recipe": "computercraft:turtle_normal_overlays/turtle_trans_overlay"}, + "conditions": {"recipe": "computercraft:turtle_normal_overlays/rainbow_flag"}, "trigger": "minecraft:recipe_unlocked" }, "has_turtle": { @@ -12,5 +12,5 @@ } }, "requirements": [["has_the_recipe", "has_turtle", "has_dye"]], - "rewards": {"recipes": ["computercraft:turtle_normal_overlays/turtle_trans_overlay"]} + "rewards": {"recipes": ["computercraft:turtle_normal_overlays/rainbow_flag"]} } diff --git a/projects/common/src/generated/resources/data/computercraft/advancement/recipes/redstone/turtle_normal_overlays/turtle_rainbow_overlay.json b/projects/common/src/generated/resources/data/computercraft/advancement/recipes/redstone/turtle_normal_overlays/trans_flag.json similarity index 91% rename from projects/common/src/generated/resources/data/computercraft/advancement/recipes/redstone/turtle_normal_overlays/turtle_rainbow_overlay.json rename to projects/common/src/generated/resources/data/computercraft/advancement/recipes/redstone/turtle_normal_overlays/trans_flag.json index 8a4e65614..5fea92702 100644 --- a/projects/common/src/generated/resources/data/computercraft/advancement/recipes/redstone/turtle_normal_overlays/turtle_rainbow_overlay.json +++ b/projects/common/src/generated/resources/data/computercraft/advancement/recipes/redstone/turtle_normal_overlays/trans_flag.json @@ -3,7 +3,7 @@ "criteria": { "has_dye": {"conditions": {"items": [{"items": "#c:dyes"}]}, "trigger": "minecraft:inventory_changed"}, "has_the_recipe": { - "conditions": {"recipe": "computercraft:turtle_normal_overlays/turtle_rainbow_overlay"}, + "conditions": {"recipe": "computercraft:turtle_normal_overlays/trans_flag"}, "trigger": "minecraft:recipe_unlocked" }, "has_turtle": { @@ -12,5 +12,5 @@ } }, "requirements": [["has_the_recipe", "has_turtle", "has_dye"]], - "rewards": {"recipes": ["computercraft:turtle_normal_overlays/turtle_rainbow_overlay"]} + "rewards": {"recipes": ["computercraft:turtle_normal_overlays/trans_flag"]} } diff --git a/projects/common/src/generated/resources/data/computercraft/computercraft/turtle_overlay/rainbow_flag.json b/projects/common/src/generated/resources/data/computercraft/computercraft/turtle_overlay/rainbow_flag.json new file mode 100644 index 000000000..721b98406 --- /dev/null +++ b/projects/common/src/generated/resources/data/computercraft/computercraft/turtle_overlay/rainbow_flag.json @@ -0,0 +1 @@ +{"model": "computercraft:block/turtle_rainbow_overlay", "show_elf_overlay": true} diff --git a/projects/common/src/generated/resources/data/computercraft/computercraft/turtle_overlay/trans_flag.json b/projects/common/src/generated/resources/data/computercraft/computercraft/turtle_overlay/trans_flag.json new file mode 100644 index 000000000..201c672fc --- /dev/null +++ b/projects/common/src/generated/resources/data/computercraft/computercraft/turtle_overlay/trans_flag.json @@ -0,0 +1 @@ +{"model": "computercraft:block/turtle_trans_overlay", "show_elf_overlay": true} diff --git a/projects/common/src/generated/resources/data/computercraft/recipe/turtle_advanced_overlays/turtle_rainbow_overlay.json b/projects/common/src/generated/resources/data/computercraft/recipe/turtle_advanced_overlays/rainbow_flag.json similarity index 87% rename from projects/common/src/generated/resources/data/computercraft/recipe/turtle_advanced_overlays/turtle_rainbow_overlay.json rename to projects/common/src/generated/resources/data/computercraft/recipe/turtle_advanced_overlays/rainbow_flag.json index b08d4c7c4..03790f733 100644 --- a/projects/common/src/generated/resources/data/computercraft/recipe/turtle_advanced_overlays/turtle_rainbow_overlay.json +++ b/projects/common/src/generated/resources/data/computercraft/recipe/turtle_advanced_overlays/rainbow_flag.json @@ -20,7 +20,7 @@ {"item": "computercraft:turtle_advanced"} ], "result": { - "components": {"computercraft:overlay": "computercraft:block/turtle_rainbow_overlay"}, + "components": {"computercraft:overlay": "computercraft:rainbow_flag"}, "count": 1, "id": "computercraft:turtle_advanced" } diff --git a/projects/common/src/generated/resources/data/computercraft/recipe/turtle_advanced_overlays/turtle_trans_overlay.json b/projects/common/src/generated/resources/data/computercraft/recipe/turtle_advanced_overlays/trans_flag.json similarity index 86% rename from projects/common/src/generated/resources/data/computercraft/recipe/turtle_advanced_overlays/turtle_trans_overlay.json rename to projects/common/src/generated/resources/data/computercraft/recipe/turtle_advanced_overlays/trans_flag.json index 03f8cdea1..9bd459124 100644 --- a/projects/common/src/generated/resources/data/computercraft/recipe/turtle_advanced_overlays/turtle_trans_overlay.json +++ b/projects/common/src/generated/resources/data/computercraft/recipe/turtle_advanced_overlays/trans_flag.json @@ -17,7 +17,7 @@ {"item": "computercraft:turtle_advanced"} ], "result": { - "components": {"computercraft:overlay": "computercraft:block/turtle_trans_overlay"}, + "components": {"computercraft:overlay": "computercraft:trans_flag"}, "count": 1, "id": "computercraft:turtle_advanced" } diff --git a/projects/common/src/generated/resources/data/computercraft/recipe/turtle_normal_overlays/turtle_rainbow_overlay.json b/projects/common/src/generated/resources/data/computercraft/recipe/turtle_normal_overlays/rainbow_flag.json similarity index 87% rename from projects/common/src/generated/resources/data/computercraft/recipe/turtle_normal_overlays/turtle_rainbow_overlay.json rename to projects/common/src/generated/resources/data/computercraft/recipe/turtle_normal_overlays/rainbow_flag.json index 64cd0675f..0e269ef6e 100644 --- a/projects/common/src/generated/resources/data/computercraft/recipe/turtle_normal_overlays/turtle_rainbow_overlay.json +++ b/projects/common/src/generated/resources/data/computercraft/recipe/turtle_normal_overlays/rainbow_flag.json @@ -20,7 +20,7 @@ {"item": "computercraft:turtle_normal"} ], "result": { - "components": {"computercraft:overlay": "computercraft:block/turtle_rainbow_overlay"}, + "components": {"computercraft:overlay": "computercraft:rainbow_flag"}, "count": 1, "id": "computercraft:turtle_normal" } diff --git a/projects/common/src/generated/resources/data/computercraft/recipe/turtle_normal_overlays/turtle_trans_overlay.json b/projects/common/src/generated/resources/data/computercraft/recipe/turtle_normal_overlays/trans_flag.json similarity index 86% rename from projects/common/src/generated/resources/data/computercraft/recipe/turtle_normal_overlays/turtle_trans_overlay.json rename to projects/common/src/generated/resources/data/computercraft/recipe/turtle_normal_overlays/trans_flag.json index 63df05d9d..76b8115ee 100644 --- a/projects/common/src/generated/resources/data/computercraft/recipe/turtle_normal_overlays/turtle_trans_overlay.json +++ b/projects/common/src/generated/resources/data/computercraft/recipe/turtle_normal_overlays/trans_flag.json @@ -17,7 +17,7 @@ {"item": "computercraft:turtle_normal"} ], "result": { - "components": {"computercraft:overlay": "computercraft:block/turtle_trans_overlay"}, + "components": {"computercraft:overlay": "computercraft:trans_flag"}, "count": 1, "id": "computercraft:turtle_normal" } diff --git a/projects/common/src/main/java/dan200/computercraft/data/DataProviders.java b/projects/common/src/main/java/dan200/computercraft/data/DataProviders.java index 898f119f8..dd68cf5b7 100644 --- a/projects/common/src/main/java/dan200/computercraft/data/DataProviders.java +++ b/projects/common/src/main/java/dan200/computercraft/data/DataProviders.java @@ -7,6 +7,7 @@ package dan200.computercraft.data; import com.mojang.serialization.Codec; import dan200.computercraft.api.pocket.IPocketUpgrade; import dan200.computercraft.api.turtle.ITurtleUpgrade; +import dan200.computercraft.shared.turtle.TurtleOverlay; import net.minecraft.Util; import net.minecraft.core.HolderLookup; import net.minecraft.core.RegistrySetBuilder; @@ -38,6 +39,7 @@ public final class DataProviders { Util.make(new RegistrySetBuilder(), builder -> { builder.add(ITurtleUpgrade.REGISTRY, TurtleUpgradeProvider::addUpgrades); builder.add(IPocketUpgrade.REGISTRY, PocketUpgradeProvider::addUpgrades); + builder.add(TurtleOverlay.REGISTRY, TurtleOverlays::register); })); var fullRegistries = fullRegistryPatch.thenApply(RegistrySetBuilder.PatchedRegistries::full); @@ -57,7 +59,8 @@ public final class DataProviders { // and invoke that. try { Class.forName("dan200.computercraft.data.client.ClientDataProviders") - .getMethod("add", GeneratorSink.class).invoke(null, generator); + .getMethod("add", GeneratorSink.class, CompletableFuture.class) + .invoke(null, generator, fullRegistries); } catch (ReflectiveOperationException e) { throw new RuntimeException(e); } diff --git a/projects/common/src/main/java/dan200/computercraft/data/RecipeProvider.java b/projects/common/src/main/java/dan200/computercraft/data/RecipeProvider.java index b62e1384d..257b0a881 100644 --- a/projects/common/src/main/java/dan200/computercraft/data/RecipeProvider.java +++ b/projects/common/src/main/java/dan200/computercraft/data/RecipeProvider.java @@ -28,6 +28,7 @@ import dan200.computercraft.shared.recipe.ImpostorShapelessRecipe; import dan200.computercraft.shared.recipe.TransformShapedRecipe; import dan200.computercraft.shared.recipe.TransformShapelessRecipe; import dan200.computercraft.shared.recipe.function.CopyComponents; +import dan200.computercraft.shared.turtle.TurtleOverlay; import dan200.computercraft.shared.turtle.items.TurtleItem; import dan200.computercraft.shared.turtle.recipes.TurtleUpgradeRecipe; import dan200.computercraft.shared.util.ColourUtils; @@ -45,6 +46,7 @@ import net.minecraft.data.recipes.RecipeCategory; import net.minecraft.data.recipes.RecipeOutput; import net.minecraft.data.recipes.ShapedRecipeBuilder; import net.minecraft.data.recipes.ShapelessRecipeBuilder; +import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; import net.minecraft.tags.ItemTags; import net.minecraft.tags.TagKey; @@ -96,7 +98,7 @@ final class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider { diskColours(add); pocketUpgrades(add, registries); turtleUpgrades(add, registries); - turtleOverlays(add); + turtleOverlays(add, registries); addSpecial(add, new DiskRecipe(CraftingBookCategory.MISC)); addSpecial(add, new ColourableRecipe(CraftingBookCategory.MISC)); @@ -189,8 +191,8 @@ final class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider { } } - private void turtleOverlays(RecipeOutput add) { - turtleOverlay(add, "turtle_trans_overlay", x -> x + private void turtleOverlays(RecipeOutput add, HolderLookup.Provider registries) { + turtleOverlay(add, registries, TurtleOverlays.TRANS_FLAG, x -> x .unlockedBy("has_dye", inventoryChange(itemPredicate(ingredients.dye()))) .requires(ColourUtils.getDyeTag(DyeColor.LIGHT_BLUE)) .requires(ColourUtils.getDyeTag(DyeColor.PINK)) @@ -198,7 +200,7 @@ final class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider { .requires(Items.STICK) ); - turtleOverlay(add, "turtle_rainbow_overlay", x -> x + turtleOverlay(add, registries, TurtleOverlays.RAINBOW_FLAG, x -> x .unlockedBy("has_dye", inventoryChange(itemPredicate(ingredients.dye()))) .requires(ColourUtils.getDyeTag(DyeColor.RED)) .requires(ColourUtils.getDyeTag(DyeColor.ORANGE)) @@ -210,14 +212,14 @@ final class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider { ); } - private void turtleOverlay(RecipeOutput add, String overlay, Consumer build) { - var overlayId = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/" + overlay); + private void turtleOverlay(RecipeOutput add, HolderLookup.Provider registries, ResourceKey overlay, Consumer build) { + var holder = registries.lookupOrThrow(overlay.registryKey()).getOrThrow(overlay); for (var turtleItem : turtleItems()) { var name = RegistryHelper.getKeyOrThrow(BuiltInRegistries.ITEM, turtleItem); var builder = ShapelessSpecBuilder - .shapeless(RecipeCategory.REDSTONE, DataComponentUtil.createStack(turtleItem, ModRegistry.DataComponents.OVERLAY.get(), overlayId)) + .shapeless(RecipeCategory.REDSTONE, DataComponentUtil.createStack(turtleItem, ModRegistry.DataComponents.OVERLAY.get(), holder)) .group(name.withSuffix("_overlay").toString()) .unlockedBy("has_turtle", inventoryChange(turtleItem)); build.accept(builder); @@ -226,7 +228,7 @@ final class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider { .build(s -> new TransformShapelessRecipe(s, List.of( CopyComponents.builder(turtleItem).exclude(ModRegistry.DataComponents.OVERLAY.get()).build() ))) - .save(add, name.withSuffix("_overlays/" + overlay)); + .save(add, name.withSuffix("_overlays/" + overlay.location().getPath())); } } diff --git a/projects/common/src/main/java/dan200/computercraft/data/TurtleOverlays.java b/projects/common/src/main/java/dan200/computercraft/data/TurtleOverlays.java new file mode 100644 index 000000000..7f86c343b --- /dev/null +++ b/projects/common/src/main/java/dan200/computercraft/data/TurtleOverlays.java @@ -0,0 +1,38 @@ +// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers +// +// SPDX-License-Identifier: MPL-2.0 + +package dan200.computercraft.data; + +import dan200.computercraft.api.ComputerCraftAPI; +import dan200.computercraft.shared.turtle.TurtleOverlay; +import net.minecraft.data.worldgen.BootstrapContext; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; + +/** + * Built-in turtle overlays. + */ +final class TurtleOverlays { + public static final ResourceKey RAINBOW_FLAG = create("rainbow_flag"); + public static final ResourceKey TRANS_FLAG = create("trans_flag"); + + private static ResourceKey create(String name) { + return ResourceKey.create(TurtleOverlay.REGISTRY, ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, name)); + } + + private TurtleOverlays() { + } + + public static void register(BootstrapContext registry) { + registry.register(RAINBOW_FLAG, new TurtleOverlay( + ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_rainbow_overlay"), + true + )); + + registry.register(TRANS_FLAG, new TurtleOverlay( + ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_trans_overlay"), + true + )); + } +} diff --git a/projects/common/src/main/java/dan200/computercraft/shared/ModRegistry.java b/projects/common/src/main/java/dan200/computercraft/shared/ModRegistry.java index 527f704ce..5fb03b706 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/ModRegistry.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/ModRegistry.java @@ -72,6 +72,7 @@ import dan200.computercraft.shared.recipe.*; import dan200.computercraft.shared.recipe.function.CopyComponents; import dan200.computercraft.shared.recipe.function.RecipeFunction; import dan200.computercraft.shared.turtle.FurnaceRefuelHandler; +import dan200.computercraft.shared.turtle.TurtleOverlay; import dan200.computercraft.shared.turtle.blocks.TurtleBlock; import dan200.computercraft.shared.turtle.blocks.TurtleBlockEntity; import dan200.computercraft.shared.turtle.inventory.TurtleMenu; @@ -95,7 +96,6 @@ import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.network.chat.Component; import net.minecraft.network.codec.ByteBufCodecs; import net.minecraft.network.codec.StreamCodec; -import net.minecraft.resources.ResourceLocation; import net.minecraft.world.flag.FeatureFlags; import net.minecraft.world.inventory.MenuType; import net.minecraft.world.item.BlockItem; @@ -320,8 +320,8 @@ public final class ModRegistry { /** * The overlay on a turtle. */ - public static final RegistryEntry> OVERLAY = register("overlay", b -> b - .persistent(ResourceLocation.CODEC).networkSynchronized(ResourceLocation.STREAM_CODEC) + public static final RegistryEntry>> OVERLAY = register("overlay", b -> b + .persistent(TurtleOverlay.CODEC).networkSynchronized(TurtleOverlay.STREAM_CODEC) ); /** diff --git a/projects/common/src/main/java/dan200/computercraft/shared/turtle/TurtleOverlay.java b/projects/common/src/main/java/dan200/computercraft/shared/turtle/TurtleOverlay.java new file mode 100644 index 000000000..1db355d2f --- /dev/null +++ b/projects/common/src/main/java/dan200/computercraft/shared/turtle/TurtleOverlay.java @@ -0,0 +1,78 @@ +// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers +// +// SPDX-License-Identifier: MPL-2.0 + +package dan200.computercraft.shared.turtle; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import dan200.computercraft.api.ComputerCraftAPI; +import dan200.computercraft.shared.ModRegistry; +import dan200.computercraft.shared.util.Holiday; +import net.minecraft.core.Holder; +import net.minecraft.core.Registry; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.resources.RegistryFileCodec; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; + +import javax.annotation.Nullable; + +/** + * A cosmetic overlay on a turtle. + * + * @param model The path to the overlay's model. + * @param showElfOverlay Whether this overlay is compatible with the {@linkplain #ELF_MODEL Christmas elf model}. + * @see ModRegistry.DataComponents#OVERLAY + */ +public record TurtleOverlay(ResourceLocation model, boolean showElfOverlay) { + /** + * The registry turtle overlays are stored in. + */ + public static final ResourceKey> REGISTRY = ResourceKey.createRegistryKey(ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "turtle_overlay")); + + /** + * The codec used to read/write turtle overlay definitions from datapacks. + */ + public static final Codec DIRECT_CODEC = RecordCodecBuilder.create(instance -> instance.group( + ResourceLocation.CODEC.fieldOf("model").forGetter(TurtleOverlay::model), + Codec.BOOL.optionalFieldOf("show_elf_overlay", false).forGetter(TurtleOverlay::showElfOverlay) + ).apply(instance, TurtleOverlay::new)); + + /** + * The codec used for {@link TurtleOverlay} instances. + * + * @see ModRegistry.DataComponents#OVERLAY + */ + public static final Codec> CODEC = RegistryFileCodec.create(REGISTRY, DIRECT_CODEC); + + /** + * The stream codec used for {@link TurtleOverlay} instances. + */ + public static final StreamCodec> STREAM_CODEC = ByteBufCodecs.holder(REGISTRY, StreamCodec.composite( + ResourceLocation.STREAM_CODEC, TurtleOverlay::model, + ByteBufCodecs.BOOL, TurtleOverlay::showElfOverlay, + TurtleOverlay::new + )); + + /** + * An additional overlay that is rendered on all turtles at {@linkplain Holiday#CHRISTMAS Christmas}. + * + * @see #showElfOverlay() + * @see #showElfOverlay(TurtleOverlay, boolean) + */ + public static final ResourceLocation ELF_MODEL = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_elf_overlay"); + + /** + * Determine whether we should show the {@linkplain #ELF_MODEL elf overlay}. + * + * @param overlay The current {@link TurtleOverlay}. + * @param christmas Whether it is Christmas. + * @return Whether we should show the elf overlay. + */ + public static boolean showElfOverlay(@Nullable TurtleOverlay overlay, boolean christmas) { + return christmas && (overlay == null || overlay.showElfOverlay()); + } +} diff --git a/projects/common/src/main/java/dan200/computercraft/shared/turtle/blocks/TurtleBlockEntity.java b/projects/common/src/main/java/dan200/computercraft/shared/turtle/blocks/TurtleBlockEntity.java index 220e99ab5..4be47bef4 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/turtle/blocks/TurtleBlockEntity.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/turtle/blocks/TurtleBlockEntity.java @@ -20,6 +20,7 @@ import dan200.computercraft.shared.computer.core.ServerComputer; import dan200.computercraft.shared.config.Config; import dan200.computercraft.shared.container.BasicContainer; import dan200.computercraft.shared.platform.PlatformHelper; +import dan200.computercraft.shared.turtle.TurtleOverlay; import dan200.computercraft.shared.turtle.apis.TurtleAPI; import dan200.computercraft.shared.turtle.core.TurtleBrain; import dan200.computercraft.shared.turtle.inventory.TurtleMenu; @@ -30,7 +31,6 @@ import net.minecraft.core.NonNullList; import net.minecraft.core.component.DataComponentMap; import net.minecraft.core.component.DataComponents; import net.minecraft.nbt.CompoundTag; -import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.Container; import net.minecraft.world.ContainerHelper; @@ -222,8 +222,9 @@ public class TurtleBlockEntity extends AbstractComputerBlockEntity implements Ba return brain.getColour(); } - public @Nullable ResourceLocation getOverlay() { - return brain.getOverlay(); + public @Nullable TurtleOverlay getOverlay() { + var overlay = brain.getOverlay(); + return overlay == null ? null : overlay.value(); } public ITurtleAccess getAccess() { diff --git a/projects/common/src/main/java/dan200/computercraft/shared/turtle/core/TurtleBrain.java b/projects/common/src/main/java/dan200/computercraft/shared/turtle/core/TurtleBrain.java index 7e8781793..9112a7be1 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/turtle/core/TurtleBrain.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/turtle/core/TurtleBrain.java @@ -20,6 +20,7 @@ import dan200.computercraft.shared.computer.core.ComputerFamily; import dan200.computercraft.shared.computer.core.ServerComputer; import dan200.computercraft.shared.config.Config; import dan200.computercraft.shared.container.InventoryDelegate; +import dan200.computercraft.shared.turtle.TurtleOverlay; import dan200.computercraft.shared.turtle.blocks.TurtleBlockEntity; import dan200.computercraft.shared.util.BlockEntityHelpers; import dan200.computercraft.shared.util.Holiday; @@ -32,7 +33,6 @@ import net.minecraft.core.component.DataComponentPatch; import net.minecraft.core.particles.ParticleTypes; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.Tag; -import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerLevel; import net.minecraft.tags.FluidTags; import net.minecraft.world.Container; @@ -74,7 +74,7 @@ public class TurtleBrain implements TurtleAccessInternal { private int selectedSlot = 0; private int fuelLevel = 0; private int colourHex = -1; - private @Nullable ResourceLocation overlay = null; + private @Nullable Holder overlay = null; private TurtleAnimation animation = TurtleAnimation.NONE; private int animationProgress = 0; @@ -134,7 +134,7 @@ public class TurtleBrain implements TurtleAccessInternal { // Read fields colourHex = nbt.contains(NBT_COLOUR) ? nbt.getInt(NBT_COLOUR) : -1; fuelLevel = nbt.contains(NBT_FUEL) ? nbt.getInt(NBT_FUEL) : 0; - overlay = nbt.contains(NBT_OVERLAY) ? ResourceLocation.parse(nbt.getString(NBT_OVERLAY)) : null; + overlay = nbt.contains(NBT_OVERLAY) ? NBTUtil.decodeFrom(TurtleOverlay.CODEC, registries, nbt, NBT_OVERLAY) : null; // Read upgrades setUpgradeDirect(TurtleSide.LEFT, NBTUtil.decodeFrom(TurtleUpgrades.instance().upgradeDataCodec(), registries, nbt, NBT_LEFT_UPGRADE)); @@ -144,7 +144,7 @@ public class TurtleBrain implements TurtleAccessInternal { private void writeCommon(CompoundTag nbt, HolderLookup.Provider registries) { nbt.putInt(NBT_FUEL, fuelLevel); if (colourHex != -1) nbt.putInt(NBT_COLOUR, colourHex); - if (overlay != null) nbt.putString(NBT_OVERLAY, overlay.toString()); + if (overlay != null) NBTUtil.encodeTo(TurtleOverlay.CODEC, registries, nbt, NBT_OVERLAY, overlay); // Write upgrades NBTUtil.encodeTo(TurtleUpgrades.instance().upgradeDataCodec(), registries, nbt, NBT_LEFT_UPGRADE, getUpgradeWithData(TurtleSide.LEFT)); @@ -418,11 +418,11 @@ public class TurtleBrain implements TurtleAccessInternal { BlockEntityHelpers.updateBlock(owner); } - public @Nullable ResourceLocation getOverlay() { + public @Nullable Holder getOverlay() { return overlay; } - public void setOverlay(@Nullable ResourceLocation overlay) { + public void setOverlay(@Nullable Holder overlay) { if (!Objects.equals(this.overlay, overlay)) { this.overlay = overlay; BlockEntityHelpers.updateBlock(owner); diff --git a/projects/common/src/main/java/dan200/computercraft/shared/turtle/items/TurtleItem.java b/projects/common/src/main/java/dan200/computercraft/shared/turtle/items/TurtleItem.java index 2c26ac23c..0b73f436e 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/turtle/items/TurtleItem.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/turtle/items/TurtleItem.java @@ -12,11 +12,11 @@ import dan200.computercraft.api.upgrades.UpgradeData; import dan200.computercraft.impl.TurtleUpgrades; import dan200.computercraft.shared.ModRegistry; import dan200.computercraft.shared.computer.items.AbstractComputerItem; +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.resources.ResourceLocation; import net.minecraft.world.ItemInteractionResult; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.block.LayeredCauldronBlock; @@ -74,8 +74,9 @@ public class TurtleItem extends AbstractComputerItem { return stack.get(side == TurtleSide.LEFT ? ModRegistry.DataComponents.LEFT_TURTLE_UPGRADE.get() : ModRegistry.DataComponents.RIGHT_TURTLE_UPGRADE.get()); } - public static @Nullable ResourceLocation getOverlay(ItemStack stack) { - return stack.get(ModRegistry.DataComponents.OVERLAY.get()); + public static @Nullable TurtleOverlay getOverlay(ItemStack stack) { + var overlay = stack.get(ModRegistry.DataComponents.OVERLAY.get()); + return overlay == null ? null : overlay.value(); } public static int getFuelLevel(ItemStack stack) { diff --git a/projects/common/src/main/java/dan200/computercraft/shared/util/ComponentizationFixers.java b/projects/common/src/main/java/dan200/computercraft/shared/util/ComponentizationFixers.java index cc49eb75d..43d289056 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/util/ComponentizationFixers.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/util/ComponentizationFixers.java @@ -82,7 +82,8 @@ public class ComponentizationFixers { if (item.is(TURTLES)) { item.moveTagToComponent("Fuel", "computercraft:fuel"); - item.moveTagToComponent("Overlay", "computercraft:overlay"); + + item.removeTag("Overlay").result().ifPresent(x -> item.setComponent("computercraft:overlay", fixOverlay(x))); moveUpgradeToComponent(item, ops, "LeftUpgrade", "LeftUpgradeNbt", "computercraft:left_turtle_upgrade"); moveUpgradeToComponent(item, ops, "RightUpgrade", "RightUpgradeNbt", "computercraft:right_turtle_upgrade"); @@ -152,6 +153,7 @@ public class ComponentizationFixers { return typed -> typed.updateTyped(input, output, typed2 -> typed2.update(DSL.remainderFinder(), x -> { x = moveUpgradeData(x, "LeftUpgrade", "LeftUpgradeNbt"); x = moveUpgradeData(x, "RightUpgrade", "RightUpgradeNbt"); + x = x.update("Overlay", ComponentizationFixers::fixOverlay); return x; })); } @@ -164,6 +166,20 @@ public class ComponentizationFixers { return ops.set(key, createUpgradeData(ops, upgradeId, ops.get(dataKey))).remove(dataKey); } + private static Dynamic fixOverlay(Dynamic overlay) { + // Rewrite known overlays to their new ids. + var overlayId = overlay.asString(null); + if (overlayId == null) return overlay; + + return switch (overlayId) { + // Map known overlays to their new id. + case "computercraft:block/turtle_trans_overlay" -> overlay.createString("computercraft:trans_flag"); + case "computercraft:block/turtle_rainbow_overlay" -> overlay.createString("computercraft:rainbow_flag"); + // And unknown overlays to a direct entry. + default -> overlay.emptyMap().set("model", overlay); + }; + } + /** * Create a new upgrade data. * diff --git a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Turtle_Test.kt b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Turtle_Test.kt index 3fde0e6ce..ea8bb8373 100644 --- a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Turtle_Test.kt +++ b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Turtle_Test.kt @@ -4,6 +4,7 @@ package dan200.computercraft.gametest +import dan200.computercraft.api.ComputerCraftAPI import dan200.computercraft.api.detail.BasicItemDetailProvider import dan200.computercraft.api.detail.VanillaDetailRegistries import dan200.computercraft.api.lua.ObjectArguments @@ -21,7 +22,9 @@ import dan200.computercraft.shared.peripheral.modem.wired.CableBlock import dan200.computercraft.shared.peripheral.modem.wired.CableModemVariant import dan200.computercraft.shared.peripheral.monitor.MonitorBlock import dan200.computercraft.shared.peripheral.monitor.MonitorEdgeState +import dan200.computercraft.shared.turtle.TurtleOverlay import dan200.computercraft.shared.turtle.apis.TurtleAPI +import dan200.computercraft.shared.turtle.items.TurtleItem import dan200.computercraft.shared.util.WaterloggableHelpers import dan200.computercraft.test.core.assertArrayEquals import dan200.computercraft.test.core.computer.LuaTaskContext @@ -654,6 +657,27 @@ class Turtle_Test { } } + /** + * Loads a structure created on an older version of the game, and checks that data fixers have been applied. + */ + @GameTest + fun Data_fixers(helper: GameTestHelper) = helper.sequence { + thenExecute { + val overlay = helper.level.registryAccess().registryOrThrow(TurtleOverlay.REGISTRY) + .get(ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "trans_flag"))!! + val upgrade = helper.level.registryAccess().registryOrThrow(ITurtleUpgrade.REGISTRY) + .get(ResourceLocation.withDefaultNamespace("diamond_pickaxe"))!! + + val turtleBe = helper.getBlockEntity(BlockPos(1, 2, 1), ModRegistry.BlockEntities.TURTLE_NORMAL.get()) + assertEquals(overlay, turtleBe.overlay) + assertEquals(upgrade, turtleBe.getUpgrade(TurtleSide.LEFT)) + + val turtleItem = turtleBe.getItem(0) + assertEquals(overlay, TurtleItem.getOverlay(turtleItem)) + assertEquals(upgrade, TurtleItem.getUpgrade(turtleItem, TurtleSide.LEFT)) + } + } + /** * `turtle.suck` only pulls for the current side. */ diff --git a/projects/common/src/testMod/resources/data/cctest/structures/turtle_test.data_fixers.snbt b/projects/common/src/testMod/resources/data/cctest/structures/turtle_test.data_fixers.snbt new file mode 100644 index 000000000..a7456bd44 --- /dev/null +++ b/projects/common/src/testMod/resources/data/cctest/structures/turtle_test.data_fixers.snbt @@ -0,0 +1,39 @@ +{ + DataVersion: 3465, + size: [3, 3, 3], + data: [ + {pos: [0, 0, 0], state: "minecraft:polished_andesite"}, + {pos: [0, 0, 1], state: "minecraft:polished_andesite"}, + {pos: [0, 0, 2], state: "minecraft:polished_andesite"}, + {pos: [1, 0, 0], state: "minecraft:polished_andesite"}, + {pos: [1, 0, 1], state: "minecraft:polished_andesite"}, + {pos: [1, 0, 2], state: "minecraft:polished_andesite"}, + {pos: [2, 0, 0], state: "minecraft:polished_andesite"}, + {pos: [2, 0, 1], state: "minecraft:polished_andesite"}, + {pos: [2, 0, 2], state: "minecraft:polished_andesite"}, + {pos: [0, 1, 0], state: "minecraft:air"}, + {pos: [0, 1, 1], state: "minecraft:air"}, + {pos: [0, 1, 2], state: "minecraft:air"}, + {pos: [1, 1, 0], state: "minecraft:air"}, + {pos: [1, 1, 1], state: "computercraft:turtle_normal{facing:south,waterlogged:false}", nbt: {ComputerId: 123, Fuel: 0, Items: [{Count: 1b, Slot: 0b, id: "computercraft:turtle_normal", tag: {ComputerId: 123, LeftUpgrade: "minecraft:diamond_pickaxe", Overlay: "computercraft:block/turtle_trans_overlay"}}], LeftUpgrade: "minecraft:diamond_pickaxe", LeftUpgradeNbt: {}, On: 0b, Overlay: "computercraft:block/turtle_trans_overlay", Owner: {LowerId: -4770154215762454977L, Name: "Player436", UpperId: 114963728012426084L}, Slot: 0, id: "computercraft:turtle_normal"}}, + {pos: [1, 1, 2], state: "minecraft:air"}, + {pos: [2, 1, 0], state: "minecraft:air"}, + {pos: [2, 1, 1], state: "minecraft:air"}, + {pos: [2, 1, 2], state: "minecraft:air"}, + {pos: [0, 2, 0], state: "minecraft:air"}, + {pos: [0, 2, 1], state: "minecraft:air"}, + {pos: [0, 2, 2], state: "minecraft:air"}, + {pos: [1, 2, 0], state: "minecraft:air"}, + {pos: [1, 2, 1], state: "minecraft:air"}, + {pos: [1, 2, 2], state: "minecraft:air"}, + {pos: [2, 2, 0], state: "minecraft:air"}, + {pos: [2, 2, 1], state: "minecraft:air"}, + {pos: [2, 2, 2], state: "minecraft:air"} + ], + entities: [], + palette: [ + "minecraft:polished_andesite", + "minecraft:air", + "computercraft:turtle_normal{facing:south,waterlogged:false}" + ] +} diff --git a/projects/fabric/src/client/java/dan200/computercraft/client/ComputerCraftClient.java b/projects/fabric/src/client/java/dan200/computercraft/client/ComputerCraftClient.java index 3f4c28de2..224ac9bc5 100644 --- a/projects/fabric/src/client/java/dan200/computercraft/client/ComputerCraftClient.java +++ b/projects/fabric/src/client/java/dan200/computercraft/client/ComputerCraftClient.java @@ -53,7 +53,7 @@ public class ComputerCraftClient { ClientRegistry.registerMainThread(ItemProperties::register); PreparableModelLoadingPlugin.register(CustomModelLoader::prepare, (state, context) -> { - ClientRegistry.registerExtraModels(context::addModels); + ClientRegistry.registerExtraModels(context::addModels, state.getExtraModels()); context.resolveModel().register(ctx -> state.loadModel(ctx.id())); context.modifyModelAfterBake().register((model, ctx) -> model == null ? null : state.wrapModel(ctx, model)); }); diff --git a/projects/fabric/src/client/java/dan200/computercraft/client/model/CustomModelLoader.java b/projects/fabric/src/client/java/dan200/computercraft/client/model/CustomModelLoader.java index cd2b8e287..7addcdced 100644 --- a/projects/fabric/src/client/java/dan200/computercraft/client/model/CustomModelLoader.java +++ b/projects/fabric/src/client/java/dan200/computercraft/client/model/CustomModelLoader.java @@ -25,6 +25,7 @@ import org.slf4j.LoggerFactory; import javax.annotation.Nullable; import java.io.IOException; import java.io.Reader; +import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.concurrent.CompletableFuture; @@ -46,13 +47,15 @@ public final class CustomModelLoader { private final Map models = new HashMap<>(); private final Map emissiveModels = new HashMap<>(); + private final Collection extraModels; - private CustomModelLoader() { + private CustomModelLoader(Collection extraModels) { + this.extraModels = extraModels; } public static CompletableFuture prepare(ResourceManager resources, Executor executor) { return CompletableFuture.supplyAsync(() -> { - var loader = new CustomModelLoader(); + var loader = new CustomModelLoader(ExtraModels.loadAll(resources)); for (var resource : resources.listResources("models", x -> x.getNamespace().equals(ComputerCraftAPI.MOD_ID) && x.getPath().endsWith(".json")).entrySet()) { loader.loadModel(resource.getKey(), resource.getValue()); } @@ -85,6 +88,10 @@ public final class CustomModelLoader { } } + public Collection getExtraModels() { + return extraModels; + } + /** * Load a custom model. This searches for CC models with a custom {@code loader} field. * diff --git a/projects/fabric/src/main/java/dan200/computercraft/shared/ComputerCraft.java b/projects/fabric/src/main/java/dan200/computercraft/shared/ComputerCraft.java index ab9f4bf71..0a4a08331 100644 --- a/projects/fabric/src/main/java/dan200/computercraft/shared/ComputerCraft.java +++ b/projects/fabric/src/main/java/dan200/computercraft/shared/ComputerCraft.java @@ -25,6 +25,7 @@ import dan200.computercraft.shared.peripheral.modem.wired.WiredModemFullBlockEnt import dan200.computercraft.shared.peripheral.modem.wireless.WirelessModemBlockEntity; import dan200.computercraft.shared.platform.FabricConfigFile; import dan200.computercraft.shared.recipe.function.RecipeFunction; +import dan200.computercraft.shared.turtle.TurtleOverlay; import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerChunkEvents; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; @@ -70,6 +71,7 @@ public class ComputerCraft { DynamicRegistries.registerSynced(ITurtleUpgrade.REGISTRY, TurtleUpgrades.instance().upgradeCodec()); DynamicRegistries.registerSynced(IPocketUpgrade.REGISTRY, PocketUpgrades.instance().upgradeCodec()); + DynamicRegistries.registerSynced(TurtleOverlay.REGISTRY, TurtleOverlay.DIRECT_CODEC); ModRegistry.register(); ModRegistry.registerMainThread(); diff --git a/projects/forge/src/client/java/dan200/computercraft/client/ForgeClientRegistry.java b/projects/forge/src/client/java/dan200/computercraft/client/ForgeClientRegistry.java index 64ea01d23..84704d7da 100644 --- a/projects/forge/src/client/java/dan200/computercraft/client/ForgeClientRegistry.java +++ b/projects/forge/src/client/java/dan200/computercraft/client/ForgeClientRegistry.java @@ -6,6 +6,7 @@ package dan200.computercraft.client; import dan200.computercraft.api.ComputerCraftAPI; import dan200.computercraft.api.client.turtle.RegisterTurtleModellersEvent; +import dan200.computercraft.client.model.ExtraModels; import dan200.computercraft.client.model.turtle.TurtleModelLoader; import dan200.computercraft.client.turtle.TurtleUpgradeModellers; import net.minecraft.client.Minecraft; @@ -57,7 +58,8 @@ public final class ForgeClientRegistry { @SubscribeEvent public static void registerModels(ModelEvent.RegisterAdditional event) { gatherModellers(); - ClientRegistry.registerExtraModels(x -> event.register(ModelResourceLocation.standalone(x))); + var extraModels = ExtraModels.loadAll(Minecraft.getInstance().getResourceManager()); + ClientRegistry.registerExtraModels(x -> event.register(ModelResourceLocation.standalone(x)), extraModels); } @SubscribeEvent diff --git a/projects/forge/src/main/java/dan200/computercraft/ComputerCraft.java b/projects/forge/src/main/java/dan200/computercraft/ComputerCraft.java index d4351b17c..bf3887d4f 100644 --- a/projects/forge/src/main/java/dan200/computercraft/ComputerCraft.java +++ b/projects/forge/src/main/java/dan200/computercraft/ComputerCraft.java @@ -33,6 +33,7 @@ import dan200.computercraft.shared.peripheral.modem.wired.WiredModemFullBlockEnt import dan200.computercraft.shared.peripheral.modem.wireless.WirelessModemBlockEntity; import dan200.computercraft.shared.platform.ForgeConfigFile; import dan200.computercraft.shared.recipe.function.RecipeFunction; +import dan200.computercraft.shared.turtle.TurtleOverlay; import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.network.protocol.common.custom.CustomPacketPayload; import net.minecraft.server.level.ServerPlayer; @@ -92,6 +93,7 @@ public final class ComputerCraft { public static void registerDynamicRegistries(DataPackRegistryEvent.NewRegistry event) { event.dataPackRegistry(ITurtleUpgrade.REGISTRY, TurtleUpgrades.instance().upgradeCodec(), TurtleUpgrades.instance().upgradeCodec()); event.dataPackRegistry(IPocketUpgrade.REGISTRY, PocketUpgrades.instance().upgradeCodec(), PocketUpgrades.instance().upgradeCodec()); + event.dataPackRegistry(TurtleOverlay.REGISTRY, TurtleOverlay.DIRECT_CODEC, TurtleOverlay.DIRECT_CODEC); } @SubscribeEvent