1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-01-10 09:20:28 +00:00

Load turtle overlays from a registry

- Add a new computercraft:turtle_overlay dynamic registry, which stores
   turtle overlays. Turtle overlays are just a model id and an
   (optional) boolean flag, which specifies whether this overlay is
   compatible with the elf/christmas model.

 - Change the computercraft:overlay component to accept a
   Holder<TurtleOverlay> (instead of just a model ID). This accepts both
   an overlay ID or an inline overlay object (e.g. you can do
   cc:turtle_normal[computercraft:overlay={model:"foo"}].

 - Update turtle model and BE rendering code to render both the overlay
   and elf (if compatible). Fixes #1663.

 - Ideally we'd automatically load all models listed in the overlay
   registry. However, resource loading happens separately to datapacks,
   so we can't link the two.

   Instead, we add a new assets/computercraft/extra_models.json file
   that lists any additional models that should be loaded and baked.

   This file includes all built-in overlay models, but external resource
   packs and/or mods can easily extend it.
This commit is contained in:
Jonathan Coates 2024-06-27 20:57:43 +01:00
parent 1a1623075f
commit f10e401aea
No known key found for this signature in database
GPG Key ID: B9E431FF07C98D06
33 changed files with 416 additions and 67 deletions

View File

@ -25,6 +25,7 @@ import dan200.computercraft.shared.computer.core.ComputerState;
import dan200.computercraft.shared.computer.core.ServerContext; import dan200.computercraft.shared.computer.core.ServerContext;
import dan200.computercraft.shared.computer.inventory.AbstractComputerMenu; import dan200.computercraft.shared.computer.inventory.AbstractComputerMenu;
import dan200.computercraft.shared.media.items.DiskItem; import dan200.computercraft.shared.media.items.DiskItem;
import dan200.computercraft.shared.turtle.TurtleOverlay;
import net.minecraft.Util; import net.minecraft.Util;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.color.item.ItemColor; import net.minecraft.client.color.item.ItemColor;
@ -53,6 +54,7 @@ import net.minecraft.world.level.ItemLike;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.Collection;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Supplier; import java.util.function.Supplier;
@ -143,15 +145,14 @@ public final class ClientRegistry {
register.accept(GuiSprites.initialise(minecraft.getTextureManager())); register.accept(GuiSprites.initialise(minecraft.getTextureManager()));
} }
private static final String[] EXTRA_MODELS = new String[]{ private static final ResourceLocation[] EXTRA_MODELS = {
"block/turtle_colour", TurtleOverlay.ELF_MODEL,
"block/turtle_elf_overlay", TurtleBlockEntityRenderer.COLOUR_TURTLE_MODEL,
"block/turtle_rainbow_overlay",
"block/turtle_trans_overlay",
}; };
public static void registerExtraModels(Consumer<ResourceLocation> register) { public static void registerExtraModels(Consumer<ResourceLocation> register, Collection<ResourceLocation> extraModels) {
for (var model : EXTRA_MODELS) register.accept(ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, model)); for (var model : EXTRA_MODELS) register.accept(model);
extraModels.forEach(register);
TurtleUpgradeModellers.getDependencies().forEach(register); TurtleUpgradeModellers.getDependencies().forEach(register);
} }

View File

@ -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.
* <p>
* 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<ResourceLocation> 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<ExtraModels> 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<ResourceLocation> loadAll(ResourceManager resources) {
Set<ResourceLocation> 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);
}
}

View File

@ -12,13 +12,14 @@ import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleSide; import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.api.upgrades.UpgradeData; import dan200.computercraft.api.upgrades.UpgradeData;
import dan200.computercraft.client.platform.ClientPlatformHelper; import dan200.computercraft.client.platform.ClientPlatformHelper;
import dan200.computercraft.client.render.TurtleBlockEntityRenderer;
import dan200.computercraft.client.turtle.TurtleUpgradeModellers; import dan200.computercraft.client.turtle.TurtleUpgradeModellers;
import dan200.computercraft.shared.turtle.TurtleOverlay;
import dan200.computercraft.shared.turtle.items.TurtleItem; import dan200.computercraft.shared.turtle.items.TurtleItem;
import dan200.computercraft.shared.util.DataComponentUtil; import dan200.computercraft.shared.util.DataComponentUtil;
import dan200.computercraft.shared.util.Holiday; import dan200.computercraft.shared.util.Holiday;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.resources.model.BakedModel; import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.ModelManager;
import net.minecraft.core.component.DataComponents; import net.minecraft.core.component.DataComponents;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
@ -52,7 +53,7 @@ public final class TurtleModelParts<T> {
boolean colour, boolean colour,
@Nullable UpgradeData<ITurtleUpgrade> leftUpgrade, @Nullable UpgradeData<ITurtleUpgrade> leftUpgrade,
@Nullable UpgradeData<ITurtleUpgrade> rightUpgrade, @Nullable UpgradeData<ITurtleUpgrade> rightUpgrade,
@Nullable ResourceLocation overlay, @Nullable TurtleOverlay overlay,
boolean christmas, boolean christmas,
boolean flip boolean flip
) { ) {
@ -113,10 +114,10 @@ public final class TurtleModelParts<T> {
var parts = new ArrayList<BakedModel>(4); var parts = new ArrayList<BakedModel>(4);
parts.add(transform(combo.colour() ? colourModel : familyModel, transformation)); parts.add(transform(combo.colour() ? colourModel : familyModel, transformation));
var overlayModelLocation = TurtleBlockEntityRenderer.getTurtleOverlayModel(combo.overlay(), combo.christmas()); if (combo.overlay() != null) addPart(parts, modelManager, transformation, combo.overlay().model());
if (overlayModelLocation != null) {
parts.add(transform(ClientPlatformHelper.get().getModel(modelManager, overlayModelLocation), transformation)); 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.LEFT, combo.leftUpgrade());
addUpgrade(parts, transformation, TurtleSide.RIGHT, combo.rightUpgrade()); addUpgrade(parts, transformation, TurtleSide.RIGHT, combo.rightUpgrade());
@ -124,6 +125,10 @@ public final class TurtleModelParts<T> {
return parts; return parts;
} }
private void addPart(List<BakedModel> parts, ModelManager modelManager, Transformation transformation, ResourceLocation model) {
parts.add(transform(ClientPlatformHelper.get().getModel(modelManager, model), transformation));
}
private void addUpgrade(List<BakedModel> parts, Transformation transformation, TurtleSide side, @Nullable UpgradeData<ITurtleUpgrade> upgrade) { private void addUpgrade(List<BakedModel> parts, Transformation transformation, TurtleSide side, @Nullable UpgradeData<ITurtleUpgrade> upgrade) {
if (upgrade == null) return; if (upgrade == null) return;
var model = TurtleUpgradeModellers.getModel(upgrade.upgrade(), upgrade.data(), side); var model = TurtleUpgradeModellers.getModel(upgrade.upgrade(), upgrade.data(), side);

View File

@ -11,6 +11,7 @@ import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.turtle.TurtleSide; import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.client.platform.ClientPlatformHelper; import dan200.computercraft.client.platform.ClientPlatformHelper;
import dan200.computercraft.client.turtle.TurtleUpgradeModellers; import dan200.computercraft.client.turtle.TurtleUpgradeModellers;
import dan200.computercraft.shared.turtle.TurtleOverlay;
import dan200.computercraft.shared.turtle.blocks.TurtleBlockEntity; import dan200.computercraft.shared.turtle.blocks.TurtleBlockEntity;
import dan200.computercraft.shared.util.Holiday; import dan200.computercraft.shared.util.Holiday;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
@ -28,8 +29,7 @@ import net.minecraft.world.phys.HitResult;
import javax.annotation.Nullable; import javax.annotation.Nullable;
public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBlockEntity> { public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBlockEntity> {
private static final ResourceLocation COLOUR_TURTLE_MODEL = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_colour"); public 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");
private final BlockEntityRenderDispatcher renderer; private final BlockEntityRenderDispatcher renderer;
private final Font font; private final Font font;
@ -39,12 +39,6 @@ public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBloc
font = context.getFont(); font = context.getFont();
} }
public static @Nullable ResourceLocation getTurtleOverlayModel(@Nullable ResourceLocation overlay, boolean christmas) {
if (overlay != null) return overlay;
if (christmas) return ELF_OVERLAY_MODEL;
return null;
}
@Override @Override
public void render(TurtleBlockEntity turtle, float partialTicks, PoseStack transform, MultiBufferSource buffers, int lightmapCoord, int overlayLight) { public void render(TurtleBlockEntity turtle, float partialTicks, PoseStack transform, MultiBufferSource buffers, int lightmapCoord, int overlayLight) {
transform.pushPose(); transform.pushPose();
@ -99,10 +93,11 @@ public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBloc
} }
// Render the overlay // Render the overlay
var overlayModel = getTurtleOverlayModel(overlay, Holiday.getCurrent() == Holiday.CHRISTMAS); if (overlay != null) renderModel(transform, buffers, lightmapCoord, overlayLight, overlay.model(), null);
if (overlayModel != null) {
renderModel(transform, buffers, lightmapCoord, overlayLight, overlayModel, null); // And the Christmas overlay.
} var showChristmas = TurtleOverlay.showElfOverlay(overlay, Holiday.getCurrent() == Holiday.CHRISTMAS);
if (showChristmas) renderModel(transform, buffers, lightmapCoord, overlayLight, TurtleOverlay.ELF_MODEL, null);
// Render the upgrades // Render the upgrades
renderUpgrade(transform, buffers, lightmapCoord, overlayLight, turtle, TurtleSide.LEFT, partialTicks); renderUpgrade(transform, buffers, lightmapCoord, overlayLight, turtle, TurtleSide.LEFT, partialTicks);

View File

@ -6,15 +6,18 @@ package dan200.computercraft.data.client;
import dan200.computercraft.client.gui.GuiSprites; import dan200.computercraft.client.gui.GuiSprites;
import dan200.computercraft.data.DataProviders; import dan200.computercraft.data.DataProviders;
import dan200.computercraft.shared.turtle.TurtleOverlay;
import dan200.computercraft.shared.turtle.inventory.UpgradeSlot; import dan200.computercraft.shared.turtle.inventory.UpgradeSlot;
import net.minecraft.client.renderer.texture.atlas.SpriteSource; import net.minecraft.client.renderer.texture.atlas.SpriteSource;
import net.minecraft.client.renderer.texture.atlas.SpriteSources; import net.minecraft.client.renderer.texture.atlas.SpriteSources;
import net.minecraft.client.renderer.texture.atlas.sources.SingleFile; import net.minecraft.client.renderer.texture.atlas.sources.SingleFile;
import net.minecraft.core.HolderLookup;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.PackType; import net.minecraft.server.packs.PackType;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Stream; import java.util.stream.Stream;
/** /**
@ -26,7 +29,7 @@ public final class ClientDataProviders {
private ClientDataProviders() { private ClientDataProviders() {
} }
public static void add(DataProviders.GeneratorSink generator) { public static void add(DataProviders.GeneratorSink generator, CompletableFuture<HolderLookup.Provider> registries) {
generator.addFromCodec("Block atlases", PackType.CLIENT_RESOURCES, "atlases", SpriteSources.FILE_CODEC, out -> { generator.addFromCodec("Block atlases", PackType.CLIENT_RESOURCES, "atlases", SpriteSources.FILE_CODEC, out -> {
out.accept(ResourceLocation.withDefaultNamespace("blocks"), List.of( out.accept(ResourceLocation.withDefaultNamespace("blocks"), List.of(
new SingleFile(UpgradeSlot.LEFT_UPGRADE, Optional.empty()), new SingleFile(UpgradeSlot.LEFT_UPGRADE, Optional.empty()),
@ -44,5 +47,12 @@ public final class ClientDataProviders {
GuiSprites.COMPUTER_COLOUR.textures() GuiSprites.COMPUTER_COLOUR.textures()
).flatMap(x -> x).<SpriteSource>map(x -> new SingleFile(x, Optional.empty())).toList()); ).flatMap(x -> x).<SpriteSource>map(x -> new SingleFile(x, Optional.empty())).toList());
}); });
generator.add(pack -> new ExtraModelsProvider(pack, registries) {
@Override
public Stream<ResourceLocation> getModels(HolderLookup.Provider registries) {
return registries.lookupOrThrow(TurtleOverlay.REGISTRY).listElements().map(x -> x.value().model());
}
});
} }
} }

View File

@ -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<HolderLookup.Provider> registries;
ExtraModelsProvider(PackOutput output, CompletableFuture<HolderLookup.Provider> 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<ResourceLocation> 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";
}
}

View File

@ -0,0 +1 @@
["computercraft:block/turtle_rainbow_overlay", "computercraft:block/turtle_trans_overlay"]

View File

@ -3,7 +3,7 @@
"criteria": { "criteria": {
"has_dye": {"conditions": {"items": [{"items": "#c:dyes"}]}, "trigger": "minecraft:inventory_changed"}, "has_dye": {"conditions": {"items": [{"items": "#c:dyes"}]}, "trigger": "minecraft:inventory_changed"},
"has_the_recipe": { "has_the_recipe": {
"conditions": {"recipe": "computercraft:turtle_advanced_overlays/turtle_trans_overlay"}, "conditions": {"recipe": "computercraft:turtle_advanced_overlays/rainbow_flag"},
"trigger": "minecraft:recipe_unlocked" "trigger": "minecraft:recipe_unlocked"
}, },
"has_turtle": { "has_turtle": {
@ -12,5 +12,5 @@
} }
}, },
"requirements": [["has_the_recipe", "has_turtle", "has_dye"]], "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"]}
} }

View File

@ -3,7 +3,7 @@
"criteria": { "criteria": {
"has_dye": {"conditions": {"items": [{"items": "#c:dyes"}]}, "trigger": "minecraft:inventory_changed"}, "has_dye": {"conditions": {"items": [{"items": "#c:dyes"}]}, "trigger": "minecraft:inventory_changed"},
"has_the_recipe": { "has_the_recipe": {
"conditions": {"recipe": "computercraft:turtle_advanced_overlays/turtle_rainbow_overlay"}, "conditions": {"recipe": "computercraft:turtle_advanced_overlays/trans_flag"},
"trigger": "minecraft:recipe_unlocked" "trigger": "minecraft:recipe_unlocked"
}, },
"has_turtle": { "has_turtle": {
@ -12,5 +12,5 @@
} }
}, },
"requirements": [["has_the_recipe", "has_turtle", "has_dye"]], "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"]}
} }

View File

@ -3,7 +3,7 @@
"criteria": { "criteria": {
"has_dye": {"conditions": {"items": [{"items": "#c:dyes"}]}, "trigger": "minecraft:inventory_changed"}, "has_dye": {"conditions": {"items": [{"items": "#c:dyes"}]}, "trigger": "minecraft:inventory_changed"},
"has_the_recipe": { "has_the_recipe": {
"conditions": {"recipe": "computercraft:turtle_normal_overlays/turtle_trans_overlay"}, "conditions": {"recipe": "computercraft:turtle_normal_overlays/rainbow_flag"},
"trigger": "minecraft:recipe_unlocked" "trigger": "minecraft:recipe_unlocked"
}, },
"has_turtle": { "has_turtle": {
@ -12,5 +12,5 @@
} }
}, },
"requirements": [["has_the_recipe", "has_turtle", "has_dye"]], "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"]}
} }

View File

@ -3,7 +3,7 @@
"criteria": { "criteria": {
"has_dye": {"conditions": {"items": [{"items": "#c:dyes"}]}, "trigger": "minecraft:inventory_changed"}, "has_dye": {"conditions": {"items": [{"items": "#c:dyes"}]}, "trigger": "minecraft:inventory_changed"},
"has_the_recipe": { "has_the_recipe": {
"conditions": {"recipe": "computercraft:turtle_normal_overlays/turtle_rainbow_overlay"}, "conditions": {"recipe": "computercraft:turtle_normal_overlays/trans_flag"},
"trigger": "minecraft:recipe_unlocked" "trigger": "minecraft:recipe_unlocked"
}, },
"has_turtle": { "has_turtle": {
@ -12,5 +12,5 @@
} }
}, },
"requirements": [["has_the_recipe", "has_turtle", "has_dye"]], "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"]}
} }

View File

@ -0,0 +1 @@
{"model": "computercraft:block/turtle_rainbow_overlay", "show_elf_overlay": true}

View File

@ -0,0 +1 @@
{"model": "computercraft:block/turtle_trans_overlay", "show_elf_overlay": true}

View File

@ -20,7 +20,7 @@
{"item": "computercraft:turtle_advanced"} {"item": "computercraft:turtle_advanced"}
], ],
"result": { "result": {
"components": {"computercraft:overlay": "computercraft:block/turtle_rainbow_overlay"}, "components": {"computercraft:overlay": "computercraft:rainbow_flag"},
"count": 1, "count": 1,
"id": "computercraft:turtle_advanced" "id": "computercraft:turtle_advanced"
} }

View File

@ -17,7 +17,7 @@
{"item": "computercraft:turtle_advanced"} {"item": "computercraft:turtle_advanced"}
], ],
"result": { "result": {
"components": {"computercraft:overlay": "computercraft:block/turtle_trans_overlay"}, "components": {"computercraft:overlay": "computercraft:trans_flag"},
"count": 1, "count": 1,
"id": "computercraft:turtle_advanced" "id": "computercraft:turtle_advanced"
} }

View File

@ -20,7 +20,7 @@
{"item": "computercraft:turtle_normal"} {"item": "computercraft:turtle_normal"}
], ],
"result": { "result": {
"components": {"computercraft:overlay": "computercraft:block/turtle_rainbow_overlay"}, "components": {"computercraft:overlay": "computercraft:rainbow_flag"},
"count": 1, "count": 1,
"id": "computercraft:turtle_normal" "id": "computercraft:turtle_normal"
} }

View File

@ -17,7 +17,7 @@
{"item": "computercraft:turtle_normal"} {"item": "computercraft:turtle_normal"}
], ],
"result": { "result": {
"components": {"computercraft:overlay": "computercraft:block/turtle_trans_overlay"}, "components": {"computercraft:overlay": "computercraft:trans_flag"},
"count": 1, "count": 1,
"id": "computercraft:turtle_normal" "id": "computercraft:turtle_normal"
} }

View File

@ -7,6 +7,7 @@ package dan200.computercraft.data;
import com.mojang.serialization.Codec; import com.mojang.serialization.Codec;
import dan200.computercraft.api.pocket.IPocketUpgrade; import dan200.computercraft.api.pocket.IPocketUpgrade;
import dan200.computercraft.api.turtle.ITurtleUpgrade; import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.shared.turtle.TurtleOverlay;
import net.minecraft.Util; import net.minecraft.Util;
import net.minecraft.core.HolderLookup; import net.minecraft.core.HolderLookup;
import net.minecraft.core.RegistrySetBuilder; import net.minecraft.core.RegistrySetBuilder;
@ -38,6 +39,7 @@ public final class DataProviders {
Util.make(new RegistrySetBuilder(), builder -> { Util.make(new RegistrySetBuilder(), builder -> {
builder.add(ITurtleUpgrade.REGISTRY, TurtleUpgradeProvider::addUpgrades); builder.add(ITurtleUpgrade.REGISTRY, TurtleUpgradeProvider::addUpgrades);
builder.add(IPocketUpgrade.REGISTRY, PocketUpgradeProvider::addUpgrades); builder.add(IPocketUpgrade.REGISTRY, PocketUpgradeProvider::addUpgrades);
builder.add(TurtleOverlay.REGISTRY, TurtleOverlays::register);
})); }));
var fullRegistries = fullRegistryPatch.thenApply(RegistrySetBuilder.PatchedRegistries::full); var fullRegistries = fullRegistryPatch.thenApply(RegistrySetBuilder.PatchedRegistries::full);
@ -57,7 +59,8 @@ public final class DataProviders {
// and invoke that. // and invoke that.
try { try {
Class.forName("dan200.computercraft.data.client.ClientDataProviders") 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) { } catch (ReflectiveOperationException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }

View File

@ -28,6 +28,7 @@ import dan200.computercraft.shared.recipe.ImpostorShapelessRecipe;
import dan200.computercraft.shared.recipe.TransformShapedRecipe; import dan200.computercraft.shared.recipe.TransformShapedRecipe;
import dan200.computercraft.shared.recipe.TransformShapelessRecipe; import dan200.computercraft.shared.recipe.TransformShapelessRecipe;
import dan200.computercraft.shared.recipe.function.CopyComponents; 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.items.TurtleItem;
import dan200.computercraft.shared.turtle.recipes.TurtleUpgradeRecipe; import dan200.computercraft.shared.turtle.recipes.TurtleUpgradeRecipe;
import dan200.computercraft.shared.util.ColourUtils; 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.RecipeOutput;
import net.minecraft.data.recipes.ShapedRecipeBuilder; import net.minecraft.data.recipes.ShapedRecipeBuilder;
import net.minecraft.data.recipes.ShapelessRecipeBuilder; import net.minecraft.data.recipes.ShapelessRecipeBuilder;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.ItemTags; import net.minecraft.tags.ItemTags;
import net.minecraft.tags.TagKey; import net.minecraft.tags.TagKey;
@ -96,7 +98,7 @@ final class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
diskColours(add); diskColours(add);
pocketUpgrades(add, registries); pocketUpgrades(add, registries);
turtleUpgrades(add, registries); turtleUpgrades(add, registries);
turtleOverlays(add); turtleOverlays(add, registries);
addSpecial(add, new DiskRecipe(CraftingBookCategory.MISC)); addSpecial(add, new DiskRecipe(CraftingBookCategory.MISC));
addSpecial(add, new ColourableRecipe(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) { private void turtleOverlays(RecipeOutput add, HolderLookup.Provider registries) {
turtleOverlay(add, "turtle_trans_overlay", x -> x turtleOverlay(add, registries, TurtleOverlays.TRANS_FLAG, x -> x
.unlockedBy("has_dye", inventoryChange(itemPredicate(ingredients.dye()))) .unlockedBy("has_dye", inventoryChange(itemPredicate(ingredients.dye())))
.requires(ColourUtils.getDyeTag(DyeColor.LIGHT_BLUE)) .requires(ColourUtils.getDyeTag(DyeColor.LIGHT_BLUE))
.requires(ColourUtils.getDyeTag(DyeColor.PINK)) .requires(ColourUtils.getDyeTag(DyeColor.PINK))
@ -198,7 +200,7 @@ final class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
.requires(Items.STICK) .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()))) .unlockedBy("has_dye", inventoryChange(itemPredicate(ingredients.dye())))
.requires(ColourUtils.getDyeTag(DyeColor.RED)) .requires(ColourUtils.getDyeTag(DyeColor.RED))
.requires(ColourUtils.getDyeTag(DyeColor.ORANGE)) .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<ShapelessSpecBuilder> build) { private void turtleOverlay(RecipeOutput add, HolderLookup.Provider registries, ResourceKey<TurtleOverlay> overlay, Consumer<ShapelessSpecBuilder> build) {
var overlayId = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/" + overlay); var holder = registries.lookupOrThrow(overlay.registryKey()).getOrThrow(overlay);
for (var turtleItem : turtleItems()) { for (var turtleItem : turtleItems()) {
var name = RegistryHelper.getKeyOrThrow(BuiltInRegistries.ITEM, turtleItem); var name = RegistryHelper.getKeyOrThrow(BuiltInRegistries.ITEM, turtleItem);
var builder = ShapelessSpecBuilder 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()) .group(name.withSuffix("_overlay").toString())
.unlockedBy("has_turtle", inventoryChange(turtleItem)); .unlockedBy("has_turtle", inventoryChange(turtleItem));
build.accept(builder); build.accept(builder);
@ -226,7 +228,7 @@ final class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
.build(s -> new TransformShapelessRecipe(s, List.of( .build(s -> new TransformShapelessRecipe(s, List.of(
CopyComponents.builder(turtleItem).exclude(ModRegistry.DataComponents.OVERLAY.get()).build() CopyComponents.builder(turtleItem).exclude(ModRegistry.DataComponents.OVERLAY.get()).build()
))) )))
.save(add, name.withSuffix("_overlays/" + overlay)); .save(add, name.withSuffix("_overlays/" + overlay.location().getPath()));
} }
} }

View File

@ -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<TurtleOverlay> RAINBOW_FLAG = create("rainbow_flag");
public static final ResourceKey<TurtleOverlay> TRANS_FLAG = create("trans_flag");
private static ResourceKey<TurtleOverlay> create(String name) {
return ResourceKey.create(TurtleOverlay.REGISTRY, ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, name));
}
private TurtleOverlays() {
}
public static void register(BootstrapContext<TurtleOverlay> 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
));
}
}

View File

@ -72,6 +72,7 @@ import dan200.computercraft.shared.recipe.*;
import dan200.computercraft.shared.recipe.function.CopyComponents; import dan200.computercraft.shared.recipe.function.CopyComponents;
import dan200.computercraft.shared.recipe.function.RecipeFunction; import dan200.computercraft.shared.recipe.function.RecipeFunction;
import dan200.computercraft.shared.turtle.FurnaceRefuelHandler; 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.TurtleBlock;
import dan200.computercraft.shared.turtle.blocks.TurtleBlockEntity; import dan200.computercraft.shared.turtle.blocks.TurtleBlockEntity;
import dan200.computercraft.shared.turtle.inventory.TurtleMenu; 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.chat.Component;
import net.minecraft.network.codec.ByteBufCodecs; import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec; import net.minecraft.network.codec.StreamCodec;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.flag.FeatureFlags; import net.minecraft.world.flag.FeatureFlags;
import net.minecraft.world.inventory.MenuType; import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.item.BlockItem; import net.minecraft.world.item.BlockItem;
@ -320,8 +320,8 @@ public final class ModRegistry {
/** /**
* The overlay on a turtle. * The overlay on a turtle.
*/ */
public static final RegistryEntry<DataComponentType<ResourceLocation>> OVERLAY = register("overlay", b -> b public static final RegistryEntry<DataComponentType<Holder<TurtleOverlay>>> OVERLAY = register("overlay", b -> b
.persistent(ResourceLocation.CODEC).networkSynchronized(ResourceLocation.STREAM_CODEC) .persistent(TurtleOverlay.CODEC).networkSynchronized(TurtleOverlay.STREAM_CODEC)
); );
/** /**

View File

@ -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<TurtleOverlay>> 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<TurtleOverlay> 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<Holder<TurtleOverlay>> CODEC = RegistryFileCodec.create(REGISTRY, DIRECT_CODEC);
/**
* The stream codec used for {@link TurtleOverlay} instances.
*/
public static final StreamCodec<RegistryFriendlyByteBuf, Holder<TurtleOverlay>> 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());
}
}

View File

@ -20,6 +20,7 @@ import dan200.computercraft.shared.computer.core.ServerComputer;
import dan200.computercraft.shared.config.Config; import dan200.computercraft.shared.config.Config;
import dan200.computercraft.shared.container.BasicContainer; import dan200.computercraft.shared.container.BasicContainer;
import dan200.computercraft.shared.platform.PlatformHelper; import dan200.computercraft.shared.platform.PlatformHelper;
import dan200.computercraft.shared.turtle.TurtleOverlay;
import dan200.computercraft.shared.turtle.apis.TurtleAPI; import dan200.computercraft.shared.turtle.apis.TurtleAPI;
import dan200.computercraft.shared.turtle.core.TurtleBrain; import dan200.computercraft.shared.turtle.core.TurtleBrain;
import dan200.computercraft.shared.turtle.inventory.TurtleMenu; 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.DataComponentMap;
import net.minecraft.core.component.DataComponents; import net.minecraft.core.component.DataComponents;
import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.Container; import net.minecraft.world.Container;
import net.minecraft.world.ContainerHelper; import net.minecraft.world.ContainerHelper;
@ -222,8 +222,9 @@ public class TurtleBlockEntity extends AbstractComputerBlockEntity implements Ba
return brain.getColour(); return brain.getColour();
} }
public @Nullable ResourceLocation getOverlay() { public @Nullable TurtleOverlay getOverlay() {
return brain.getOverlay(); var overlay = brain.getOverlay();
return overlay == null ? null : overlay.value();
} }
public ITurtleAccess getAccess() { public ITurtleAccess getAccess() {

View File

@ -20,6 +20,7 @@ import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.computer.core.ServerComputer; import dan200.computercraft.shared.computer.core.ServerComputer;
import dan200.computercraft.shared.config.Config; import dan200.computercraft.shared.config.Config;
import dan200.computercraft.shared.container.InventoryDelegate; import dan200.computercraft.shared.container.InventoryDelegate;
import dan200.computercraft.shared.turtle.TurtleOverlay;
import dan200.computercraft.shared.turtle.blocks.TurtleBlockEntity; import dan200.computercraft.shared.turtle.blocks.TurtleBlockEntity;
import dan200.computercraft.shared.util.BlockEntityHelpers; import dan200.computercraft.shared.util.BlockEntityHelpers;
import dan200.computercraft.shared.util.Holiday; 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.core.particles.ParticleTypes;
import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.Tag; import net.minecraft.nbt.Tag;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerLevel;
import net.minecraft.tags.FluidTags; import net.minecraft.tags.FluidTags;
import net.minecraft.world.Container; import net.minecraft.world.Container;
@ -74,7 +74,7 @@ public class TurtleBrain implements TurtleAccessInternal {
private int selectedSlot = 0; private int selectedSlot = 0;
private int fuelLevel = 0; private int fuelLevel = 0;
private int colourHex = -1; private int colourHex = -1;
private @Nullable ResourceLocation overlay = null; private @Nullable Holder<TurtleOverlay> overlay = null;
private TurtleAnimation animation = TurtleAnimation.NONE; private TurtleAnimation animation = TurtleAnimation.NONE;
private int animationProgress = 0; private int animationProgress = 0;
@ -134,7 +134,7 @@ public class TurtleBrain implements TurtleAccessInternal {
// Read fields // Read fields
colourHex = nbt.contains(NBT_COLOUR) ? nbt.getInt(NBT_COLOUR) : -1; colourHex = nbt.contains(NBT_COLOUR) ? nbt.getInt(NBT_COLOUR) : -1;
fuelLevel = nbt.contains(NBT_FUEL) ? nbt.getInt(NBT_FUEL) : 0; 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 // Read upgrades
setUpgradeDirect(TurtleSide.LEFT, NBTUtil.decodeFrom(TurtleUpgrades.instance().upgradeDataCodec(), registries, nbt, NBT_LEFT_UPGRADE)); 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) { private void writeCommon(CompoundTag nbt, HolderLookup.Provider registries) {
nbt.putInt(NBT_FUEL, fuelLevel); nbt.putInt(NBT_FUEL, fuelLevel);
if (colourHex != -1) nbt.putInt(NBT_COLOUR, colourHex); 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 // Write upgrades
NBTUtil.encodeTo(TurtleUpgrades.instance().upgradeDataCodec(), registries, nbt, NBT_LEFT_UPGRADE, getUpgradeWithData(TurtleSide.LEFT)); 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); BlockEntityHelpers.updateBlock(owner);
} }
public @Nullable ResourceLocation getOverlay() { public @Nullable Holder<TurtleOverlay> getOverlay() {
return overlay; return overlay;
} }
public void setOverlay(@Nullable ResourceLocation overlay) { public void setOverlay(@Nullable Holder<TurtleOverlay> overlay) {
if (!Objects.equals(this.overlay, overlay)) { if (!Objects.equals(this.overlay, overlay)) {
this.overlay = overlay; this.overlay = overlay;
BlockEntityHelpers.updateBlock(owner); BlockEntityHelpers.updateBlock(owner);

View File

@ -12,11 +12,11 @@ import dan200.computercraft.api.upgrades.UpgradeData;
import dan200.computercraft.impl.TurtleUpgrades; import dan200.computercraft.impl.TurtleUpgrades;
import dan200.computercraft.shared.ModRegistry; import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.computer.items.AbstractComputerItem; import dan200.computercraft.shared.computer.items.AbstractComputerItem;
import dan200.computercraft.shared.turtle.TurtleOverlay;
import dan200.computercraft.shared.turtle.blocks.TurtleBlock; import dan200.computercraft.shared.turtle.blocks.TurtleBlock;
import net.minecraft.core.cauldron.CauldronInteraction; import net.minecraft.core.cauldron.CauldronInteraction;
import net.minecraft.core.component.DataComponents; import net.minecraft.core.component.DataComponents;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.ItemInteractionResult; import net.minecraft.world.ItemInteractionResult;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.LayeredCauldronBlock; 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()); 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) { public static @Nullable TurtleOverlay getOverlay(ItemStack stack) {
return stack.get(ModRegistry.DataComponents.OVERLAY.get()); var overlay = stack.get(ModRegistry.DataComponents.OVERLAY.get());
return overlay == null ? null : overlay.value();
} }
public static int getFuelLevel(ItemStack stack) { public static int getFuelLevel(ItemStack stack) {

View File

@ -82,7 +82,8 @@ public class ComponentizationFixers {
if (item.is(TURTLES)) { if (item.is(TURTLES)) {
item.moveTagToComponent("Fuel", "computercraft:fuel"); 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, "LeftUpgrade", "LeftUpgradeNbt", "computercraft:left_turtle_upgrade");
moveUpgradeToComponent(item, ops, "RightUpgrade", "RightUpgradeNbt", "computercraft:right_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 -> { return typed -> typed.updateTyped(input, output, typed2 -> typed2.update(DSL.remainderFinder(), x -> {
x = moveUpgradeData(x, "LeftUpgrade", "LeftUpgradeNbt"); x = moveUpgradeData(x, "LeftUpgrade", "LeftUpgradeNbt");
x = moveUpgradeData(x, "RightUpgrade", "RightUpgradeNbt"); x = moveUpgradeData(x, "RightUpgrade", "RightUpgradeNbt");
x = x.update("Overlay", ComponentizationFixers::fixOverlay);
return x; return x;
})); }));
} }
@ -164,6 +166,20 @@ public class ComponentizationFixers {
return ops.set(key, createUpgradeData(ops, upgradeId, ops.get(dataKey))).remove(dataKey); 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. * Create a new upgrade data.
* *

View File

@ -4,6 +4,7 @@
package dan200.computercraft.gametest package dan200.computercraft.gametest
import dan200.computercraft.api.ComputerCraftAPI
import dan200.computercraft.api.detail.BasicItemDetailProvider import dan200.computercraft.api.detail.BasicItemDetailProvider
import dan200.computercraft.api.detail.VanillaDetailRegistries import dan200.computercraft.api.detail.VanillaDetailRegistries
import dan200.computercraft.api.lua.ObjectArguments 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.modem.wired.CableModemVariant
import dan200.computercraft.shared.peripheral.monitor.MonitorBlock import dan200.computercraft.shared.peripheral.monitor.MonitorBlock
import dan200.computercraft.shared.peripheral.monitor.MonitorEdgeState 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.apis.TurtleAPI
import dan200.computercraft.shared.turtle.items.TurtleItem
import dan200.computercraft.shared.util.WaterloggableHelpers import dan200.computercraft.shared.util.WaterloggableHelpers
import dan200.computercraft.test.core.assertArrayEquals import dan200.computercraft.test.core.assertArrayEquals
import dan200.computercraft.test.core.computer.LuaTaskContext 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. * `turtle.suck` only pulls for the current side.
*/ */

View File

@ -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}"
]
}

View File

@ -53,7 +53,7 @@ public class ComputerCraftClient {
ClientRegistry.registerMainThread(ItemProperties::register); ClientRegistry.registerMainThread(ItemProperties::register);
PreparableModelLoadingPlugin.register(CustomModelLoader::prepare, (state, context) -> { 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.resolveModel().register(ctx -> state.loadModel(ctx.id()));
context.modifyModelAfterBake().register((model, ctx) -> model == null ? null : state.wrapModel(ctx, model)); context.modifyModelAfterBake().register((model, ctx) -> model == null ? null : state.wrapModel(ctx, model));
}); });

View File

@ -25,6 +25,7 @@ import org.slf4j.LoggerFactory;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.io.IOException; import java.io.IOException;
import java.io.Reader; import java.io.Reader;
import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
@ -46,13 +47,15 @@ public final class CustomModelLoader {
private final Map<ResourceLocation, UnbakedModel> models = new HashMap<>(); private final Map<ResourceLocation, UnbakedModel> models = new HashMap<>();
private final Map<ResourceLocation, String> emissiveModels = new HashMap<>(); private final Map<ResourceLocation, String> emissiveModels = new HashMap<>();
private final Collection<ResourceLocation> extraModels;
private CustomModelLoader() { private CustomModelLoader(Collection<ResourceLocation> extraModels) {
this.extraModels = extraModels;
} }
public static CompletableFuture<CustomModelLoader> prepare(ResourceManager resources, Executor executor) { public static CompletableFuture<CustomModelLoader> prepare(ResourceManager resources, Executor executor) {
return CompletableFuture.supplyAsync(() -> { 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()) { for (var resource : resources.listResources("models", x -> x.getNamespace().equals(ComputerCraftAPI.MOD_ID) && x.getPath().endsWith(".json")).entrySet()) {
loader.loadModel(resource.getKey(), resource.getValue()); loader.loadModel(resource.getKey(), resource.getValue());
} }
@ -85,6 +88,10 @@ public final class CustomModelLoader {
} }
} }
public Collection<ResourceLocation> getExtraModels() {
return extraModels;
}
/** /**
* Load a custom model. This searches for CC models with a custom {@code loader} field. * Load a custom model. This searches for CC models with a custom {@code loader} field.
* *

View File

@ -25,6 +25,7 @@ import dan200.computercraft.shared.peripheral.modem.wired.WiredModemFullBlockEnt
import dan200.computercraft.shared.peripheral.modem.wireless.WirelessModemBlockEntity; import dan200.computercraft.shared.peripheral.modem.wireless.WirelessModemBlockEntity;
import dan200.computercraft.shared.platform.FabricConfigFile; import dan200.computercraft.shared.platform.FabricConfigFile;
import dan200.computercraft.shared.recipe.function.RecipeFunction; 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.command.v2.CommandRegistrationCallback;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerChunkEvents; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerChunkEvents;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; 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(ITurtleUpgrade.REGISTRY, TurtleUpgrades.instance().upgradeCodec());
DynamicRegistries.registerSynced(IPocketUpgrade.REGISTRY, PocketUpgrades.instance().upgradeCodec()); DynamicRegistries.registerSynced(IPocketUpgrade.REGISTRY, PocketUpgrades.instance().upgradeCodec());
DynamicRegistries.registerSynced(TurtleOverlay.REGISTRY, TurtleOverlay.DIRECT_CODEC);
ModRegistry.register(); ModRegistry.register();
ModRegistry.registerMainThread(); ModRegistry.registerMainThread();

View File

@ -6,6 +6,7 @@ package dan200.computercraft.client;
import dan200.computercraft.api.ComputerCraftAPI; import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.client.turtle.RegisterTurtleModellersEvent; import dan200.computercraft.api.client.turtle.RegisterTurtleModellersEvent;
import dan200.computercraft.client.model.ExtraModels;
import dan200.computercraft.client.model.turtle.TurtleModelLoader; import dan200.computercraft.client.model.turtle.TurtleModelLoader;
import dan200.computercraft.client.turtle.TurtleUpgradeModellers; import dan200.computercraft.client.turtle.TurtleUpgradeModellers;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
@ -57,7 +58,8 @@ public final class ForgeClientRegistry {
@SubscribeEvent @SubscribeEvent
public static void registerModels(ModelEvent.RegisterAdditional event) { public static void registerModels(ModelEvent.RegisterAdditional event) {
gatherModellers(); 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 @SubscribeEvent

View File

@ -33,6 +33,7 @@ import dan200.computercraft.shared.peripheral.modem.wired.WiredModemFullBlockEnt
import dan200.computercraft.shared.peripheral.modem.wireless.WirelessModemBlockEntity; import dan200.computercraft.shared.peripheral.modem.wireless.WirelessModemBlockEntity;
import dan200.computercraft.shared.platform.ForgeConfigFile; import dan200.computercraft.shared.platform.ForgeConfigFile;
import dan200.computercraft.shared.recipe.function.RecipeFunction; import dan200.computercraft.shared.recipe.function.RecipeFunction;
import dan200.computercraft.shared.turtle.TurtleOverlay;
import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload; import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.level.ServerPlayer;
@ -92,6 +93,7 @@ public final class ComputerCraft {
public static void registerDynamicRegistries(DataPackRegistryEvent.NewRegistry event) { public static void registerDynamicRegistries(DataPackRegistryEvent.NewRegistry event) {
event.dataPackRegistry(ITurtleUpgrade.REGISTRY, TurtleUpgrades.instance().upgradeCodec(), TurtleUpgrades.instance().upgradeCodec()); event.dataPackRegistry(ITurtleUpgrade.REGISTRY, TurtleUpgrades.instance().upgradeCodec(), TurtleUpgrades.instance().upgradeCodec());
event.dataPackRegistry(IPocketUpgrade.REGISTRY, PocketUpgrades.instance().upgradeCodec(), PocketUpgrades.instance().upgradeCodec()); event.dataPackRegistry(IPocketUpgrade.REGISTRY, PocketUpgrades.instance().upgradeCodec(), PocketUpgrades.instance().upgradeCodec());
event.dataPackRegistry(TurtleOverlay.REGISTRY, TurtleOverlay.DIRECT_CODEC, TurtleOverlay.DIRECT_CODEC);
} }
@SubscribeEvent @SubscribeEvent