mirror of
				https://github.com/SquidDev-CC/CC-Tweaked
				synced 2025-10-31 21:52:59 +00:00 
			
		
		
		
	Load turtle overlays during model loading
Back in f10e401aea, we changed turtle
overlays to be loaded as a dynamic registry. This solved some of the
problems we had with upgrades (elf-compatibility), and seemed like a
good idea at the time.
However, because overlays are part of datapacks (not resource packs), we
also needed support for loading overlay models, which we did via an
"extra_models.json" file.
With the ItemModel changes, we can go for a different approach:
 - Turtle overlays are now stored on the item/BE as a simple
   ResourceLocation again.
 - The TurtleOverlay class is moved to the client, and loaded from
   resource packs, during model loading. They're now split into a baked
   form (holding a StandaloneModel) and an unbaked one (holding the
   ResourceLocation).
 - extra_model.json is no longer supported.
			
			
This commit is contained in:
		| @@ -20,10 +20,11 @@ import dan200.computercraft.client.render.CustomLecternRenderer; | |||||||
| import dan200.computercraft.client.render.TurtleBlockEntityRenderer; | import dan200.computercraft.client.render.TurtleBlockEntityRenderer; | ||||||
| import dan200.computercraft.client.render.monitor.MonitorBlockEntityRenderer; | import dan200.computercraft.client.render.monitor.MonitorBlockEntityRenderer; | ||||||
| import dan200.computercraft.client.turtle.TurtleModemModel; | 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.client.turtle.TurtleUpgradeModels; | ||||||
| import dan200.computercraft.shared.ModRegistry; | import dan200.computercraft.shared.ModRegistry; | ||||||
| import dan200.computercraft.shared.computer.inventory.AbstractComputerMenu; | import dan200.computercraft.shared.computer.inventory.AbstractComputerMenu; | ||||||
| import dan200.computercraft.shared.turtle.TurtleOverlay; |  | ||||||
| import net.minecraft.client.Minecraft; | import net.minecraft.client.Minecraft; | ||||||
| import net.minecraft.client.color.item.ItemTintSource; | import net.minecraft.client.color.item.ItemTintSource; | ||||||
| import net.minecraft.client.gui.screens.MenuScreens; | 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.conditional.ConditionalItemModelProperty; | ||||||
| import net.minecraft.client.renderer.item.properties.select.SelectItemModelProperty; | import net.minecraft.client.renderer.item.properties.select.SelectItemModelProperty; | ||||||
| import net.minecraft.client.resources.model.MissingBlockModel; | 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.ModelManager; | ||||||
|  | import net.minecraft.client.resources.model.ResolvableModel; | ||||||
| import net.minecraft.resources.ResourceLocation; | import net.minecraft.resources.ResourceLocation; | ||||||
| import net.minecraft.server.packs.resources.PreparableReloadListener; | 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.AbstractContainerMenu; | ||||||
| import net.minecraft.world.inventory.MenuType; | import net.minecraft.world.inventory.MenuType; | ||||||
| 
 | 
 | ||||||
| import java.util.Collection; |  | ||||||
| import java.util.Map; | import java.util.Map; | ||||||
| import java.util.Objects; | import java.util.Objects; | ||||||
|  | import java.util.concurrent.CompletableFuture; | ||||||
| import java.util.concurrent.ConcurrentHashMap; | import java.util.concurrent.ConcurrentHashMap; | ||||||
|  | import java.util.concurrent.Executor; | ||||||
| import java.util.function.BiConsumer; | import java.util.function.BiConsumer; | ||||||
|  | import java.util.function.BiFunction; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Registers client-side objects, such as {@link BlockEntityRendererProvider}s and |  * Registers client-side objects, such as {@link BlockEntityRendererProvider}s and | ||||||
| @@ -122,14 +128,61 @@ public final class ClientRegistry { | |||||||
|         MissingBlockModel.LOCATION, |         MissingBlockModel.LOCATION, | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     public static void registerExtraModels( |     /** | ||||||
|         BiConsumer<ModelKey<StandaloneModel>, ResourceLocation> registerBasic, |      * Additional models to load. | ||||||
|         BiConsumer<ModelKey<? extends TurtleUpgradeModel<?>>, TurtleUpgradeModel.Unbaked<?>> registerTurtle, |      * | ||||||
|         Collection<ResourceLocation> extraModels |      * @param turtleOverlays The unbaked turtle models. | ||||||
|  |      * @see #gatherExtraModels(ResourceManager, Executor) | ||||||
|  |      * @see #registerExtraModels(RegisterExtraModels, ExtraModels) | ||||||
|  |      */ | ||||||
|  |     public record ExtraModels( | ||||||
|  |         Map<ResourceLocation, TurtleOverlay.Unbaked> 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<ExtraModels> 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 <U extends ResolvableModel, T> void register(ModelKey<T> key, U unbaked, BiFunction<U, ModelBaker, T> bake) { | ||||||
|  |             register(key, unbaked, ResolvableModel::resolveDependencies, bake); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         /** | ||||||
|  |          * Register an extra model. | ||||||
|  |          * <p> | ||||||
|  |          * 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 <U>     The type of unbaked model. | ||||||
|  |          * @param <T>     The type of baked model. | ||||||
|  |          */ | ||||||
|  |         <U, T> void register(ModelKey<T> key, U unbaked, BiConsumer<U, ResolvableModel.Resolver> resolve, BiFunction<U, ModelBaker, T> 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<ResourceLocation, MapCodec<? extends ItemModel.Unbaked>> register) { |     public static void registerItemModels(BiConsumer<ResourceLocation, MapCodec<? extends ItemModel.Unbaked>> register) { | ||||||
|   | |||||||
| @@ -7,8 +7,8 @@ package dan200.computercraft.client.item.model; | |||||||
| import com.mojang.serialization.MapCodec; | import com.mojang.serialization.MapCodec; | ||||||
| import com.mojang.serialization.codecs.RecordCodecBuilder; | import com.mojang.serialization.codecs.RecordCodecBuilder; | ||||||
| import dan200.computercraft.api.ComputerCraftAPI; | import dan200.computercraft.api.ComputerCraftAPI; | ||||||
| import dan200.computercraft.client.ClientRegistry; | import dan200.computercraft.client.turtle.TurtleOverlay; | ||||||
| import dan200.computercraft.shared.turtle.TurtleOverlay; | import dan200.computercraft.client.turtle.TurtleOverlayManager; | ||||||
| import dan200.computercraft.shared.turtle.items.TurtleItem; | import dan200.computercraft.shared.turtle.items.TurtleItem; | ||||||
| import net.minecraft.client.Minecraft; | import net.minecraft.client.Minecraft; | ||||||
| import net.minecraft.client.multiplayer.ClientLevel; | import net.minecraft.client.multiplayer.ClientLevel; | ||||||
| @@ -40,7 +40,7 @@ public record TurtleOverlayModel(ItemTransforms transforms) implements ItemModel | |||||||
|         if (overlay == null) return; |         if (overlay == null) return; | ||||||
| 
 | 
 | ||||||
|         var layer = state.newLayer(); |         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)); |         layer.setTransform(transforms().getTransform(context)); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|   | |||||||
| @@ -6,8 +6,10 @@ package dan200.computercraft.client.item.properties; | |||||||
| 
 | 
 | ||||||
| import com.mojang.serialization.MapCodec; | import com.mojang.serialization.MapCodec; | ||||||
| import dan200.computercraft.api.ComputerCraftAPI; | 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 dan200.computercraft.shared.turtle.items.TurtleItem; | ||||||
|  | import net.minecraft.client.Minecraft; | ||||||
| import net.minecraft.client.multiplayer.ClientLevel; | import net.minecraft.client.multiplayer.ClientLevel; | ||||||
| import net.minecraft.client.renderer.item.properties.conditional.ConditionalItemModelProperty; | import net.minecraft.client.renderer.item.properties.conditional.ConditionalItemModelProperty; | ||||||
| import net.minecraft.resources.ResourceLocation; | import net.minecraft.resources.ResourceLocation; | ||||||
| @@ -29,7 +31,7 @@ public class TurtleShowElfOverlay implements ConditionalItemModelProperty { | |||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     public boolean get(ItemStack stack, @Nullable ClientLevel level, @Nullable LivingEntity holder, int i, ItemDisplayContext context) { |     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(); |         return overlay == null || overlay.showElfOverlay(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|   | |||||||
| @@ -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. |  | ||||||
|  * <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); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -9,9 +9,10 @@ import com.mojang.math.Axis; | |||||||
| import dan200.computercraft.api.ComputerCraftAPI; | import dan200.computercraft.api.ComputerCraftAPI; | ||||||
| import dan200.computercraft.api.turtle.TurtleSide; | import dan200.computercraft.api.turtle.TurtleSide; | ||||||
| import dan200.computercraft.client.ClientRegistry; | 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.client.turtle.TurtleUpgradeModels; | ||||||
| import dan200.computercraft.shared.computer.core.ComputerFamily; | import dan200.computercraft.shared.computer.core.ComputerFamily; | ||||||
| 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; | ||||||
| @@ -78,7 +79,7 @@ public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBloc | |||||||
| 
 | 
 | ||||||
|         // Render the turtle |         // Render the turtle | ||||||
|         var colour = turtle.getColour(); |         var colour = turtle.getColour(); | ||||||
|         var overlay = turtle.getOverlay(); |         var overlay = TurtleOverlayManager.getOverlay(Minecraft.getInstance().getModelManager(), turtle.getOverlay()); | ||||||
| 
 | 
 | ||||||
|         if (colour == -1) { |         if (colour == -1) { | ||||||
|             renderModel(transform, buffers, lightmapCoord, overlayLight, turtle.getFamily() == ComputerFamily.NORMAL ? NORMAL_TURTLE_MODEL : ADVANCED_TURTLE_MODEL, null); |             renderModel(transform, buffers, lightmapCoord, overlayLight, turtle.getFamily() == ComputerFamily.NORMAL ? NORMAL_TURTLE_MODEL : ADVANCED_TURTLE_MODEL, null); | ||||||
| @@ -88,10 +89,10 @@ public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBloc | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // Render the overlay |         // Render the overlay | ||||||
|         if (overlay != null) renderModel(transform, buffers, lightmapCoord, overlayLight, overlay.model(), null); |         if (overlay != null) overlay.model().render(transform, buffers, lightmapCoord, overlayLight); | ||||||
| 
 | 
 | ||||||
|         // And the Christmas overlay. |         // And the Christmas overlay. | ||||||
|         var showChristmas = TurtleOverlay.showElfOverlay(overlay, Holiday.getCurrent() == Holiday.CHRISTMAS); |         var showChristmas = Holiday.getCurrent() == Holiday.CHRISTMAS && (overlay == null || overlay.showElfOverlay()); | ||||||
|         if (showChristmas) renderModel(transform, buffers, lightmapCoord, overlayLight, TurtleOverlay.ELF_MODEL, null); |         if (showChristmas) renderModel(transform, buffers, lightmapCoord, overlayLight, TurtleOverlay.ELF_MODEL, null); | ||||||
| 
 | 
 | ||||||
|         // Render the upgrades |         // Render the upgrades | ||||||
|   | |||||||
| @@ -0,0 +1,55 @@ | |||||||
|  | // SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers | ||||||
|  | // | ||||||
|  | // SPDX-License-Identifier: MPL-2.0 | ||||||
|  | 
 | ||||||
|  | package dan200.computercraft.client.turtle; | ||||||
|  | 
 | ||||||
|  | import com.mojang.serialization.Codec; | ||||||
|  | import com.mojang.serialization.codecs.RecordCodecBuilder; | ||||||
|  | import dan200.computercraft.api.ComputerCraftAPI; | ||||||
|  | import dan200.computercraft.api.client.StandaloneModel; | ||||||
|  | import dan200.computercraft.shared.ModRegistry; | ||||||
|  | import dan200.computercraft.shared.util.Holiday; | ||||||
|  | import net.minecraft.client.resources.model.ModelBaker; | ||||||
|  | import net.minecraft.client.resources.model.ResolvableModel; | ||||||
|  | import net.minecraft.resources.ResourceLocation; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 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(StandaloneModel model, boolean showElfOverlay) { | ||||||
|  |     /** | ||||||
|  |      * The folder where upgrades are loaded from. | ||||||
|  |      */ | ||||||
|  |     public static final String SOURCE = ComputerCraftAPI.MOD_ID + "/turtle_overlay"; | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * The codec used to read/write turtle overlay definitions from resource packs. | ||||||
|  |      */ | ||||||
|  |     public static final Codec<TurtleOverlay.Unbaked> 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()); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -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<TurtleOverlay> MISSING_KEY = ClientPlatformHelper.get().createModelKey( | ||||||
|  |         MissingBlockModel.LOCATION, () -> "Missing turtle overlay" | ||||||
|  |     ); | ||||||
|  | 
 | ||||||
|  |     private static final Map<ResourceLocation, ModelKey<TurtleOverlay>> modelKeys = new ConcurrentHashMap<>(); | ||||||
|  | 
 | ||||||
|  |     private static ModelKey<TurtleOverlay> 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<Map<ResourceLocation, TurtleOverlay.Unbaked>> 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<ResourceLocation, TurtleOverlay.Unbaked> 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; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -7,6 +7,7 @@ package dan200.computercraft.client.turtle; | |||||||
| import dan200.computercraft.api.client.turtle.TurtleUpgradeModel; | import dan200.computercraft.api.client.turtle.TurtleUpgradeModel; | ||||||
| import dan200.computercraft.api.turtle.ITurtleUpgrade; | import dan200.computercraft.api.turtle.ITurtleUpgrade; | ||||||
| import dan200.computercraft.api.upgrades.UpgradeType; | import dan200.computercraft.api.upgrades.UpgradeType; | ||||||
|  | import dan200.computercraft.client.ClientRegistry; | ||||||
| import dan200.computercraft.client.platform.ClientPlatformHelper; | import dan200.computercraft.client.platform.ClientPlatformHelper; | ||||||
| import dan200.computercraft.client.platform.ModelKey; | import dan200.computercraft.client.platform.ModelKey; | ||||||
| import dan200.computercraft.shared.util.RegistryHelper; | import dan200.computercraft.shared.util.RegistryHelper; | ||||||
| @@ -15,7 +16,6 @@ import net.minecraft.client.resources.model.MissingBlockModel; | |||||||
| 
 | 
 | ||||||
| import java.util.Map; | import java.util.Map; | ||||||
| import java.util.concurrent.ConcurrentHashMap; | import java.util.concurrent.ConcurrentHashMap; | ||||||
| import java.util.function.BiConsumer; |  | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * A registry of {@link TurtleUpgradeModel}s. |  * A registry of {@link TurtleUpgradeModel}s. | ||||||
| @@ -79,8 +79,10 @@ public final class TurtleUpgradeModels { | |||||||
|         return missing; |         return missing; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public static void bake(BiConsumer<ModelKey<? extends TurtleUpgradeModel<?>>, TurtleUpgradeModel.Unbaked<?>> baker) { |     @SuppressWarnings("unchecked") | ||||||
|         unbaked.forEach(baker); |     public static void bake(ClientRegistry.RegisterExtraModels register) { | ||||||
|         baker.accept(missingModelKey, TurtleUpgradeModel.sided(MissingBlockModel.LOCATION, MissingBlockModel.LOCATION)); |         unbaked.forEach((id, model) -> | ||||||
|  |             register.register((ModelKey<TurtleUpgradeModel<?>>) id, model, TurtleUpgradeModel.Unbaked::bake)); | ||||||
|  |         register.register(missingModelKey, TurtleUpgradeModel.sided(MissingBlockModel.LOCATION, MissingBlockModel.LOCATION), TurtleUpgradeModel.Unbaked::bake); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -10,10 +10,9 @@ import dan200.computercraft.api.turtle.ITurtleUpgrade; | |||||||
| import dan200.computercraft.client.gui.GuiSprites; | import dan200.computercraft.client.gui.GuiSprites; | ||||||
| import dan200.computercraft.client.model.LecternPocketModel; | import dan200.computercraft.client.model.LecternPocketModel; | ||||||
| import dan200.computercraft.client.model.LecternPrintoutModel; | import dan200.computercraft.client.model.LecternPrintoutModel; | ||||||
|  | import dan200.computercraft.client.turtle.TurtleOverlay; | ||||||
| import dan200.computercraft.data.client.BlockModelProvider; | import dan200.computercraft.data.client.BlockModelProvider; | ||||||
| import dan200.computercraft.data.client.ExtraModelsProvider; |  | ||||||
| import dan200.computercraft.data.client.ItemModelProvider; | import dan200.computercraft.data.client.ItemModelProvider; | ||||||
| import dan200.computercraft.shared.turtle.TurtleOverlay; |  | ||||||
| import dan200.computercraft.shared.turtle.inventory.UpgradeSlot; | import dan200.computercraft.shared.turtle.inventory.UpgradeSlot; | ||||||
| import net.minecraft.Util; | import net.minecraft.Util; | ||||||
| import net.minecraft.client.data.models.BlockModelGenerators; | import net.minecraft.client.data.models.BlockModelGenerators; | ||||||
| @@ -55,7 +54,6 @@ 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); | ||||||
| 
 | 
 | ||||||
| @@ -90,12 +88,7 @@ public final class DataProviders { | |||||||
|             )); |             )); | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         generator.add(pack -> new ExtraModelsProvider(pack, fullRegistries) { |         generator.addFromCodec("Turtle overlays", PackOutput.Target.RESOURCE_PACK, TurtleOverlay.SOURCE, TurtleOverlay.CODEC, TurtleOverlays::register); | ||||||
|             @Override |  | ||||||
|             public Stream<ResourceLocation> getModels(HolderLookup.Provider registries) { |  | ||||||
|                 return registries.lookupOrThrow(TurtleOverlay.REGISTRY).listElements().map(x -> x.value().model()); |  | ||||||
|             } |  | ||||||
|         }); |  | ||||||
| 
 | 
 | ||||||
|         generator.addModels(BlockModelProvider::addBlockModels, ItemModelProvider::addItemModels); |         generator.addModels(BlockModelProvider::addBlockModels, ItemModelProvider::addItemModels); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -24,7 +24,6 @@ import dan200.computercraft.shared.recipe.ImpostorShapedRecipe; | |||||||
| 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; | ||||||
| @@ -178,13 +177,11 @@ final class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider { | |||||||
|         ); |         ); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private void turtleOverlay(ResourceKey<TurtleOverlay> overlay, Consumer<ShapelessSpecBuilder> build) { |     private void turtleOverlay(ResourceLocation overlay, Consumer<ShapelessSpecBuilder> build) { | ||||||
|         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 = 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()) |                 .group(name.withSuffix("_overlay").toString()) | ||||||
|                 .unlockedBy("has_turtle", has(turtleItem)); |                 .unlockedBy("has_turtle", has(turtleItem)); | ||||||
|             build.accept(builder); |             build.accept(builder); | ||||||
| @@ -193,7 +190,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(output, name.withSuffix("_overlays/" + overlay.location().getPath())); |                 .save(output, name.withSuffix("_overlays/" + overlay.getPath())); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|   | |||||||
| @@ -5,32 +5,32 @@ | |||||||
| package dan200.computercraft.data; | package dan200.computercraft.data; | ||||||
| 
 | 
 | ||||||
| import dan200.computercraft.api.ComputerCraftAPI; | import dan200.computercraft.api.ComputerCraftAPI; | ||||||
| import dan200.computercraft.shared.turtle.TurtleOverlay; | import dan200.computercraft.client.turtle.TurtleOverlay; | ||||||
| import net.minecraft.data.worldgen.BootstrapContext; |  | ||||||
| import net.minecraft.resources.ResourceKey; |  | ||||||
| import net.minecraft.resources.ResourceLocation; | import net.minecraft.resources.ResourceLocation; | ||||||
| 
 | 
 | ||||||
|  | import java.util.function.BiConsumer; | ||||||
|  | 
 | ||||||
| /** | /** | ||||||
|  * Built-in turtle overlays. |  * Built-in turtle overlays. | ||||||
|  */ |  */ | ||||||
| final class TurtleOverlays { | final class TurtleOverlays { | ||||||
|     public static final ResourceKey<TurtleOverlay> RAINBOW_FLAG = create("rainbow_flag"); |     public static final ResourceLocation RAINBOW_FLAG = create("rainbow_flag"); | ||||||
|     public static final ResourceKey<TurtleOverlay> TRANS_FLAG = create("trans_flag"); |     public static final ResourceLocation TRANS_FLAG = create("trans_flag"); | ||||||
| 
 | 
 | ||||||
|     private static ResourceKey<TurtleOverlay> create(String name) { |     private static ResourceLocation create(String name) { | ||||||
|         return ResourceKey.create(TurtleOverlay.REGISTRY, ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, name)); |         return ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, name); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private TurtleOverlays() { |     private TurtleOverlays() { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public static void register(BootstrapContext<TurtleOverlay> registry) { |     public static void register(BiConsumer<ResourceLocation, TurtleOverlay.Unbaked> registry) { | ||||||
|         registry.register(RAINBOW_FLAG, new TurtleOverlay( |         registry.accept(RAINBOW_FLAG, new TurtleOverlay.Unbaked( | ||||||
|             ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_rainbow_overlay"), |             ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_rainbow_overlay"), | ||||||
|             true |             true | ||||||
|         )); |         )); | ||||||
| 
 | 
 | ||||||
|         registry.register(TRANS_FLAG, new TurtleOverlay( |         registry.accept(TRANS_FLAG, new TurtleOverlay.Unbaked( | ||||||
|             ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_trans_overlay"), |             ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_trans_overlay"), | ||||||
|             true |             true | ||||||
|         )); |         )); | ||||||
|   | |||||||
| @@ -11,6 +11,7 @@ import dan200.computercraft.client.item.model.TurtleOverlayModel; | |||||||
| import dan200.computercraft.client.item.model.TurtleUpgradeModel; | import dan200.computercraft.client.item.model.TurtleUpgradeModel; | ||||||
| import dan200.computercraft.client.item.properties.TurtleShowElfOverlay; | import dan200.computercraft.client.item.properties.TurtleShowElfOverlay; | ||||||
| import dan200.computercraft.client.render.TurtleBlockEntityRenderer; | import dan200.computercraft.client.render.TurtleBlockEntityRenderer; | ||||||
|  | import dan200.computercraft.client.turtle.TurtleOverlay; | ||||||
| import dan200.computercraft.shared.ModRegistry; | import dan200.computercraft.shared.ModRegistry; | ||||||
| import dan200.computercraft.shared.computer.blocks.ComputerBlock; | import dan200.computercraft.shared.computer.blocks.ComputerBlock; | ||||||
| import dan200.computercraft.shared.peripheral.diskdrive.DiskDriveBlock; | 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.MonitorBlock; | ||||||
| import dan200.computercraft.shared.peripheral.monitor.MonitorEdgeState; | import dan200.computercraft.shared.peripheral.monitor.MonitorEdgeState; | ||||||
| import dan200.computercraft.shared.peripheral.printer.PrinterBlock; | import dan200.computercraft.shared.peripheral.printer.PrinterBlock; | ||||||
| import dan200.computercraft.shared.turtle.TurtleOverlay; |  | ||||||
| import dan200.computercraft.shared.turtle.blocks.TurtleBlock; | import dan200.computercraft.shared.turtle.blocks.TurtleBlock; | ||||||
| import dan200.computercraft.shared.util.DirectionUtil; | import dan200.computercraft.shared.util.DirectionUtil; | ||||||
| import net.minecraft.client.data.models.BlockModelGenerators; | import net.minecraft.client.data.models.BlockModelGenerators; | ||||||
|   | |||||||
| @@ -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<HolderLookup.Provider> registries; |  | ||||||
| 
 |  | ||||||
|     public 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"; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1 +0,0 @@ | |||||||
| ["computercraft:block/turtle_rainbow_overlay", "computercraft:block/turtle_trans_overlay"] |  | ||||||
| @@ -81,7 +81,6 @@ 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.apis.TurtleAPI; | import dan200.computercraft.shared.turtle.apis.TurtleAPI; | ||||||
| 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; | ||||||
| @@ -389,8 +388,8 @@ public final class ModRegistry { | |||||||
|         /** |         /** | ||||||
|          * The overlay on a turtle. |          * The overlay on a turtle. | ||||||
|          */ |          */ | ||||||
|         public static final RegistryEntry<DataComponentType<Holder<TurtleOverlay>>> OVERLAY = register("overlay", b -> b |         public static final RegistryEntry<DataComponentType<ResourceLocation>> OVERLAY = register("overlay", b -> b | ||||||
|             .persistent(TurtleOverlay.CODEC).networkSynchronized(TurtleOverlay.STREAM_CODEC) |             .persistent(ResourceLocation.CODEC).networkSynchronized(ResourceLocation.STREAM_CODEC).cacheEncoding() | ||||||
|         ); |         ); | ||||||
| 
 | 
 | ||||||
|         /** |         /** | ||||||
|   | |||||||
| @@ -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<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()); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -21,7 +21,6 @@ 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.core.TurtleBrain; | import dan200.computercraft.shared.turtle.core.TurtleBrain; | ||||||
| import dan200.computercraft.shared.turtle.inventory.TurtleMenu; | import dan200.computercraft.shared.turtle.inventory.TurtleMenu; | ||||||
| import net.minecraft.core.BlockPos; | import net.minecraft.core.BlockPos; | ||||||
| @@ -33,6 +32,7 @@ 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.network.protocol.game.ClientboundBlockEntityDataPacket; | import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket; | ||||||
|  | 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; | ||||||
| @@ -233,9 +233,8 @@ public class TurtleBlockEntity extends AbstractComputerBlockEntity implements Ba | |||||||
|         return brain.getColour(); |         return brain.getColour(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public @Nullable TurtleOverlay getOverlay() { |     public @Nullable ResourceLocation getOverlay() { | ||||||
|         var overlay = brain.getOverlay(); |         return brain.getOverlay(); | ||||||
|         return overlay == null ? null : overlay.value(); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public ITurtleAccess getAccess() { |     public ITurtleAccess getAccess() { | ||||||
|   | |||||||
| @@ -20,7 +20,6 @@ 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,6 +31,7 @@ import net.minecraft.core.HolderLookup; | |||||||
| import net.minecraft.core.component.DataComponentPatch; | 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.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 Holder<TurtleOverlay> overlay = null; |     private @Nullable ResourceLocation 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.getIntOr(NBT_COLOUR, -1); |         colourHex = nbt.getIntOr(NBT_COLOUR, -1); | ||||||
|         fuelLevel = nbt.getIntOr(NBT_FUEL, 0); |         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 |         // 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); | ||||||
|         NBTUtil.encodeTo(TurtleOverlay.CODEC, registries, nbt, NBT_OVERLAY, overlay); |         NBTUtil.encodeTo(ResourceLocation.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 Holder<TurtleOverlay> getOverlay() { |     public @Nullable ResourceLocation getOverlay() { | ||||||
|         return overlay; |         return overlay; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public void setOverlay(@Nullable Holder<TurtleOverlay> overlay) { |     public void setOverlay(@Nullable ResourceLocation 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); | ||||||
|   | |||||||
| @@ -11,12 +11,12 @@ import dan200.computercraft.api.upgrades.UpgradeData; | |||||||
| import dan200.computercraft.impl.TurtleUpgrades; | import dan200.computercraft.impl.TurtleUpgrades; | ||||||
| import dan200.computercraft.impl.UpgradeManager; | import dan200.computercraft.impl.UpgradeManager; | ||||||
| import dan200.computercraft.shared.ModRegistry; | import dan200.computercraft.shared.ModRegistry; | ||||||
| import dan200.computercraft.shared.turtle.TurtleOverlay; |  | ||||||
| import dan200.computercraft.shared.turtle.blocks.TurtleBlock; | import dan200.computercraft.shared.turtle.blocks.TurtleBlock; | ||||||
| import net.minecraft.core.HolderLookup; | import net.minecraft.core.HolderLookup; | ||||||
| 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.InteractionResult; | import net.minecraft.world.InteractionResult; | ||||||
| import net.minecraft.world.item.BlockItem; | import net.minecraft.world.item.BlockItem; | ||||||
| import net.minecraft.world.item.ItemStack; | import net.minecraft.world.item.ItemStack; | ||||||
| @@ -51,9 +51,8 @@ public class TurtleItem extends BlockItem { | |||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public static @Nullable TurtleOverlay getOverlay(ItemStack stack) { |     public static @Nullable ResourceLocation getOverlay(ItemStack stack) { | ||||||
|         var overlay = stack.get(ModRegistry.DataComponents.OVERLAY.get()); |         return stack.get(ModRegistry.DataComponents.OVERLAY.get()); | ||||||
|         return overlay == null ? null : overlay.value(); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public static final CauldronInteraction CAULDRON_INTERACTION = (blockState, level, pos, player, hand, stack) -> { |     public static final CauldronInteraction CAULDRON_INTERACTION = (blockState, level, pos, player, hand, stack) -> { | ||||||
|   | |||||||
| @@ -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 <T> The type of item in the list. |  | ||||||
|  */ |  | ||||||
| public final class ConsList<T> extends AbstractList<T> { |  | ||||||
|     private final T head; |  | ||||||
|     private final List<T> tail; |  | ||||||
| 
 |  | ||||||
|     public ConsList(T head, List<T> 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<T> iterator() { |  | ||||||
|         return new Iterator<>() { |  | ||||||
|             private @Nullable Iterator<T> 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; |  | ||||||
|             } |  | ||||||
|         }; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -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. | ||||||
|  |      * <p> | ||||||
|  |      * 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 <T>             The type of the entry to load | ||||||
|  |      * @return The loaded resources. | ||||||
|  |      */ | ||||||
|  |     public static <T> CompletableFuture<Map<ResourceLocation, T>> load( | ||||||
|  |         ResourceManager resourceManager, Executor executor, String kind, FileToIdConverter lister, DynamicOps<JsonElement> ops, Codec<T> codec | ||||||
|  |     ) { | ||||||
|  |         return CompletableFuture.supplyAsync(() -> lister.listMatchingResources(resourceManager), executor).thenCompose(resources -> { | ||||||
|  |             List<CompletableFuture<@Nullable Pair<ResourceLocation, T>>> 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))); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -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)); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -14,7 +14,6 @@ import dan200.computercraft.api.turtle.TurtleSide | |||||||
| import dan200.computercraft.api.upgrades.UpgradeData | import dan200.computercraft.api.upgrades.UpgradeData | ||||||
| import dan200.computercraft.core.apis.PeripheralAPI | import dan200.computercraft.core.apis.PeripheralAPI | ||||||
| import dan200.computercraft.gametest.api.* | import dan200.computercraft.gametest.api.* | ||||||
| import dan200.computercraft.gametest.api.GameTest |  | ||||||
| import dan200.computercraft.gametest.core.TestHooks | import dan200.computercraft.gametest.core.TestHooks | ||||||
| import dan200.computercraft.mixin.gametest.GameTestHelperAccessor | import dan200.computercraft.mixin.gametest.GameTestHelperAccessor | ||||||
| import dan200.computercraft.mixin.gametest.GameTestInfoAccessor | 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.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.blocks.TurtleBlockEntity | import dan200.computercraft.shared.turtle.blocks.TurtleBlockEntity | ||||||
| import dan200.computercraft.shared.turtle.core.TurtleCraftCommand | import dan200.computercraft.shared.turtle.core.TurtleCraftCommand | ||||||
| @@ -772,8 +770,7 @@ class Turtle_Test { | |||||||
|     @GameTest |     @GameTest | ||||||
|     fun Data_fixers(helper: GameTestHelper) = helper.sequence { |     fun Data_fixers(helper: GameTestHelper) = helper.sequence { | ||||||
|         thenExecute { |         thenExecute { | ||||||
|             val overlay = helper.level.registryAccess().lookupOrThrow(TurtleOverlay.REGISTRY) |             val overlay = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "trans_flag") | ||||||
|                 .getValue(ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "trans_flag"))!! |  | ||||||
|             val upgrade = helper.level.registryAccess().lookupOrThrow(ITurtleUpgrade.REGISTRY) |             val upgrade = helper.level.registryAccess().lookupOrThrow(ITurtleUpgrade.REGISTRY) | ||||||
|                 .getValue(ResourceLocation.withDefaultNamespace("diamond_pickaxe"))!! |                 .getValue(ResourceLocation.withDefaultNamespace("diamond_pickaxe"))!! | ||||||
| 
 | 
 | ||||||
|   | |||||||
| @@ -9,11 +9,8 @@ import com.mojang.brigadier.builder.LiteralArgumentBuilder; | |||||||
| import com.mojang.brigadier.builder.RequiredArgumentBuilder; | import com.mojang.brigadier.builder.RequiredArgumentBuilder; | ||||||
| import dan200.computercraft.api.ComputerCraftAPI; | import dan200.computercraft.api.ComputerCraftAPI; | ||||||
| import dan200.computercraft.api.client.FabricComputerCraftAPIClient; | 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.FabricModelKey; | ||||||
|  | import dan200.computercraft.client.platform.ModelKey; | ||||||
| import dan200.computercraft.core.util.Nullability; | import dan200.computercraft.core.util.Nullability; | ||||||
| import dan200.computercraft.impl.Services; | import dan200.computercraft.impl.Services; | ||||||
| import dan200.computercraft.shared.ComputerCraft; | 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.command.v2.FabricClientCommandSource; | ||||||
| import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; | 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.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.model.loading.v1.UnbakedExtraModel; | ||||||
| import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; | import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; | ||||||
| import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents; | 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.conditional.ConditionalItemModelProperties; | ||||||
| import net.minecraft.client.renderer.item.properties.select.SelectItemModelProperties; | import net.minecraft.client.renderer.item.properties.select.SelectItemModelProperties; | ||||||
| import net.minecraft.client.resources.model.ModelBaker; | import net.minecraft.client.resources.model.ModelBaker; | ||||||
|  | import net.minecraft.client.resources.model.ResolvableModel; | ||||||
| import net.minecraft.world.phys.BlockHitResult; | import net.minecraft.world.phys.BlockHitResult; | ||||||
| 
 | 
 | ||||||
| import java.nio.file.Files; | import java.nio.file.Files; | ||||||
| import java.nio.file.Path; | 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; | import static dan200.computercraft.core.util.Nullability.assertNonNull; | ||||||
| 
 | 
 | ||||||
| @@ -67,12 +65,13 @@ public class ComputerCraftClient { | |||||||
|         ClientRegistry.registerConditionalItemProperties(ConditionalItemModelProperties.ID_MAPPER::put); |         ClientRegistry.registerConditionalItemProperties(ConditionalItemModelProperties.ID_MAPPER::put); | ||||||
| 
 | 
 | ||||||
|         PreparableModelLoadingPlugin.register( |         PreparableModelLoadingPlugin.register( | ||||||
|             (resources, executor) -> CompletableFuture.supplyAsync(() -> ExtraModels.loadAll(resources), executor), |             ClientRegistry::gatherExtraModels, | ||||||
|             (state, context) -> ClientRegistry.registerExtraModels( |             (state, context) -> ClientRegistry.registerExtraModels(new ClientRegistry.RegisterExtraModels() { | ||||||
|                 (key, model) -> context.addModel(FabricModelKey.key(key), new SimpleUnbakedExtraModel<>(model, StandaloneModel::of)), |                 @Override | ||||||
|                 (key, model) -> context.addModel(FabricModelKey.erased(key), new TurtleModelWrapper<>(model)), |                 public <U, T> void register(ModelKey<T> key, U unbaked, BiConsumer<U, ResolvableModel.Resolver> resolve, BiFunction<U, ModelBaker, T> bake) { | ||||||
|                 state |                     context.addModel(FabricModelKey.key(key), new ModelWrapper<>(unbaked, resolve, bake)); | ||||||
|             ) |                 } | ||||||
|  |             }, state) | ||||||
|         ); |         ); | ||||||
| 
 | 
 | ||||||
|         BlockRenderLayerMap.INSTANCE.putBlock(ModRegistry.Blocks.COMPUTER_NORMAL.get(), RenderType.cutout()); |         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")); |         ((FabricConfigFile) ConfigSpec.clientSpec).load(FabricLoader.getInstance().getConfigDir().resolve(ComputerCraftAPI.MOD_ID + "-client.toml")); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private record TurtleModelWrapper<T extends ITurtleUpgrade>( |     private record ModelWrapper<U, T>( | ||||||
|         TurtleUpgradeModel.Unbaked<T> model |         U model, BiConsumer<U, Resolver> resolve, BiFunction<U, ModelBaker, T> bake | ||||||
|     ) implements UnbakedExtraModel<TurtleUpgradeModel<T>> { |     ) implements UnbakedExtraModel<T> { | ||||||
|         @Override |         @Override | ||||||
|         public TurtleUpgradeModel<T> bake(ModelBaker baker) { |         public T bake(ModelBaker baker) { | ||||||
|             return model().bake(baker); |             return bake().apply(model(), baker); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         @Override |         @Override | ||||||
|         public void resolveDependencies(Resolver resolver) { |         public void resolveDependencies(Resolver resolver) { | ||||||
|             model().resolveDependencies(resolver); |             resolve().accept(model(), resolver); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -24,11 +24,6 @@ public final class FabricModelKey<T> implements ModelKey<T> { | |||||||
|         return ((FabricModelKey<T>) key).key; |         return ((FabricModelKey<T>) key).key; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @SuppressWarnings("unchecked") |  | ||||||
|     public static <T> ExtraModelKey<T> erased(ModelKey<?> key) { |  | ||||||
|         return ((FabricModelKey<T>) key).key; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Override |     @Override | ||||||
|     public @Nullable T get(ModelManager manager) { |     public @Nullable T get(ModelManager manager) { | ||||||
|         return manager.getModel(key); |         return manager.getModel(key); | ||||||
|   | |||||||
| @@ -23,7 +23,6 @@ import dan200.computercraft.shared.peripheral.generic.methods.InventoryMethods; | |||||||
| import dan200.computercraft.shared.peripheral.modem.wired.CableBlock; | import dan200.computercraft.shared.peripheral.modem.wired.CableBlock; | ||||||
| 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; | ||||||
| @@ -85,7 +84,6 @@ 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(); | ||||||
|   | |||||||
| @@ -6,12 +6,9 @@ package dan200.computercraft.client; | |||||||
| 
 | 
 | ||||||
| import com.google.common.reflect.TypeToken; | import com.google.common.reflect.TypeToken; | ||||||
| import dan200.computercraft.api.ComputerCraftAPI; | import dan200.computercraft.api.ComputerCraftAPI; | ||||||
| import dan200.computercraft.api.client.StandaloneModel; |  | ||||||
| import dan200.computercraft.api.client.turtle.RegisterTurtleModelEvent; | 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.ForgeModelKey; | ||||||
|  | import dan200.computercraft.client.platform.ModelKey; | ||||||
| import dan200.computercraft.client.render.ExtendedItemFrameRenderState; | import dan200.computercraft.client.render.ExtendedItemFrameRenderState; | ||||||
| import dan200.computercraft.client.turtle.TurtleUpgradeModels; | import dan200.computercraft.client.turtle.TurtleUpgradeModels; | ||||||
| import net.minecraft.client.Minecraft; | 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.model.standalone.UnbakedStandaloneModel; | ||||||
| import net.neoforged.neoforge.client.renderstate.RegisterRenderStateModifiersEvent; | 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. |  * Registers textures and models for items. | ||||||
| @@ -44,12 +46,18 @@ public final class ForgeClientRegistry { | |||||||
|     public static void registerModels(ModelEvent.RegisterStandalone event) { |     public static void registerModels(ModelEvent.RegisterStandalone event) { | ||||||
|         TurtleUpgradeModels.fetch(() -> ModLoader.postEvent(new RegisterTurtleModelEvent(TurtleUpgradeModels::register))); |         TurtleUpgradeModels.fetch(() -> ModLoader.postEvent(new RegisterTurtleModelEvent(TurtleUpgradeModels::register))); | ||||||
| 
 | 
 | ||||||
|         var extraModels = ExtraModels.loadAll(Minecraft.getInstance().getResourceManager()); |         // Load resources | ||||||
|         ClientRegistry.registerExtraModels( |         Queue<Runnable> tasks = new ArrayDeque<>(); | ||||||
|             (key, model) -> event.register(ForgeModelKey.key(key), StandaloneModel::of), |         var state = ClientRegistry.gatherExtraModels(Minecraft.getInstance().getResourceManager(), tasks::add); | ||||||
|             (key, model) -> event.register(ForgeModelKey.erased(key), new TurtleModelWrapper<>(model)), |         Runnable task; | ||||||
|             extraModels |         while ((task = tasks.poll()) != null) task.run(); | ||||||
|         ); | 
 | ||||||
|  |         ClientRegistry.registerExtraModels(new ClientRegistry.RegisterExtraModels() { | ||||||
|  |             @Override | ||||||
|  |             public <U, T> void register(ModelKey<T> key, U unbaked, BiConsumer<U, ResolvableModel.Resolver> resolve, BiFunction<U, ModelBaker, T> bake) { | ||||||
|  |                 event.register(ForgeModelKey.key(key), new ModelWrapper<>(unbaked, resolve, bake)); | ||||||
|  |             } | ||||||
|  |         }, state.resultNow()); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @SubscribeEvent |     @SubscribeEvent | ||||||
| @@ -102,17 +110,17 @@ public final class ForgeClientRegistry { | |||||||
|         ClientRegistry.register(); |         ClientRegistry.register(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private record TurtleModelWrapper<T extends ITurtleUpgrade>( |     private record ModelWrapper<U, T>( | ||||||
|         TurtleUpgradeModel.Unbaked<T> model |         U model, BiConsumer<U, ResolvableModel.Resolver> resolve, BiFunction<U, ModelBaker, T> bake | ||||||
|     ) implements UnbakedStandaloneModel<TurtleUpgradeModel<T>> { |     ) implements UnbakedStandaloneModel<T> { | ||||||
|         @Override |         @Override | ||||||
|         public TurtleUpgradeModel<T> bake(ModelBaker baker) { |         public T bake(ModelBaker baker) { | ||||||
|             return model().bake(baker); |             return bake().apply(model(), baker); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         @Override |         @Override | ||||||
|         public void resolveDependencies(ResolvableModel.Resolver resolver) { |         public void resolveDependencies(ResolvableModel.Resolver resolver) { | ||||||
|             model().resolveDependencies(resolver); |             resolve().accept(model(), resolver); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -24,11 +24,6 @@ public final class ForgeModelKey<T> implements ModelKey<T> { | |||||||
|         return ((ForgeModelKey<T>) key).key; |         return ((ForgeModelKey<T>) key).key; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @SuppressWarnings("unchecked") |  | ||||||
|     public static <T> StandaloneModelKey<T> erased(ModelKey<?> key) { |  | ||||||
|         return ((ForgeModelKey<T>) key).key; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Override |     @Override | ||||||
|     public @Nullable T get(ModelManager manager) { |     public @Nullable T get(ModelManager manager) { | ||||||
|         return manager.getStandaloneModel(key); |         return manager.getStandaloneModel(key); | ||||||
|   | |||||||
| @@ -30,7 +30,6 @@ import dan200.computercraft.shared.peripheral.generic.methods.FluidMethods; | |||||||
| import dan200.computercraft.shared.peripheral.generic.methods.InventoryMethods; | import dan200.computercraft.shared.peripheral.generic.methods.InventoryMethods; | ||||||
| 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.core.registries.BuiltInRegistries; | import net.minecraft.core.registries.BuiltInRegistries; | ||||||
| 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; | ||||||
| @@ -104,7 +103,6 @@ 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 | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Jonathan Coates
					Jonathan Coates