mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2025-09-01 18:17:55 +00:00
Make turtle upgrade models data driven
It's ItemModel, but for upgrades!
This commit is contained in:
@@ -7,15 +7,11 @@ package dan200.computercraft.api.client;
|
||||
import com.google.common.base.Suppliers;
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import dan200.computercraft.api.client.turtle.TurtleUpgradeModel;
|
||||
import dan200.computercraft.api.turtle.ITurtleAccess;
|
||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
||||
import dan200.computercraft.api.turtle.TurtleSide;
|
||||
import net.minecraft.client.multiplayer.ClientLevel;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.client.renderer.RenderType;
|
||||
import net.minecraft.client.renderer.Sheets;
|
||||
import net.minecraft.client.renderer.block.model.BakedQuad;
|
||||
import net.minecraft.client.renderer.block.model.ItemTransform;
|
||||
import net.minecraft.client.renderer.item.BlockModelWrapper;
|
||||
import net.minecraft.client.renderer.item.ItemModel;
|
||||
import net.minecraft.client.renderer.item.ItemModelResolver;
|
||||
@@ -24,7 +20,6 @@ import net.minecraft.client.renderer.texture.TextureAtlasSprite;
|
||||
import net.minecraft.client.resources.model.BlockModelRotation;
|
||||
import net.minecraft.client.resources.model.ModelBaker;
|
||||
import net.minecraft.client.resources.model.ResolvedModel;
|
||||
import net.minecraft.core.component.DataComponentPatch;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.util.ARGB;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
@@ -84,6 +79,27 @@ public final class StandaloneModel {
|
||||
* @return The baked {@link StandaloneModel}.
|
||||
*/
|
||||
public static StandaloneModel of(ResolvedModel model, ModelBaker baker) {
|
||||
return baker.compute(new CacheKey(model));
|
||||
}
|
||||
|
||||
private record CacheKey(ResolvedModel model) implements ModelBaker.SharedOperationKey<StandaloneModel> {
|
||||
@Override
|
||||
public StandaloneModel compute(ModelBaker baker) {
|
||||
return ofUncached(model(), baker);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
return other instanceof CacheKey(var otherModel) && model() == otherModel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return System.identityHashCode(model());
|
||||
}
|
||||
}
|
||||
|
||||
private static StandaloneModel ofUncached(ResolvedModel model, ModelBaker baker) {
|
||||
var slots = model.getTopTextureSlots();
|
||||
return new StandaloneModel(
|
||||
model.bakeTopGeometry(slots, baker, BlockModelRotation.X0_Y0).getAll(),
|
||||
@@ -98,7 +114,6 @@ public final class StandaloneModel {
|
||||
*
|
||||
* @param layer The layer to set up.
|
||||
* @see ItemModel#update(ItemStackRenderState, ItemStack, ItemModelResolver, ItemDisplayContext, ClientLevel, LivingEntity, int)
|
||||
* @see TurtleUpgradeModel#renderForItem(ITurtleUpgrade, TurtleSide, DataComponentPatch, ItemStackRenderState, ItemModelResolver, ItemTransform, int)
|
||||
*/
|
||||
public void setupItemLayer(ItemStackRenderState.LayerRenderState layer) {
|
||||
layer.setExtents(extents);
|
||||
@@ -115,7 +130,6 @@ public final class StandaloneModel {
|
||||
* @param buffers The buffer source to use for rendering.
|
||||
* @param light The current light texture coordinate.
|
||||
* @param overlay The current overlay texture coordinate.
|
||||
* @see TurtleUpgradeModel#renderForLevel(ITurtleUpgrade, ITurtleAccess, TurtleSide, DataComponentPatch, PoseStack, MultiBufferSource, int, int)
|
||||
*/
|
||||
public void render(PoseStack transform, MultiBufferSource buffers, int light, int overlay) {
|
||||
render(transform, buffers, light, overlay, null);
|
||||
@@ -129,7 +143,6 @@ public final class StandaloneModel {
|
||||
* @param light The current light texture coordinate.
|
||||
* @param overlay The current overlay texture coordinate.
|
||||
* @param tints The tints for this model.
|
||||
* @see TurtleUpgradeModel#renderForLevel(ITurtleUpgrade, ITurtleAccess, TurtleSide, DataComponentPatch, PoseStack, MultiBufferSource, int, int)
|
||||
*/
|
||||
public void render(PoseStack transform, MultiBufferSource buffers, int light, int overlay, int @Nullable [] tints) {
|
||||
var pose = transform.last();
|
||||
|
@@ -0,0 +1,91 @@
|
||||
// SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.api.client.turtle;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.serialization.MapCodec;
|
||||
import com.mojang.serialization.codecs.RecordCodecBuilder;
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.api.client.StandaloneModel;
|
||||
import dan200.computercraft.api.turtle.ITurtleAccess;
|
||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
||||
import dan200.computercraft.api.turtle.TurtleSide;
|
||||
import dan200.computercraft.api.upgrades.UpgradeData;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.client.renderer.block.model.ItemTransform;
|
||||
import net.minecraft.client.renderer.item.BlockModelWrapper;
|
||||
import net.minecraft.client.renderer.item.ItemModelResolver;
|
||||
import net.minecraft.client.renderer.item.ItemStackRenderState;
|
||||
import net.minecraft.client.resources.model.ModelBaker;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
/**
|
||||
* A {@link TurtleUpgradeModel} that renders a basic model.
|
||||
* <p>
|
||||
* This is the {@link TurtleUpgradeModel} equivalent of {@link BlockModelWrapper}.
|
||||
*/
|
||||
public final class BasicUpgradeModel implements TurtleUpgradeModel {
|
||||
public static final ResourceLocation ID = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "sided");
|
||||
public static final MapCodec<? extends TurtleUpgradeModel.Unbaked> CODEC = RecordCodecBuilder.<Unbaked>mapCodec(instance -> instance.group(
|
||||
ResourceLocation.CODEC.fieldOf("left").forGetter(Unbaked::left),
|
||||
ResourceLocation.CODEC.fieldOf("right").forGetter(Unbaked::right)
|
||||
).apply(instance, Unbaked::new));
|
||||
|
||||
private final StandaloneModel left;
|
||||
private final StandaloneModel right;
|
||||
|
||||
private BasicUpgradeModel(StandaloneModel left, StandaloneModel right) {
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an unbaked {@link BasicUpgradeModel}.
|
||||
*
|
||||
* @param left The model when equipped on the left.
|
||||
* @param right The model when equipped on the right.
|
||||
* @return The unbaked turtle upgrade model.
|
||||
*/
|
||||
public static TurtleUpgradeModel.Unbaked unbaked(ResourceLocation left, ResourceLocation right) {
|
||||
return new Unbaked(left, right);
|
||||
}
|
||||
|
||||
private StandaloneModel getModel(TurtleSide side) {
|
||||
return switch (side) {
|
||||
case LEFT -> left;
|
||||
case RIGHT -> right;
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void renderForItem(UpgradeData<ITurtleUpgrade> upgrade, TurtleSide side, ItemStackRenderState renderer, ItemModelResolver resolver, ItemTransform transform, int seed) {
|
||||
var layer = renderer.newLayer();
|
||||
layer.setTransform(transform);
|
||||
getModel(side).setupItemLayer(layer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void renderForLevel(UpgradeData<ITurtleUpgrade> upgrade, TurtleSide side, ITurtleAccess turtle, PoseStack transform, MultiBufferSource buffers, int light, int overlay) {
|
||||
getModel(side).render(transform, buffers, light, overlay);
|
||||
}
|
||||
|
||||
private record Unbaked(ResourceLocation left, ResourceLocation right) implements TurtleUpgradeModel.Unbaked {
|
||||
@Override
|
||||
public MapCodec<? extends TurtleUpgradeModel.Unbaked> type() {
|
||||
return CODEC;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TurtleUpgradeModel bake(ModelBaker baker) {
|
||||
return new BasicUpgradeModel(StandaloneModel.of(left(), baker), StandaloneModel.of(right(), baker));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resolveDependencies(Resolver resolver) {
|
||||
resolver.markDependency(left());
|
||||
resolver.markDependency(right());
|
||||
}
|
||||
}
|
||||
}
|
@@ -7,9 +7,12 @@ package dan200.computercraft.api.client.turtle;
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.math.Axis;
|
||||
import com.mojang.math.Transformation;
|
||||
import com.mojang.serialization.MapCodec;
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.api.turtle.ITurtleAccess;
|
||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
||||
import dan200.computercraft.api.turtle.TurtleSide;
|
||||
import dan200.computercraft.api.upgrades.UpgradeData;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.client.renderer.block.model.ItemTransform;
|
||||
@@ -18,15 +21,26 @@ import net.minecraft.client.renderer.item.ItemStackRenderState;
|
||||
import net.minecraft.client.renderer.special.SpecialModelRenderer;
|
||||
import net.minecraft.client.resources.model.ModelBaker;
|
||||
import net.minecraft.core.component.DataComponentPatch;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.item.ItemDisplayContext;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import org.joml.Matrix4f;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
final class ItemUpgradeModel<T extends ITurtleUpgrade> implements TurtleUpgradeModel<T> {
|
||||
static final TurtleUpgradeModel.Unbaked<ITurtleUpgrade> UNBAKED = new Unbaked();
|
||||
static final TurtleUpgradeModel<ITurtleUpgrade> INSTANCE = new ItemUpgradeModel<>();
|
||||
/**
|
||||
* A sic {@link TurtleUpgradeModel} that renders the upgrade's {@linkplain ITurtleUpgrade#getUpgradeItem(DataComponentPatch)
|
||||
* upgrade item}.
|
||||
* <p>
|
||||
* This uses appropriate transformations for "flat" items, namely those extending the {@literal minecraft:item/generated}
|
||||
* model type. It will not appear correct for 3D models with additional depth, such as blocks.
|
||||
*/
|
||||
public final class ItemUpgradeModel implements TurtleUpgradeModel {
|
||||
private static final TurtleUpgradeModel.Unbaked UNBAKED = new Unbaked();
|
||||
private static final TurtleUpgradeModel INSTANCE = new ItemUpgradeModel();
|
||||
|
||||
public static final ResourceLocation ID = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "item");
|
||||
public static final MapCodec<TurtleUpgradeModel.Unbaked> CODEC = MapCodec.unit(UNBAKED);
|
||||
|
||||
private static final TransformedRenderer LEFT = computeRenderer(TurtleSide.LEFT);
|
||||
private static final TransformedRenderer RIGHT = computeRenderer(TurtleSide.RIGHT);
|
||||
@@ -34,10 +48,19 @@ final class ItemUpgradeModel<T extends ITurtleUpgrade> implements TurtleUpgradeM
|
||||
private ItemUpgradeModel() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the unbaked {@link ItemUpgradeModel}.
|
||||
*
|
||||
* @return The unbaked item upgrade model.
|
||||
*/
|
||||
public static TurtleUpgradeModel.Unbaked unbaked() {
|
||||
return UNBAKED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void renderForItem(T upgrade, TurtleSide side, DataComponentPatch data, ItemStackRenderState renderer, ItemModelResolver resolver, ItemTransform transform, int seed) {
|
||||
public void renderForItem(UpgradeData<ITurtleUpgrade> upgrade, TurtleSide side, ItemStackRenderState renderer, ItemModelResolver resolver, ItemTransform transform, int seed) {
|
||||
var childState = new ItemStackRenderState();
|
||||
resolver.updateForTopItem(childState, upgrade.getUpgradeItem(data), ItemDisplayContext.NONE, null, null, seed);
|
||||
resolver.updateForTopItem(childState, upgrade.getUpgradeItem(), ItemDisplayContext.NONE, null, null, seed);
|
||||
if (!childState.isEmpty()) {
|
||||
var layer = renderer.newLayer();
|
||||
layer.setTransform(transform);
|
||||
@@ -46,17 +69,22 @@ final class ItemUpgradeModel<T extends ITurtleUpgrade> implements TurtleUpgradeM
|
||||
}
|
||||
|
||||
@Override
|
||||
public void renderForLevel(T upgrade, ITurtleAccess turtle, TurtleSide side, DataComponentPatch data, PoseStack transform, MultiBufferSource buffers, int light, int overlay) {
|
||||
public void renderForLevel(UpgradeData<ITurtleUpgrade> upgrade, TurtleSide side, ITurtleAccess turtle, PoseStack transform, MultiBufferSource buffers, int light, int overlay) {
|
||||
transform.mulPose(getRenderer(side).transform().getMatrix());
|
||||
transform.mulPose(Axis.YP.rotation(Mth.PI));
|
||||
Minecraft.getInstance().getItemRenderer().renderStatic(
|
||||
upgrade.getUpgradeItem(data), ItemDisplayContext.FIXED, light, overlay, transform, buffers, turtle.getLevel(), 0
|
||||
upgrade.getUpgradeItem(), ItemDisplayContext.FIXED, light, overlay, transform, buffers, turtle.getLevel(), 0
|
||||
);
|
||||
}
|
||||
|
||||
private static final class Unbaked implements TurtleUpgradeModel.Unbaked<ITurtleUpgrade> {
|
||||
private static final class Unbaked implements TurtleUpgradeModel.Unbaked {
|
||||
@Override
|
||||
public TurtleUpgradeModel<ITurtleUpgrade> bake(ModelBaker baker) {
|
||||
public MapCodec<? extends TurtleUpgradeModel.Unbaked> type() {
|
||||
return CODEC;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TurtleUpgradeModel bake(ModelBaker baker) {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
|
@@ -4,11 +4,11 @@
|
||||
|
||||
package dan200.computercraft.api.client.turtle;
|
||||
|
||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
||||
import dan200.computercraft.api.upgrades.UpgradeType;
|
||||
import com.mojang.serialization.MapCodec;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
/**
|
||||
* A functional interface to register a {@link TurtleUpgradeModel} for a class of turtle upgrades.
|
||||
* A functional interface to register a {@link TurtleUpgradeModel}.
|
||||
* <p>
|
||||
* This interface is largely intended to be used from multi-loader code, to allow sharing registration code between
|
||||
* multiple loaders.
|
||||
@@ -18,9 +18,8 @@ public interface RegisterTurtleUpgradeModel {
|
||||
/**
|
||||
* Register a {@link TurtleUpgradeModel}.
|
||||
*
|
||||
* @param type The turtle upgrade type.
|
||||
* @param mode The unbaked upgrade model.
|
||||
* @param <T> The type of the turtle upgrade.
|
||||
* @param id The id used for this type of upgrade model.
|
||||
* @param model The codec used to read/decode an upgrade model.
|
||||
*/
|
||||
<T extends ITurtleUpgrade> void register(UpgradeType<T> type, TurtleUpgradeModel.Unbaked<? super T> mode);
|
||||
void register(ResourceLocation id, MapCodec<? extends TurtleUpgradeModel.Unbaked> model);
|
||||
}
|
||||
|
@@ -0,0 +1,209 @@
|
||||
// SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.api.client.turtle;
|
||||
|
||||
import com.google.common.collect.HashMultiset;
|
||||
import com.google.common.collect.Multiset;
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.datafixers.util.Pair;
|
||||
import com.mojang.serialization.Codec;
|
||||
import com.mojang.serialization.DataResult;
|
||||
import com.mojang.serialization.MapCodec;
|
||||
import com.mojang.serialization.codecs.RecordCodecBuilder;
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.api.turtle.ITurtleAccess;
|
||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
||||
import dan200.computercraft.api.turtle.TurtleSide;
|
||||
import dan200.computercraft.api.upgrades.UpgradeData;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||
import net.minecraft.Util;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.client.renderer.block.model.ItemTransform;
|
||||
import net.minecraft.client.renderer.item.ItemModelResolver;
|
||||
import net.minecraft.client.renderer.item.ItemStackRenderState;
|
||||
import net.minecraft.client.renderer.item.SelectItemModel;
|
||||
import net.minecraft.client.resources.model.MissingBlockModel;
|
||||
import net.minecraft.client.resources.model.ModelBaker;
|
||||
import net.minecraft.core.component.DataComponentType;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* A {@link TurtleUpgradeModel} which selects between different models based on the value of a component in
|
||||
* {@linkplain UpgradeData#data() the upgrade's data}.
|
||||
* <p>
|
||||
* This is the {@link TurtleUpgradeModel} equivalent of {@link SelectItemModel}.
|
||||
*
|
||||
* @param <T> The type of value to switch on.
|
||||
*/
|
||||
public final class SelectUpgradeModel<T> implements TurtleUpgradeModel {
|
||||
public static final ResourceLocation ID = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "select");
|
||||
public static final MapCodec<? extends TurtleUpgradeModel.Unbaked> CODEC = RecordCodecBuilder.<Unbaked<?>>mapCodec(instance -> instance.group(
|
||||
Cases.CODEC.forGetter(Unbaked::cases),
|
||||
TurtleUpgradeModel.CODEC.optionalFieldOf("fallback").forGetter(Unbaked::fallback)
|
||||
).apply(instance, Unbaked::new));
|
||||
|
||||
private final DataComponentType<T> component;
|
||||
private final Map<T, TurtleUpgradeModel> cases;
|
||||
private final TurtleUpgradeModel fallback;
|
||||
|
||||
private SelectUpgradeModel(DataComponentType<T> component, Map<T, TurtleUpgradeModel> cases, TurtleUpgradeModel fallback) {
|
||||
this.component = component;
|
||||
this.cases = cases;
|
||||
this.fallback = fallback;
|
||||
}
|
||||
|
||||
private TurtleUpgradeModel getModel(UpgradeData<ITurtleUpgrade> upgrade) {
|
||||
var value = upgrade.data().get(component);
|
||||
if (value == null || value.isEmpty()) return fallback;
|
||||
|
||||
var model = cases.get(value.get());
|
||||
return model != null ? model : fallback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void renderForItem(UpgradeData<ITurtleUpgrade> upgrade, TurtleSide side, ItemStackRenderState renderer, ItemModelResolver resolver, ItemTransform transform, int seed) {
|
||||
getModel(upgrade).renderForItem(upgrade, side, renderer, resolver, transform, seed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void renderForLevel(UpgradeData<ITurtleUpgrade> upgrade, TurtleSide side, ITurtleAccess turtle, PoseStack transform, MultiBufferSource buffers, int light, int overlay) {
|
||||
getModel(upgrade).renderForLevel(upgrade, side, turtle, transform, buffers, light, overlay);
|
||||
}
|
||||
|
||||
private record Unbaked<T>(
|
||||
Cases<T> cases,
|
||||
Optional<TurtleUpgradeModel.Unbaked> fallback
|
||||
) implements TurtleUpgradeModel.Unbaked {
|
||||
private static final TurtleUpgradeModel.Unbaked MISSING = BasicUpgradeModel.unbaked(MissingBlockModel.LOCATION, MissingBlockModel.LOCATION);
|
||||
|
||||
@Override
|
||||
public MapCodec<? extends TurtleUpgradeModel.Unbaked> type() {
|
||||
return CODEC;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TurtleUpgradeModel bake(ModelBaker baker) {
|
||||
Map<T, TurtleUpgradeModel> cases = new Object2ObjectOpenHashMap<>();
|
||||
for (var condition : cases().cases()) {
|
||||
var model = condition.getSecond().bake(baker);
|
||||
for (var when : condition.getFirst()) cases.put(when, model);
|
||||
}
|
||||
|
||||
return new SelectUpgradeModel<>(cases().component(), cases, fallback().orElse(MISSING).bake(baker));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resolveDependencies(Resolver resolver) {
|
||||
cases().cases().forEach(x -> x.getSecond().resolveDependencies(resolver));
|
||||
fallback().orElse(MISSING).resolveDependencies(resolver);
|
||||
}
|
||||
}
|
||||
|
||||
private record Cases<T>(DataComponentType<T> component, List<Pair<List<T>, TurtleUpgradeModel.Unbaked>> cases) {
|
||||
private static final MapCodec<Cases<?>> CODEC = DataComponentType.CODEC.dispatchMap("property", Cases::component, Util.memoize(Cases::codec));
|
||||
|
||||
private static <T> MapCodec<Cases<T>> codec(DataComponentType<T> component) {
|
||||
return RecordCodecBuilder.mapCodec(instance -> instance.group(
|
||||
MapCodec.unit(component).forGetter(Cases::component),
|
||||
caseCodec(component.codecOrThrow()).listOf().fieldOf("cases").validate(Cases::validate).forGetter(Cases::cases)
|
||||
).apply(instance, Cases<T>::new));
|
||||
}
|
||||
|
||||
private static <T> Codec<Pair<List<T>, TurtleUpgradeModel.Unbaked>> caseCodec(Codec<T> codec) {
|
||||
return RecordCodecBuilder.create(instance -> instance.group(
|
||||
codec.listOf().fieldOf("when").forGetter(Pair::getFirst),
|
||||
TurtleUpgradeModel.CODEC.fieldOf("model").forGetter(Pair::getSecond)
|
||||
).apply(instance, Pair::new));
|
||||
}
|
||||
|
||||
private static <T> DataResult<List<Pair<List<T>, TurtleUpgradeModel.Unbaked>>> validate(List<Pair<List<T>, TurtleUpgradeModel.Unbaked>> cases) {
|
||||
Multiset<T> multiset = HashMultiset.create();
|
||||
for (var condition : cases) multiset.addAll(condition.getFirst());
|
||||
|
||||
if (multiset.isEmpty()) return DataResult.error(() -> "Empty cases");
|
||||
if (multiset.size() != multiset.entrySet().size()) {
|
||||
return DataResult.error(() -> "Duplicate case conditions: " + multiset.entrySet().stream()
|
||||
.filter(x -> x.getCount() > 1)
|
||||
.map(x -> Objects.toString(x.getElement()))
|
||||
.collect(Collectors.joining(", ")));
|
||||
}
|
||||
|
||||
return DataResult.success(cases);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link SelectUpgradeModel} that selects a model based on a component.
|
||||
*
|
||||
* @param component The component to select.
|
||||
* @param <T> The type the component stores.
|
||||
* @return A {@link Builder}.
|
||||
*/
|
||||
public static <T> Builder<T> onComponent(DataComponentType<T> component) {
|
||||
return new Builder<>(component);
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder for constructing {@link SelectUpgradeModel}s.
|
||||
*
|
||||
* @param <T> The type of value to switch on.
|
||||
*/
|
||||
public static final class Builder<T> {
|
||||
private final DataComponentType<T> component;
|
||||
private final List<Pair<List<T>, TurtleUpgradeModel.Unbaked>> cases = new ArrayList<>();
|
||||
private TurtleUpgradeModel.@Nullable Unbaked fallback;
|
||||
|
||||
private Builder(DataComponentType<T> component) {
|
||||
this.component = component;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a case to our model.
|
||||
*
|
||||
* @param value The value for this case.
|
||||
* @param model The model to use.
|
||||
* @return {@code this}, for chaining.
|
||||
*/
|
||||
public Builder<T> when(T value, TurtleUpgradeModel.Unbaked model) {
|
||||
return when(List.of(value), model);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a case to our model.
|
||||
*
|
||||
* @param values The value(s) for this case.
|
||||
* @param model The model to use.
|
||||
* @return {@code this}, for chaining.
|
||||
*/
|
||||
public Builder<T> when(List<T> values, TurtleUpgradeModel.Unbaked model) {
|
||||
cases.add(Pair.of(values, model));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a fallback value, when no previous value matches or the component is not present.
|
||||
*
|
||||
* @param model The fallback model.
|
||||
* @return {@code this}, for chaining.
|
||||
*/
|
||||
public Builder<T> fallback(TurtleUpgradeModel.Unbaked model) {
|
||||
this.fallback = model;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert this builder into an unbaked model.
|
||||
*
|
||||
* @return The unbaked {@link SelectUpgradeModel}.
|
||||
*/
|
||||
public TurtleUpgradeModel.Unbaked create() {
|
||||
return new Unbaked<>(new Cases<>(component, cases), Optional.ofNullable(fallback));
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,40 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.api.client.turtle;
|
||||
|
||||
import dan200.computercraft.api.client.StandaloneModel;
|
||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
||||
import dan200.computercraft.api.turtle.TurtleSide;
|
||||
import net.minecraft.client.resources.model.ModelBaker;
|
||||
import net.minecraft.core.component.DataComponentPatch;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
record SidedUpgradeModel<T extends ITurtleUpgrade>(
|
||||
StandaloneModel left, StandaloneModel right
|
||||
) implements TurtleUpgradeModelViaStandalone<T> {
|
||||
@Override
|
||||
public StandaloneModel getModel(T upgrade, TurtleSide side, DataComponentPatch data) {
|
||||
return switch (side) {
|
||||
case LEFT -> left();
|
||||
case RIGHT -> right();
|
||||
};
|
||||
}
|
||||
|
||||
record Unbaked<T extends ITurtleUpgrade>(
|
||||
ResourceLocation left, ResourceLocation right
|
||||
) implements TurtleUpgradeModel.Unbaked<T> {
|
||||
|
||||
@Override
|
||||
public TurtleUpgradeModel<T> bake(ModelBaker baker) {
|
||||
return new SidedUpgradeModel<>(StandaloneModel.of(left(), baker), StandaloneModel.of(right(), baker));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resolveDependencies(Resolver resolver) {
|
||||
resolver.markDependency(left());
|
||||
resolver.markDependency(right());
|
||||
}
|
||||
}
|
||||
}
|
@@ -5,9 +5,14 @@
|
||||
package dan200.computercraft.api.client.turtle;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.serialization.Codec;
|
||||
import com.mojang.serialization.MapCodec;
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.api.turtle.ITurtleAccess;
|
||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
||||
import dan200.computercraft.api.turtle.TurtleSide;
|
||||
import dan200.computercraft.api.upgrades.UpgradeData;
|
||||
import dan200.computercraft.impl.client.ComputerCraftAPIClientService;
|
||||
import net.minecraft.client.multiplayer.ClientLevel;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.client.renderer.block.model.ItemTransform;
|
||||
@@ -16,8 +21,6 @@ import net.minecraft.client.renderer.item.ItemModelResolver;
|
||||
import net.minecraft.client.renderer.item.ItemStackRenderState;
|
||||
import net.minecraft.client.resources.model.ModelBaker;
|
||||
import net.minecraft.client.resources.model.ResolvableModel;
|
||||
import net.minecraft.core.component.DataComponentPatch;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.item.ItemDisplayContext;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
@@ -25,83 +28,80 @@ import net.minecraft.world.item.ItemStack;
|
||||
/**
|
||||
* The model for a {@link ITurtleUpgrade}.
|
||||
* <p>
|
||||
* Use {@code dan200.computercraft.api.client.FabricComputerCraftAPIClient#registerTurtleUpgradeModeller} to register a
|
||||
* modeller on Fabric and {@code dan200.computercraft.api.client.turtle.RegisterTurtleModellersEvent} to register one
|
||||
* Turtle upgrade models are very similar to vanilla's {@link ItemModel}. Each upgrade's model is defined in JSON, and
|
||||
* loaded from resource packs with other assets.
|
||||
* <p>
|
||||
* In most cases, upgrades can use one of the existing implementations of {@link TurtleUpgradeModel} (e.g.
|
||||
* {@link BasicUpgradeModel} or {@link ItemUpgradeModel}), and do not need to subclass it. However, in the cases where
|
||||
* a custom model is required, one should use
|
||||
* {@code dan200.computercraft.api.client.FabricComputerCraftAPIClient#registerTurtleUpgradeModel} to register a
|
||||
* model on Fabric and {@code dan200.computercraft.api.client.turtle.RegisterTurtleModelEvent} to register one
|
||||
* on Forge.
|
||||
* <p>
|
||||
* See {@link ITurtleUpgrade} for a full example of registering turtle upgrades and their models.
|
||||
*
|
||||
* <h2>Example</h2>
|
||||
* <h3>Fabric</h3>
|
||||
* {@snippet class=com.example.examplemod.FabricExampleModClient region=turtle_model}
|
||||
*
|
||||
* <h3>Forge</h3>
|
||||
* {@snippet class=com.example.examplemod.FabricExampleModClient region=turtle_model}
|
||||
*
|
||||
* @param <T> The type of turtle upgrade this modeller applies to.
|
||||
* @see RegisterTurtleUpgradeModel For multi-loader registration support.
|
||||
* @see ItemUpgradeModel A {@code TurtleUpgradeModel} which uses the upgrade's item.
|
||||
* @see BasicUpgradeModel A {@code TurtleUpgradeModel} which renders a simple model.
|
||||
*/
|
||||
public interface TurtleUpgradeModel<T extends ITurtleUpgrade> {
|
||||
public interface TurtleUpgradeModel {
|
||||
/**
|
||||
* The directory from which turtle upgrade models are loaded. This may be used by data generators.
|
||||
*/
|
||||
String SOURCE = ComputerCraftAPI.MOD_ID + "/turtle_upgrade";
|
||||
|
||||
/**
|
||||
* The codec used to read/write {@linkplain TurtleUpgradeModel.Unbaked unbaked upgrade models}.
|
||||
*/
|
||||
Codec<TurtleUpgradeModel.Unbaked> CODEC = Codec.lazyInitialized(() -> ComputerCraftAPIClientService.get().getTurtleUpgradeModelCodec());
|
||||
|
||||
/**
|
||||
* Render this upgrade to an {@link ItemStackRenderState}. This is used for rendering the item form of the upgrade.
|
||||
*
|
||||
* @param upgrade The upgrade being rendered.
|
||||
* @param side Which side of the turtle (left or right) the upgrade resides on.
|
||||
* @param data Upgrade data instance for current turtle side.
|
||||
* @param side Which side of the turtle (left or right) the upgrade is equipped on.
|
||||
* @param renderer The render state to draw to.
|
||||
* @param resolver The model resolver.
|
||||
* @param transform The root model's transformation.
|
||||
* @param seed The current model seed.
|
||||
* @see ItemModel#update(ItemStackRenderState, ItemStack, ItemModelResolver, ItemDisplayContext, ClientLevel, LivingEntity, int)
|
||||
*/
|
||||
void renderForItem(T upgrade, TurtleSide side, DataComponentPatch data, ItemStackRenderState renderer, ItemModelResolver resolver, ItemTransform transform, int seed);
|
||||
void renderForItem(UpgradeData<ITurtleUpgrade> upgrade, TurtleSide side, ItemStackRenderState renderer, ItemModelResolver resolver, ItemTransform transform, int seed);
|
||||
|
||||
/**
|
||||
* Render this upgrade to a {@link MultiBufferSource}. This is used for rendering the block-entity form of the
|
||||
* upgrade.
|
||||
*
|
||||
* @param upgrade The upgrade being rendered.
|
||||
* @param turtle Access to the turtle that the upgrade resides on. This will be null when getting item models.
|
||||
* @param side Which side of the turtle (left or right) the upgrade resides on.
|
||||
* @param data Upgrade data instance for current turtle side.
|
||||
* @param side Which side of the turtle (left or right) the upgrade is equipped on.
|
||||
* @param turtle Access to the turtle that the upgrade resides on.
|
||||
* @param transform The current pose stack.
|
||||
* @param buffers The buffers to render to.
|
||||
* @param light The lightmap coordinate.
|
||||
* @param overlay The overlay coordinate.
|
||||
*/
|
||||
void renderForLevel(T upgrade, ITurtleAccess turtle, TurtleSide side, DataComponentPatch data, PoseStack transform, MultiBufferSource buffers, int light, int overlay);
|
||||
void renderForLevel(UpgradeData<ITurtleUpgrade> upgrade, TurtleSide side, ITurtleAccess turtle, PoseStack transform, MultiBufferSource buffers, int light, int overlay);
|
||||
|
||||
/**
|
||||
* An unbaked turtle model. Much like other unbaked models (e.g. {@link ItemModel.Unbaked}), this should resolve
|
||||
* any dependencies and returned the fully-resolved model.
|
||||
*
|
||||
* @param <T> The type of turtle upgrade for this model.
|
||||
*/
|
||||
interface Unbaked<T extends ITurtleUpgrade> extends ResolvableModel {
|
||||
TurtleUpgradeModel<T> bake(ModelBaker baker);
|
||||
}
|
||||
interface Unbaked extends ResolvableModel {
|
||||
/**
|
||||
* The {@link MapCodec} used to read/write this unbaked model.
|
||||
*
|
||||
* @return The codec used to read/write this model.
|
||||
* @see ItemModel.Unbaked#type()
|
||||
*/
|
||||
MapCodec<? extends Unbaked> type();
|
||||
|
||||
/**
|
||||
* A basic {@link TurtleUpgradeModel} which renders using the upgrade's {@linkplain ITurtleUpgrade#getUpgradeItem(DataComponentPatch)
|
||||
* upgrade item}.
|
||||
* <p>
|
||||
* This uses appropriate transformations for "flat" items, namely those extending the {@literal minecraft:item/generated}
|
||||
* model type. It will not appear correct for 3D models with additional depth, such as blocks.
|
||||
*
|
||||
* @param <T> The type of the turtle upgrade.
|
||||
* @return The constructed modeller.
|
||||
*/
|
||||
static <T extends ITurtleUpgrade> TurtleUpgradeModel.Unbaked<? super T> flatItem() {
|
||||
return ItemUpgradeModel.UNBAKED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a {@link TurtleUpgradeModel} which has a single model for the left and right side.
|
||||
*
|
||||
* @param left The model to use on the left.
|
||||
* @param right The model to use on the right.
|
||||
* @param <T> The type of the turtle upgrade.
|
||||
* @return The constructed modeller.
|
||||
*/
|
||||
static <T extends ITurtleUpgrade> TurtleUpgradeModel.Unbaked<T> sided(ResourceLocation left, ResourceLocation right) {
|
||||
return new SidedUpgradeModel.Unbaked<>(left, right);
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
|
@@ -1,32 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.api.client.turtle;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import dan200.computercraft.api.client.StandaloneModel;
|
||||
import dan200.computercraft.api.turtle.ITurtleAccess;
|
||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
||||
import dan200.computercraft.api.turtle.TurtleSide;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.client.renderer.block.model.ItemTransform;
|
||||
import net.minecraft.client.renderer.item.ItemModelResolver;
|
||||
import net.minecraft.client.renderer.item.ItemStackRenderState;
|
||||
import net.minecraft.core.component.DataComponentPatch;
|
||||
|
||||
public interface TurtleUpgradeModelViaStandalone<T extends ITurtleUpgrade> extends TurtleUpgradeModel<T> {
|
||||
StandaloneModel getModel(T upgrade, TurtleSide side, DataComponentPatch data);
|
||||
|
||||
@Override
|
||||
default void renderForItem(T upgrade, TurtleSide side, DataComponentPatch data, ItemStackRenderState renderer, ItemModelResolver resolver, ItemTransform transform, int seed) {
|
||||
var layer = renderer.newLayer();
|
||||
layer.setTransform(transform);
|
||||
getModel(upgrade, side, data).setupItemLayer(layer);
|
||||
}
|
||||
|
||||
@Override
|
||||
default void renderForLevel(T upgrade, ITurtleAccess turtle, TurtleSide side, DataComponentPatch data, PoseStack transform, MultiBufferSource buffers, int light, int overlay) {
|
||||
getModel(upgrade, side, data).render(transform, buffers, light, overlay);
|
||||
}
|
||||
}
|
@@ -0,0 +1,40 @@
|
||||
// SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.impl.client;
|
||||
|
||||
import com.mojang.serialization.Codec;
|
||||
import dan200.computercraft.api.client.turtle.TurtleUpgradeModel;
|
||||
import dan200.computercraft.impl.Services;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Bridge between implementation
|
||||
* <p>
|
||||
* Do <strong>NOT</strong> directly reference this class. It exists for internal use by the API.
|
||||
*/
|
||||
@ApiStatus.Internal
|
||||
public interface ComputerCraftAPIClientService {
|
||||
static ComputerCraftAPIClientService get() {
|
||||
var instance = Instance.INSTANCE;
|
||||
return instance == null ? Services.raise(ComputerCraftAPIClientService.class, Instance.ERROR) : instance;
|
||||
}
|
||||
|
||||
Codec<TurtleUpgradeModel.Unbaked> getTurtleUpgradeModelCodec();
|
||||
|
||||
final class Instance {
|
||||
static final @Nullable ComputerCraftAPIClientService INSTANCE;
|
||||
static final @Nullable Throwable ERROR;
|
||||
|
||||
static {
|
||||
var helper = Services.tryLoad(ComputerCraftAPIClientService.class);
|
||||
INSTANCE = helper.instance();
|
||||
ERROR = helper.error();
|
||||
}
|
||||
|
||||
private Instance() {
|
||||
}
|
||||
}
|
||||
}
|
@@ -51,16 +51,6 @@ import java.util.function.Function;
|
||||
* <h4>Forge</h4>
|
||||
* {@snippet class=com.example.examplemod.ForgeExampleMod region=turtle_upgrades}
|
||||
*
|
||||
* <h3>Rendering the upgrade</h3>
|
||||
* Next, we need to register a model for our upgrade. This is done by registering a
|
||||
* {@link dan200.computercraft.api.client.turtle.TurtleUpgradeModel} for your upgrade type.
|
||||
*
|
||||
* <h4>Fabric</h4>
|
||||
* {@snippet class=com.example.examplemod.FabricExampleModClient region=turtle_model}
|
||||
*
|
||||
* <h4>Forge</h4>
|
||||
* {@snippet class=com.example.examplemod.FabricExampleModClient region=turtle_model}
|
||||
*
|
||||
* <h3 id="datagen">Registering the upgrade itself</h3>
|
||||
* Upgrades themselves are loaded from datapacks when a level is loaded. In order to register our new upgrade, we must
|
||||
* create a new JSON file at {@code data/<my_mod>/computercraft/turtle_upgrade/<my_upgrade_id>.json}.
|
||||
@@ -71,8 +61,15 @@ import java.util.function.Function;
|
||||
* by the type itself. As our upgrade was defined with {@link UpgradeType#simpleWithCustomItem(Function)}, the
|
||||
* {@code "item"} field will construct our upgrade with {@link Items#COMPASS}.
|
||||
* <p>
|
||||
* Rather than manually creating the file, it is recommended to use data-generators to generate this file. First, we
|
||||
* register our new upgrades into a {@linkplain PatchedRegistries patched registry}.
|
||||
* Similarly, {@linkplain dan200.computercraft.api.client.turtle.TurtleUpgradeModel the upgrade's model} is loaded from
|
||||
* a resource pack, and so we must also create a new JSON file at
|
||||
* {@code assets/<my_mod>/computercraft/turtle_upgrade/<my_upgrade_id>.json}.
|
||||
*
|
||||
* {@snippet file=assets/examplemod/computercraft/turtle_upgrade/example_turtle_upgrade.json}
|
||||
*
|
||||
* Rather than manually creating these file, it is recommended to use data-generators to generate this file. First, we
|
||||
* register our new upgrades into a {@linkplain PatchedRegistries patched registry}. Models must similarly be
|
||||
* registered.
|
||||
*
|
||||
* {@snippet class=com.example.examplemod.data.TurtleUpgradeProvider region=body}
|
||||
*
|
||||
|
Reference in New Issue
Block a user