diff --git a/projects/common-api/src/client/java/dan200/computercraft/api/client/StandaloneModel.java b/projects/common-api/src/client/java/dan200/computercraft/api/client/StandaloneModel.java index 008ed08ce..36d61d03f 100644 --- a/projects/common-api/src/client/java/dan200/computercraft/api/client/StandaloneModel.java +++ b/projects/common-api/src/client/java/dan200/computercraft/api/client/StandaloneModel.java @@ -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 { + @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(); diff --git a/projects/common-api/src/client/java/dan200/computercraft/api/client/turtle/BasicUpgradeModel.java b/projects/common-api/src/client/java/dan200/computercraft/api/client/turtle/BasicUpgradeModel.java new file mode 100644 index 000000000..300e30d05 --- /dev/null +++ b/projects/common-api/src/client/java/dan200/computercraft/api/client/turtle/BasicUpgradeModel.java @@ -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. + *

+ * 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 CODEC = RecordCodecBuilder.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 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 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 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()); + } + } +} diff --git a/projects/common-api/src/client/java/dan200/computercraft/api/client/turtle/ItemUpgradeModel.java b/projects/common-api/src/client/java/dan200/computercraft/api/client/turtle/ItemUpgradeModel.java index 190bf5361..5a23de62a 100644 --- a/projects/common-api/src/client/java/dan200/computercraft/api/client/turtle/ItemUpgradeModel.java +++ b/projects/common-api/src/client/java/dan200/computercraft/api/client/turtle/ItemUpgradeModel.java @@ -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 implements TurtleUpgradeModel { - static final TurtleUpgradeModel.Unbaked UNBAKED = new Unbaked(); - static final TurtleUpgradeModel INSTANCE = new ItemUpgradeModel<>(); +/** + * A sic {@link TurtleUpgradeModel} that renders the upgrade's {@linkplain ITurtleUpgrade#getUpgradeItem(DataComponentPatch) + * upgrade item}. + *

+ * 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 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 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 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 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 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 { + private static final class Unbaked implements TurtleUpgradeModel.Unbaked { @Override - public TurtleUpgradeModel bake(ModelBaker baker) { + public MapCodec type() { + return CODEC; + } + + @Override + public TurtleUpgradeModel bake(ModelBaker baker) { return INSTANCE; } diff --git a/projects/common-api/src/client/java/dan200/computercraft/api/client/turtle/RegisterTurtleUpgradeModel.java b/projects/common-api/src/client/java/dan200/computercraft/api/client/turtle/RegisterTurtleUpgradeModel.java index 3100d2b5a..e6d94478c 100644 --- a/projects/common-api/src/client/java/dan200/computercraft/api/client/turtle/RegisterTurtleUpgradeModel.java +++ b/projects/common-api/src/client/java/dan200/computercraft/api/client/turtle/RegisterTurtleUpgradeModel.java @@ -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}. *

* 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 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. */ - void register(UpgradeType type, TurtleUpgradeModel.Unbaked mode); + void register(ResourceLocation id, MapCodec model); } diff --git a/projects/common-api/src/client/java/dan200/computercraft/api/client/turtle/SelectUpgradeModel.java b/projects/common-api/src/client/java/dan200/computercraft/api/client/turtle/SelectUpgradeModel.java new file mode 100644 index 000000000..2288b7e71 --- /dev/null +++ b/projects/common-api/src/client/java/dan200/computercraft/api/client/turtle/SelectUpgradeModel.java @@ -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}. + *

+ * This is the {@link TurtleUpgradeModel} equivalent of {@link SelectItemModel}. + * + * @param The type of value to switch on. + */ +public final class SelectUpgradeModel implements TurtleUpgradeModel { + public static final ResourceLocation ID = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "select"); + public static final MapCodec CODEC = RecordCodecBuilder.>mapCodec(instance -> instance.group( + Cases.CODEC.forGetter(Unbaked::cases), + TurtleUpgradeModel.CODEC.optionalFieldOf("fallback").forGetter(Unbaked::fallback) + ).apply(instance, Unbaked::new)); + + private final DataComponentType component; + private final Map cases; + private final TurtleUpgradeModel fallback; + + private SelectUpgradeModel(DataComponentType component, Map cases, TurtleUpgradeModel fallback) { + this.component = component; + this.cases = cases; + this.fallback = fallback; + } + + private TurtleUpgradeModel getModel(UpgradeData 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 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 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( + Cases cases, + Optional fallback + ) implements TurtleUpgradeModel.Unbaked { + private static final TurtleUpgradeModel.Unbaked MISSING = BasicUpgradeModel.unbaked(MissingBlockModel.LOCATION, MissingBlockModel.LOCATION); + + @Override + public MapCodec type() { + return CODEC; + } + + @Override + public TurtleUpgradeModel bake(ModelBaker baker) { + Map 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(DataComponentType component, List, TurtleUpgradeModel.Unbaked>> cases) { + private static final MapCodec> CODEC = DataComponentType.CODEC.dispatchMap("property", Cases::component, Util.memoize(Cases::codec)); + + private static MapCodec> codec(DataComponentType 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::new)); + } + + private static Codec, TurtleUpgradeModel.Unbaked>> caseCodec(Codec 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 DataResult, TurtleUpgradeModel.Unbaked>>> validate(List, TurtleUpgradeModel.Unbaked>> cases) { + Multiset 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 The type the component stores. + * @return A {@link Builder}. + */ + public static Builder onComponent(DataComponentType component) { + return new Builder<>(component); + } + + /** + * A builder for constructing {@link SelectUpgradeModel}s. + * + * @param The type of value to switch on. + */ + public static final class Builder { + private final DataComponentType component; + private final List, TurtleUpgradeModel.Unbaked>> cases = new ArrayList<>(); + private TurtleUpgradeModel.@Nullable Unbaked fallback; + + private Builder(DataComponentType 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 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 when(List 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 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)); + } + } +} diff --git a/projects/common-api/src/client/java/dan200/computercraft/api/client/turtle/SidedUpgradeModel.java b/projects/common-api/src/client/java/dan200/computercraft/api/client/turtle/SidedUpgradeModel.java deleted file mode 100644 index 4020607d2..000000000 --- a/projects/common-api/src/client/java/dan200/computercraft/api/client/turtle/SidedUpgradeModel.java +++ /dev/null @@ -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( - StandaloneModel left, StandaloneModel right -) implements TurtleUpgradeModelViaStandalone { - @Override - public StandaloneModel getModel(T upgrade, TurtleSide side, DataComponentPatch data) { - return switch (side) { - case LEFT -> left(); - case RIGHT -> right(); - }; - } - - record Unbaked( - ResourceLocation left, ResourceLocation right - ) implements TurtleUpgradeModel.Unbaked { - - @Override - public TurtleUpgradeModel 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()); - } - } -} diff --git a/projects/common-api/src/client/java/dan200/computercraft/api/client/turtle/TurtleUpgradeModel.java b/projects/common-api/src/client/java/dan200/computercraft/api/client/turtle/TurtleUpgradeModel.java index 786c288b4..49e75e602 100644 --- a/projects/common-api/src/client/java/dan200/computercraft/api/client/turtle/TurtleUpgradeModel.java +++ b/projects/common-api/src/client/java/dan200/computercraft/api/client/turtle/TurtleUpgradeModel.java @@ -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}. *

- * 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. + *

+ * 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. + *

+ * See {@link ITurtleUpgrade} for a full example of registering turtle upgrades and their models. * - *

Example

- *

Fabric

- * {@snippet class=com.example.examplemod.FabricExampleModClient region=turtle_model} - * - *

Forge

- * {@snippet class=com.example.examplemod.FabricExampleModClient region=turtle_model} - * - * @param 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 { +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 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 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 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 The type of turtle upgrade for this model. */ - interface Unbaked extends ResolvableModel { - TurtleUpgradeModel 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 type(); - /** - * A basic {@link TurtleUpgradeModel} which renders using the upgrade's {@linkplain ITurtleUpgrade#getUpgradeItem(DataComponentPatch) - * upgrade item}. - *

- * 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. - * - * @param The type of the turtle upgrade. - * @return The constructed modeller. - */ - static TurtleUpgradeModel.Unbaked 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 The type of the turtle upgrade. - * @return The constructed modeller. - */ - static TurtleUpgradeModel.Unbaked sided(ResourceLocation left, ResourceLocation right) { - return new SidedUpgradeModel.Unbaked<>(left, right); + /** + * Bake this turtle model. + * + * @param baker The current model baker + * @return The baked upgrade model. + * @see ItemModel.Unbaked#bake(ItemModel.BakingContext) + */ + TurtleUpgradeModel bake(ModelBaker baker); } } diff --git a/projects/common-api/src/client/java/dan200/computercraft/api/client/turtle/TurtleUpgradeModelViaStandalone.java b/projects/common-api/src/client/java/dan200/computercraft/api/client/turtle/TurtleUpgradeModelViaStandalone.java deleted file mode 100644 index 81ea70ad6..000000000 --- a/projects/common-api/src/client/java/dan200/computercraft/api/client/turtle/TurtleUpgradeModelViaStandalone.java +++ /dev/null @@ -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 extends TurtleUpgradeModel { - 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); - } -} diff --git a/projects/common-api/src/client/java/dan200/computercraft/impl/client/ComputerCraftAPIClientService.java b/projects/common-api/src/client/java/dan200/computercraft/impl/client/ComputerCraftAPIClientService.java new file mode 100644 index 000000000..8f811b807 --- /dev/null +++ b/projects/common-api/src/client/java/dan200/computercraft/impl/client/ComputerCraftAPIClientService.java @@ -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 + *

+ * Do NOT 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 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() { + } + } +} diff --git a/projects/common-api/src/main/java/dan200/computercraft/api/turtle/ITurtleUpgrade.java b/projects/common-api/src/main/java/dan200/computercraft/api/turtle/ITurtleUpgrade.java index d15fe4e46..88830317d 100644 --- a/projects/common-api/src/main/java/dan200/computercraft/api/turtle/ITurtleUpgrade.java +++ b/projects/common-api/src/main/java/dan200/computercraft/api/turtle/ITurtleUpgrade.java @@ -51,16 +51,6 @@ import java.util.function.Function; *

Forge

* {@snippet class=com.example.examplemod.ForgeExampleMod region=turtle_upgrades} * - *

Rendering the upgrade

- * 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. - * - *

Fabric

- * {@snippet class=com.example.examplemod.FabricExampleModClient region=turtle_model} - * - *

Forge

- * {@snippet class=com.example.examplemod.FabricExampleModClient region=turtle_model} - * *

Registering the upgrade itself

* 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//computercraft/turtle_upgrade/.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}. *

- * 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//computercraft/turtle_upgrade/.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} * diff --git a/projects/common/src/client/java/dan200/computercraft/client/ClientRegistry.java b/projects/common/src/client/java/dan200/computercraft/client/ClientRegistry.java index ec97a004b..5ce1ef4eb 100644 --- a/projects/common/src/client/java/dan200/computercraft/client/ClientRegistry.java +++ b/projects/common/src/client/java/dan200/computercraft/client/ClientRegistry.java @@ -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 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 turtleOverlays + Map turtleOverlays, + Map turtleUpgrades ) { } @@ -148,8 +141,9 @@ public final class ClientRegistry { * @return A promise which contains our extra models. */ public static CompletableFuture gatherExtraModels(ResourceManager resources, Executor executor) { - var turtleOverlays = TurtleOverlayManager.load(resources, executor); - return turtleOverlays.thenApply(ExtraModels::new); + 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> register) { diff --git a/projects/common/src/client/java/dan200/computercraft/client/CustomModelManager.java b/projects/common/src/client/java/dan200/computercraft/client/CustomModelManager.java new file mode 100644 index 000000000..e380fe3e8 --- /dev/null +++ b/projects/common/src/client/java/dan200/computercraft/client/CustomModelManager.java @@ -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 The type of unbaked model. + * @param The type of baked model. + */ +public class CustomModelManager { + private final String kind; + private final FileToIdConverter lister; + private final Codec codec; + private final BiFunction bake; + + private final ModelKey missingModelKey; + private final U missingModel; + + private final Map> modelKeys = new ConcurrentHashMap<>(); + + public CustomModelManager(String kind, FileToIdConverter lister, Codec codec, BiFunction 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 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> 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 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; + } +} diff --git a/projects/common/src/client/java/dan200/computercraft/client/item/model/TurtleOverlayModel.java b/projects/common/src/client/java/dan200/computercraft/client/item/model/TurtleOverlayModel.java index 54dbb61e8..b2daabfee 100644 --- a/projects/common/src/client/java/dan200/computercraft/client/item/model/TurtleOverlayModel.java +++ b/projects/common/src/client/java/dan200/computercraft/client/item/model/TurtleOverlayModel.java @@ -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)); } diff --git a/projects/common/src/client/java/dan200/computercraft/client/item/model/TurtleUpgradeModel.java b/projects/common/src/client/java/dan200/computercraft/client/item/model/TurtleUpgradeModel.java index 6f92b63dd..de8bc0c07 100644 --- a/projects/common/src/client/java/dan200/computercraft/client/item/model/TurtleUpgradeModel.java +++ b/projects/common/src/client/java/dan200/computercraft/client/item/model/TurtleUpgradeModel.java @@ -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 { diff --git a/projects/common/src/client/java/dan200/computercraft/client/item/properties/TurtleShowElfOverlay.java b/projects/common/src/client/java/dan200/computercraft/client/item/properties/TurtleShowElfOverlay.java index adef290b9..4440909b2 100644 --- a/projects/common/src/client/java/dan200/computercraft/client/item/properties/TurtleShowElfOverlay.java +++ b/projects/common/src/client/java/dan200/computercraft/client/item/properties/TurtleShowElfOverlay.java @@ -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(); } diff --git a/projects/common/src/client/java/dan200/computercraft/client/render/TurtleBlockEntityRenderer.java b/projects/common/src/client/java/dan200/computercraft/client/render/TurtleBlockEntityRenderer.java index 76f0d1e1e..3fb152b99 100644 --- a/projects/common/src/client/java/dan200/computercraft/client/render/TurtleBlockEntityRenderer.java +++ b/projects/common/src/client/java/dan200/computercraft/client/render/TurtleBlockEntityRenderer.java @@ -11,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 normal, ModemModels advanced -) implements TurtleUpgradeModelViaStandalone { - public static final TurtleUpgradeModel.Unbaked 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 { - @Override - public TurtleUpgradeModel 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 leftOffModel, T rightOffModel, - T leftOnModel, T rightOnModel - ) { - private static final ModemModels NORMAL = create("normal"); - private static final ModemModels ADVANCED = create("advanced"); - - static ModemModels 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 out) { - out.accept(leftOffModel()); - out.accept(rightOffModel); - out.accept(leftOnModel()); - out.accept(rightOnModel()); - } - - public ModemModels map(Function mapper) { - return new ModemModels<>( - mapper.apply(leftOffModel()), mapper.apply(rightOffModel()), - mapper.apply(leftOnModel()), mapper.apply(rightOnModel()) - ); - } - } -} diff --git a/projects/common/src/client/java/dan200/computercraft/client/turtle/TurtleOverlayManager.java b/projects/common/src/client/java/dan200/computercraft/client/turtle/TurtleOverlayManager.java index 9728c15e9..54fec50a0 100644 --- a/projects/common/src/client/java/dan200/computercraft/client/turtle/TurtleOverlayManager.java +++ b/projects/common/src/client/java/dan200/computercraft/client/turtle/TurtleOverlayManager.java @@ -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 MISSING_KEY = ClientPlatformHelper.get().createModelKey( - MissingBlockModel.LOCATION, () -> "Missing turtle overlay" + private static final CustomModelManager loader = new CustomModelManager<>( + "turtle overlay", FileToIdConverter.json(TurtleOverlay.SOURCE), TurtleOverlay.CODEC, + TurtleOverlay.Unbaked::bake, + new TurtleOverlay.Unbaked(MissingBlockModel.LOCATION, false) ); - private static final Map> modelKeys = new ConcurrentHashMap<>(); - private static ModelKey getModelKey(ResourceLocation overlay) { - return modelKeys.computeIfAbsent(overlay, o -> ClientPlatformHelper.get().createModelKey(o, () -> "Turtle overlay " + o)); - } - - /** - * Load our overlays from resources. - * - * @param resources The current resource manager. - * @param executor The executor to schedule work on. - * @return The map of unbaked overlay. - */ - public static CompletableFuture> load(ResourceManager resources, Executor executor) { - return ResourceUtils.load(resources, executor, "turtle overlay", ID_CONVERTER, JsonOps.INSTANCE, TurtleOverlay.CODEC); - } - - /** - * Register our unbaked overlay models. - * - * @param register The callback to register models with. - * @param overlays The overlays to register. - */ - public static void register(ClientRegistry.RegisterExtraModels register, Map overlays) { - overlays.forEach((id, overlay) -> register.register(getModelKey(id), overlay, TurtleOverlay.Unbaked::bake)); - register.register(MISSING_KEY, new TurtleOverlay.Unbaked(MissingBlockModel.LOCATION, false), TurtleOverlay.Unbaked::bake); + public static CustomModelManager 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); } } diff --git a/projects/common/src/client/java/dan200/computercraft/client/turtle/TurtleUpgradeModelManager.java b/projects/common/src/client/java/dan200/computercraft/client/turtle/TurtleUpgradeModelManager.java new file mode 100644 index 000000000..2c2c5c6f8 --- /dev/null +++ b/projects/common/src/client/java/dan200/computercraft/client/turtle/TurtleUpgradeModelManager.java @@ -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 loader = new CustomModelManager<>( + "turtle upgrade", FileToIdConverter.json(TurtleUpgradeModel.SOURCE), TurtleUpgradeModel.CODEC, + TurtleUpgradeModel.Unbaked::bake, + BasicUpgradeModel.unbaked(MissingBlockModel.LOCATION, MissingBlockModel.LOCATION) + ); + + public static CustomModelManager 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 upgrade) { + return upgrade == null ? null : loader.get(modelManager, upgrade.key().location()); + } +} diff --git a/projects/common/src/client/java/dan200/computercraft/client/turtle/TurtleUpgradeModels.java b/projects/common/src/client/java/dan200/computercraft/client/turtle/TurtleUpgradeModels.java deleted file mode 100644 index bbc1fb81d..000000000 --- a/projects/common/src/client/java/dan200/computercraft/client/turtle/TurtleUpgradeModels.java +++ /dev/null @@ -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>, TurtleUpgradeModel.Unbaked> unbaked = new ConcurrentHashMap<>(); - private static final Map, ModelKey>> modelKeys = new ConcurrentHashMap<>(); - public static final ModelKey> missingModelKey = ClientPlatformHelper.get().createModelKey( - MissingBlockModel.LOCATION, - () -> "Missing turtle model" - ); - - private TurtleUpgradeModels() { - } - - public static void register(UpgradeType type, TurtleUpgradeModel.Unbaked 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 ModelKey> getModelKey(UpgradeType type) { - return (ModelKey>) 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 TurtleUpgradeModel getModeller(T upgrade) { - var modelManager = Minecraft.getInstance().getModelManager(); - - @SuppressWarnings("unchecked") - var model = getModelKey((UpgradeType) 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>) id, model, TurtleUpgradeModel.Unbaked::bake)); - register.register(missingModelKey, TurtleUpgradeModel.sided(MissingBlockModel.LOCATION, MissingBlockModel.LOCATION), TurtleUpgradeModel.Unbaked::bake); - } -} diff --git a/projects/common/src/datagen/java/dan200/computercraft/data/DataProviders.java b/projects/common/src/datagen/java/dan200/computercraft/data/DataProviders.java index 83b611925..7243a7b21 100644 --- a/projects/common/src/datagen/java/dan200/computercraft/data/DataProviders.java +++ b/projects/common/src/datagen/java/dan200/computercraft/data/DataProviders.java @@ -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); } diff --git a/projects/common/src/datagen/java/dan200/computercraft/data/TurtleUpgradeProvider.java b/projects/common/src/datagen/java/dan200/computercraft/data/TurtleUpgradeProvider.java index 2ecf48a2b..4a06b5248 100644 --- a/projects/common/src/datagen/java/dan200/computercraft/data/TurtleUpgradeProvider.java +++ b/projects/common/src/datagen/java/dan200/computercraft/data/TurtleUpgradeProvider.java @@ -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 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 SPEAKER = id("speaker"); + private static final ResourceKey CRAFTING_TABLE = vanilla("crafting_table"); + private static final ResourceKey WIRELESS_MODEM_NORMAL = id("wireless_modem_normal"); + private static final ResourceKey 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 DIAMOND_AXE = vanilla("diamond_axe"); + private static final ResourceKey DIAMOND_PICKAXE = vanilla("diamond_pickaxe"); + private static final ResourceKey DIAMOND_HOE = vanilla("diamond_hoe"); + private static final ResourceKey DIAMOND_SHOVEL = vanilla("diamond_shovel"); + private static final ResourceKey DIAMOND_SWORD = vanilla("diamond_sword"); private static ResourceKey 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 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 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") + ); + } } diff --git a/projects/common/src/datagen/java/dan200/computercraft/data/client/BlockModelProvider.java b/projects/common/src/datagen/java/dan200/computercraft/data/client/BlockModelProvider.java index 0f3da02f5..127ae3f34 100644 --- a/projects/common/src/datagen/java/dan200/computercraft/data/client/BlockModelProvider.java +++ b/projects/common/src/datagen/java/dan200/computercraft/data/client/BlockModelProvider.java @@ -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), diff --git a/projects/common/src/examples/generatedResources/assets/examplemod/computercraft/turtle_upgrade/example_turtle_upgrade.json b/projects/common/src/examples/generatedResources/assets/examplemod/computercraft/turtle_upgrade/example_turtle_upgrade.json new file mode 100644 index 000000000..777731e4d --- /dev/null +++ b/projects/common/src/examples/generatedResources/assets/examplemod/computercraft/turtle_upgrade/example_turtle_upgrade.json @@ -0,0 +1,3 @@ +{ + "type": "computercraft:item" +} \ No newline at end of file diff --git a/projects/common/src/examples/java/com/example/examplemod/ExampleTurtleUpgrade.java b/projects/common/src/examples/java/com/example/examplemod/ExampleTurtleUpgrade.java index 895b173a4..05c3be12b 100644 --- a/projects/common/src/examples/java/com/example/examplemod/ExampleTurtleUpgrade.java +++ b/projects/common/src/examples/java/com/example/examplemod/ExampleTurtleUpgrade.java @@ -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 diff --git a/projects/common/src/examples/java/com/example/examplemod/data/TurtleUpgradeProvider.java b/projects/common/src/examples/java/com/example/examplemod/data/TurtleUpgradeProvider.java index 156330962..bcc09c5a2 100644 --- a/projects/common/src/examples/java/com/example/examplemod/data/TurtleUpgradeProvider.java +++ b/projects/common/src/examples/java/com/example/examplemod/data/TurtleUpgradeProvider.java @@ -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 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 models) { + models.accept(EXAMPLE_TURTLE_UPGRADE, ItemUpgradeModel.unbaked()); + } } // @end region=body diff --git a/projects/common/src/generated/resources/assets/computercraft/computercraft/turtle_upgrade/speaker.json b/projects/common/src/generated/resources/assets/computercraft/computercraft/turtle_upgrade/speaker.json new file mode 100644 index 000000000..254938868 --- /dev/null +++ b/projects/common/src/generated/resources/assets/computercraft/computercraft/turtle_upgrade/speaker.json @@ -0,0 +1,5 @@ +{ + "type": "computercraft:sided", + "left": "computercraft:block/turtle_speaker_left", + "right": "computercraft:block/turtle_speaker_right" +} diff --git a/projects/common/src/generated/resources/assets/computercraft/computercraft/turtle_upgrade/wireless_modem_advanced.json b/projects/common/src/generated/resources/assets/computercraft/computercraft/turtle_upgrade/wireless_modem_advanced.json new file mode 100644 index 000000000..d3b09bee9 --- /dev/null +++ b/projects/common/src/generated/resources/assets/computercraft/computercraft/turtle_upgrade/wireless_modem_advanced.json @@ -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" +} diff --git a/projects/common/src/generated/resources/assets/computercraft/computercraft/turtle_upgrade/wireless_modem_normal.json b/projects/common/src/generated/resources/assets/computercraft/computercraft/turtle_upgrade/wireless_modem_normal.json new file mode 100644 index 000000000..6a25f2dae --- /dev/null +++ b/projects/common/src/generated/resources/assets/computercraft/computercraft/turtle_upgrade/wireless_modem_normal.json @@ -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" +} diff --git a/projects/common/src/generated/resources/assets/computercraft/items/turtle_advanced.json b/projects/common/src/generated/resources/assets/computercraft/items/turtle_advanced.json index 4437ea570..1b996348a 100644 --- a/projects/common/src/generated/resources/assets/computercraft/items/turtle_advanced.json +++ b/projects/common/src/generated/resources/assets/computercraft/items/turtle_advanced.json @@ -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"}, diff --git a/projects/common/src/generated/resources/assets/computercraft/items/turtle_normal.json b/projects/common/src/generated/resources/assets/computercraft/items/turtle_normal.json index feafcdd85..bd7871925 100644 --- a/projects/common/src/generated/resources/assets/computercraft/items/turtle_normal.json +++ b/projects/common/src/generated/resources/assets/computercraft/items/turtle_normal.json @@ -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"}, diff --git a/projects/common/src/generated/resources/assets/minecraft/computercraft/turtle_upgrade/crafting_table.json b/projects/common/src/generated/resources/assets/minecraft/computercraft/turtle_upgrade/crafting_table.json new file mode 100644 index 000000000..8f1e08336 --- /dev/null +++ b/projects/common/src/generated/resources/assets/minecraft/computercraft/turtle_upgrade/crafting_table.json @@ -0,0 +1,5 @@ +{ + "type": "computercraft:sided", + "left": "computercraft:block/turtle_crafting_table_left", + "right": "computercraft:block/turtle_crafting_table_right" +} diff --git a/projects/common/src/generated/resources/assets/minecraft/computercraft/turtle_upgrade/diamond_axe.json b/projects/common/src/generated/resources/assets/minecraft/computercraft/turtle_upgrade/diamond_axe.json new file mode 100644 index 000000000..809bb09ad --- /dev/null +++ b/projects/common/src/generated/resources/assets/minecraft/computercraft/turtle_upgrade/diamond_axe.json @@ -0,0 +1 @@ +{"type": "computercraft:item"} diff --git a/projects/common/src/generated/resources/assets/minecraft/computercraft/turtle_upgrade/diamond_hoe.json b/projects/common/src/generated/resources/assets/minecraft/computercraft/turtle_upgrade/diamond_hoe.json new file mode 100644 index 000000000..809bb09ad --- /dev/null +++ b/projects/common/src/generated/resources/assets/minecraft/computercraft/turtle_upgrade/diamond_hoe.json @@ -0,0 +1 @@ +{"type": "computercraft:item"} diff --git a/projects/common/src/generated/resources/assets/minecraft/computercraft/turtle_upgrade/diamond_pickaxe.json b/projects/common/src/generated/resources/assets/minecraft/computercraft/turtle_upgrade/diamond_pickaxe.json new file mode 100644 index 000000000..809bb09ad --- /dev/null +++ b/projects/common/src/generated/resources/assets/minecraft/computercraft/turtle_upgrade/diamond_pickaxe.json @@ -0,0 +1 @@ +{"type": "computercraft:item"} diff --git a/projects/common/src/generated/resources/assets/minecraft/computercraft/turtle_upgrade/diamond_shovel.json b/projects/common/src/generated/resources/assets/minecraft/computercraft/turtle_upgrade/diamond_shovel.json new file mode 100644 index 000000000..809bb09ad --- /dev/null +++ b/projects/common/src/generated/resources/assets/minecraft/computercraft/turtle_upgrade/diamond_shovel.json @@ -0,0 +1 @@ +{"type": "computercraft:item"} diff --git a/projects/common/src/generated/resources/assets/minecraft/computercraft/turtle_upgrade/diamond_sword.json b/projects/common/src/generated/resources/assets/minecraft/computercraft/turtle_upgrade/diamond_sword.json new file mode 100644 index 000000000..809bb09ad --- /dev/null +++ b/projects/common/src/generated/resources/assets/minecraft/computercraft/turtle_upgrade/diamond_sword.json @@ -0,0 +1 @@ +{"type": "computercraft:item"} diff --git a/projects/common/src/main/java/dan200/computercraft/shared/util/ResourceUtils.java b/projects/common/src/main/java/dan200/computercraft/shared/util/ResourceUtils.java index 2c8bd7e17..63f5a8198 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/util/ResourceUtils.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/util/ResourceUtils.java @@ -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; diff --git a/projects/fabric-api/src/client/java/dan200/computercraft/api/client/FabricComputerCraftAPIClient.java b/projects/fabric-api/src/client/java/dan200/computercraft/api/client/FabricComputerCraftAPIClient.java index ac5e1b4d5..b023adee4 100644 --- a/projects/fabric-api/src/client/java/dan200/computercraft/api/client/FabricComputerCraftAPIClient.java +++ b/projects/fabric-api/src/client/java/dan200/computercraft/api/client/FabricComputerCraftAPIClient.java @@ -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 { *

* 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 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 void registerTurtleUpgradeModeller(UpgradeType type, TurtleUpgradeModel.Unbaked model) { - getInstance().registerTurtleUpgradeModeller(type, model); + public static void registerTurtleUpgradeModeller(ResourceLocation id, MapCodec codec) { + getInstance().registerTurtleUpgradeModeller(id, codec); } private static FabricComputerCraftAPIClientService getInstance() { diff --git a/projects/fabric-api/src/client/java/dan200/computercraft/impl/client/FabricComputerCraftAPIClientService.java b/projects/fabric-api/src/client/java/dan200/computercraft/impl/client/FabricComputerCraftAPIClientService.java index b1e421672..9314a063d 100644 --- a/projects/fabric-api/src/client/java/dan200/computercraft/impl/client/FabricComputerCraftAPIClientService.java +++ b/projects/fabric-api/src/client/java/dan200/computercraft/impl/client/FabricComputerCraftAPIClientService.java @@ -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 NOT 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(); } - void registerTurtleUpgradeModeller(UpgradeType type, TurtleUpgradeModel.Unbaked 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 codec); } diff --git a/projects/fabric/src/client/java/dan200/computercraft/impl/client/FabricComputerCraftAPIClientImpl.java b/projects/fabric/src/client/java/dan200/computercraft/impl/client/FabricComputerCraftAPIClientImpl.java index 6b90fd004..fa35b1533 100644 --- a/projects/fabric/src/client/java/dan200/computercraft/impl/client/FabricComputerCraftAPIClientImpl.java +++ b/projects/fabric/src/client/java/dan200/computercraft/impl/client/FabricComputerCraftAPIClientImpl.java @@ -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> TURTLE_UPGRADE_MODELS = new ExtraCodecs.LateBoundIdMapper<>(); + private static final Codec TURTLE_UPGRADE_CODEC = TURTLE_UPGRADE_MODELS.codec(ResourceLocation.CODEC).dispatch(TurtleUpgradeModel.Unbaked::type, Function.identity()); + @Override - public void registerTurtleUpgradeModeller(UpgradeType type, TurtleUpgradeModel.Unbaked modeller) { - TurtleUpgradeModels.register(type, modeller); + public Codec getTurtleUpgradeModelCodec() { + return TURTLE_UPGRADE_CODEC; + } + + @Override + public void registerTurtleUpgradeModeller(ResourceLocation id, MapCodec codec) { + TURTLE_UPGRADE_MODELS.put(id, codec); } } diff --git a/projects/fabric/src/examples/java/com/example/examplemod/FabricExampleModClient.java b/projects/fabric/src/examples/java/com/example/examplemod/FabricExampleModClient.java deleted file mode 100644 index 40ecba67e..000000000 --- a/projects/fabric/src/examples/java/com/example/examplemod/FabricExampleModClient.java +++ /dev/null @@ -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 - } -} diff --git a/projects/fabric/src/examples/java/com/example/examplemod/FabricExampleModDataGenerator.java b/projects/fabric/src/examples/java/com/example/examplemod/FabricExampleModDataGenerator.java index 502298971..47d9e2a6a 100644 --- a/projects/fabric/src/examples/java/com/example/examplemod/FabricExampleModDataGenerator.java +++ b/projects/fabric/src/examples/java/com/example/examplemod/FabricExampleModDataGenerator.java @@ -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 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 out, HolderLookup.Provider provider) { + TurtleUpgradeProvider.addUpgradeModels(out); + } + }); } /** diff --git a/projects/forge-api/src/client/java/dan200/computercraft/api/client/turtle/RegisterTurtleModelEvent.java b/projects/forge-api/src/client/java/dan200/computercraft/api/client/turtle/RegisterTurtleModelEvent.java index 24b2da98a..99968d365 100644 --- a/projects/forge-api/src/client/java/dan200/computercraft/api/client/turtle/RegisterTurtleModelEvent.java +++ b/projects/forge-api/src/client/java/dan200/computercraft/api/client/turtle/RegisterTurtleModelEvent.java @@ -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. *

* 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 void register(UpgradeType type, TurtleUpgradeModel.Unbaked modeller) { - dispatch.register(type, modeller); + public void register(ResourceLocation id, MapCodec model) { + dispatch.register(id, model); } } diff --git a/projects/forge/src/client/java/dan200/computercraft/client/ForgeClientRegistry.java b/projects/forge/src/client/java/dan200/computercraft/client/ForgeClientRegistry.java index 70f4c4755..31ce44cec 100644 --- a/projects/forge/src/client/java/dan200/computercraft/client/ForgeClientRegistry.java +++ b/projects/forge/src/client/java/dan200/computercraft/client/ForgeClientRegistry.java @@ -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 tasks = new ArrayDeque<>(); var state = ClientRegistry.gatherExtraModels(Minecraft.getInstance().getResourceManager(), tasks::add); diff --git a/projects/forge/src/client/java/dan200/computercraft/impl/client/ForgeComputerCraftAPIClientImpl.java b/projects/forge/src/client/java/dan200/computercraft/impl/client/ForgeComputerCraftAPIClientImpl.java new file mode 100644 index 000000000..2a1edda7c --- /dev/null +++ b/projects/forge/src/client/java/dan200/computercraft/impl/client/ForgeComputerCraftAPIClientImpl.java @@ -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 getTurtleUpgradeModelCodec() { + var idMapper = new ExtraCodecs.LateBoundIdMapper>(); + ModLoader.postEvent(new RegisterTurtleModelEvent(idMapper::put)); + return idMapper.codec(ResourceLocation.CODEC).dispatch(TurtleUpgradeModel.Unbaked::type, Function.identity()); + } +} diff --git a/projects/forge/src/examples/java/com/example/examplemod/ForgeExampleModClient.java b/projects/forge/src/examples/java/com/example/examplemod/ForgeExampleModClient.java deleted file mode 100644 index 79656f2dd..000000000 --- a/projects/forge/src/examples/java/com/example/examplemod/ForgeExampleModClient.java +++ /dev/null @@ -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 -} diff --git a/projects/forge/src/examples/java/com/example/examplemod/ForgeExampleModDataGenerator.java b/projects/forge/src/examples/java/com/example/examplemod/ForgeExampleModDataGenerator.java index 4013c5ef9..a47bd612d 100644 --- a/projects/forge/src/examples/java/com/example/examplemod/ForgeExampleModDataGenerator.java +++ b/projects/forge/src/examples/java/com/example/examplemod/ForgeExampleModDataGenerator.java @@ -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 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 }