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 cf2b47633..ec97a004b 100644 --- a/projects/common/src/client/java/dan200/computercraft/client/ClientRegistry.java +++ b/projects/common/src/client/java/dan200/computercraft/client/ClientRegistry.java @@ -20,10 +20,11 @@ import dan200.computercraft.client.render.CustomLecternRenderer; import dan200.computercraft.client.render.TurtleBlockEntityRenderer; import dan200.computercraft.client.render.monitor.MonitorBlockEntityRenderer; import dan200.computercraft.client.turtle.TurtleModemModel; +import dan200.computercraft.client.turtle.TurtleOverlay; +import dan200.computercraft.client.turtle.TurtleOverlayManager; import dan200.computercraft.client.turtle.TurtleUpgradeModels; import dan200.computercraft.shared.ModRegistry; import dan200.computercraft.shared.computer.inventory.AbstractComputerMenu; -import dan200.computercraft.shared.turtle.TurtleOverlay; import net.minecraft.client.Minecraft; import net.minecraft.client.color.item.ItemTintSource; import net.minecraft.client.gui.screens.MenuScreens; @@ -35,17 +36,22 @@ import net.minecraft.client.renderer.item.ItemModel; import net.minecraft.client.renderer.item.properties.conditional.ConditionalItemModelProperty; import net.minecraft.client.renderer.item.properties.select.SelectItemModelProperty; import net.minecraft.client.resources.model.MissingBlockModel; +import net.minecraft.client.resources.model.ModelBaker; import net.minecraft.client.resources.model.ModelManager; +import net.minecraft.client.resources.model.ResolvableModel; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.packs.resources.PreparableReloadListener; +import net.minecraft.server.packs.resources.ResourceManager; import net.minecraft.world.inventory.AbstractContainerMenu; import net.minecraft.world.inventory.MenuType; -import java.util.Collection; import java.util.Map; import java.util.Objects; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executor; import java.util.function.BiConsumer; +import java.util.function.BiFunction; /** * Registers client-side objects, such as {@link BlockEntityRendererProvider}s and @@ -122,14 +128,61 @@ public final class ClientRegistry { MissingBlockModel.LOCATION, }; - public static void registerExtraModels( - BiConsumer, ResourceLocation> registerBasic, - BiConsumer>, TurtleUpgradeModel.Unbaked> registerTurtle, - Collection extraModels + /** + * Additional models to load. + * + * @param turtleOverlays The unbaked turtle models. + * @see #gatherExtraModels(ResourceManager, Executor) + * @see #registerExtraModels(RegisterExtraModels, ExtraModels) + */ + public record ExtraModels( + Map turtleOverlays ) { - for (var model : EXTRA_MODELS) registerBasic.accept(getModel(model), model); - for (var model : extraModels) registerBasic.accept(getModel(model), model); - TurtleUpgradeModels.bake(registerTurtle); + } + + /** + * Gather the list of extra models to load. + * + * @param resources The current resource manager. + * @param executor The executor to schedule loading on. + * @return A promise which contains our extra models. + */ + public static CompletableFuture gatherExtraModels(ResourceManager resources, Executor executor) { + var turtleOverlays = TurtleOverlayManager.load(resources, executor); + return turtleOverlays.thenApply(ExtraModels::new); + } + + /** + * A callback used to register a model for a {@link ModelKey}. + */ + public interface RegisterExtraModels { + default void register(ModelKey key, U unbaked, BiFunction bake) { + register(key, unbaked, ResolvableModel::resolveDependencies, bake); + } + + /** + * Register an extra model. + *

+ * This accepts functions to resolve dependencies and bake the model. While this would be conceptually nicer as + * an interface, it would require multiple adaptors to convert between "upgrade model", "a"bstract model" and + * "platform-specific model", so working with functions is cleaner. + * + * @param key The model key for this model. + * @param unbaked The unbaked model. + * @param resolve The function to resolve dependencies for this model. + * @param bake The function to bake this model. + * @param The type of unbaked model. + * @param The type of baked model. + */ + void register(ModelKey key, U unbaked, BiConsumer resolve, BiFunction bake); + } + + public static void registerExtraModels(RegisterExtraModels register, ExtraModels models) { + for (var model : EXTRA_MODELS) { + register.register(getModel(model), model, (id, r) -> r.markDependency(id), StandaloneModel::of); + } + TurtleOverlayManager.register(register, models.turtleOverlays()); + TurtleUpgradeModels.bake(register); } public static void registerItemModels(BiConsumer> register) { diff --git a/projects/common/src/client/java/dan200/computercraft/client/item/model/TurtleOverlayModel.java b/projects/common/src/client/java/dan200/computercraft/client/item/model/TurtleOverlayModel.java index 6b748aecb..54dbb61e8 100644 --- a/projects/common/src/client/java/dan200/computercraft/client/item/model/TurtleOverlayModel.java +++ b/projects/common/src/client/java/dan200/computercraft/client/item/model/TurtleOverlayModel.java @@ -7,8 +7,8 @@ package dan200.computercraft.client.item.model; import com.mojang.serialization.MapCodec; import com.mojang.serialization.codecs.RecordCodecBuilder; import dan200.computercraft.api.ComputerCraftAPI; -import dan200.computercraft.client.ClientRegistry; -import dan200.computercraft.shared.turtle.TurtleOverlay; +import dan200.computercraft.client.turtle.TurtleOverlay; +import dan200.computercraft.client.turtle.TurtleOverlayManager; import dan200.computercraft.shared.turtle.items.TurtleItem; import net.minecraft.client.Minecraft; import net.minecraft.client.multiplayer.ClientLevel; @@ -40,7 +40,7 @@ public record TurtleOverlayModel(ItemTransforms transforms) implements ItemModel if (overlay == null) return; var layer = state.newLayer(); - ClientRegistry.getModel(Minecraft.getInstance().getModelManager(), overlay.model()).setupItemLayer(layer); + TurtleOverlayManager.getOverlay(Minecraft.getInstance().getModelManager(), overlay).model().setupItemLayer(layer); layer.setTransform(transforms().getTransform(context)); } diff --git a/projects/common/src/client/java/dan200/computercraft/client/item/properties/TurtleShowElfOverlay.java b/projects/common/src/client/java/dan200/computercraft/client/item/properties/TurtleShowElfOverlay.java index 73d51db33..adef290b9 100644 --- a/projects/common/src/client/java/dan200/computercraft/client/item/properties/TurtleShowElfOverlay.java +++ b/projects/common/src/client/java/dan200/computercraft/client/item/properties/TurtleShowElfOverlay.java @@ -6,8 +6,10 @@ package dan200.computercraft.client.item.properties; import com.mojang.serialization.MapCodec; import dan200.computercraft.api.ComputerCraftAPI; -import dan200.computercraft.shared.turtle.TurtleOverlay; +import dan200.computercraft.client.turtle.TurtleOverlay; +import dan200.computercraft.client.turtle.TurtleOverlayManager; import dan200.computercraft.shared.turtle.items.TurtleItem; +import net.minecraft.client.Minecraft; import net.minecraft.client.multiplayer.ClientLevel; import net.minecraft.client.renderer.item.properties.conditional.ConditionalItemModelProperty; import net.minecraft.resources.ResourceLocation; @@ -29,7 +31,7 @@ public class TurtleShowElfOverlay implements ConditionalItemModelProperty { @Override public boolean get(ItemStack stack, @Nullable ClientLevel level, @Nullable LivingEntity holder, int i, ItemDisplayContext context) { - var overlay = TurtleItem.getOverlay(stack); + var overlay = TurtleOverlayManager.getOverlay(Minecraft.getInstance().getModelManager(), TurtleItem.getOverlay(stack)); return overlay == null || overlay.showElfOverlay(); } 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 deleted file mode 100644 index e7b8838ca..000000000 --- a/projects/common/src/client/java/dan200/computercraft/client/model/ExtraModels.java +++ /dev/null @@ -1,68 +0,0 @@ -// 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/render/TurtleBlockEntityRenderer.java b/projects/common/src/client/java/dan200/computercraft/client/render/TurtleBlockEntityRenderer.java index 203986f88..76f0d1e1e 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 @@ -9,9 +9,10 @@ import com.mojang.math.Axis; import dan200.computercraft.api.ComputerCraftAPI; import dan200.computercraft.api.turtle.TurtleSide; import dan200.computercraft.client.ClientRegistry; +import dan200.computercraft.client.turtle.TurtleOverlay; +import dan200.computercraft.client.turtle.TurtleOverlayManager; import dan200.computercraft.client.turtle.TurtleUpgradeModels; import dan200.computercraft.shared.computer.core.ComputerFamily; -import dan200.computercraft.shared.turtle.TurtleOverlay; import dan200.computercraft.shared.turtle.blocks.TurtleBlockEntity; import dan200.computercraft.shared.util.Holiday; import net.minecraft.client.Minecraft; @@ -78,7 +79,7 @@ public class TurtleBlockEntityRenderer implements BlockEntityRenderer CODEC = RecordCodecBuilder.create(instance -> instance.group( + ResourceLocation.CODEC.fieldOf("model").forGetter(TurtleOverlay.Unbaked::model), + Codec.BOOL.optionalFieldOf("show_elf_overlay", false).forGetter(TurtleOverlay.Unbaked::showElfOverlay) + ).apply(instance, TurtleOverlay.Unbaked::new)); + + /** + * An additional overlay that is rendered on all turtles at {@linkplain Holiday#CHRISTMAS Christmas}. + * + * @see #showElfOverlay() + */ + public static final ResourceLocation ELF_MODEL = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_elf_overlay"); + + public record Unbaked(ResourceLocation model, boolean showElfOverlay) implements ResolvableModel { + @Override + public void resolveDependencies(Resolver resolver) { + resolver.markDependency(model()); + } + + public TurtleOverlay bake(ModelBaker baker) { + return new TurtleOverlay(StandaloneModel.of(model(), baker), showElfOverlay()); + } + } +} diff --git a/projects/common/src/client/java/dan200/computercraft/client/turtle/TurtleOverlayManager.java b/projects/common/src/client/java/dan200/computercraft/client/turtle/TurtleOverlayManager.java new file mode 100644 index 000000000..9728c15e9 --- /dev/null +++ b/projects/common/src/client/java/dan200/computercraft/client/turtle/TurtleOverlayManager.java @@ -0,0 +1,88 @@ +// SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers +// +// SPDX-License-Identifier: MPL-2.0 + +package dan200.computercraft.client.turtle; + +import com.mojang.serialization.JsonOps; +import dan200.computercraft.client.ClientRegistry; +import dan200.computercraft.client.platform.ClientPlatformHelper; +import dan200.computercraft.client.platform.ModelKey; +import dan200.computercraft.shared.util.ResourceUtils; +import net.minecraft.client.resources.model.MissingBlockModel; +import net.minecraft.client.resources.model.ModelManager; +import net.minecraft.resources.FileToIdConverter; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.packs.resources.ResourceManager; +import org.jetbrains.annotations.Contract; +import org.jspecify.annotations.Nullable; + +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executor; + +/** + * A manager for loading {@link TurtleOverlay}s. This is responsible for {@linkplain #load(ResourceManager, Executor) + * loading overlays from resource packs}, {@linkplain #register(ClientRegistry.RegisterExtraModels, Map) baking them}, and + * then {@linkplain #getOverlay(ModelManager, ResourceLocation) looking them up}. + */ +public class TurtleOverlayManager { + private static final FileToIdConverter ID_CONVERTER = FileToIdConverter.json(TurtleOverlay.SOURCE); + + /** + * The {@link ModelKey} for the missing turtle overlay. This is used by + * {@link #getOverlay(ModelManager, ResourceLocation)} when an overlay does not exist. + */ + private static final ModelKey MISSING_KEY = ClientPlatformHelper.get().createModelKey( + MissingBlockModel.LOCATION, () -> "Missing turtle overlay" + ); + + private static final Map> modelKeys = new ConcurrentHashMap<>(); + + private static ModelKey getModelKey(ResourceLocation overlay) { + return modelKeys.computeIfAbsent(overlay, o -> ClientPlatformHelper.get().createModelKey(o, () -> "Turtle overlay " + o)); + } + + /** + * Load our overlays from resources. + * + * @param resources The current resource manager. + * @param executor The executor to schedule work on. + * @return The map of unbaked overlay. + */ + public static CompletableFuture> load(ResourceManager resources, Executor executor) { + return ResourceUtils.load(resources, executor, "turtle overlay", ID_CONVERTER, JsonOps.INSTANCE, TurtleOverlay.CODEC); + } + + /** + * Register our unbaked overlay models. + * + * @param register The callback to register models with. + * @param overlays The overlays to register. + */ + public static void register(ClientRegistry.RegisterExtraModels register, Map overlays) { + overlays.forEach((id, overlay) -> register.register(getModelKey(id), overlay, TurtleOverlay.Unbaked::bake)); + register.register(MISSING_KEY, new TurtleOverlay.Unbaked(MissingBlockModel.LOCATION, false), TurtleOverlay.Unbaked::bake); + } + + /** + * Find the turtle overlay with the given id. If the overlay does not exist, then the "missing model" overlay is + * returned instead. + * + * @param modelManager The model manager. + * @param id The overlay id. + * @return The turtle overlay. + */ + @Contract("_, null -> null; _, !null -> !null") + public static @Nullable TurtleOverlay getOverlay(ModelManager modelManager, @Nullable ResourceLocation id) { + if (id == null) return null; + + var overlay = getModelKey(id).get(modelManager); + if (overlay != null) return overlay; + + var missing = MISSING_KEY.get(modelManager); + if (missing == null) throw new IllegalStateException("Rendering turtles before models are baked"); + return missing; + } +} diff --git a/projects/common/src/client/java/dan200/computercraft/client/turtle/TurtleUpgradeModels.java b/projects/common/src/client/java/dan200/computercraft/client/turtle/TurtleUpgradeModels.java index b231f09ed..bbc1fb81d 100644 --- a/projects/common/src/client/java/dan200/computercraft/client/turtle/TurtleUpgradeModels.java +++ b/projects/common/src/client/java/dan200/computercraft/client/turtle/TurtleUpgradeModels.java @@ -7,6 +7,7 @@ package dan200.computercraft.client.turtle; import dan200.computercraft.api.client.turtle.TurtleUpgradeModel; import dan200.computercraft.api.turtle.ITurtleUpgrade; import dan200.computercraft.api.upgrades.UpgradeType; +import dan200.computercraft.client.ClientRegistry; import dan200.computercraft.client.platform.ClientPlatformHelper; import dan200.computercraft.client.platform.ModelKey; import dan200.computercraft.shared.util.RegistryHelper; @@ -15,7 +16,6 @@ import net.minecraft.client.resources.model.MissingBlockModel; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import java.util.function.BiConsumer; /** * A registry of {@link TurtleUpgradeModel}s. @@ -79,8 +79,10 @@ public final class TurtleUpgradeModels { return missing; } - public static void bake(BiConsumer>, TurtleUpgradeModel.Unbaked> baker) { - unbaked.forEach(baker); - baker.accept(missingModelKey, TurtleUpgradeModel.sided(MissingBlockModel.LOCATION, MissingBlockModel.LOCATION)); + @SuppressWarnings("unchecked") + public static void bake(ClientRegistry.RegisterExtraModels register) { + unbaked.forEach((id, model) -> + register.register((ModelKey>) id, model, TurtleUpgradeModel.Unbaked::bake)); + register.register(missingModelKey, TurtleUpgradeModel.sided(MissingBlockModel.LOCATION, MissingBlockModel.LOCATION), TurtleUpgradeModel.Unbaked::bake); } } diff --git a/projects/common/src/datagen/java/dan200/computercraft/data/DataProviders.java b/projects/common/src/datagen/java/dan200/computercraft/data/DataProviders.java index f44ddf2a5..83b611925 100644 --- a/projects/common/src/datagen/java/dan200/computercraft/data/DataProviders.java +++ b/projects/common/src/datagen/java/dan200/computercraft/data/DataProviders.java @@ -10,10 +10,9 @@ import dan200.computercraft.api.turtle.ITurtleUpgrade; import dan200.computercraft.client.gui.GuiSprites; import dan200.computercraft.client.model.LecternPocketModel; import dan200.computercraft.client.model.LecternPrintoutModel; +import dan200.computercraft.client.turtle.TurtleOverlay; import dan200.computercraft.data.client.BlockModelProvider; -import dan200.computercraft.data.client.ExtraModelsProvider; import dan200.computercraft.data.client.ItemModelProvider; -import dan200.computercraft.shared.turtle.TurtleOverlay; import dan200.computercraft.shared.turtle.inventory.UpgradeSlot; import net.minecraft.Util; import net.minecraft.client.data.models.BlockModelGenerators; @@ -55,7 +54,6 @@ 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); @@ -90,12 +88,7 @@ public final class DataProviders { )); }); - generator.add(pack -> new ExtraModelsProvider(pack, fullRegistries) { - @Override - public Stream getModels(HolderLookup.Provider registries) { - return registries.lookupOrThrow(TurtleOverlay.REGISTRY).listElements().map(x -> x.value().model()); - } - }); + generator.addFromCodec("Turtle overlays", PackOutput.Target.RESOURCE_PACK, TurtleOverlay.SOURCE, TurtleOverlay.CODEC, TurtleOverlays::register); generator.addModels(BlockModelProvider::addBlockModels, ItemModelProvider::addItemModels); } diff --git a/projects/common/src/datagen/java/dan200/computercraft/data/RecipeProvider.java b/projects/common/src/datagen/java/dan200/computercraft/data/RecipeProvider.java index 263031feb..20e1e590d 100644 --- a/projects/common/src/datagen/java/dan200/computercraft/data/RecipeProvider.java +++ b/projects/common/src/datagen/java/dan200/computercraft/data/RecipeProvider.java @@ -24,7 +24,6 @@ import dan200.computercraft.shared.recipe.ImpostorShapedRecipe; 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; @@ -178,13 +177,11 @@ final class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider { ); } - private void turtleOverlay(ResourceKey overlay, Consumer build) { - var holder = registries.lookupOrThrow(overlay.registryKey()).getOrThrow(overlay); - + private void turtleOverlay(ResourceLocation overlay, Consumer build) { for (var turtleItem : turtleItems()) { var name = RegistryHelper.getKeyOrThrow(BuiltInRegistries.ITEM, turtleItem); - var builder = customShapeless(RecipeCategory.REDSTONE, DataComponentUtil.createStack(turtleItem, ModRegistry.DataComponents.OVERLAY.get(), holder)) + var builder = customShapeless(RecipeCategory.REDSTONE, DataComponentUtil.createStack(turtleItem, ModRegistry.DataComponents.OVERLAY.get(), overlay)) .group(name.withSuffix("_overlay").toString()) .unlockedBy("has_turtle", has(turtleItem)); build.accept(builder); @@ -193,7 +190,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(output, name.withSuffix("_overlays/" + overlay.location().getPath())); + .save(output, name.withSuffix("_overlays/" + overlay.getPath())); } } diff --git a/projects/common/src/datagen/java/dan200/computercraft/data/TurtleOverlays.java b/projects/common/src/datagen/java/dan200/computercraft/data/TurtleOverlays.java index 7f86c343b..d1589226f 100644 --- a/projects/common/src/datagen/java/dan200/computercraft/data/TurtleOverlays.java +++ b/projects/common/src/datagen/java/dan200/computercraft/data/TurtleOverlays.java @@ -5,32 +5,32 @@ 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 dan200.computercraft.client.turtle.TurtleOverlay; import net.minecraft.resources.ResourceLocation; +import java.util.function.BiConsumer; + /** * 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"); + public static final ResourceLocation RAINBOW_FLAG = create("rainbow_flag"); + public static final ResourceLocation TRANS_FLAG = create("trans_flag"); - private static ResourceKey create(String name) { - return ResourceKey.create(TurtleOverlay.REGISTRY, ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, name)); + private static ResourceLocation create(String name) { + return ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, name); } private TurtleOverlays() { } - public static void register(BootstrapContext registry) { - registry.register(RAINBOW_FLAG, new TurtleOverlay( + public static void register(BiConsumer registry) { + registry.accept(RAINBOW_FLAG, new TurtleOverlay.Unbaked( ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_rainbow_overlay"), true )); - registry.register(TRANS_FLAG, new TurtleOverlay( + registry.accept(TRANS_FLAG, new TurtleOverlay.Unbaked( ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_trans_overlay"), true )); diff --git a/projects/common/src/datagen/java/dan200/computercraft/data/client/BlockModelProvider.java b/projects/common/src/datagen/java/dan200/computercraft/data/client/BlockModelProvider.java index 3cab0be55..0f3da02f5 100644 --- a/projects/common/src/datagen/java/dan200/computercraft/data/client/BlockModelProvider.java +++ b/projects/common/src/datagen/java/dan200/computercraft/data/client/BlockModelProvider.java @@ -11,6 +11,7 @@ import dan200.computercraft.client.item.model.TurtleOverlayModel; import dan200.computercraft.client.item.model.TurtleUpgradeModel; import dan200.computercraft.client.item.properties.TurtleShowElfOverlay; import dan200.computercraft.client.render.TurtleBlockEntityRenderer; +import dan200.computercraft.client.turtle.TurtleOverlay; import dan200.computercraft.shared.ModRegistry; import dan200.computercraft.shared.computer.blocks.ComputerBlock; import dan200.computercraft.shared.peripheral.diskdrive.DiskDriveBlock; @@ -21,7 +22,6 @@ import dan200.computercraft.shared.peripheral.modem.wireless.WirelessModemBlock; import dan200.computercraft.shared.peripheral.monitor.MonitorBlock; import dan200.computercraft.shared.peripheral.monitor.MonitorEdgeState; import dan200.computercraft.shared.peripheral.printer.PrinterBlock; -import dan200.computercraft.shared.turtle.TurtleOverlay; import dan200.computercraft.shared.turtle.blocks.TurtleBlock; import dan200.computercraft.shared.util.DirectionUtil; import net.minecraft.client.data.models.BlockModelGenerators; diff --git a/projects/common/src/datagen/java/dan200/computercraft/data/client/ExtraModelsProvider.java b/projects/common/src/datagen/java/dan200/computercraft/data/client/ExtraModelsProvider.java deleted file mode 100644 index 6fdcae771..000000000 --- a/projects/common/src/datagen/java/dan200/computercraft/data/client/ExtraModelsProvider.java +++ /dev/null @@ -1,52 +0,0 @@ -// 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}. - */ -public abstract class ExtraModelsProvider implements DataProvider { - private final Path path; - private final CompletableFuture registries; - - public ExtraModelsProvider(PackOutput output, CompletableFuture registries) { - path = output.getOutputFolder(PackOutput.Target.RESOURCE_PACK).resolve(ExtraModels.PATH.getNamespace()).resolve(ExtraModels.PATH.getPath()); - this.registries = registries; - } - - /** - * 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/data/computercraft/computercraft/turtle_overlay/rainbow_flag.json b/projects/common/src/generated/resources/assets/computercraft/computercraft/turtle_overlay/rainbow_flag.json similarity index 100% rename from projects/common/src/generated/resources/data/computercraft/computercraft/turtle_overlay/rainbow_flag.json rename to projects/common/src/generated/resources/assets/computercraft/computercraft/turtle_overlay/rainbow_flag.json diff --git a/projects/common/src/generated/resources/data/computercraft/computercraft/turtle_overlay/trans_flag.json b/projects/common/src/generated/resources/assets/computercraft/computercraft/turtle_overlay/trans_flag.json similarity index 100% rename from projects/common/src/generated/resources/data/computercraft/computercraft/turtle_overlay/trans_flag.json rename to projects/common/src/generated/resources/assets/computercraft/computercraft/turtle_overlay/trans_flag.json diff --git a/projects/common/src/generated/resources/assets/computercraft/extra_models.json b/projects/common/src/generated/resources/assets/computercraft/extra_models.json deleted file mode 100644 index 4e592f53d..000000000 --- a/projects/common/src/generated/resources/assets/computercraft/extra_models.json +++ /dev/null @@ -1 +0,0 @@ -["computercraft:block/turtle_rainbow_overlay", "computercraft:block/turtle_trans_overlay"] 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 5446ef7c6..edfd2f91f 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/ModRegistry.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/ModRegistry.java @@ -81,7 +81,6 @@ 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.apis.TurtleAPI; import dan200.computercraft.shared.turtle.blocks.TurtleBlock; import dan200.computercraft.shared.turtle.blocks.TurtleBlockEntity; @@ -389,8 +388,8 @@ public final class ModRegistry { /** * The overlay on a turtle. */ - public static final RegistryEntry>> OVERLAY = register("overlay", b -> b - .persistent(TurtleOverlay.CODEC).networkSynchronized(TurtleOverlay.STREAM_CODEC) + public static final RegistryEntry> OVERLAY = register("overlay", b -> b + .persistent(ResourceLocation.CODEC).networkSynchronized(ResourceLocation.STREAM_CODEC).cacheEncoding() ); /** 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 deleted file mode 100644 index f0d8127fe..000000000 --- a/projects/common/src/main/java/dan200/computercraft/shared/turtle/TurtleOverlay.java +++ /dev/null @@ -1,77 +0,0 @@ -// 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 org.jspecify.annotations.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 27507d4e5..9203f91b4 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 @@ -21,7 +21,6 @@ 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.core.TurtleBrain; import dan200.computercraft.shared.turtle.inventory.TurtleMenu; import net.minecraft.core.BlockPos; @@ -33,6 +32,7 @@ import net.minecraft.core.component.DataComponentMap; import net.minecraft.core.component.DataComponents; import net.minecraft.nbt.CompoundTag; import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket; +import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.Container; import net.minecraft.world.ContainerHelper; @@ -233,9 +233,8 @@ public class TurtleBlockEntity extends AbstractComputerBlockEntity implements Ba return brain.getColour(); } - public @Nullable TurtleOverlay getOverlay() { - var overlay = brain.getOverlay(); - return overlay == null ? null : overlay.value(); + public @Nullable ResourceLocation getOverlay() { + return brain.getOverlay(); } 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 73bc9d056..fa4f79804 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,7 +20,6 @@ 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,6 +31,7 @@ import net.minecraft.core.HolderLookup; import net.minecraft.core.component.DataComponentPatch; import net.minecraft.core.particles.ParticleTypes; import net.minecraft.nbt.CompoundTag; +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 Holder overlay = null; + private @Nullable ResourceLocation overlay = null; private TurtleAnimation animation = TurtleAnimation.NONE; private int animationProgress = 0; @@ -134,7 +134,7 @@ public class TurtleBrain implements TurtleAccessInternal { // Read fields colourHex = nbt.getIntOr(NBT_COLOUR, -1); fuelLevel = nbt.getIntOr(NBT_FUEL, 0); - overlay = nbt.contains(NBT_OVERLAY) ? NBTUtil.decodeFrom(TurtleOverlay.CODEC, registries, nbt, NBT_OVERLAY) : null; + overlay = nbt.contains(NBT_OVERLAY) ? NBTUtil.decodeFrom(ResourceLocation.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); - NBTUtil.encodeTo(TurtleOverlay.CODEC, registries, nbt, NBT_OVERLAY, overlay); + NBTUtil.encodeTo(ResourceLocation.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 Holder getOverlay() { + public @Nullable ResourceLocation getOverlay() { return overlay; } - public void setOverlay(@Nullable Holder overlay) { + public void setOverlay(@Nullable ResourceLocation 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 95e9c4514..9c31ca994 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 @@ -11,12 +11,12 @@ import dan200.computercraft.api.upgrades.UpgradeData; import dan200.computercraft.impl.TurtleUpgrades; import dan200.computercraft.impl.UpgradeManager; import dan200.computercraft.shared.ModRegistry; -import dan200.computercraft.shared.turtle.TurtleOverlay; import dan200.computercraft.shared.turtle.blocks.TurtleBlock; import net.minecraft.core.HolderLookup; 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.InteractionResult; import net.minecraft.world.item.BlockItem; import net.minecraft.world.item.ItemStack; @@ -51,9 +51,8 @@ public class TurtleItem extends BlockItem { }); } - public static @Nullable TurtleOverlay getOverlay(ItemStack stack) { - var overlay = stack.get(ModRegistry.DataComponents.OVERLAY.get()); - return overlay == null ? null : overlay.value(); + public static @Nullable ResourceLocation getOverlay(ItemStack stack) { + return stack.get(ModRegistry.DataComponents.OVERLAY.get()); } public static final CauldronInteraction CAULDRON_INTERACTION = (blockState, level, pos, player, hand, stack) -> { diff --git a/projects/common/src/main/java/dan200/computercraft/shared/util/ConsList.java b/projects/common/src/main/java/dan200/computercraft/shared/util/ConsList.java deleted file mode 100644 index 67c0bdfc8..000000000 --- a/projects/common/src/main/java/dan200/computercraft/shared/util/ConsList.java +++ /dev/null @@ -1,56 +0,0 @@ -// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers -// -// SPDX-License-Identifier: MPL-2.0 - -package dan200.computercraft.shared.util; - -import org.jspecify.annotations.Nullable; - -import java.util.AbstractList; -import java.util.Iterator; -import java.util.List; - -/** - * A list which prepends a single value to another list. - * - * @param The type of item in the list. - */ -public final class ConsList extends AbstractList { - private final T head; - private final List tail; - - public ConsList(T head, List tail) { - this.head = head; - this.tail = tail; - } - - @Override - public T get(int index) { - return index == 0 ? head : tail.get(index - 1); - } - - @Override - public int size() { - return 1 + tail.size(); - } - - @Override - public Iterator iterator() { - return new Iterator<>() { - private @Nullable Iterator tailIterator; - - @Override - public boolean hasNext() { - return tailIterator == null || tailIterator.hasNext(); - } - - @Override - public T next() { - if (tailIterator != null) return tailIterator.next(); - - tailIterator = tail.iterator(); - return head; - } - }; - } -} diff --git a/projects/common/src/main/java/dan200/computercraft/shared/util/ResourceUtils.java b/projects/common/src/main/java/dan200/computercraft/shared/util/ResourceUtils.java new file mode 100644 index 000000000..2c8bd7e17 --- /dev/null +++ b/projects/common/src/main/java/dan200/computercraft/shared/util/ResourceUtils.java @@ -0,0 +1,70 @@ +// SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers +// +// SPDX-License-Identifier: MPL-2.0 + +package dan200.computercraft.shared.util; + +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; +import com.mojang.datafixers.util.Pair; +import com.mojang.serialization.Codec; +import com.mojang.serialization.DynamicOps; +import net.minecraft.Util; +import net.minecraft.resources.FileToIdConverter; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.packs.resources.ResourceManager; +import net.minecraft.server.packs.resources.SimpleJsonResourceReloadListener; +import org.jspecify.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.stream.Collectors; + +public class ResourceUtils { + private static final Logger LOG = LoggerFactory.getLogger(ResourceUtils.class); + + /** + * A version of {@link SimpleJsonResourceReloadListener#scanDirectory(ResourceManager, FileToIdConverter, DynamicOps, Codec, Map)}, + * that runs in parallel over multiple executors. + *

+ * This shares some similarity with how {@link net.minecraft.client.resources.model.ModelManager#loadBlockModels(ResourceManager, Executor)} + * loads resources. + * + * @param resourceManager The current resource manager. + * @param executor The executor to schedule work on. + * @param kind The name of the thing we're loading, for use in error messages. + * @param lister The file-to-id-converter used to locate files. + * @param ops The dynamic ops used when decoding. + * @param codec The codec used to decode each file. + * @param The type of the entry to load + * @return The loaded resources. + */ + public static CompletableFuture> load( + ResourceManager resourceManager, Executor executor, String kind, FileToIdConverter lister, DynamicOps ops, Codec codec + ) { + return CompletableFuture.supplyAsync(() -> lister.listMatchingResources(resourceManager), executor).thenCompose(resources -> { + List>> futures = new ArrayList<>(resources.size()); + resources.forEach((path, resource) -> futures.add(CompletableFuture.supplyAsync(() -> { + var id = lister.fileToId(path); + try (var reader = resource.openAsReader()) { + var result = codec.parse(ops, JsonParser.parseReader(reader)) + .ifError(e -> LOG.error("Couldn't parse {} '{}' from pack '{}': {}", kind, id, resource.sourcePackId(), e.message())) + .result().orElse(null); + return result == null ? null : new Pair<>(id, result); + } catch (Exception exception) { + LOG.error("Failed to open {} {} from pack '{}'", kind, resource, resource.sourcePackId(), exception); + return null; + } + }, executor))); + + return Util.sequence(futures).thenApply(result -> result + .stream().filter(Objects::nonNull).collect(Collectors.toUnmodifiableMap(Pair::getFirst, Pair::getSecond))); + }); + } +} diff --git a/projects/common/src/test/java/dan200/computercraft/shared/util/ConsListTest.java b/projects/common/src/test/java/dan200/computercraft/shared/util/ConsListTest.java deleted file mode 100644 index 7c65b836f..000000000 --- a/projects/common/src/test/java/dan200/computercraft/shared/util/ConsListTest.java +++ /dev/null @@ -1,37 +0,0 @@ -// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers -// -// SPDX-License-Identifier: MPL-2.0 - -package dan200.computercraft.shared.util; - -import org.junit.jupiter.api.Test; - -import java.util.List; - -import static org.junit.jupiter.api.Assertions.assertArrayEquals; -import static org.junit.jupiter.api.Assertions.assertEquals; - -/** - * A couple of trivial tests for {@link ConsList}, mostly as a quick safety check. - */ -public class ConsListTest { - @Test - public void testGet() { - var list = new ConsList<>(1, List.of(2, 3, 4)); - assertEquals(1, list.get(0)); - assertEquals(2, list.get(1)); - assertEquals(4, list.get(3)); - } - - @Test - public void testSize() { - var list = new ConsList<>(1, List.of(2, 3, 4)); - assertEquals(4, list.size()); - } - - @Test - public void testIterator() { - var list = new ConsList<>(1, List.of(2, 3, 4)); - assertArrayEquals(new Integer[]{ 1, 2, 3, 4 }, list.toArray(Integer[]::new)); - } -} 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 d5e7eb880..2befddee3 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 @@ -14,7 +14,6 @@ import dan200.computercraft.api.turtle.TurtleSide import dan200.computercraft.api.upgrades.UpgradeData import dan200.computercraft.core.apis.PeripheralAPI import dan200.computercraft.gametest.api.* -import dan200.computercraft.gametest.api.GameTest import dan200.computercraft.gametest.core.TestHooks import dan200.computercraft.mixin.gametest.GameTestHelperAccessor import dan200.computercraft.mixin.gametest.GameTestInfoAccessor @@ -24,7 +23,6 @@ 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.blocks.TurtleBlockEntity import dan200.computercraft.shared.turtle.core.TurtleCraftCommand @@ -772,8 +770,7 @@ class Turtle_Test { @GameTest fun Data_fixers(helper: GameTestHelper) = helper.sequence { thenExecute { - val overlay = helper.level.registryAccess().lookupOrThrow(TurtleOverlay.REGISTRY) - .getValue(ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "trans_flag"))!! + val overlay = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "trans_flag") val upgrade = helper.level.registryAccess().lookupOrThrow(ITurtleUpgrade.REGISTRY) .getValue(ResourceLocation.withDefaultNamespace("diamond_pickaxe"))!! 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 faf9711b3..d72952e95 100644 --- a/projects/fabric/src/client/java/dan200/computercraft/client/ComputerCraftClient.java +++ b/projects/fabric/src/client/java/dan200/computercraft/client/ComputerCraftClient.java @@ -9,11 +9,8 @@ import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.builder.RequiredArgumentBuilder; import dan200.computercraft.api.ComputerCraftAPI; import dan200.computercraft.api.client.FabricComputerCraftAPIClient; -import dan200.computercraft.api.client.StandaloneModel; -import dan200.computercraft.api.client.turtle.TurtleUpgradeModel; -import dan200.computercraft.api.turtle.ITurtleUpgrade; -import dan200.computercraft.client.model.ExtraModels; import dan200.computercraft.client.platform.FabricModelKey; +import dan200.computercraft.client.platform.ModelKey; import dan200.computercraft.core.util.Nullability; import dan200.computercraft.impl.Services; import dan200.computercraft.shared.ComputerCraft; @@ -27,7 +24,6 @@ import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallba import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; import net.fabricmc.fabric.api.client.model.loading.v1.PreparableModelLoadingPlugin; -import net.fabricmc.fabric.api.client.model.loading.v1.SimpleUnbakedExtraModel; import net.fabricmc.fabric.api.client.model.loading.v1.UnbakedExtraModel; import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents; @@ -41,11 +37,13 @@ import net.minecraft.client.renderer.item.ItemModels; import net.minecraft.client.renderer.item.properties.conditional.ConditionalItemModelProperties; import net.minecraft.client.renderer.item.properties.select.SelectItemModelProperties; import net.minecraft.client.resources.model.ModelBaker; +import net.minecraft.client.resources.model.ResolvableModel; import net.minecraft.world.phys.BlockHitResult; import java.nio.file.Files; import java.nio.file.Path; -import java.util.concurrent.CompletableFuture; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; import static dan200.computercraft.core.util.Nullability.assertNonNull; @@ -67,12 +65,13 @@ public class ComputerCraftClient { ClientRegistry.registerConditionalItemProperties(ConditionalItemModelProperties.ID_MAPPER::put); PreparableModelLoadingPlugin.register( - (resources, executor) -> CompletableFuture.supplyAsync(() -> ExtraModels.loadAll(resources), executor), - (state, context) -> ClientRegistry.registerExtraModels( - (key, model) -> context.addModel(FabricModelKey.key(key), new SimpleUnbakedExtraModel<>(model, StandaloneModel::of)), - (key, model) -> context.addModel(FabricModelKey.erased(key), new TurtleModelWrapper<>(model)), - state - ) + ClientRegistry::gatherExtraModels, + (state, context) -> ClientRegistry.registerExtraModels(new ClientRegistry.RegisterExtraModels() { + @Override + public void register(ModelKey key, U unbaked, BiConsumer resolve, BiFunction bake) { + context.addModel(FabricModelKey.key(key), new ModelWrapper<>(unbaked, resolve, bake)); + } + }, state) ); BlockRenderLayerMap.INSTANCE.putBlock(ModRegistry.Blocks.COMPUTER_NORMAL.get(), RenderType.cutout()); @@ -108,17 +107,17 @@ public class ComputerCraftClient { ((FabricConfigFile) ConfigSpec.clientSpec).load(FabricLoader.getInstance().getConfigDir().resolve(ComputerCraftAPI.MOD_ID + "-client.toml")); } - private record TurtleModelWrapper( - TurtleUpgradeModel.Unbaked model - ) implements UnbakedExtraModel> { + private record ModelWrapper( + U model, BiConsumer resolve, BiFunction bake + ) implements UnbakedExtraModel { @Override - public TurtleUpgradeModel bake(ModelBaker baker) { - return model().bake(baker); + public T bake(ModelBaker baker) { + return bake().apply(model(), baker); } @Override public void resolveDependencies(Resolver resolver) { - model().resolveDependencies(resolver); + resolve().accept(model(), resolver); } } } diff --git a/projects/fabric/src/client/java/dan200/computercraft/client/platform/FabricModelKey.java b/projects/fabric/src/client/java/dan200/computercraft/client/platform/FabricModelKey.java index efcc380f0..e0109fe56 100644 --- a/projects/fabric/src/client/java/dan200/computercraft/client/platform/FabricModelKey.java +++ b/projects/fabric/src/client/java/dan200/computercraft/client/platform/FabricModelKey.java @@ -24,11 +24,6 @@ public final class FabricModelKey implements ModelKey { return ((FabricModelKey) key).key; } - @SuppressWarnings("unchecked") - public static ExtraModelKey erased(ModelKey key) { - return ((FabricModelKey) key).key; - } - @Override public @Nullable T get(ModelManager manager) { return manager.getModel(key); 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 0e9e4c1d4..50d37c3dd 100644 --- a/projects/fabric/src/main/java/dan200/computercraft/shared/ComputerCraft.java +++ b/projects/fabric/src/main/java/dan200/computercraft/shared/ComputerCraft.java @@ -23,7 +23,6 @@ import dan200.computercraft.shared.peripheral.generic.methods.InventoryMethods; import dan200.computercraft.shared.peripheral.modem.wired.CableBlock; 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; @@ -85,7 +84,6 @@ 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 82f5d9ffd..70f4c4755 100644 --- a/projects/forge/src/client/java/dan200/computercraft/client/ForgeClientRegistry.java +++ b/projects/forge/src/client/java/dan200/computercraft/client/ForgeClientRegistry.java @@ -6,12 +6,9 @@ package dan200.computercraft.client; import com.google.common.reflect.TypeToken; import dan200.computercraft.api.ComputerCraftAPI; -import dan200.computercraft.api.client.StandaloneModel; import dan200.computercraft.api.client.turtle.RegisterTurtleModelEvent; -import dan200.computercraft.api.client.turtle.TurtleUpgradeModel; -import dan200.computercraft.api.turtle.ITurtleUpgrade; -import dan200.computercraft.client.model.ExtraModels; import dan200.computercraft.client.platform.ForgeModelKey; +import dan200.computercraft.client.platform.ModelKey; import dan200.computercraft.client.render.ExtendedItemFrameRenderState; import dan200.computercraft.client.turtle.TurtleUpgradeModels; import net.minecraft.client.Minecraft; @@ -29,6 +26,11 @@ import net.neoforged.neoforge.client.event.*; import net.neoforged.neoforge.client.model.standalone.UnbakedStandaloneModel; import net.neoforged.neoforge.client.renderstate.RegisterRenderStateModifiersEvent; +import java.util.ArrayDeque; +import java.util.Queue; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; + /** * Registers textures and models for items. @@ -44,12 +46,18 @@ public final class ForgeClientRegistry { public static void registerModels(ModelEvent.RegisterStandalone event) { TurtleUpgradeModels.fetch(() -> ModLoader.postEvent(new RegisterTurtleModelEvent(TurtleUpgradeModels::register))); - var extraModels = ExtraModels.loadAll(Minecraft.getInstance().getResourceManager()); - ClientRegistry.registerExtraModels( - (key, model) -> event.register(ForgeModelKey.key(key), StandaloneModel::of), - (key, model) -> event.register(ForgeModelKey.erased(key), new TurtleModelWrapper<>(model)), - extraModels - ); + // Load resources + Queue tasks = new ArrayDeque<>(); + var state = ClientRegistry.gatherExtraModels(Minecraft.getInstance().getResourceManager(), tasks::add); + Runnable task; + while ((task = tasks.poll()) != null) task.run(); + + ClientRegistry.registerExtraModels(new ClientRegistry.RegisterExtraModels() { + @Override + public void register(ModelKey key, U unbaked, BiConsumer resolve, BiFunction bake) { + event.register(ForgeModelKey.key(key), new ModelWrapper<>(unbaked, resolve, bake)); + } + }, state.resultNow()); } @SubscribeEvent @@ -102,17 +110,17 @@ public final class ForgeClientRegistry { ClientRegistry.register(); } - private record TurtleModelWrapper( - TurtleUpgradeModel.Unbaked model - ) implements UnbakedStandaloneModel> { + private record ModelWrapper( + U model, BiConsumer resolve, BiFunction bake + ) implements UnbakedStandaloneModel { @Override - public TurtleUpgradeModel bake(ModelBaker baker) { - return model().bake(baker); + public T bake(ModelBaker baker) { + return bake().apply(model(), baker); } @Override public void resolveDependencies(ResolvableModel.Resolver resolver) { - model().resolveDependencies(resolver); + resolve().accept(model(), resolver); } } } diff --git a/projects/forge/src/client/java/dan200/computercraft/client/platform/ForgeModelKey.java b/projects/forge/src/client/java/dan200/computercraft/client/platform/ForgeModelKey.java index 09c2aa6a3..8599c7400 100644 --- a/projects/forge/src/client/java/dan200/computercraft/client/platform/ForgeModelKey.java +++ b/projects/forge/src/client/java/dan200/computercraft/client/platform/ForgeModelKey.java @@ -24,11 +24,6 @@ public final class ForgeModelKey implements ModelKey { return ((ForgeModelKey) key).key; } - @SuppressWarnings("unchecked") - public static StandaloneModelKey erased(ModelKey key) { - return ((ForgeModelKey) key).key; - } - @Override public @Nullable T get(ModelManager manager) { return manager.getStandaloneModel(key); diff --git a/projects/forge/src/main/java/dan200/computercraft/ComputerCraft.java b/projects/forge/src/main/java/dan200/computercraft/ComputerCraft.java index 1f9472d34..90231f037 100644 --- a/projects/forge/src/main/java/dan200/computercraft/ComputerCraft.java +++ b/projects/forge/src/main/java/dan200/computercraft/ComputerCraft.java @@ -30,7 +30,6 @@ import dan200.computercraft.shared.peripheral.generic.methods.FluidMethods; import dan200.computercraft.shared.peripheral.generic.methods.InventoryMethods; import dan200.computercraft.shared.platform.ForgeConfigFile; import dan200.computercraft.shared.recipe.function.RecipeFunction; -import dan200.computercraft.shared.turtle.TurtleOverlay; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.network.protocol.common.custom.CustomPacketPayload; @@ -104,7 +103,6 @@ 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