mirror of
				https://github.com/SquidDev-CC/CC-Tweaked
				synced 2025-10-26 19:37:39 +00:00 
			
		
		
		
	Make turtle upgrade models data driven
It's ItemModel, but for upgrades!
This commit is contained in:
		| @@ -7,15 +7,11 @@ package dan200.computercraft.api.client; | ||||
| import com.google.common.base.Suppliers; | ||||
| import com.mojang.blaze3d.vertex.PoseStack; | ||||
| import dan200.computercraft.api.client.turtle.TurtleUpgradeModel; | ||||
| import dan200.computercraft.api.turtle.ITurtleAccess; | ||||
| import dan200.computercraft.api.turtle.ITurtleUpgrade; | ||||
| import dan200.computercraft.api.turtle.TurtleSide; | ||||
| import net.minecraft.client.multiplayer.ClientLevel; | ||||
| import net.minecraft.client.renderer.MultiBufferSource; | ||||
| import net.minecraft.client.renderer.RenderType; | ||||
| import net.minecraft.client.renderer.Sheets; | ||||
| import net.minecraft.client.renderer.block.model.BakedQuad; | ||||
| import net.minecraft.client.renderer.block.model.ItemTransform; | ||||
| import net.minecraft.client.renderer.item.BlockModelWrapper; | ||||
| import net.minecraft.client.renderer.item.ItemModel; | ||||
| import net.minecraft.client.renderer.item.ItemModelResolver; | ||||
| @@ -24,7 +20,6 @@ import net.minecraft.client.renderer.texture.TextureAtlasSprite; | ||||
| import net.minecraft.client.resources.model.BlockModelRotation; | ||||
| import net.minecraft.client.resources.model.ModelBaker; | ||||
| import net.minecraft.client.resources.model.ResolvedModel; | ||||
| import net.minecraft.core.component.DataComponentPatch; | ||||
| import net.minecraft.resources.ResourceLocation; | ||||
| import net.minecraft.util.ARGB; | ||||
| import net.minecraft.world.entity.LivingEntity; | ||||
| @@ -84,6 +79,27 @@ public final class StandaloneModel { | ||||
|      * @return The baked {@link StandaloneModel}. | ||||
|      */ | ||||
|     public static StandaloneModel of(ResolvedModel model, ModelBaker baker) { | ||||
|         return baker.compute(new CacheKey(model)); | ||||
|     } | ||||
| 
 | ||||
|     private record CacheKey(ResolvedModel model) implements ModelBaker.SharedOperationKey<StandaloneModel> { | ||||
|         @Override | ||||
|         public StandaloneModel compute(ModelBaker baker) { | ||||
|             return ofUncached(model(), baker); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public boolean equals(Object other) { | ||||
|             return other instanceof CacheKey(var otherModel) && model() == otherModel; | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public int hashCode() { | ||||
|             return System.identityHashCode(model()); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private static StandaloneModel ofUncached(ResolvedModel model, ModelBaker baker) { | ||||
|         var slots = model.getTopTextureSlots(); | ||||
|         return new StandaloneModel( | ||||
|             model.bakeTopGeometry(slots, baker, BlockModelRotation.X0_Y0).getAll(), | ||||
| @@ -98,7 +114,6 @@ public final class StandaloneModel { | ||||
|      * | ||||
|      * @param layer The layer to set up. | ||||
|      * @see ItemModel#update(ItemStackRenderState, ItemStack, ItemModelResolver, ItemDisplayContext, ClientLevel, LivingEntity, int) | ||||
|      * @see TurtleUpgradeModel#renderForItem(ITurtleUpgrade, TurtleSide, DataComponentPatch, ItemStackRenderState, ItemModelResolver, ItemTransform, int) | ||||
|      */ | ||||
|     public void setupItemLayer(ItemStackRenderState.LayerRenderState layer) { | ||||
|         layer.setExtents(extents); | ||||
| @@ -115,7 +130,6 @@ public final class StandaloneModel { | ||||
|      * @param buffers   The buffer source to use for rendering. | ||||
|      * @param light     The current light texture coordinate. | ||||
|      * @param overlay   The current overlay texture coordinate. | ||||
|      * @see TurtleUpgradeModel#renderForLevel(ITurtleUpgrade, ITurtleAccess, TurtleSide, DataComponentPatch, PoseStack, MultiBufferSource, int, int) | ||||
|      */ | ||||
|     public void render(PoseStack transform, MultiBufferSource buffers, int light, int overlay) { | ||||
|         render(transform, buffers, light, overlay, null); | ||||
| @@ -129,7 +143,6 @@ public final class StandaloneModel { | ||||
|      * @param light     The current light texture coordinate. | ||||
|      * @param overlay   The current overlay texture coordinate. | ||||
|      * @param tints     The tints for this model. | ||||
|      * @see TurtleUpgradeModel#renderForLevel(ITurtleUpgrade, ITurtleAccess, TurtleSide, DataComponentPatch, PoseStack, MultiBufferSource, int, int) | ||||
|      */ | ||||
|     public void render(PoseStack transform, MultiBufferSource buffers, int light, int overlay, int @Nullable [] tints) { | ||||
|         var pose = transform.last(); | ||||
|   | ||||
| @@ -0,0 +1,91 @@ | ||||
| // SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers | ||||
| // | ||||
| // SPDX-License-Identifier: MPL-2.0 | ||||
| 
 | ||||
| package dan200.computercraft.api.client.turtle; | ||||
| 
 | ||||
| import com.mojang.blaze3d.vertex.PoseStack; | ||||
| import com.mojang.serialization.MapCodec; | ||||
| import com.mojang.serialization.codecs.RecordCodecBuilder; | ||||
| import dan200.computercraft.api.ComputerCraftAPI; | ||||
| import dan200.computercraft.api.client.StandaloneModel; | ||||
| import dan200.computercraft.api.turtle.ITurtleAccess; | ||||
| import dan200.computercraft.api.turtle.ITurtleUpgrade; | ||||
| import dan200.computercraft.api.turtle.TurtleSide; | ||||
| import dan200.computercraft.api.upgrades.UpgradeData; | ||||
| import net.minecraft.client.renderer.MultiBufferSource; | ||||
| import net.minecraft.client.renderer.block.model.ItemTransform; | ||||
| import net.minecraft.client.renderer.item.BlockModelWrapper; | ||||
| import net.minecraft.client.renderer.item.ItemModelResolver; | ||||
| import net.minecraft.client.renderer.item.ItemStackRenderState; | ||||
| import net.minecraft.client.resources.model.ModelBaker; | ||||
| import net.minecraft.resources.ResourceLocation; | ||||
| 
 | ||||
| /** | ||||
|  * A {@link TurtleUpgradeModel} that renders a basic model. | ||||
|  * <p> | ||||
|  * This is the {@link TurtleUpgradeModel} equivalent of {@link BlockModelWrapper}. | ||||
|  */ | ||||
| public final class BasicUpgradeModel implements TurtleUpgradeModel { | ||||
|     public static final ResourceLocation ID = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "sided"); | ||||
|     public static final MapCodec<? extends TurtleUpgradeModel.Unbaked> CODEC = RecordCodecBuilder.<Unbaked>mapCodec(instance -> instance.group( | ||||
|         ResourceLocation.CODEC.fieldOf("left").forGetter(Unbaked::left), | ||||
|         ResourceLocation.CODEC.fieldOf("right").forGetter(Unbaked::right) | ||||
|     ).apply(instance, Unbaked::new)); | ||||
| 
 | ||||
|     private final StandaloneModel left; | ||||
|     private final StandaloneModel right; | ||||
| 
 | ||||
|     private BasicUpgradeModel(StandaloneModel left, StandaloneModel right) { | ||||
|         this.left = left; | ||||
|         this.right = right; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Create an unbaked {@link BasicUpgradeModel}. | ||||
|      * | ||||
|      * @param left  The model when equipped on the left. | ||||
|      * @param right The model when equipped on the right. | ||||
|      * @return The unbaked turtle upgrade model. | ||||
|      */ | ||||
|     public static TurtleUpgradeModel.Unbaked unbaked(ResourceLocation left, ResourceLocation right) { | ||||
|         return new Unbaked(left, right); | ||||
|     } | ||||
| 
 | ||||
|     private StandaloneModel getModel(TurtleSide side) { | ||||
|         return switch (side) { | ||||
|             case LEFT -> left; | ||||
|             case RIGHT -> right; | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void renderForItem(UpgradeData<ITurtleUpgrade> upgrade, TurtleSide side, ItemStackRenderState renderer, ItemModelResolver resolver, ItemTransform transform, int seed) { | ||||
|         var layer = renderer.newLayer(); | ||||
|         layer.setTransform(transform); | ||||
|         getModel(side).setupItemLayer(layer); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void renderForLevel(UpgradeData<ITurtleUpgrade> upgrade, TurtleSide side, ITurtleAccess turtle, PoseStack transform, MultiBufferSource buffers, int light, int overlay) { | ||||
|         getModel(side).render(transform, buffers, light, overlay); | ||||
|     } | ||||
| 
 | ||||
|     private record Unbaked(ResourceLocation left, ResourceLocation right) implements TurtleUpgradeModel.Unbaked { | ||||
|         @Override | ||||
|         public MapCodec<? extends TurtleUpgradeModel.Unbaked> type() { | ||||
|             return CODEC; | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public TurtleUpgradeModel bake(ModelBaker baker) { | ||||
|             return new BasicUpgradeModel(StandaloneModel.of(left(), baker), StandaloneModel.of(right(), baker)); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public void resolveDependencies(Resolver resolver) { | ||||
|             resolver.markDependency(left()); | ||||
|             resolver.markDependency(right()); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -7,9 +7,12 @@ package dan200.computercraft.api.client.turtle; | ||||
| import com.mojang.blaze3d.vertex.PoseStack; | ||||
| import com.mojang.math.Axis; | ||||
| import com.mojang.math.Transformation; | ||||
| import com.mojang.serialization.MapCodec; | ||||
| import dan200.computercraft.api.ComputerCraftAPI; | ||||
| import dan200.computercraft.api.turtle.ITurtleAccess; | ||||
| import dan200.computercraft.api.turtle.ITurtleUpgrade; | ||||
| import dan200.computercraft.api.turtle.TurtleSide; | ||||
| import dan200.computercraft.api.upgrades.UpgradeData; | ||||
| import net.minecraft.client.Minecraft; | ||||
| import net.minecraft.client.renderer.MultiBufferSource; | ||||
| import net.minecraft.client.renderer.block.model.ItemTransform; | ||||
| @@ -18,15 +21,26 @@ import net.minecraft.client.renderer.item.ItemStackRenderState; | ||||
| import net.minecraft.client.renderer.special.SpecialModelRenderer; | ||||
| import net.minecraft.client.resources.model.ModelBaker; | ||||
| import net.minecraft.core.component.DataComponentPatch; | ||||
| import net.minecraft.resources.ResourceLocation; | ||||
| import net.minecraft.util.Mth; | ||||
| import net.minecraft.world.item.ItemDisplayContext; | ||||
| import net.minecraft.world.item.ItemStack; | ||||
| import org.joml.Matrix4f; | ||||
| import org.jspecify.annotations.Nullable; | ||||
| 
 | ||||
| final class ItemUpgradeModel<T extends ITurtleUpgrade> implements TurtleUpgradeModel<T> { | ||||
|     static final TurtleUpgradeModel.Unbaked<ITurtleUpgrade> UNBAKED = new Unbaked(); | ||||
|     static final TurtleUpgradeModel<ITurtleUpgrade> INSTANCE = new ItemUpgradeModel<>(); | ||||
| /** | ||||
|  * A sic {@link TurtleUpgradeModel} that renders the upgrade's {@linkplain ITurtleUpgrade#getUpgradeItem(DataComponentPatch) | ||||
|  * upgrade item}. | ||||
|  * <p> | ||||
|  * This uses appropriate transformations for "flat" items, namely those extending the {@literal minecraft:item/generated} | ||||
|  * model type. It will not appear correct for 3D models with additional depth, such as blocks. | ||||
|  */ | ||||
| public final class ItemUpgradeModel implements TurtleUpgradeModel { | ||||
|     private static final TurtleUpgradeModel.Unbaked UNBAKED = new Unbaked(); | ||||
|     private static final TurtleUpgradeModel INSTANCE = new ItemUpgradeModel(); | ||||
| 
 | ||||
|     public static final ResourceLocation ID = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "item"); | ||||
|     public static final MapCodec<TurtleUpgradeModel.Unbaked> CODEC = MapCodec.unit(UNBAKED); | ||||
| 
 | ||||
|     private static final TransformedRenderer LEFT = computeRenderer(TurtleSide.LEFT); | ||||
|     private static final TransformedRenderer RIGHT = computeRenderer(TurtleSide.RIGHT); | ||||
| @@ -34,10 +48,19 @@ final class ItemUpgradeModel<T extends ITurtleUpgrade> implements TurtleUpgradeM | ||||
|     private ItemUpgradeModel() { | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the unbaked {@link ItemUpgradeModel}. | ||||
|      * | ||||
|      * @return The unbaked item upgrade model. | ||||
|      */ | ||||
|     public static TurtleUpgradeModel.Unbaked unbaked() { | ||||
|         return UNBAKED; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void renderForItem(T upgrade, TurtleSide side, DataComponentPatch data, ItemStackRenderState renderer, ItemModelResolver resolver, ItemTransform transform, int seed) { | ||||
|     public void renderForItem(UpgradeData<ITurtleUpgrade> upgrade, TurtleSide side, ItemStackRenderState renderer, ItemModelResolver resolver, ItemTransform transform, int seed) { | ||||
|         var childState = new ItemStackRenderState(); | ||||
|         resolver.updateForTopItem(childState, upgrade.getUpgradeItem(data), ItemDisplayContext.NONE, null, null, seed); | ||||
|         resolver.updateForTopItem(childState, upgrade.getUpgradeItem(), ItemDisplayContext.NONE, null, null, seed); | ||||
|         if (!childState.isEmpty()) { | ||||
|             var layer = renderer.newLayer(); | ||||
|             layer.setTransform(transform); | ||||
| @@ -46,17 +69,22 @@ final class ItemUpgradeModel<T extends ITurtleUpgrade> implements TurtleUpgradeM | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void renderForLevel(T upgrade, ITurtleAccess turtle, TurtleSide side, DataComponentPatch data, PoseStack transform, MultiBufferSource buffers, int light, int overlay) { | ||||
|     public void renderForLevel(UpgradeData<ITurtleUpgrade> upgrade, TurtleSide side, ITurtleAccess turtle, PoseStack transform, MultiBufferSource buffers, int light, int overlay) { | ||||
|         transform.mulPose(getRenderer(side).transform().getMatrix()); | ||||
|         transform.mulPose(Axis.YP.rotation(Mth.PI)); | ||||
|         Minecraft.getInstance().getItemRenderer().renderStatic( | ||||
|             upgrade.getUpgradeItem(data), ItemDisplayContext.FIXED, light, overlay, transform, buffers, turtle.getLevel(), 0 | ||||
|             upgrade.getUpgradeItem(), ItemDisplayContext.FIXED, light, overlay, transform, buffers, turtle.getLevel(), 0 | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     private static final class Unbaked implements TurtleUpgradeModel.Unbaked<ITurtleUpgrade> { | ||||
|     private static final class Unbaked implements TurtleUpgradeModel.Unbaked { | ||||
|         @Override | ||||
|         public TurtleUpgradeModel<ITurtleUpgrade> bake(ModelBaker baker) { | ||||
|         public MapCodec<? extends TurtleUpgradeModel.Unbaked> type() { | ||||
|             return CODEC; | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public TurtleUpgradeModel bake(ModelBaker baker) { | ||||
|             return INSTANCE; | ||||
|         } | ||||
| 
 | ||||
|   | ||||
| @@ -4,11 +4,11 @@ | ||||
| 
 | ||||
| package dan200.computercraft.api.client.turtle; | ||||
| 
 | ||||
| import dan200.computercraft.api.turtle.ITurtleUpgrade; | ||||
| import dan200.computercraft.api.upgrades.UpgradeType; | ||||
| import com.mojang.serialization.MapCodec; | ||||
| import net.minecraft.resources.ResourceLocation; | ||||
| 
 | ||||
| /** | ||||
|  * A functional interface to register a {@link TurtleUpgradeModel} for a class of turtle upgrades. | ||||
|  * A functional interface to register a {@link TurtleUpgradeModel}. | ||||
|  * <p> | ||||
|  * This interface is largely intended to be used from multi-loader code, to allow sharing registration code between | ||||
|  * multiple loaders. | ||||
| @@ -18,9 +18,8 @@ public interface RegisterTurtleUpgradeModel { | ||||
|     /** | ||||
|      * Register a {@link TurtleUpgradeModel}. | ||||
|      * | ||||
|      * @param type The turtle upgrade type. | ||||
|      * @param mode The unbaked upgrade model. | ||||
|      * @param <T>  The type of the turtle upgrade. | ||||
|      * @param id    The id used for this type of upgrade model. | ||||
|      * @param model The codec used to read/decode an upgrade model. | ||||
|      */ | ||||
|     <T extends ITurtleUpgrade> void register(UpgradeType<T> type, TurtleUpgradeModel.Unbaked<? super T> mode); | ||||
|     void register(ResourceLocation id, MapCodec<? extends TurtleUpgradeModel.Unbaked> model); | ||||
| } | ||||
|   | ||||
| @@ -0,0 +1,209 @@ | ||||
| // SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers | ||||
| // | ||||
| // SPDX-License-Identifier: MPL-2.0 | ||||
| 
 | ||||
| package dan200.computercraft.api.client.turtle; | ||||
| 
 | ||||
| import com.google.common.collect.HashMultiset; | ||||
| import com.google.common.collect.Multiset; | ||||
| import com.mojang.blaze3d.vertex.PoseStack; | ||||
| import com.mojang.datafixers.util.Pair; | ||||
| import com.mojang.serialization.Codec; | ||||
| import com.mojang.serialization.DataResult; | ||||
| import com.mojang.serialization.MapCodec; | ||||
| import com.mojang.serialization.codecs.RecordCodecBuilder; | ||||
| import dan200.computercraft.api.ComputerCraftAPI; | ||||
| import dan200.computercraft.api.turtle.ITurtleAccess; | ||||
| import dan200.computercraft.api.turtle.ITurtleUpgrade; | ||||
| import dan200.computercraft.api.turtle.TurtleSide; | ||||
| import dan200.computercraft.api.upgrades.UpgradeData; | ||||
| import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; | ||||
| import net.minecraft.Util; | ||||
| import net.minecraft.client.renderer.MultiBufferSource; | ||||
| import net.minecraft.client.renderer.block.model.ItemTransform; | ||||
| import net.minecraft.client.renderer.item.ItemModelResolver; | ||||
| import net.minecraft.client.renderer.item.ItemStackRenderState; | ||||
| import net.minecraft.client.renderer.item.SelectItemModel; | ||||
| import net.minecraft.client.resources.model.MissingBlockModel; | ||||
| import net.minecraft.client.resources.model.ModelBaker; | ||||
| import net.minecraft.core.component.DataComponentType; | ||||
| import net.minecraft.resources.ResourceLocation; | ||||
| import org.jspecify.annotations.Nullable; | ||||
| 
 | ||||
| import java.util.*; | ||||
| import java.util.stream.Collectors; | ||||
| 
 | ||||
| /** | ||||
|  * A {@link TurtleUpgradeModel} which selects between different models based on the value of a component in | ||||
|  * {@linkplain UpgradeData#data() the upgrade's data}. | ||||
|  * <p> | ||||
|  * This is the {@link TurtleUpgradeModel} equivalent of {@link SelectItemModel}. | ||||
|  * | ||||
|  * @param <T> The type of value to switch on. | ||||
|  */ | ||||
| public final class SelectUpgradeModel<T> implements TurtleUpgradeModel { | ||||
|     public static final ResourceLocation ID = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "select"); | ||||
|     public static final MapCodec<? extends TurtleUpgradeModel.Unbaked> CODEC = RecordCodecBuilder.<Unbaked<?>>mapCodec(instance -> instance.group( | ||||
|         Cases.CODEC.forGetter(Unbaked::cases), | ||||
|         TurtleUpgradeModel.CODEC.optionalFieldOf("fallback").forGetter(Unbaked::fallback) | ||||
|     ).apply(instance, Unbaked::new)); | ||||
| 
 | ||||
|     private final DataComponentType<T> component; | ||||
|     private final Map<T, TurtleUpgradeModel> cases; | ||||
|     private final TurtleUpgradeModel fallback; | ||||
| 
 | ||||
|     private SelectUpgradeModel(DataComponentType<T> component, Map<T, TurtleUpgradeModel> cases, TurtleUpgradeModel fallback) { | ||||
|         this.component = component; | ||||
|         this.cases = cases; | ||||
|         this.fallback = fallback; | ||||
|     } | ||||
| 
 | ||||
|     private TurtleUpgradeModel getModel(UpgradeData<ITurtleUpgrade> upgrade) { | ||||
|         var value = upgrade.data().get(component); | ||||
|         if (value == null || value.isEmpty()) return fallback; | ||||
| 
 | ||||
|         var model = cases.get(value.get()); | ||||
|         return model != null ? model : fallback; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void renderForItem(UpgradeData<ITurtleUpgrade> upgrade, TurtleSide side, ItemStackRenderState renderer, ItemModelResolver resolver, ItemTransform transform, int seed) { | ||||
|         getModel(upgrade).renderForItem(upgrade, side, renderer, resolver, transform, seed); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void renderForLevel(UpgradeData<ITurtleUpgrade> upgrade, TurtleSide side, ITurtleAccess turtle, PoseStack transform, MultiBufferSource buffers, int light, int overlay) { | ||||
|         getModel(upgrade).renderForLevel(upgrade, side, turtle, transform, buffers, light, overlay); | ||||
|     } | ||||
| 
 | ||||
|     private record Unbaked<T>( | ||||
|         Cases<T> cases, | ||||
|         Optional<TurtleUpgradeModel.Unbaked> fallback | ||||
|     ) implements TurtleUpgradeModel.Unbaked { | ||||
|         private static final TurtleUpgradeModel.Unbaked MISSING = BasicUpgradeModel.unbaked(MissingBlockModel.LOCATION, MissingBlockModel.LOCATION); | ||||
| 
 | ||||
|         @Override | ||||
|         public MapCodec<? extends TurtleUpgradeModel.Unbaked> type() { | ||||
|             return CODEC; | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public TurtleUpgradeModel bake(ModelBaker baker) { | ||||
|             Map<T, TurtleUpgradeModel> cases = new Object2ObjectOpenHashMap<>(); | ||||
|             for (var condition : cases().cases()) { | ||||
|                 var model = condition.getSecond().bake(baker); | ||||
|                 for (var when : condition.getFirst()) cases.put(when, model); | ||||
|             } | ||||
| 
 | ||||
|             return new SelectUpgradeModel<>(cases().component(), cases, fallback().orElse(MISSING).bake(baker)); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public void resolveDependencies(Resolver resolver) { | ||||
|             cases().cases().forEach(x -> x.getSecond().resolveDependencies(resolver)); | ||||
|             fallback().orElse(MISSING).resolveDependencies(resolver); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private record Cases<T>(DataComponentType<T> component, List<Pair<List<T>, TurtleUpgradeModel.Unbaked>> cases) { | ||||
|         private static final MapCodec<Cases<?>> CODEC = DataComponentType.CODEC.dispatchMap("property", Cases::component, Util.memoize(Cases::codec)); | ||||
| 
 | ||||
|         private static <T> MapCodec<Cases<T>> codec(DataComponentType<T> component) { | ||||
|             return RecordCodecBuilder.mapCodec(instance -> instance.group( | ||||
|                 MapCodec.unit(component).forGetter(Cases::component), | ||||
|                 caseCodec(component.codecOrThrow()).listOf().fieldOf("cases").validate(Cases::validate).forGetter(Cases::cases) | ||||
|             ).apply(instance, Cases<T>::new)); | ||||
|         } | ||||
| 
 | ||||
|         private static <T> Codec<Pair<List<T>, TurtleUpgradeModel.Unbaked>> caseCodec(Codec<T> codec) { | ||||
|             return RecordCodecBuilder.create(instance -> instance.group( | ||||
|                 codec.listOf().fieldOf("when").forGetter(Pair::getFirst), | ||||
|                 TurtleUpgradeModel.CODEC.fieldOf("model").forGetter(Pair::getSecond) | ||||
|             ).apply(instance, Pair::new)); | ||||
|         } | ||||
| 
 | ||||
|         private static <T> DataResult<List<Pair<List<T>, TurtleUpgradeModel.Unbaked>>> validate(List<Pair<List<T>, TurtleUpgradeModel.Unbaked>> cases) { | ||||
|             Multiset<T> multiset = HashMultiset.create(); | ||||
|             for (var condition : cases) multiset.addAll(condition.getFirst()); | ||||
| 
 | ||||
|             if (multiset.isEmpty()) return DataResult.error(() -> "Empty cases"); | ||||
|             if (multiset.size() != multiset.entrySet().size()) { | ||||
|                 return DataResult.error(() -> "Duplicate case conditions: " + multiset.entrySet().stream() | ||||
|                     .filter(x -> x.getCount() > 1) | ||||
|                     .map(x -> Objects.toString(x.getElement())) | ||||
|                     .collect(Collectors.joining(", "))); | ||||
|             } | ||||
| 
 | ||||
|             return DataResult.success(cases); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Create a {@link SelectUpgradeModel} that selects a model based on a component. | ||||
|      * | ||||
|      * @param component The component to select. | ||||
|      * @param <T>       The type the component stores. | ||||
|      * @return A {@link Builder}. | ||||
|      */ | ||||
|     public static <T> Builder<T> onComponent(DataComponentType<T> component) { | ||||
|         return new Builder<>(component); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * A builder for constructing {@link SelectUpgradeModel}s. | ||||
|      * | ||||
|      * @param <T> The type of value to switch on. | ||||
|      */ | ||||
|     public static final class Builder<T> { | ||||
|         private final DataComponentType<T> component; | ||||
|         private final List<Pair<List<T>, TurtleUpgradeModel.Unbaked>> cases = new ArrayList<>(); | ||||
|         private TurtleUpgradeModel.@Nullable Unbaked fallback; | ||||
| 
 | ||||
|         private Builder(DataComponentType<T> component) { | ||||
|             this.component = component; | ||||
|         } | ||||
| 
 | ||||
|         /** | ||||
|          * Add a case to our model. | ||||
|          * | ||||
|          * @param value The value for this case. | ||||
|          * @param model The model to use. | ||||
|          * @return {@code this}, for chaining. | ||||
|          */ | ||||
|         public Builder<T> when(T value, TurtleUpgradeModel.Unbaked model) { | ||||
|             return when(List.of(value), model); | ||||
|         } | ||||
| 
 | ||||
|         /** | ||||
|          * Add a case to our model. | ||||
|          * | ||||
|          * @param values The value(s) for this case. | ||||
|          * @param model  The model to use. | ||||
|          * @return {@code this}, for chaining. | ||||
|          */ | ||||
|         public Builder<T> when(List<T> values, TurtleUpgradeModel.Unbaked model) { | ||||
|             cases.add(Pair.of(values, model)); | ||||
|             return this; | ||||
|         } | ||||
| 
 | ||||
|         /** | ||||
|          * Add a fallback value, when no previous value matches or the component is not present. | ||||
|          * | ||||
|          * @param model The fallback model. | ||||
|          * @return {@code this}, for chaining. | ||||
|          */ | ||||
|         public Builder<T> fallback(TurtleUpgradeModel.Unbaked model) { | ||||
|             this.fallback = model; | ||||
|             return this; | ||||
|         } | ||||
| 
 | ||||
|         /** | ||||
|          * Convert this builder into an unbaked model. | ||||
|          * | ||||
|          * @return The unbaked {@link SelectUpgradeModel}. | ||||
|          */ | ||||
|         public TurtleUpgradeModel.Unbaked create() { | ||||
|             return new Unbaked<>(new Cases<>(component, cases), Optional.ofNullable(fallback)); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,40 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers | ||||
| // | ||||
| // SPDX-License-Identifier: MPL-2.0 | ||||
| 
 | ||||
| package dan200.computercraft.api.client.turtle; | ||||
| 
 | ||||
| import dan200.computercraft.api.client.StandaloneModel; | ||||
| import dan200.computercraft.api.turtle.ITurtleUpgrade; | ||||
| import dan200.computercraft.api.turtle.TurtleSide; | ||||
| import net.minecraft.client.resources.model.ModelBaker; | ||||
| import net.minecraft.core.component.DataComponentPatch; | ||||
| import net.minecraft.resources.ResourceLocation; | ||||
| 
 | ||||
| record SidedUpgradeModel<T extends ITurtleUpgrade>( | ||||
|     StandaloneModel left, StandaloneModel right | ||||
| ) implements TurtleUpgradeModelViaStandalone<T> { | ||||
|     @Override | ||||
|     public StandaloneModel getModel(T upgrade, TurtleSide side, DataComponentPatch data) { | ||||
|         return switch (side) { | ||||
|             case LEFT -> left(); | ||||
|             case RIGHT -> right(); | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     record Unbaked<T extends ITurtleUpgrade>( | ||||
|         ResourceLocation left, ResourceLocation right | ||||
|     ) implements TurtleUpgradeModel.Unbaked<T> { | ||||
| 
 | ||||
|         @Override | ||||
|         public TurtleUpgradeModel<T> bake(ModelBaker baker) { | ||||
|             return new SidedUpgradeModel<>(StandaloneModel.of(left(), baker), StandaloneModel.of(right(), baker)); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public void resolveDependencies(Resolver resolver) { | ||||
|             resolver.markDependency(left()); | ||||
|             resolver.markDependency(right()); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -5,9 +5,14 @@ | ||||
| package dan200.computercraft.api.client.turtle; | ||||
| 
 | ||||
| import com.mojang.blaze3d.vertex.PoseStack; | ||||
| import com.mojang.serialization.Codec; | ||||
| import com.mojang.serialization.MapCodec; | ||||
| import dan200.computercraft.api.ComputerCraftAPI; | ||||
| import dan200.computercraft.api.turtle.ITurtleAccess; | ||||
| import dan200.computercraft.api.turtle.ITurtleUpgrade; | ||||
| import dan200.computercraft.api.turtle.TurtleSide; | ||||
| import dan200.computercraft.api.upgrades.UpgradeData; | ||||
| import dan200.computercraft.impl.client.ComputerCraftAPIClientService; | ||||
| import net.minecraft.client.multiplayer.ClientLevel; | ||||
| import net.minecraft.client.renderer.MultiBufferSource; | ||||
| import net.minecraft.client.renderer.block.model.ItemTransform; | ||||
| @@ -16,8 +21,6 @@ import net.minecraft.client.renderer.item.ItemModelResolver; | ||||
| import net.minecraft.client.renderer.item.ItemStackRenderState; | ||||
| import net.minecraft.client.resources.model.ModelBaker; | ||||
| import net.minecraft.client.resources.model.ResolvableModel; | ||||
| import net.minecraft.core.component.DataComponentPatch; | ||||
| import net.minecraft.resources.ResourceLocation; | ||||
| import net.minecraft.world.entity.LivingEntity; | ||||
| import net.minecraft.world.item.ItemDisplayContext; | ||||
| import net.minecraft.world.item.ItemStack; | ||||
| @@ -25,83 +28,80 @@ import net.minecraft.world.item.ItemStack; | ||||
| /** | ||||
|  * The model for a {@link ITurtleUpgrade}. | ||||
|  * <p> | ||||
|  * Use {@code dan200.computercraft.api.client.FabricComputerCraftAPIClient#registerTurtleUpgradeModeller} to register a | ||||
|  * modeller on Fabric and {@code dan200.computercraft.api.client.turtle.RegisterTurtleModellersEvent} to register one | ||||
|  * Turtle upgrade models are very similar to vanilla's {@link ItemModel}. Each upgrade's model is defined in JSON, and | ||||
|  * loaded from resource packs with other assets. | ||||
|  * <p> | ||||
|  * In most cases, upgrades can use one of the existing implementations of {@link TurtleUpgradeModel} (e.g. | ||||
|  * {@link BasicUpgradeModel} or {@link ItemUpgradeModel}), and do not need to subclass it. However, in the cases where | ||||
|  * a custom model is required, one should use | ||||
|  * {@code dan200.computercraft.api.client.FabricComputerCraftAPIClient#registerTurtleUpgradeModel} to register a | ||||
|  * model on Fabric and {@code dan200.computercraft.api.client.turtle.RegisterTurtleModelEvent} to register one | ||||
|  * on Forge. | ||||
|  * <p> | ||||
|  * See {@link ITurtleUpgrade} for a full example of registering turtle upgrades and their models. | ||||
|  * | ||||
|  * <h2>Example</h2> | ||||
|  * <h3>Fabric</h3> | ||||
|  * {@snippet class=com.example.examplemod.FabricExampleModClient region=turtle_model} | ||||
|  * | ||||
|  * <h3>Forge</h3> | ||||
|  * {@snippet class=com.example.examplemod.FabricExampleModClient region=turtle_model} | ||||
|  * | ||||
|  * @param <T> The type of turtle upgrade this modeller applies to. | ||||
|  * @see RegisterTurtleUpgradeModel For multi-loader registration support. | ||||
|  * @see ItemUpgradeModel A {@code TurtleUpgradeModel} which uses the upgrade's item. | ||||
|  * @see BasicUpgradeModel A {@code TurtleUpgradeModel} which renders a simple model. | ||||
|  */ | ||||
| public interface TurtleUpgradeModel<T extends ITurtleUpgrade> { | ||||
| public interface TurtleUpgradeModel { | ||||
|     /** | ||||
|      * The directory from which turtle upgrade models are loaded. This may be used by data generators. | ||||
|      */ | ||||
|     String SOURCE = ComputerCraftAPI.MOD_ID + "/turtle_upgrade"; | ||||
| 
 | ||||
|     /** | ||||
|      * The codec used to read/write {@linkplain TurtleUpgradeModel.Unbaked unbaked upgrade models}. | ||||
|      */ | ||||
|     Codec<TurtleUpgradeModel.Unbaked> CODEC = Codec.lazyInitialized(() -> ComputerCraftAPIClientService.get().getTurtleUpgradeModelCodec()); | ||||
| 
 | ||||
|     /** | ||||
|      * Render this upgrade to an {@link ItemStackRenderState}. This is used for rendering the item form of the upgrade. | ||||
|      * | ||||
|      * @param upgrade   The upgrade being rendered. | ||||
|      * @param side      Which side of the turtle (left or right) the upgrade resides on. | ||||
|      * @param data      Upgrade data instance for current turtle side. | ||||
|      * @param side      Which side of the turtle (left or right) the upgrade is equipped on. | ||||
|      * @param renderer  The render state to draw to. | ||||
|      * @param resolver  The model resolver. | ||||
|      * @param transform The root model's transformation. | ||||
|      * @param seed      The current model seed. | ||||
|      * @see ItemModel#update(ItemStackRenderState, ItemStack, ItemModelResolver, ItemDisplayContext, ClientLevel, LivingEntity, int) | ||||
|      */ | ||||
|     void renderForItem(T upgrade, TurtleSide side, DataComponentPatch data, ItemStackRenderState renderer, ItemModelResolver resolver, ItemTransform transform, int seed); | ||||
|     void renderForItem(UpgradeData<ITurtleUpgrade> upgrade, TurtleSide side, ItemStackRenderState renderer, ItemModelResolver resolver, ItemTransform transform, int seed); | ||||
| 
 | ||||
|     /** | ||||
|      * Render this upgrade to a {@link MultiBufferSource}. This is used for rendering the block-entity form of the | ||||
|      * upgrade. | ||||
|      * | ||||
|      * @param upgrade   The upgrade being rendered. | ||||
|      * @param turtle    Access to the turtle that the upgrade resides on. This will be null when getting item models. | ||||
|      * @param side      Which side of the turtle (left or right) the upgrade resides on. | ||||
|      * @param data      Upgrade data instance for current turtle side. | ||||
|      * @param side      Which side of the turtle (left or right) the upgrade is equipped on. | ||||
|      * @param turtle    Access to the turtle that the upgrade resides on. | ||||
|      * @param transform The current pose stack. | ||||
|      * @param buffers   The buffers to render to. | ||||
|      * @param light     The lightmap coordinate. | ||||
|      * @param overlay   The overlay coordinate. | ||||
|      */ | ||||
|     void renderForLevel(T upgrade, ITurtleAccess turtle, TurtleSide side, DataComponentPatch data, PoseStack transform, MultiBufferSource buffers, int light, int overlay); | ||||
|     void renderForLevel(UpgradeData<ITurtleUpgrade> upgrade, TurtleSide side, ITurtleAccess turtle, PoseStack transform, MultiBufferSource buffers, int light, int overlay); | ||||
| 
 | ||||
|     /** | ||||
|      * An unbaked turtle model. Much like other unbaked models (e.g. {@link ItemModel.Unbaked}), this should resolve | ||||
|      * any dependencies and returned the fully-resolved model. | ||||
|      * | ||||
|      * @param <T> The type of turtle upgrade for this model. | ||||
|      */ | ||||
|     interface Unbaked<T extends ITurtleUpgrade> extends ResolvableModel { | ||||
|         TurtleUpgradeModel<T> bake(ModelBaker baker); | ||||
|     } | ||||
|     interface Unbaked extends ResolvableModel { | ||||
|         /** | ||||
|          * The {@link MapCodec} used to read/write this unbaked model. | ||||
|          * | ||||
|          * @return The codec used to read/write this model. | ||||
|          * @see ItemModel.Unbaked#type() | ||||
|          */ | ||||
|         MapCodec<? extends Unbaked> type(); | ||||
| 
 | ||||
|         /** | ||||
|      * A basic {@link TurtleUpgradeModel} which renders using the upgrade's {@linkplain ITurtleUpgrade#getUpgradeItem(DataComponentPatch) | ||||
|      * upgrade item}. | ||||
|      * <p> | ||||
|      * This uses appropriate transformations for "flat" items, namely those extending the {@literal minecraft:item/generated} | ||||
|      * model type. It will not appear correct for 3D models with additional depth, such as blocks. | ||||
|          * Bake this turtle model. | ||||
|          * | ||||
|      * @param <T> The type of the turtle upgrade. | ||||
|      * @return The constructed modeller. | ||||
|          * @param baker The current model baker | ||||
|          * @return The baked upgrade model. | ||||
|          * @see ItemModel.Unbaked#bake(ItemModel.BakingContext) | ||||
|          */ | ||||
|     static <T extends ITurtleUpgrade> TurtleUpgradeModel.Unbaked<? super T> flatItem() { | ||||
|         return ItemUpgradeModel.UNBAKED; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Construct a {@link TurtleUpgradeModel} which has a single model for the left and right side. | ||||
|      * | ||||
|      * @param left  The model to use on the left. | ||||
|      * @param right The model to use on the right. | ||||
|      * @param <T>   The type of the turtle upgrade. | ||||
|      * @return The constructed modeller. | ||||
|      */ | ||||
|     static <T extends ITurtleUpgrade> TurtleUpgradeModel.Unbaked<T> sided(ResourceLocation left, ResourceLocation right) { | ||||
|         return new SidedUpgradeModel.Unbaked<>(left, right); | ||||
|         TurtleUpgradeModel bake(ModelBaker baker); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,32 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers | ||||
| // | ||||
| // SPDX-License-Identifier: MPL-2.0 | ||||
| 
 | ||||
| package dan200.computercraft.api.client.turtle; | ||||
| 
 | ||||
| import com.mojang.blaze3d.vertex.PoseStack; | ||||
| import dan200.computercraft.api.client.StandaloneModel; | ||||
| import dan200.computercraft.api.turtle.ITurtleAccess; | ||||
| import dan200.computercraft.api.turtle.ITurtleUpgrade; | ||||
| import dan200.computercraft.api.turtle.TurtleSide; | ||||
| import net.minecraft.client.renderer.MultiBufferSource; | ||||
| import net.minecraft.client.renderer.block.model.ItemTransform; | ||||
| import net.minecraft.client.renderer.item.ItemModelResolver; | ||||
| import net.minecraft.client.renderer.item.ItemStackRenderState; | ||||
| import net.minecraft.core.component.DataComponentPatch; | ||||
| 
 | ||||
| public interface TurtleUpgradeModelViaStandalone<T extends ITurtleUpgrade> extends TurtleUpgradeModel<T> { | ||||
|     StandaloneModel getModel(T upgrade, TurtleSide side, DataComponentPatch data); | ||||
| 
 | ||||
|     @Override | ||||
|     default void renderForItem(T upgrade, TurtleSide side, DataComponentPatch data, ItemStackRenderState renderer, ItemModelResolver resolver, ItemTransform transform, int seed) { | ||||
|         var layer = renderer.newLayer(); | ||||
|         layer.setTransform(transform); | ||||
|         getModel(upgrade, side, data).setupItemLayer(layer); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     default void renderForLevel(T upgrade, ITurtleAccess turtle, TurtleSide side, DataComponentPatch data, PoseStack transform, MultiBufferSource buffers, int light, int overlay) { | ||||
|         getModel(upgrade, side, data).render(transform, buffers, light, overlay); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,40 @@ | ||||
| // SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers | ||||
| // | ||||
| // SPDX-License-Identifier: MPL-2.0 | ||||
| 
 | ||||
| package dan200.computercraft.impl.client; | ||||
| 
 | ||||
| import com.mojang.serialization.Codec; | ||||
| import dan200.computercraft.api.client.turtle.TurtleUpgradeModel; | ||||
| import dan200.computercraft.impl.Services; | ||||
| import org.jetbrains.annotations.ApiStatus; | ||||
| import org.jspecify.annotations.Nullable; | ||||
| 
 | ||||
| /** | ||||
|  * Bridge between implementation | ||||
|  * <p> | ||||
|  * Do <strong>NOT</strong> directly reference this class. It exists for internal use by the API. | ||||
|  */ | ||||
| @ApiStatus.Internal | ||||
| public interface ComputerCraftAPIClientService { | ||||
|     static ComputerCraftAPIClientService get() { | ||||
|         var instance = Instance.INSTANCE; | ||||
|         return instance == null ? Services.raise(ComputerCraftAPIClientService.class, Instance.ERROR) : instance; | ||||
|     } | ||||
| 
 | ||||
|     Codec<TurtleUpgradeModel.Unbaked> getTurtleUpgradeModelCodec(); | ||||
| 
 | ||||
|     final class Instance { | ||||
|         static final @Nullable ComputerCraftAPIClientService INSTANCE; | ||||
|         static final @Nullable Throwable ERROR; | ||||
| 
 | ||||
|         static { | ||||
|             var helper = Services.tryLoad(ComputerCraftAPIClientService.class); | ||||
|             INSTANCE = helper.instance(); | ||||
|             ERROR = helper.error(); | ||||
|         } | ||||
| 
 | ||||
|         private Instance() { | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -51,16 +51,6 @@ import java.util.function.Function; | ||||
|  * <h4>Forge</h4> | ||||
|  * {@snippet class=com.example.examplemod.ForgeExampleMod region=turtle_upgrades} | ||||
|  * | ||||
|  * <h3>Rendering the upgrade</h3> | ||||
|  * Next, we need to register a model for our upgrade. This is done by registering a | ||||
|  * {@link dan200.computercraft.api.client.turtle.TurtleUpgradeModel} for your upgrade type. | ||||
|  * | ||||
|  * <h4>Fabric</h4> | ||||
|  * {@snippet class=com.example.examplemod.FabricExampleModClient region=turtle_model} | ||||
|  * | ||||
|  * <h4>Forge</h4> | ||||
|  * {@snippet class=com.example.examplemod.FabricExampleModClient region=turtle_model} | ||||
|  * | ||||
|  * <h3 id="datagen">Registering the upgrade itself</h3> | ||||
|  * Upgrades themselves are loaded from datapacks when a level is loaded. In order to register our new upgrade, we must | ||||
|  * create a new JSON file at {@code data/<my_mod>/computercraft/turtle_upgrade/<my_upgrade_id>.json}. | ||||
| @@ -71,8 +61,15 @@ import java.util.function.Function; | ||||
|  * by the type itself. As our upgrade was defined with {@link UpgradeType#simpleWithCustomItem(Function)}, the | ||||
|  * {@code "item"} field will construct our upgrade with {@link Items#COMPASS}. | ||||
|  * <p> | ||||
|  * Rather than manually creating the file, it is recommended to use data-generators to generate this file. First, we | ||||
|  * register our new upgrades into a {@linkplain PatchedRegistries patched registry}. | ||||
|  * Similarly, {@linkplain dan200.computercraft.api.client.turtle.TurtleUpgradeModel the upgrade's model} is loaded from | ||||
|  * a resource pack, and so we must also create a new JSON file at | ||||
|  * {@code assets/<my_mod>/computercraft/turtle_upgrade/<my_upgrade_id>.json}. | ||||
|  * | ||||
|  * {@snippet file=assets/examplemod/computercraft/turtle_upgrade/example_turtle_upgrade.json} | ||||
|  * | ||||
|  * Rather than manually creating these file, it is recommended to use data-generators to generate this file. First, we | ||||
|  * register our new upgrades into a {@linkplain PatchedRegistries patched registry}. Models must similarly be | ||||
|  * registered. | ||||
|  * | ||||
|  * {@snippet class=com.example.examplemod.data.TurtleUpgradeProvider region=body} | ||||
|  * | ||||
|   | ||||
| @@ -7,8 +7,7 @@ package dan200.computercraft.client; | ||||
| import com.mojang.serialization.MapCodec; | ||||
| import dan200.computercraft.api.ComputerCraftAPI; | ||||
| import dan200.computercraft.api.client.StandaloneModel; | ||||
| import dan200.computercraft.api.client.turtle.RegisterTurtleUpgradeModel; | ||||
| import dan200.computercraft.api.client.turtle.TurtleUpgradeModel; | ||||
| import dan200.computercraft.api.client.turtle.*; | ||||
| import dan200.computercraft.client.gui.*; | ||||
| import dan200.computercraft.client.item.colour.PocketComputerLight; | ||||
| import dan200.computercraft.client.item.model.TurtleOverlayModel; | ||||
| @@ -19,10 +18,9 @@ import dan200.computercraft.client.platform.ModelKey; | ||||
| 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.client.turtle.TurtleUpgradeModelManager; | ||||
| import dan200.computercraft.shared.ModRegistry; | ||||
| import dan200.computercraft.shared.computer.inventory.AbstractComputerMenu; | ||||
| import net.minecraft.client.Minecraft; | ||||
| @@ -104,16 +102,9 @@ public final class ClientRegistry { | ||||
|     } | ||||
| 
 | ||||
|     public static void registerTurtleModels(RegisterTurtleUpgradeModel register) { | ||||
|         register.register(ModRegistry.TurtleUpgradeTypes.SPEAKER.get(), TurtleUpgradeModel.sided( | ||||
|             ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_speaker_left"), | ||||
|             ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_speaker_right") | ||||
|         )); | ||||
|         register.register(ModRegistry.TurtleUpgradeTypes.WORKBENCH.get(), TurtleUpgradeModel.sided( | ||||
|             ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_crafting_table_left"), | ||||
|             ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_crafting_table_right") | ||||
|         )); | ||||
|         register.register(ModRegistry.TurtleUpgradeTypes.WIRELESS_MODEM.get(), TurtleModemModel.UNBAKED); | ||||
|         register.register(ModRegistry.TurtleUpgradeTypes.TOOL.get(), TurtleUpgradeModel.flatItem()); | ||||
|         register.register(BasicUpgradeModel.ID, BasicUpgradeModel.CODEC); | ||||
|         register.register(ItemUpgradeModel.ID, ItemUpgradeModel.CODEC); | ||||
|         register.register(SelectUpgradeModel.ID, SelectUpgradeModel.CODEC); | ||||
|     } | ||||
| 
 | ||||
|     public static void registerReloadListeners(BiConsumer<ResourceLocation, PreparableReloadListener> register, Minecraft minecraft) { | ||||
| @@ -132,11 +123,13 @@ public final class ClientRegistry { | ||||
|      * Additional models to load. | ||||
|      * | ||||
|      * @param turtleOverlays The unbaked turtle models. | ||||
|      * @param turtleUpgrades The unbaked turtle upgrades. | ||||
|      * @see #gatherExtraModels(ResourceManager, Executor) | ||||
|      * @see #registerExtraModels(RegisterExtraModels, ExtraModels) | ||||
|      */ | ||||
|     public record ExtraModels( | ||||
|         Map<ResourceLocation, TurtleOverlay.Unbaked> turtleOverlays | ||||
|         Map<ResourceLocation, TurtleOverlay.Unbaked> turtleOverlays, | ||||
|         Map<ResourceLocation, TurtleUpgradeModel.Unbaked> turtleUpgrades | ||||
|     ) { | ||||
|     } | ||||
| 
 | ||||
| @@ -148,8 +141,9 @@ public final class ClientRegistry { | ||||
|      * @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); | ||||
|         var turtleOverlays = TurtleOverlayManager.loader().load(resources, executor); | ||||
|         var turtleUpgrades = TurtleUpgradeModelManager.loader().load(resources, executor); | ||||
|         return turtleOverlays.thenCombine(turtleUpgrades, ExtraModels::new); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @@ -181,8 +175,8 @@ public final class ClientRegistry { | ||||
|         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); | ||||
|         TurtleOverlayManager.loader().register(register, models.turtleOverlays()); | ||||
|         TurtleUpgradeModelManager.loader().register(register, models.turtleUpgrades()); | ||||
|     } | ||||
| 
 | ||||
|     public static void registerItemModels(BiConsumer<ResourceLocation, MapCodec<? extends ItemModel.Unbaked>> register) { | ||||
|   | ||||
| @@ -0,0 +1,96 @@ | ||||
| // SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers | ||||
| // | ||||
| // SPDX-License-Identifier: MPL-2.0 | ||||
| 
 | ||||
| package dan200.computercraft.client; | ||||
| 
 | ||||
| import com.mojang.serialization.Codec; | ||||
| import com.mojang.serialization.JsonOps; | ||||
| 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.ModelBaker; | ||||
| import net.minecraft.client.resources.model.ModelManager; | ||||
| import net.minecraft.client.resources.model.ResolvableModel; | ||||
| import net.minecraft.resources.FileToIdConverter; | ||||
| import net.minecraft.resources.ResourceLocation; | ||||
| import net.minecraft.server.packs.resources.ResourceManager; | ||||
| 
 | ||||
| import java.util.Map; | ||||
| import java.util.concurrent.CompletableFuture; | ||||
| import java.util.concurrent.ConcurrentHashMap; | ||||
| import java.util.concurrent.Executor; | ||||
| import java.util.function.BiFunction; | ||||
| 
 | ||||
| /** | ||||
|  * A manager for loading custom models. This is responsible for {@linkplain #load(ResourceManager, Executor) loading | ||||
|  * models from resource packs}, {@linkplain #register(ClientRegistry.RegisterExtraModels, Map) registering them as | ||||
|  * extra models}, and then {@linkplain #get(ModelManager, ResourceLocation) looking them up}. | ||||
|  * | ||||
|  * @param <U> The type of unbaked model. | ||||
|  * @param <T> The type of baked model. | ||||
|  */ | ||||
| public class CustomModelManager<U extends ResolvableModel, T> { | ||||
|     private final String kind; | ||||
|     private final FileToIdConverter lister; | ||||
|     private final Codec<U> codec; | ||||
|     private final BiFunction<U, ModelBaker, T> bake; | ||||
| 
 | ||||
|     private final ModelKey<T> missingModelKey; | ||||
|     private final U missingModel; | ||||
| 
 | ||||
|     private final Map<ResourceLocation, ModelKey<T>> modelKeys = new ConcurrentHashMap<>(); | ||||
| 
 | ||||
|     public CustomModelManager(String kind, FileToIdConverter lister, Codec<U> codec, BiFunction<U, ModelBaker, T> bake, U missingModel) { | ||||
|         this.kind = kind; | ||||
|         this.lister = lister; | ||||
|         this.codec = codec; | ||||
|         this.bake = bake; | ||||
| 
 | ||||
|         this.missingModelKey = ClientPlatformHelper.get().createModelKey(MissingBlockModel.LOCATION, () -> "Missing " + kind); | ||||
|         this.missingModel = missingModel; | ||||
|     } | ||||
| 
 | ||||
|     private ModelKey<T> getModelKey(ResourceLocation id) { | ||||
|         return modelKeys.computeIfAbsent(id, o -> ClientPlatformHelper.get().createModelKey(o, () -> kind + " " + o)); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Load our models from resources. | ||||
|      * | ||||
|      * @param resources The current resource manager. | ||||
|      * @param executor  The executor to schedule work on. | ||||
|      * @return The map of unbaked models. | ||||
|      */ | ||||
|     public CompletableFuture<Map<ResourceLocation, U>> load(ResourceManager resources, Executor executor) { | ||||
|         return ResourceUtils.load(resources, executor, kind, lister, JsonOps.INSTANCE, codec); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Register our unbaked models. | ||||
|      * | ||||
|      * @param register The callback to register models with. | ||||
|      * @param models   The models to register. | ||||
|      */ | ||||
|     public void register(ClientRegistry.RegisterExtraModels register, Map<ResourceLocation, U> models) { | ||||
|         models.forEach((id, model) -> register.register(getModelKey(id), model, bake)); | ||||
|         register.register(missingModelKey, missingModel, bake); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Find the model with the given id. If the model does not exist, then the missing model is returned instead. | ||||
|      * | ||||
|      * @param modelManager The model manager. | ||||
|      * @param id           The model id. | ||||
|      * @return The loaded model. | ||||
|      */ | ||||
|     public T get(ModelManager modelManager, ResourceLocation id) { | ||||
|         var model = getModelKey(id).get(modelManager); | ||||
|         if (model != null) return model; | ||||
| 
 | ||||
|         var missing = missingModelKey.get(modelManager); | ||||
|         if (missing == null) throw new IllegalStateException("Models have not yet been loaded."); | ||||
|         return missing; | ||||
|     } | ||||
| } | ||||
| @@ -40,7 +40,7 @@ public record TurtleOverlayModel(ItemTransforms transforms) implements ItemModel | ||||
|         if (overlay == null) return; | ||||
| 
 | ||||
|         var layer = state.newLayer(); | ||||
|         TurtleOverlayManager.getOverlay(Minecraft.getInstance().getModelManager(), overlay).model().setupItemLayer(layer); | ||||
|         TurtleOverlayManager.get(Minecraft.getInstance().getModelManager(), overlay).model().setupItemLayer(layer); | ||||
|         layer.setTransform(transforms().getTransform(context)); | ||||
|     } | ||||
| 
 | ||||
|   | ||||
| @@ -8,8 +8,9 @@ import com.mojang.serialization.MapCodec; | ||||
| import com.mojang.serialization.codecs.RecordCodecBuilder; | ||||
| import dan200.computercraft.api.ComputerCraftAPI; | ||||
| import dan200.computercraft.api.turtle.TurtleSide; | ||||
| import dan200.computercraft.client.turtle.TurtleUpgradeModels; | ||||
| import dan200.computercraft.client.turtle.TurtleUpgradeModelManager; | ||||
| import dan200.computercraft.shared.turtle.items.TurtleItem; | ||||
| import net.minecraft.client.Minecraft; | ||||
| import net.minecraft.client.multiplayer.ClientLevel; | ||||
| import net.minecraft.client.renderer.block.model.ItemTransforms; | ||||
| import net.minecraft.client.renderer.item.ItemModel; | ||||
| @@ -39,7 +40,8 @@ public record TurtleUpgradeModel(TurtleSide side, ItemTransforms base) implement | ||||
|         var upgrade = TurtleItem.getUpgradeWithData(stack, side); | ||||
|         if (upgrade == null) return; | ||||
| 
 | ||||
|         TurtleUpgradeModels.getModeller(upgrade.upgrade()).renderForItem(upgrade.upgrade(), side, upgrade.data(), state, resolver, base.getTransform(context), seed); | ||||
|         TurtleUpgradeModelManager.get(Minecraft.getInstance().getModelManager(), upgrade.holder()) | ||||
|             .renderForItem(upgrade, side, state, resolver, base.getTransform(context), seed); | ||||
|     } | ||||
| 
 | ||||
|     public record Unbaked(TurtleSide side, ResourceLocation base) implements ItemModel.Unbaked { | ||||
|   | ||||
| @@ -31,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 = TurtleOverlayManager.getOverlay(Minecraft.getInstance().getModelManager(), TurtleItem.getOverlay(stack)); | ||||
|         var overlay = TurtleOverlayManager.get(Minecraft.getInstance().getModelManager(), TurtleItem.getOverlay(stack)); | ||||
|         return overlay == null || overlay.showElfOverlay(); | ||||
|     } | ||||
| 
 | ||||
|   | ||||
| @@ -11,7 +11,7 @@ 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.client.turtle.TurtleUpgradeModelManager; | ||||
| import dan200.computercraft.shared.computer.core.ComputerFamily; | ||||
| import dan200.computercraft.shared.turtle.blocks.TurtleBlockEntity; | ||||
| import dan200.computercraft.shared.util.Holiday; | ||||
| @@ -79,7 +79,7 @@ public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBloc | ||||
| 
 | ||||
|         // Render the turtle | ||||
|         var colour = turtle.getColour(); | ||||
|         var overlay = TurtleOverlayManager.getOverlay(Minecraft.getInstance().getModelManager(), turtle.getOverlay()); | ||||
|         var overlay = TurtleOverlayManager.get(Minecraft.getInstance().getModelManager(), turtle.getOverlay()); | ||||
| 
 | ||||
|         if (colour == -1) { | ||||
|             renderModel(transform, buffers, lightmapCoord, overlayLight, turtle.getFamily() == ComputerFamily.NORMAL ? NORMAL_TURTLE_MODEL : ADVANCED_TURTLE_MODEL, null); | ||||
| @@ -103,7 +103,7 @@ public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBloc | ||||
|     } | ||||
| 
 | ||||
|     private void renderUpgrade(PoseStack transform, MultiBufferSource buffers, int lightmapCoord, int overlayLight, TurtleBlockEntity turtle, TurtleSide side, float f) { | ||||
|         var upgrade = turtle.getUpgrade(side); | ||||
|         var upgrade = turtle.getAccess().getUpgradeWithData(side); | ||||
|         if (upgrade == null) return; | ||||
|         transform.pushPose(); | ||||
| 
 | ||||
| @@ -112,8 +112,8 @@ public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBloc | ||||
|         transform.mulPose(Axis.XN.rotationDegrees(toolAngle)); | ||||
|         transform.translate(0.0f, -0.5f, -0.5f); | ||||
| 
 | ||||
|         TurtleUpgradeModels.getModeller(upgrade).renderForLevel(upgrade, turtle.getAccess(), side, turtle.getAccess().getUpgradeData(side), transform, buffers, lightmapCoord, overlayLight); | ||||
| 
 | ||||
|         TurtleUpgradeModelManager.get(Minecraft.getInstance().getModelManager(), upgrade.holder()) | ||||
|             .renderForLevel(upgrade, side, turtle.getAccess(), transform, buffers, lightmapCoord, overlayLight); | ||||
| 
 | ||||
|         transform.popPose(); | ||||
|     } | ||||
|   | ||||
| @@ -1,89 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers | ||||
| // | ||||
| // SPDX-License-Identifier: MPL-2.0 | ||||
| 
 | ||||
| package dan200.computercraft.client.turtle; | ||||
| 
 | ||||
| import dan200.computercraft.api.ComputerCraftAPI; | ||||
| import dan200.computercraft.api.client.StandaloneModel; | ||||
| import dan200.computercraft.api.client.turtle.TurtleUpgradeModel; | ||||
| import dan200.computercraft.api.client.turtle.TurtleUpgradeModelViaStandalone; | ||||
| import dan200.computercraft.api.turtle.TurtleSide; | ||||
| import dan200.computercraft.shared.ModRegistry; | ||||
| import dan200.computercraft.shared.turtle.upgrades.TurtleModem; | ||||
| import dan200.computercraft.shared.util.DataComponentUtil; | ||||
| import net.minecraft.client.resources.model.ModelBaker; | ||||
| import net.minecraft.core.component.DataComponentPatch; | ||||
| import net.minecraft.resources.ResourceLocation; | ||||
| 
 | ||||
| import java.util.function.Consumer; | ||||
| import java.util.function.Function; | ||||
| 
 | ||||
| /** | ||||
|  * A {@link TurtleUpgradeModel} for modems, providing different models depending on if the modem is on/off. | ||||
|  * | ||||
|  * @param normal   The models for a normal wireless modem. | ||||
|  * @param advanced The models for an advanced/ender modem. | ||||
|  */ | ||||
| public record TurtleModemModel( | ||||
|     ModemModels<StandaloneModel> normal, ModemModels<StandaloneModel> advanced | ||||
| ) implements TurtleUpgradeModelViaStandalone<TurtleModem> { | ||||
|     public static final TurtleUpgradeModel.Unbaked<TurtleModem> UNBAKED = new Unbaked(); | ||||
| 
 | ||||
|     @Override | ||||
|     public StandaloneModel getModel(TurtleModem modem, TurtleSide side, DataComponentPatch data) { | ||||
|         var active = DataComponentUtil.isPresent(data, ModRegistry.DataComponents.ON.get(), x -> x); | ||||
| 
 | ||||
|         var models = modem.advanced() ? advanced() : normal(); | ||||
|         return side == TurtleSide.LEFT | ||||
|             ? (active ? models.leftOnModel() : models.leftOffModel()) | ||||
|             : (active ? models.rightOnModel() : models.rightOffModel()); | ||||
|     } | ||||
| 
 | ||||
|     private static final class Unbaked implements TurtleUpgradeModel.Unbaked<TurtleModem> { | ||||
|         @Override | ||||
|         public TurtleUpgradeModel<TurtleModem> bake(ModelBaker baker) { | ||||
|             return new TurtleModemModel( | ||||
|                 ModemModels.NORMAL.map(x -> StandaloneModel.of(x, baker)), | ||||
|                 ModemModels.ADVANCED.map(x -> StandaloneModel.of(x, baker)) | ||||
|             ); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public void resolveDependencies(Resolver resolver) { | ||||
|             ModemModels.NORMAL.forEach(resolver::markDependency); | ||||
|             ModemModels.ADVANCED.forEach(resolver::markDependency); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private record ModemModels<T>( | ||||
|         T leftOffModel, T rightOffModel, | ||||
|         T leftOnModel, T rightOnModel | ||||
|     ) { | ||||
|         private static final ModemModels<ResourceLocation> NORMAL = create("normal"); | ||||
|         private static final ModemModels<ResourceLocation> ADVANCED = create("advanced"); | ||||
| 
 | ||||
|         static ModemModels<ResourceLocation> create(String type) { | ||||
|             return new ModemModels<>( | ||||
|                 ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_modem_" + type + "_off_left"), | ||||
|                 ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_modem_" + type + "_off_right"), | ||||
|                 ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_modem_" + type + "_on_left"), | ||||
|                 ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_modem_" + type + "_on_right") | ||||
|             ); | ||||
|         } | ||||
| 
 | ||||
|         public void forEach(Consumer<T> out) { | ||||
|             out.accept(leftOffModel()); | ||||
|             out.accept(rightOffModel); | ||||
|             out.accept(leftOnModel()); | ||||
|             out.accept(rightOnModel()); | ||||
|         } | ||||
| 
 | ||||
|         public <U> ModemModels<U> map(Function<T, U> mapper) { | ||||
|             return new ModemModels<>( | ||||
|                 mapper.apply(leftOffModel()), mapper.apply(rightOffModel()), | ||||
|                 mapper.apply(leftOnModel()), mapper.apply(rightOnModel()) | ||||
|             ); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -4,66 +4,27 @@ | ||||
| 
 | ||||
| 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 dan200.computercraft.client.CustomModelManager; | ||||
| 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}. | ||||
|  * The model manager for {@link TurtleOverlay}s. | ||||
|  */ | ||||
| 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 CustomModelManager<TurtleOverlay.Unbaked, TurtleOverlay> loader = new CustomModelManager<>( | ||||
|         "turtle overlay", FileToIdConverter.json(TurtleOverlay.SOURCE), TurtleOverlay.CODEC, | ||||
|         TurtleOverlay.Unbaked::bake, | ||||
|         new TurtleOverlay.Unbaked(MissingBlockModel.LOCATION, false) | ||||
|     ); | ||||
| 
 | ||||
|     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); | ||||
|     public static CustomModelManager<TurtleOverlay.Unbaked, TurtleOverlay> loader() { | ||||
|         return loader; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @@ -75,14 +36,7 @@ public class TurtleOverlayManager { | ||||
|      * @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; | ||||
|     public static @Nullable TurtleOverlay get(ModelManager modelManager, @Nullable ResourceLocation id) { | ||||
|         return id == null ? null : loader.get(modelManager, id); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -0,0 +1,43 @@ | ||||
| // SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers | ||||
| // | ||||
| // SPDX-License-Identifier: MPL-2.0 | ||||
| 
 | ||||
| package dan200.computercraft.client.turtle; | ||||
| 
 | ||||
| import dan200.computercraft.api.client.turtle.BasicUpgradeModel; | ||||
| import dan200.computercraft.api.client.turtle.TurtleUpgradeModel; | ||||
| import dan200.computercraft.api.turtle.ITurtleUpgrade; | ||||
| import dan200.computercraft.client.CustomModelManager; | ||||
| import net.minecraft.client.resources.model.MissingBlockModel; | ||||
| import net.minecraft.client.resources.model.ModelManager; | ||||
| import net.minecraft.core.Holder; | ||||
| import net.minecraft.resources.FileToIdConverter; | ||||
| import org.jetbrains.annotations.Contract; | ||||
| import org.jspecify.annotations.Nullable; | ||||
| 
 | ||||
| /** | ||||
|  * The model manager for {@link TurtleUpgradeModel}s. | ||||
|  */ | ||||
| public final class TurtleUpgradeModelManager { | ||||
|     private static final CustomModelManager<TurtleUpgradeModel.Unbaked, TurtleUpgradeModel> loader = new CustomModelManager<>( | ||||
|         "turtle upgrade", FileToIdConverter.json(TurtleUpgradeModel.SOURCE), TurtleUpgradeModel.CODEC, | ||||
|         TurtleUpgradeModel.Unbaked::bake, | ||||
|         BasicUpgradeModel.unbaked(MissingBlockModel.LOCATION, MissingBlockModel.LOCATION) | ||||
|     ); | ||||
| 
 | ||||
|     public static CustomModelManager<TurtleUpgradeModel.Unbaked, TurtleUpgradeModel> loader() { | ||||
|         return loader; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Find the model for the given turtle upgrade. | ||||
|      * | ||||
|      * @param modelManager The model manager. | ||||
|      * @param upgrade      The turtle upgrade | ||||
|      * @return The turtle upgrade model. | ||||
|      */ | ||||
|     @Contract("_, null -> null; _, !null -> !null") | ||||
|     public static @Nullable TurtleUpgradeModel get(ModelManager modelManager, Holder.@Nullable Reference<ITurtleUpgrade> upgrade) { | ||||
|         return upgrade == null ? null : loader.get(modelManager, upgrade.key().location()); | ||||
|     } | ||||
| } | ||||
| @@ -1,88 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers | ||||
| // | ||||
| // SPDX-License-Identifier: MPL-2.0 | ||||
| 
 | ||||
| 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; | ||||
| import net.minecraft.client.Minecraft; | ||||
| import net.minecraft.client.resources.model.MissingBlockModel; | ||||
| 
 | ||||
| import java.util.Map; | ||||
| import java.util.concurrent.ConcurrentHashMap; | ||||
| 
 | ||||
| /** | ||||
|  * A registry of {@link TurtleUpgradeModel}s. | ||||
|  */ | ||||
| public final class TurtleUpgradeModels { | ||||
|     private static final Object fetchedLock = new Object(); | ||||
|     private static volatile boolean fetchedModels; | ||||
| 
 | ||||
|     private static final Map<ModelKey<? extends TurtleUpgradeModel<?>>, TurtleUpgradeModel.Unbaked<?>> unbaked = new ConcurrentHashMap<>(); | ||||
|     private static final Map<UpgradeType<? extends ITurtleUpgrade>, ModelKey<? extends TurtleUpgradeModel<?>>> modelKeys = new ConcurrentHashMap<>(); | ||||
|     public static final ModelKey<TurtleUpgradeModel<ITurtleUpgrade>> missingModelKey = ClientPlatformHelper.get().createModelKey( | ||||
|         MissingBlockModel.LOCATION, | ||||
|         () -> "Missing turtle model" | ||||
|     ); | ||||
| 
 | ||||
|     private TurtleUpgradeModels() { | ||||
|     } | ||||
| 
 | ||||
|     public static <T extends ITurtleUpgrade> void register(UpgradeType<T> type, TurtleUpgradeModel.Unbaked<? super T> modeller) { | ||||
|         if (fetchedModels) { | ||||
|             throw new IllegalStateException(String.format( | ||||
|                 "Turtle upgrade type %s must be registered before models are baked.", | ||||
|                 RegistryHelper.getKeyOrThrow(RegistryHelper.getRegistry(ITurtleUpgrade.typeRegistry()), type) | ||||
|             )); | ||||
|         } | ||||
| 
 | ||||
|         if (unbaked.putIfAbsent(getModelKey(type), modeller) != null) { | ||||
|             throw new IllegalStateException("Modeller already registered for serialiser"); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public static void fetch(Runnable action) { | ||||
|         if (fetchedModels) return; | ||||
|         synchronized (fetchedLock) { | ||||
|             if (fetchedModels) return; | ||||
|             action.run(); | ||||
|             fetchedModels = true; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @SuppressWarnings("unchecked") | ||||
|     private static <T extends ITurtleUpgrade> ModelKey<TurtleUpgradeModel<? super T>> getModelKey(UpgradeType<T> type) { | ||||
|         return (ModelKey<TurtleUpgradeModel<? super T>>) modelKeys.computeIfAbsent(type, t -> { | ||||
|             var id = RegistryHelper.getKeyOrThrow(RegistryHelper.getRegistry(ITurtleUpgrade.typeRegistry()), t); | ||||
|             return ClientPlatformHelper.get().createModelKey( | ||||
|                 RegistryHelper.getKeyOrThrow(RegistryHelper.getRegistry(ITurtleUpgrade.typeRegistry()), t), | ||||
|                 () -> "Turtle upgrade " + id | ||||
|             ); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     public static <T extends ITurtleUpgrade> TurtleUpgradeModel<? super T> getModeller(T upgrade) { | ||||
|         var modelManager = Minecraft.getInstance().getModelManager(); | ||||
| 
 | ||||
|         @SuppressWarnings("unchecked") | ||||
|         var model = getModelKey((UpgradeType<T>) upgrade.getType()).get(modelManager); | ||||
|         if (model != null) return model; | ||||
| 
 | ||||
|         var missing = missingModelKey.get(modelManager); | ||||
|         if (missing == null) throw new IllegalStateException("Rendering turtles before models are baked"); | ||||
|         return missing; | ||||
|     } | ||||
| 
 | ||||
|     @SuppressWarnings("unchecked") | ||||
|     public static void bake(ClientRegistry.RegisterExtraModels register) { | ||||
|         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); | ||||
|     } | ||||
| } | ||||
| @@ -5,6 +5,7 @@ | ||||
| package dan200.computercraft.data; | ||||
| 
 | ||||
| import com.mojang.serialization.Codec; | ||||
| import dan200.computercraft.api.client.turtle.TurtleUpgradeModel; | ||||
| import dan200.computercraft.api.pocket.IPocketUpgrade; | ||||
| import dan200.computercraft.api.turtle.ITurtleUpgrade; | ||||
| import dan200.computercraft.client.gui.GuiSprites; | ||||
| @@ -52,7 +53,7 @@ public final class DataProviders { | ||||
|         var fullRegistryPatch = RegistryPatchGenerator.createLookup( | ||||
|             generator.registries(), | ||||
|             Util.make(new RegistrySetBuilder(), builder -> { | ||||
|                 builder.add(ITurtleUpgrade.REGISTRY, TurtleUpgradeProvider::addUpgrades); | ||||
|                 builder.add(ITurtleUpgrade.REGISTRY, TurtleUpgradeProvider::register); | ||||
|                 builder.add(IPocketUpgrade.REGISTRY, PocketUpgradeProvider::addUpgrades); | ||||
|             })); | ||||
|         var fullRegistries = fullRegistryPatch.thenApply(RegistrySetBuilder.PatchedRegistries::full); | ||||
| @@ -89,6 +90,7 @@ public final class DataProviders { | ||||
|         }); | ||||
| 
 | ||||
|         generator.addFromCodec("Turtle overlays", PackOutput.Target.RESOURCE_PACK, TurtleOverlay.SOURCE, TurtleOverlay.CODEC, TurtleOverlays::register); | ||||
|         generator.addFromCodec("Turtle upgrade models", PackOutput.Target.RESOURCE_PACK, TurtleUpgradeModel.SOURCE, TurtleUpgradeModel.CODEC, TurtleUpgradeProvider::addModels); | ||||
| 
 | ||||
|         generator.addModels(BlockModelProvider::addBlockModels, ItemModelProvider::addItemModels); | ||||
|     } | ||||
|   | ||||
| @@ -6,6 +6,10 @@ package dan200.computercraft.data; | ||||
| 
 | ||||
| import dan200.computercraft.api.ComputerCraftAPI; | ||||
| import dan200.computercraft.api.ComputerCraftTags; | ||||
| import dan200.computercraft.api.client.turtle.BasicUpgradeModel; | ||||
| import dan200.computercraft.api.client.turtle.ItemUpgradeModel; | ||||
| import dan200.computercraft.api.client.turtle.SelectUpgradeModel; | ||||
| import dan200.computercraft.api.client.turtle.TurtleUpgradeModel; | ||||
| import dan200.computercraft.api.turtle.ITurtleUpgrade; | ||||
| import dan200.computercraft.shared.ModRegistry; | ||||
| import dan200.computercraft.shared.turtle.upgrades.TurtleCraftingTable; | ||||
| @@ -17,21 +21,21 @@ import net.minecraft.resources.ResourceLocation; | ||||
| import net.minecraft.world.item.ItemStack; | ||||
| import net.minecraft.world.item.Items; | ||||
| 
 | ||||
| import java.util.function.BiConsumer; | ||||
| 
 | ||||
| import static dan200.computercraft.api.turtle.TurtleToolBuilder.tool; | ||||
| 
 | ||||
| class TurtleUpgradeProvider { | ||||
|     public static void addUpgrades(BootstrapContext<ITurtleUpgrade> upgrades) { | ||||
|         upgrades.register(id("speaker"), new TurtleSpeaker(new ItemStack(ModRegistry.Items.SPEAKER.get()))); | ||||
|         upgrades.register(vanilla("crafting_table"), new TurtleCraftingTable(new ItemStack(Items.CRAFTING_TABLE))); | ||||
|         upgrades.register(id("wireless_modem_normal"), new TurtleModem(new ItemStack(ModRegistry.Items.WIRELESS_MODEM_NORMAL.get()), false)); | ||||
|         upgrades.register(id("wireless_modem_advanced"), new TurtleModem(new ItemStack(ModRegistry.Items.WIRELESS_MODEM_ADVANCED.get()), true)); | ||||
|     private static final ResourceKey<ITurtleUpgrade> SPEAKER = id("speaker"); | ||||
|     private static final ResourceKey<ITurtleUpgrade> CRAFTING_TABLE = vanilla("crafting_table"); | ||||
|     private static final ResourceKey<ITurtleUpgrade> WIRELESS_MODEM_NORMAL = id("wireless_modem_normal"); | ||||
|     private static final ResourceKey<ITurtleUpgrade> WIRELESS_MODEM_ADVANCED = id("wireless_modem_advanced"); | ||||
| 
 | ||||
|         tool(vanilla("diamond_axe").location(), Items.DIAMOND_AXE).damageMultiplier(6.0f).register(upgrades); | ||||
|         tool(vanilla("diamond_pickaxe"), Items.DIAMOND_PICKAXE).register(upgrades); | ||||
|         tool(vanilla("diamond_hoe"), Items.DIAMOND_HOE).breakable(ComputerCraftTags.Blocks.TURTLE_HOE_BREAKABLE).register(upgrades); | ||||
|         tool(vanilla("diamond_shovel"), Items.DIAMOND_SHOVEL).breakable(ComputerCraftTags.Blocks.TURTLE_SHOVEL_BREAKABLE).register(upgrades); | ||||
|         tool(vanilla("diamond_sword"), Items.DIAMOND_SWORD).breakable(ComputerCraftTags.Blocks.TURTLE_SWORD_BREAKABLE).damageMultiplier(9.0f).register(upgrades); | ||||
|     } | ||||
|     private static final ResourceKey<ITurtleUpgrade> DIAMOND_AXE = vanilla("diamond_axe"); | ||||
|     private static final ResourceKey<ITurtleUpgrade> DIAMOND_PICKAXE = vanilla("diamond_pickaxe"); | ||||
|     private static final ResourceKey<ITurtleUpgrade> DIAMOND_HOE = vanilla("diamond_hoe"); | ||||
|     private static final ResourceKey<ITurtleUpgrade> DIAMOND_SHOVEL = vanilla("diamond_shovel"); | ||||
|     private static final ResourceKey<ITurtleUpgrade> DIAMOND_SWORD = vanilla("diamond_sword"); | ||||
| 
 | ||||
|     private static ResourceKey<ITurtleUpgrade> id(String id) { | ||||
|         return ITurtleUpgrade.createKey(ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, id)); | ||||
| @@ -41,4 +45,52 @@ class TurtleUpgradeProvider { | ||||
|         // Naughty, please don't do this. Mostly here for some semblance of backwards compatibility. | ||||
|         return ITurtleUpgrade.createKey(ResourceLocation.fromNamespaceAndPath("minecraft", id)); | ||||
|     } | ||||
| 
 | ||||
|     public static void register(BootstrapContext<ITurtleUpgrade> upgrades) { | ||||
|         upgrades.register(SPEAKER, new TurtleSpeaker(new ItemStack(ModRegistry.Items.SPEAKER.get()))); | ||||
|         upgrades.register(CRAFTING_TABLE, new TurtleCraftingTable(new ItemStack(Items.CRAFTING_TABLE))); | ||||
|         upgrades.register(WIRELESS_MODEM_NORMAL, new TurtleModem(new ItemStack(ModRegistry.Items.WIRELESS_MODEM_NORMAL.get()), false)); | ||||
|         upgrades.register(WIRELESS_MODEM_ADVANCED, new TurtleModem(new ItemStack(ModRegistry.Items.WIRELESS_MODEM_ADVANCED.get()), true)); | ||||
| 
 | ||||
|         tool(DIAMOND_AXE, Items.DIAMOND_AXE).damageMultiplier(6.0f).register(upgrades); | ||||
|         tool(DIAMOND_PICKAXE, Items.DIAMOND_PICKAXE).register(upgrades); | ||||
|         tool(DIAMOND_HOE, Items.DIAMOND_HOE).breakable(ComputerCraftTags.Blocks.TURTLE_HOE_BREAKABLE).register(upgrades); | ||||
|         tool(DIAMOND_SHOVEL, Items.DIAMOND_SHOVEL).breakable(ComputerCraftTags.Blocks.TURTLE_SHOVEL_BREAKABLE).register(upgrades); | ||||
|         tool(DIAMOND_SWORD, Items.DIAMOND_SWORD).breakable(ComputerCraftTags.Blocks.TURTLE_SWORD_BREAKABLE).damageMultiplier(9.0f).register(upgrades); | ||||
|     } | ||||
| 
 | ||||
|     public static void addModels(BiConsumer<ResourceLocation, TurtleUpgradeModel.Unbaked> out) { | ||||
|         out.accept(SPEAKER.location(), BasicUpgradeModel.unbaked( | ||||
|             ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_speaker_left"), | ||||
|             ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_speaker_right") | ||||
|         )); | ||||
|         out.accept(CRAFTING_TABLE.location(), BasicUpgradeModel.unbaked( | ||||
|             ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_crafting_table_left"), | ||||
|             ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_crafting_table_right") | ||||
|         )); | ||||
| 
 | ||||
|         out.accept(WIRELESS_MODEM_NORMAL.location(), createModemModel("normal")); | ||||
|         out.accept(WIRELESS_MODEM_ADVANCED.location(), createModemModel("advanced")); | ||||
| 
 | ||||
|         out.accept(DIAMOND_AXE.location(), ItemUpgradeModel.unbaked()); | ||||
|         out.accept(DIAMOND_PICKAXE.location(), ItemUpgradeModel.unbaked()); | ||||
|         out.accept(DIAMOND_HOE.location(), ItemUpgradeModel.unbaked()); | ||||
|         out.accept(DIAMOND_SHOVEL.location(), ItemUpgradeModel.unbaked()); | ||||
|         out.accept(DIAMOND_SWORD.location(), ItemUpgradeModel.unbaked()); | ||||
|     } | ||||
| 
 | ||||
|     private static TurtleUpgradeModel.Unbaked createModemModel(String type) { | ||||
|         return SelectUpgradeModel.onComponent(ModRegistry.DataComponents.ON.get()) | ||||
|             .when(false, createBaseModemModel(type, "off")) | ||||
|             .when(true, createBaseModemModel(type, "on")) | ||||
|             .fallback(createBaseModemModel(type, "off")) | ||||
|             .create(); | ||||
|     } | ||||
| 
 | ||||
|     private static TurtleUpgradeModel.Unbaked createBaseModemModel(String type, String state) { | ||||
|         return BasicUpgradeModel.unbaked( | ||||
|             ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_modem_" + type + "_" + state + "_left"), | ||||
|             ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_modem_" + type + "_" + state + "_right") | ||||
|         ); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -24,6 +24,7 @@ import dan200.computercraft.shared.peripheral.monitor.MonitorEdgeState; | ||||
| import dan200.computercraft.shared.peripheral.printer.PrinterBlock; | ||||
| import dan200.computercraft.shared.turtle.blocks.TurtleBlock; | ||||
| import dan200.computercraft.shared.util.DirectionUtil; | ||||
| import net.minecraft.client.color.item.Dye; | ||||
| import net.minecraft.client.data.models.BlockModelGenerators; | ||||
| import net.minecraft.client.data.models.MultiVariant; | ||||
| import net.minecraft.client.data.models.blockstates.ConditionBuilder; | ||||
| @@ -216,7 +217,7 @@ public class BlockModelProvider { | ||||
|         generators.itemModelOutput.accept(block.asItem(), ItemModelUtils.composite( | ||||
|             ItemModelUtils.conditional( | ||||
|                 new HasComponent(DataComponents.DYED_COLOR, false), | ||||
|                 ItemModelUtils.plainModel(TurtleBlockEntityRenderer.COLOUR_TURTLE_MODEL), | ||||
|                 ItemModelUtils.tintedModel(TurtleBlockEntityRenderer.COLOUR_TURTLE_MODEL, new Dye(-1)), | ||||
|                 ItemModelUtils.plainModel(model) | ||||
|             ), | ||||
|             new TurtleUpgradeModel.Unbaked(TurtleSide.LEFT, model), | ||||
|   | ||||
| @@ -0,0 +1,3 @@ | ||||
| { | ||||
|   "type": "computercraft:item" | ||||
| } | ||||
| @@ -11,7 +11,7 @@ import net.minecraft.world.item.ItemStack; | ||||
| // @start region=body | ||||
| public class ExampleTurtleUpgrade extends AbstractTurtleUpgrade { | ||||
|     public ExampleTurtleUpgrade(ItemStack stack) { | ||||
|         super(TurtleUpgradeType.PERIPHERAL, "example", stack); | ||||
|         super(TurtleUpgradeType.PERIPHERAL, "upgrade.examplemod.example_turtle_upgrade.adjective", stack); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|   | ||||
| @@ -2,6 +2,8 @@ package com.example.examplemod.data; | ||||
| 
 | ||||
| import com.example.examplemod.ExampleMod; | ||||
| import com.example.examplemod.ExampleTurtleUpgrade; | ||||
| import dan200.computercraft.api.client.turtle.ItemUpgradeModel; | ||||
| import dan200.computercraft.api.client.turtle.TurtleUpgradeModel; | ||||
| import dan200.computercraft.api.turtle.ITurtleUpgrade; | ||||
| import net.minecraft.Util; | ||||
| import net.minecraft.core.HolderLookup; | ||||
| @@ -13,18 +15,19 @@ import net.minecraft.world.item.ItemStack; | ||||
| import net.minecraft.world.item.Items; | ||||
| 
 | ||||
| import java.util.concurrent.CompletableFuture; | ||||
| import java.util.function.BiConsumer; | ||||
| 
 | ||||
| /** | ||||
|  * Extends the bootstrap registries with our {@linkplain ExampleTurtleUpgrade example turtle upgrade}. | ||||
|  */ | ||||
| // @start region=body | ||||
| public class TurtleUpgradeProvider { | ||||
|     // Define our upgrade ids. | ||||
|     private static final ResourceLocation EXAMPLE_TURTLE_UPGRADE = ResourceLocation.fromNamespaceAndPath(ExampleMod.MOD_ID, "example_turtle_upgrade"); | ||||
| 
 | ||||
|     // Register our turtle upgrades. | ||||
|     public static void addUpgrades(BootstrapContext<ITurtleUpgrade> upgrades) { | ||||
|         upgrades.register( | ||||
|             ITurtleUpgrade.createKey(ResourceLocation.fromNamespaceAndPath(ExampleMod.MOD_ID, "example_turtle_upgrade")), | ||||
|             new ExampleTurtleUpgrade(new ItemStack(Items.COMPASS)) | ||||
|         ); | ||||
|         upgrades.register(ITurtleUpgrade.createKey(EXAMPLE_TURTLE_UPGRADE), new ExampleTurtleUpgrade(new ItemStack(Items.COMPASS))); | ||||
|     } | ||||
| 
 | ||||
|     // Set up the dynamic registries to contain our turtle upgrades. | ||||
| @@ -33,5 +36,10 @@ public class TurtleUpgradeProvider { | ||||
|             builder.add(ITurtleUpgrade.REGISTRY, TurtleUpgradeProvider::addUpgrades); | ||||
|         })); | ||||
|     } | ||||
| 
 | ||||
|     // Register our turtle models. | ||||
|     public static void addUpgradeModels(BiConsumer<ResourceLocation, TurtleUpgradeModel.Unbaked> models) { | ||||
|         models.accept(EXAMPLE_TURTLE_UPGRADE, ItemUpgradeModel.unbaked()); | ||||
|     } | ||||
| } | ||||
| // @end region=body | ||||
|   | ||||
| @@ -0,0 +1,5 @@ | ||||
| { | ||||
|   "type": "computercraft:sided", | ||||
|   "left": "computercraft:block/turtle_speaker_left", | ||||
|   "right": "computercraft:block/turtle_speaker_right" | ||||
| } | ||||
| @@ -0,0 +1,27 @@ | ||||
| { | ||||
|   "type": "computercraft:select", | ||||
|   "cases": [ | ||||
|     { | ||||
|       "model": { | ||||
|         "type": "computercraft:sided", | ||||
|         "left": "computercraft:block/turtle_modem_advanced_off_left", | ||||
|         "right": "computercraft:block/turtle_modem_advanced_off_right" | ||||
|       }, | ||||
|       "when": [false] | ||||
|     }, | ||||
|     { | ||||
|       "model": { | ||||
|         "type": "computercraft:sided", | ||||
|         "left": "computercraft:block/turtle_modem_advanced_on_left", | ||||
|         "right": "computercraft:block/turtle_modem_advanced_on_right" | ||||
|       }, | ||||
|       "when": [true] | ||||
|     } | ||||
|   ], | ||||
|   "fallback": { | ||||
|     "type": "computercraft:sided", | ||||
|     "left": "computercraft:block/turtle_modem_advanced_off_left", | ||||
|     "right": "computercraft:block/turtle_modem_advanced_off_right" | ||||
|   }, | ||||
|   "property": "computercraft:on" | ||||
| } | ||||
| @@ -0,0 +1,27 @@ | ||||
| { | ||||
|   "type": "computercraft:select", | ||||
|   "cases": [ | ||||
|     { | ||||
|       "model": { | ||||
|         "type": "computercraft:sided", | ||||
|         "left": "computercraft:block/turtle_modem_normal_off_left", | ||||
|         "right": "computercraft:block/turtle_modem_normal_off_right" | ||||
|       }, | ||||
|       "when": [false] | ||||
|     }, | ||||
|     { | ||||
|       "model": { | ||||
|         "type": "computercraft:sided", | ||||
|         "left": "computercraft:block/turtle_modem_normal_on_left", | ||||
|         "right": "computercraft:block/turtle_modem_normal_on_right" | ||||
|       }, | ||||
|       "when": [true] | ||||
|     } | ||||
|   ], | ||||
|   "fallback": { | ||||
|     "type": "computercraft:sided", | ||||
|     "left": "computercraft:block/turtle_modem_normal_off_left", | ||||
|     "right": "computercraft:block/turtle_modem_normal_off_right" | ||||
|   }, | ||||
|   "property": "computercraft:on" | ||||
| } | ||||
| @@ -6,7 +6,11 @@ | ||||
|         "type": "minecraft:condition", | ||||
|         "component": "minecraft:dyed_color", | ||||
|         "on_false": {"type": "minecraft:model", "model": "computercraft:block/turtle_advanced"}, | ||||
|         "on_true": {"type": "minecraft:model", "model": "computercraft:block/turtle_colour"}, | ||||
|         "on_true": { | ||||
|           "type": "minecraft:model", | ||||
|           "model": "computercraft:block/turtle_colour", | ||||
|           "tints": [{"type": "minecraft:dye", "default": -1}] | ||||
|         }, | ||||
|         "property": "minecraft:has_component" | ||||
|       }, | ||||
|       {"type": "computercraft:turtle/upgrade", "side": "left", "transforms": "computercraft:block/turtle_advanced"}, | ||||
|   | ||||
| @@ -6,7 +6,11 @@ | ||||
|         "type": "minecraft:condition", | ||||
|         "component": "minecraft:dyed_color", | ||||
|         "on_false": {"type": "minecraft:model", "model": "computercraft:block/turtle_normal"}, | ||||
|         "on_true": {"type": "minecraft:model", "model": "computercraft:block/turtle_colour"}, | ||||
|         "on_true": { | ||||
|           "type": "minecraft:model", | ||||
|           "model": "computercraft:block/turtle_colour", | ||||
|           "tints": [{"type": "minecraft:dye", "default": -1}] | ||||
|         }, | ||||
|         "property": "minecraft:has_component" | ||||
|       }, | ||||
|       {"type": "computercraft:turtle/upgrade", "side": "left", "transforms": "computercraft:block/turtle_normal"}, | ||||
|   | ||||
| @@ -0,0 +1,5 @@ | ||||
| { | ||||
|   "type": "computercraft:sided", | ||||
|   "left": "computercraft:block/turtle_crafting_table_left", | ||||
|   "right": "computercraft:block/turtle_crafting_table_right" | ||||
| } | ||||
| @@ -0,0 +1 @@ | ||||
| {"type": "computercraft:item"} | ||||
| @@ -0,0 +1 @@ | ||||
| {"type": "computercraft:item"} | ||||
| @@ -0,0 +1 @@ | ||||
| {"type": "computercraft:item"} | ||||
| @@ -0,0 +1 @@ | ||||
| {"type": "computercraft:item"} | ||||
| @@ -0,0 +1 @@ | ||||
| {"type": "computercraft:item"} | ||||
| @@ -56,7 +56,7 @@ public class ResourceUtils { | ||||
|                     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); | ||||
|                     return result == null ? null : Pair.of(id, result); | ||||
|                 } catch (Exception exception) { | ||||
|                     LOG.error("Failed to open {} {} from pack '{}'", kind, resource, resource.sourcePackId(), exception); | ||||
|                     return null; | ||||
|   | ||||
| @@ -4,11 +4,11 @@ | ||||
| 
 | ||||
| package dan200.computercraft.api.client; | ||||
| 
 | ||||
| import com.mojang.serialization.MapCodec; | ||||
| import dan200.computercraft.api.client.turtle.RegisterTurtleUpgradeModel; | ||||
| import dan200.computercraft.api.client.turtle.TurtleUpgradeModel; | ||||
| import dan200.computercraft.api.turtle.ITurtleUpgrade; | ||||
| import dan200.computercraft.api.upgrades.UpgradeType; | ||||
| import dan200.computercraft.impl.client.FabricComputerCraftAPIClientService; | ||||
| import net.minecraft.resources.ResourceLocation; | ||||
| 
 | ||||
| /** | ||||
|  * The Fabric-specific entrypoint for ComputerCraft's client-side API. | ||||
| @@ -27,12 +27,11 @@ public final class FabricComputerCraftAPIClient { | ||||
|      * <p> | ||||
|      * This method may be used as a {@link RegisterTurtleUpgradeModel}, for convenient use in multi-loader code. | ||||
|      * | ||||
|      * @param type  The turtle upgrade type. | ||||
|      * @param model The upgrade model. | ||||
|      * @param <T>   The type of the turtle upgrade. | ||||
|      * @param id    The id used for this type of upgrade model. | ||||
|      * @param codec The codec used to read/decode an upgrade model. | ||||
|      */ | ||||
|     public static <T extends ITurtleUpgrade> void registerTurtleUpgradeModeller(UpgradeType<T> type, TurtleUpgradeModel.Unbaked<? super T> model) { | ||||
|         getInstance().registerTurtleUpgradeModeller(type, model); | ||||
|     public static void registerTurtleUpgradeModeller(ResourceLocation id, MapCodec<? extends TurtleUpgradeModel.Unbaked> codec) { | ||||
|         getInstance().registerTurtleUpgradeModeller(id, codec); | ||||
|     } | ||||
| 
 | ||||
|     private static FabricComputerCraftAPIClientService getInstance() { | ||||
|   | ||||
| @@ -4,12 +4,10 @@ | ||||
| 
 | ||||
| package dan200.computercraft.impl.client; | ||||
| 
 | ||||
| import com.mojang.serialization.MapCodec; | ||||
| import dan200.computercraft.api.client.turtle.TurtleUpgradeModel; | ||||
| import dan200.computercraft.api.turtle.ITurtleUpgrade; | ||||
| import dan200.computercraft.api.upgrades.UpgradeType; | ||||
| import dan200.computercraft.impl.Services; | ||||
| import net.minecraft.resources.ResourceLocation; | ||||
| import org.jetbrains.annotations.ApiStatus; | ||||
| import org.jspecify.annotations.Nullable; | ||||
| 
 | ||||
| /** | ||||
|  * Backing interface for CC's client-side API. | ||||
| @@ -17,25 +15,10 @@ import org.jspecify.annotations.Nullable; | ||||
|  * Do <strong>NOT</strong> directly reference this class. It exists for internal use by the API. | ||||
|  */ | ||||
| @ApiStatus.Internal | ||||
| public interface FabricComputerCraftAPIClientService { | ||||
| public interface FabricComputerCraftAPIClientService extends ComputerCraftAPIClientService { | ||||
|     static FabricComputerCraftAPIClientService get() { | ||||
|         var instance = Instance.INSTANCE; | ||||
|         return instance == null ? Services.raise(FabricComputerCraftAPIClientService.class, Instance.ERROR) : instance; | ||||
|         return (FabricComputerCraftAPIClientService) ComputerCraftAPIClientService.get(); | ||||
|     } | ||||
| 
 | ||||
|     <T extends ITurtleUpgrade> void registerTurtleUpgradeModeller(UpgradeType<T> type, TurtleUpgradeModel.Unbaked<? super T> model); | ||||
| 
 | ||||
|     final class Instance { | ||||
|         static final @Nullable FabricComputerCraftAPIClientService INSTANCE; | ||||
|         static final @Nullable Throwable ERROR; | ||||
| 
 | ||||
|         static { | ||||
|             var helper = Services.tryLoad(FabricComputerCraftAPIClientService.class); | ||||
|             INSTANCE = helper.instance(); | ||||
|             ERROR = helper.error(); | ||||
|         } | ||||
| 
 | ||||
|         private Instance() { | ||||
|         } | ||||
|     } | ||||
|     void registerTurtleUpgradeModeller(ResourceLocation id, MapCodec<? extends TurtleUpgradeModel.Unbaked> codec); | ||||
| } | ||||
|   | ||||
| @@ -5,15 +5,26 @@ | ||||
| package dan200.computercraft.impl.client; | ||||
| 
 | ||||
| import com.google.auto.service.AutoService; | ||||
| import com.mojang.serialization.Codec; | ||||
| import com.mojang.serialization.MapCodec; | ||||
| import dan200.computercraft.api.client.turtle.TurtleUpgradeModel; | ||||
| import dan200.computercraft.api.turtle.ITurtleUpgrade; | ||||
| import dan200.computercraft.api.upgrades.UpgradeType; | ||||
| import dan200.computercraft.client.turtle.TurtleUpgradeModels; | ||||
| import net.minecraft.resources.ResourceLocation; | ||||
| import net.minecraft.util.ExtraCodecs; | ||||
| 
 | ||||
| @AutoService(FabricComputerCraftAPIClientService.class) | ||||
| import java.util.function.Function; | ||||
| 
 | ||||
| @AutoService(ComputerCraftAPIClientService.class) | ||||
| public final class FabricComputerCraftAPIClientImpl implements FabricComputerCraftAPIClientService { | ||||
|     private static final ExtraCodecs.LateBoundIdMapper<ResourceLocation, MapCodec<? extends TurtleUpgradeModel.Unbaked>> TURTLE_UPGRADE_MODELS = new ExtraCodecs.LateBoundIdMapper<>(); | ||||
|     private static final Codec<TurtleUpgradeModel.Unbaked> TURTLE_UPGRADE_CODEC = TURTLE_UPGRADE_MODELS.codec(ResourceLocation.CODEC).dispatch(TurtleUpgradeModel.Unbaked::type, Function.identity()); | ||||
| 
 | ||||
|     @Override | ||||
|     public <T extends ITurtleUpgrade> void registerTurtleUpgradeModeller(UpgradeType<T> type, TurtleUpgradeModel.Unbaked<? super T> modeller) { | ||||
|         TurtleUpgradeModels.register(type, modeller); | ||||
|     public Codec<TurtleUpgradeModel.Unbaked> getTurtleUpgradeModelCodec() { | ||||
|         return TURTLE_UPGRADE_CODEC; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void registerTurtleUpgradeModeller(ResourceLocation id, MapCodec<? extends TurtleUpgradeModel.Unbaked> codec) { | ||||
|         TURTLE_UPGRADE_MODELS.put(id, codec); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,14 +0,0 @@ | ||||
| package com.example.examplemod; | ||||
| 
 | ||||
| import dan200.computercraft.api.client.FabricComputerCraftAPIClient; | ||||
| import dan200.computercraft.api.client.turtle.TurtleUpgradeModel; | ||||
| import net.fabricmc.api.ClientModInitializer; | ||||
| 
 | ||||
| public class FabricExampleModClient implements ClientModInitializer { | ||||
|     @Override | ||||
|     public void onInitializeClient() { | ||||
|         // @start region=turtle_model | ||||
|         FabricComputerCraftAPIClient.registerTurtleUpgradeModeller(ExampleMod.EXAMPLE_TURTLE_UPGRADE, TurtleUpgradeModel.flatItem()); | ||||
|         // @end region=turtle_model | ||||
|     } | ||||
| } | ||||
| @@ -1,15 +1,20 @@ | ||||
| package com.example.examplemod; | ||||
| 
 | ||||
| import com.example.examplemod.data.TurtleUpgradeProvider; | ||||
| import dan200.computercraft.api.client.turtle.TurtleUpgradeModel; | ||||
| import net.fabricmc.fabric.api.datagen.v1.DataGeneratorEntrypoint; | ||||
| import net.fabricmc.fabric.api.datagen.v1.FabricDataGenerator; | ||||
| import net.fabricmc.fabric.api.datagen.v1.FabricDataOutput; | ||||
| import net.fabricmc.fabric.api.datagen.v1.provider.FabricCodecDataProvider; | ||||
| import net.fabricmc.fabric.api.datagen.v1.provider.FabricDynamicRegistryProvider; | ||||
| import net.fabricmc.fabric.api.event.registry.DynamicRegistries; | ||||
| import net.minecraft.core.HolderLookup; | ||||
| import net.minecraft.core.RegistrySetBuilder; | ||||
| import net.minecraft.data.PackOutput; | ||||
| import net.minecraft.resources.ResourceLocation; | ||||
| 
 | ||||
| import java.util.concurrent.CompletableFuture; | ||||
| import java.util.function.BiConsumer; | ||||
| 
 | ||||
| /** | ||||
|  * Data generators for our Fabric example mod. | ||||
| @@ -25,6 +30,17 @@ public class FabricExampleModDataGenerator implements DataGeneratorEntrypoint { | ||||
|     private static void addTurtleUpgrades(FabricDataGenerator.Pack pack, CompletableFuture<HolderLookup.Provider> registries) { | ||||
|         var fullRegistryPatch = TurtleUpgradeProvider.makeUpgradeRegistry(registries); | ||||
|         pack.addProvider((FabricDataOutput output) -> new AutomaticDynamicRegistryProvider(output, fullRegistryPatch)); | ||||
|         pack.addProvider((FabricDataOutput output) -> new FabricCodecDataProvider<>(output, registries, PackOutput.Target.RESOURCE_PACK, TurtleUpgradeModel.SOURCE, TurtleUpgradeModel.CODEC) { | ||||
|             @Override | ||||
|             public String getName() { | ||||
|                 return "Turtle upgrade models"; | ||||
|             } | ||||
| 
 | ||||
|             @Override | ||||
|             protected void configure(BiConsumer<ResourceLocation, TurtleUpgradeModel.Unbaked> out, HolderLookup.Provider provider) { | ||||
|                 TurtleUpgradeProvider.addUpgradeModels(out); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|   | ||||
| @@ -4,9 +4,8 @@ | ||||
| 
 | ||||
| package dan200.computercraft.api.client.turtle; | ||||
| 
 | ||||
| import dan200.computercraft.api.turtle.ITurtleUpgrade; | ||||
| import dan200.computercraft.api.turtle.TurtleUpgradeType; | ||||
| import dan200.computercraft.api.upgrades.UpgradeType; | ||||
| import com.mojang.serialization.MapCodec; | ||||
| import net.minecraft.resources.ResourceLocation; | ||||
| import net.neoforged.bus.api.Event; | ||||
| import net.neoforged.fml.event.IModBusEvent; | ||||
| import net.neoforged.fml.event.lifecycle.FMLClientSetupEvent; | ||||
| @@ -14,8 +13,7 @@ import net.neoforged.fml.event.lifecycle.FMLCommonSetupEvent; | ||||
| import org.jetbrains.annotations.ApiStatus; | ||||
| 
 | ||||
| /** | ||||
|  * This event is fired to register {@link TurtleUpgradeModel}s for a mod's {@linkplain TurtleUpgradeType turtle | ||||
|  * upgrades}. | ||||
|  * This event is fired to register additional {@link TurtleUpgradeModel}s. | ||||
|  * <p> | ||||
|  * This event is fired during the initial mod construction. Registries will be frozen, but mods may not be fully | ||||
|  * initialised at this point (i.e. {@link FMLCommonSetupEvent} or {@link FMLClientSetupEvent} may not have been | ||||
| @@ -30,10 +28,10 @@ public class RegisterTurtleModelEvent extends Event implements IModBusEvent, Reg | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * {@inheritDoc} | ||||
|      * {@inheritDoc}The codec used to read/decode an upgrade model. | ||||
|      */ | ||||
|     @Override | ||||
|     public <T extends ITurtleUpgrade> void register(UpgradeType<T> type, TurtleUpgradeModel.Unbaked<? super T> modeller) { | ||||
|         dispatch.register(type, modeller); | ||||
|     public void register(ResourceLocation id, MapCodec<? extends TurtleUpgradeModel.Unbaked> model) { | ||||
|         dispatch.register(id, model); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -10,7 +10,6 @@ import dan200.computercraft.api.client.turtle.RegisterTurtleModelEvent; | ||||
| 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; | ||||
| import net.minecraft.client.renderer.entity.ItemFrameRenderer; | ||||
| import net.minecraft.client.resources.model.ModelBaker; | ||||
| @@ -19,7 +18,6 @@ import net.minecraft.resources.ResourceLocation; | ||||
| import net.minecraft.util.context.ContextKey; | ||||
| import net.neoforged.api.distmarker.Dist; | ||||
| import net.neoforged.bus.api.SubscribeEvent; | ||||
| import net.neoforged.fml.ModLoader; | ||||
| import net.neoforged.fml.common.EventBusSubscriber; | ||||
| import net.neoforged.fml.event.lifecycle.FMLClientSetupEvent; | ||||
| import net.neoforged.neoforge.client.event.*; | ||||
| @@ -44,8 +42,6 @@ public final class ForgeClientRegistry { | ||||
| 
 | ||||
|     @SubscribeEvent | ||||
|     public static void registerModels(ModelEvent.RegisterStandalone event) { | ||||
|         TurtleUpgradeModels.fetch(() -> ModLoader.postEvent(new RegisterTurtleModelEvent(TurtleUpgradeModels::register))); | ||||
| 
 | ||||
|         // Load resources | ||||
|         Queue<Runnable> tasks = new ArrayDeque<>(); | ||||
|         var state = ClientRegistry.gatherExtraModels(Minecraft.getInstance().getResourceManager(), tasks::add); | ||||
|   | ||||
| @@ -0,0 +1,26 @@ | ||||
| // SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers | ||||
| // | ||||
| // SPDX-License-Identifier: MPL-2.0 | ||||
| 
 | ||||
| package dan200.computercraft.impl.client; | ||||
| 
 | ||||
| import com.google.auto.service.AutoService; | ||||
| import com.mojang.serialization.Codec; | ||||
| import com.mojang.serialization.MapCodec; | ||||
| import dan200.computercraft.api.client.turtle.RegisterTurtleModelEvent; | ||||
| import dan200.computercraft.api.client.turtle.TurtleUpgradeModel; | ||||
| import net.minecraft.resources.ResourceLocation; | ||||
| import net.minecraft.util.ExtraCodecs; | ||||
| import net.neoforged.fml.ModLoader; | ||||
| 
 | ||||
| import java.util.function.Function; | ||||
| 
 | ||||
| @AutoService(ComputerCraftAPIClientService.class) | ||||
| public final class ForgeComputerCraftAPIClientImpl implements ComputerCraftAPIClientService { | ||||
|     @Override | ||||
|     public Codec<TurtleUpgradeModel.Unbaked> getTurtleUpgradeModelCodec() { | ||||
|         var idMapper = new ExtraCodecs.LateBoundIdMapper<ResourceLocation, MapCodec<? extends TurtleUpgradeModel.Unbaked>>(); | ||||
|         ModLoader.postEvent(new RegisterTurtleModelEvent(idMapper::put)); | ||||
|         return idMapper.codec(ResourceLocation.CODEC).dispatch(TurtleUpgradeModel.Unbaked::type, Function.identity()); | ||||
|     } | ||||
| } | ||||
| @@ -1,20 +0,0 @@ | ||||
| package com.example.examplemod; | ||||
| 
 | ||||
| import dan200.computercraft.api.client.turtle.RegisterTurtleModelEvent; | ||||
| import dan200.computercraft.api.client.turtle.TurtleUpgradeModel; | ||||
| import net.neoforged.api.distmarker.Dist; | ||||
| import net.neoforged.bus.api.SubscribeEvent; | ||||
| import net.neoforged.fml.common.EventBusSubscriber; | ||||
| 
 | ||||
| /** | ||||
|  * The client-side entry point for the Forge version of our example mod. | ||||
|  */ | ||||
| @EventBusSubscriber(modid = ExampleMod.MOD_ID, value = Dist.CLIENT, bus = EventBusSubscriber.Bus.MOD) | ||||
| public class ForgeExampleModClient { | ||||
|     // @start region=turtle_model | ||||
|     @SubscribeEvent | ||||
|     public static void onRegisterTurtleModellers(RegisterTurtleModelEvent event) { | ||||
|         event.register(ExampleMod.EXAMPLE_TURTLE_UPGRADE, TurtleUpgradeModel.flatItem()); | ||||
|     } | ||||
|     // @end region=turtle_model | ||||
| } | ||||
| @@ -1,11 +1,14 @@ | ||||
| package com.example.examplemod; | ||||
| 
 | ||||
| import com.example.examplemod.data.TurtleUpgradeProvider; | ||||
| import dan200.computercraft.api.client.turtle.TurtleUpgradeModel; | ||||
| import net.minecraft.core.HolderLookup; | ||||
| import net.minecraft.data.DataGenerator; | ||||
| import net.minecraft.data.PackOutput; | ||||
| import net.neoforged.bus.api.SubscribeEvent; | ||||
| import net.neoforged.fml.common.EventBusSubscriber; | ||||
| import net.neoforged.neoforge.common.data.DatapackBuiltinEntriesProvider; | ||||
| import net.neoforged.neoforge.common.data.JsonCodecProvider; | ||||
| import net.neoforged.neoforge.data.event.GatherDataEvent; | ||||
| 
 | ||||
| import java.util.Set; | ||||
| @@ -17,7 +20,7 @@ import java.util.concurrent.CompletableFuture; | ||||
| @EventBusSubscriber(bus = EventBusSubscriber.Bus.MOD) | ||||
| public class ForgeExampleModDataGenerator { | ||||
|     @SubscribeEvent | ||||
|     public static void gather(GatherDataEvent event) { | ||||
|     public static void gather(GatherDataEvent.Client event) { | ||||
|         var pack = event.getGenerator().getVanillaPack(true); | ||||
|         addTurtleUpgrades(pack, event.getLookupProvider()); | ||||
|     } | ||||
| @@ -26,6 +29,12 @@ public class ForgeExampleModDataGenerator { | ||||
|     private static void addTurtleUpgrades(DataGenerator.PackGenerator pack, CompletableFuture<HolderLookup.Provider> registries) { | ||||
|         var fullRegistryPatch = TurtleUpgradeProvider.makeUpgradeRegistry(registries); | ||||
|         pack.addProvider(o -> new DatapackBuiltinEntriesProvider(o, fullRegistryPatch, Set.of(ExampleMod.MOD_ID))); | ||||
|         pack.addProvider(o -> new JsonCodecProvider<>(o, PackOutput.Target.RESOURCE_PACK, TurtleUpgradeModel.SOURCE, TurtleUpgradeModel.CODEC, registries, ExampleMod.MOD_ID) { | ||||
|             @Override | ||||
|             protected void gather() { | ||||
|                 TurtleUpgradeProvider.addUpgradeModels(this::unconditional); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
|     // @end region=turtle_upgrades | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Jonathan Coates
					Jonathan Coates