mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2025-05-25 10:44:11 +00:00
Make turtle upgrade models data driven
It's ItemModel, but for upgrades!
This commit is contained in:
parent
598fd98a8b
commit
acafc06449
@ -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}
|
||||
*
|
||||
|
@ -7,8 +7,7 @@ package dan200.computercraft.client;
|
||||
import com.mojang.serialization.MapCodec;
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.api.client.StandaloneModel;
|
||||
import dan200.computercraft.api.client.turtle.RegisterTurtleUpgradeModel;
|
||||
import dan200.computercraft.api.client.turtle.TurtleUpgradeModel;
|
||||
import dan200.computercraft.api.client.turtle.*;
|
||||
import dan200.computercraft.client.gui.*;
|
||||
import dan200.computercraft.client.item.colour.PocketComputerLight;
|
||||
import dan200.computercraft.client.item.model.TurtleOverlayModel;
|
||||
@ -19,10 +18,9 @@ import dan200.computercraft.client.platform.ModelKey;
|
||||
import dan200.computercraft.client.render.CustomLecternRenderer;
|
||||
import dan200.computercraft.client.render.TurtleBlockEntityRenderer;
|
||||
import dan200.computercraft.client.render.monitor.MonitorBlockEntityRenderer;
|
||||
import dan200.computercraft.client.turtle.TurtleModemModel;
|
||||
import dan200.computercraft.client.turtle.TurtleOverlay;
|
||||
import dan200.computercraft.client.turtle.TurtleOverlayManager;
|
||||
import dan200.computercraft.client.turtle.TurtleUpgradeModels;
|
||||
import dan200.computercraft.client.turtle.TurtleUpgradeModelManager;
|
||||
import dan200.computercraft.shared.ModRegistry;
|
||||
import dan200.computercraft.shared.computer.inventory.AbstractComputerMenu;
|
||||
import net.minecraft.client.Minecraft;
|
||||
@ -104,16 +102,9 @@ public final class ClientRegistry {
|
||||
}
|
||||
|
||||
public static void registerTurtleModels(RegisterTurtleUpgradeModel register) {
|
||||
register.register(ModRegistry.TurtleUpgradeTypes.SPEAKER.get(), TurtleUpgradeModel.sided(
|
||||
ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_speaker_left"),
|
||||
ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_speaker_right")
|
||||
));
|
||||
register.register(ModRegistry.TurtleUpgradeTypes.WORKBENCH.get(), TurtleUpgradeModel.sided(
|
||||
ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_crafting_table_left"),
|
||||
ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_crafting_table_right")
|
||||
));
|
||||
register.register(ModRegistry.TurtleUpgradeTypes.WIRELESS_MODEM.get(), TurtleModemModel.UNBAKED);
|
||||
register.register(ModRegistry.TurtleUpgradeTypes.TOOL.get(), TurtleUpgradeModel.flatItem());
|
||||
register.register(BasicUpgradeModel.ID, BasicUpgradeModel.CODEC);
|
||||
register.register(ItemUpgradeModel.ID, ItemUpgradeModel.CODEC);
|
||||
register.register(SelectUpgradeModel.ID, SelectUpgradeModel.CODEC);
|
||||
}
|
||||
|
||||
public static void registerReloadListeners(BiConsumer<ResourceLocation, PreparableReloadListener> register, Minecraft minecraft) {
|
||||
@ -132,11 +123,13 @@ public final class ClientRegistry {
|
||||
* Additional models to load.
|
||||
*
|
||||
* @param turtleOverlays The unbaked turtle models.
|
||||
* @param turtleUpgrades The unbaked turtle upgrades.
|
||||
* @see #gatherExtraModels(ResourceManager, Executor)
|
||||
* @see #registerExtraModels(RegisterExtraModels, ExtraModels)
|
||||
*/
|
||||
public record ExtraModels(
|
||||
Map<ResourceLocation, TurtleOverlay.Unbaked> turtleOverlays
|
||||
Map<ResourceLocation, TurtleOverlay.Unbaked> turtleOverlays,
|
||||
Map<ResourceLocation, TurtleUpgradeModel.Unbaked> turtleUpgrades
|
||||
) {
|
||||
}
|
||||
|
||||
@ -148,8 +141,9 @@ public final class ClientRegistry {
|
||||
* @return A promise which contains our extra models.
|
||||
*/
|
||||
public static CompletableFuture<ExtraModels> gatherExtraModels(ResourceManager resources, Executor executor) {
|
||||
var turtleOverlays = TurtleOverlayManager.load(resources, executor);
|
||||
return turtleOverlays.thenApply(ExtraModels::new);
|
||||
var turtleOverlays = TurtleOverlayManager.loader().load(resources, executor);
|
||||
var turtleUpgrades = TurtleUpgradeModelManager.loader().load(resources, executor);
|
||||
return turtleOverlays.thenCombine(turtleUpgrades, ExtraModels::new);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -181,8 +175,8 @@ public final class ClientRegistry {
|
||||
for (var model : EXTRA_MODELS) {
|
||||
register.register(getModel(model), model, (id, r) -> r.markDependency(id), StandaloneModel::of);
|
||||
}
|
||||
TurtleOverlayManager.register(register, models.turtleOverlays());
|
||||
TurtleUpgradeModels.bake(register);
|
||||
TurtleOverlayManager.loader().register(register, models.turtleOverlays());
|
||||
TurtleUpgradeModelManager.loader().register(register, models.turtleUpgrades());
|
||||
}
|
||||
|
||||
public static void registerItemModels(BiConsumer<ResourceLocation, MapCodec<? extends ItemModel.Unbaked>> register) {
|
||||
|
@ -0,0 +1,96 @@
|
||||
// SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.client;
|
||||
|
||||
import com.mojang.serialization.Codec;
|
||||
import com.mojang.serialization.JsonOps;
|
||||
import dan200.computercraft.client.platform.ClientPlatformHelper;
|
||||
import dan200.computercraft.client.platform.ModelKey;
|
||||
import dan200.computercraft.shared.util.ResourceUtils;
|
||||
import net.minecraft.client.resources.model.MissingBlockModel;
|
||||
import net.minecraft.client.resources.model.ModelBaker;
|
||||
import net.minecraft.client.resources.model.ModelManager;
|
||||
import net.minecraft.client.resources.model.ResolvableModel;
|
||||
import net.minecraft.resources.FileToIdConverter;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.packs.resources.ResourceManager;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
/**
|
||||
* A manager for loading custom models. This is responsible for {@linkplain #load(ResourceManager, Executor) loading
|
||||
* models from resource packs}, {@linkplain #register(ClientRegistry.RegisterExtraModels, Map) registering them as
|
||||
* extra models}, and then {@linkplain #get(ModelManager, ResourceLocation) looking them up}.
|
||||
*
|
||||
* @param <U> The type of unbaked model.
|
||||
* @param <T> The type of baked model.
|
||||
*/
|
||||
public class CustomModelManager<U extends ResolvableModel, T> {
|
||||
private final String kind;
|
||||
private final FileToIdConverter lister;
|
||||
private final Codec<U> codec;
|
||||
private final BiFunction<U, ModelBaker, T> bake;
|
||||
|
||||
private final ModelKey<T> missingModelKey;
|
||||
private final U missingModel;
|
||||
|
||||
private final Map<ResourceLocation, ModelKey<T>> modelKeys = new ConcurrentHashMap<>();
|
||||
|
||||
public CustomModelManager(String kind, FileToIdConverter lister, Codec<U> codec, BiFunction<U, ModelBaker, T> bake, U missingModel) {
|
||||
this.kind = kind;
|
||||
this.lister = lister;
|
||||
this.codec = codec;
|
||||
this.bake = bake;
|
||||
|
||||
this.missingModelKey = ClientPlatformHelper.get().createModelKey(MissingBlockModel.LOCATION, () -> "Missing " + kind);
|
||||
this.missingModel = missingModel;
|
||||
}
|
||||
|
||||
private ModelKey<T> getModelKey(ResourceLocation id) {
|
||||
return modelKeys.computeIfAbsent(id, o -> ClientPlatformHelper.get().createModelKey(o, () -> kind + " " + o));
|
||||
}
|
||||
|
||||
/**
|
||||
* Load our models from resources.
|
||||
*
|
||||
* @param resources The current resource manager.
|
||||
* @param executor The executor to schedule work on.
|
||||
* @return The map of unbaked models.
|
||||
*/
|
||||
public CompletableFuture<Map<ResourceLocation, U>> load(ResourceManager resources, Executor executor) {
|
||||
return ResourceUtils.load(resources, executor, kind, lister, JsonOps.INSTANCE, codec);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register our unbaked models.
|
||||
*
|
||||
* @param register The callback to register models with.
|
||||
* @param models The models to register.
|
||||
*/
|
||||
public void register(ClientRegistry.RegisterExtraModels register, Map<ResourceLocation, U> models) {
|
||||
models.forEach((id, model) -> register.register(getModelKey(id), model, bake));
|
||||
register.register(missingModelKey, missingModel, bake);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the model with the given id. If the model does not exist, then the missing model is returned instead.
|
||||
*
|
||||
* @param modelManager The model manager.
|
||||
* @param id The model id.
|
||||
* @return The loaded model.
|
||||
*/
|
||||
public T get(ModelManager modelManager, ResourceLocation id) {
|
||||
var model = getModelKey(id).get(modelManager);
|
||||
if (model != null) return model;
|
||||
|
||||
var missing = missingModelKey.get(modelManager);
|
||||
if (missing == null) throw new IllegalStateException("Models have not yet been loaded.");
|
||||
return missing;
|
||||
}
|
||||
}
|
@ -40,7 +40,7 @@ public record TurtleOverlayModel(ItemTransforms transforms) implements ItemModel
|
||||
if (overlay == null) return;
|
||||
|
||||
var layer = state.newLayer();
|
||||
TurtleOverlayManager.getOverlay(Minecraft.getInstance().getModelManager(), overlay).model().setupItemLayer(layer);
|
||||
TurtleOverlayManager.get(Minecraft.getInstance().getModelManager(), overlay).model().setupItemLayer(layer);
|
||||
layer.setTransform(transforms().getTransform(context));
|
||||
}
|
||||
|
||||
|
@ -8,8 +8,9 @@ import com.mojang.serialization.MapCodec;
|
||||
import com.mojang.serialization.codecs.RecordCodecBuilder;
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.api.turtle.TurtleSide;
|
||||
import dan200.computercraft.client.turtle.TurtleUpgradeModels;
|
||||
import dan200.computercraft.client.turtle.TurtleUpgradeModelManager;
|
||||
import dan200.computercraft.shared.turtle.items.TurtleItem;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.multiplayer.ClientLevel;
|
||||
import net.minecraft.client.renderer.block.model.ItemTransforms;
|
||||
import net.minecraft.client.renderer.item.ItemModel;
|
||||
@ -39,7 +40,8 @@ public record TurtleUpgradeModel(TurtleSide side, ItemTransforms base) implement
|
||||
var upgrade = TurtleItem.getUpgradeWithData(stack, side);
|
||||
if (upgrade == null) return;
|
||||
|
||||
TurtleUpgradeModels.getModeller(upgrade.upgrade()).renderForItem(upgrade.upgrade(), side, upgrade.data(), state, resolver, base.getTransform(context), seed);
|
||||
TurtleUpgradeModelManager.get(Minecraft.getInstance().getModelManager(), upgrade.holder())
|
||||
.renderForItem(upgrade, side, state, resolver, base.getTransform(context), seed);
|
||||
}
|
||||
|
||||
public record Unbaked(TurtleSide side, ResourceLocation base) implements ItemModel.Unbaked {
|
||||
|
@ -31,7 +31,7 @@ public class TurtleShowElfOverlay implements ConditionalItemModelProperty {
|
||||
|
||||
@Override
|
||||
public boolean get(ItemStack stack, @Nullable ClientLevel level, @Nullable LivingEntity holder, int i, ItemDisplayContext context) {
|
||||
var overlay = TurtleOverlayManager.getOverlay(Minecraft.getInstance().getModelManager(), TurtleItem.getOverlay(stack));
|
||||
var overlay = TurtleOverlayManager.get(Minecraft.getInstance().getModelManager(), TurtleItem.getOverlay(stack));
|
||||
return overlay == null || overlay.showElfOverlay();
|
||||
}
|
||||
|
||||
|
@ -11,7 +11,7 @@ import dan200.computercraft.api.turtle.TurtleSide;
|
||||
import dan200.computercraft.client.ClientRegistry;
|
||||
import dan200.computercraft.client.turtle.TurtleOverlay;
|
||||
import dan200.computercraft.client.turtle.TurtleOverlayManager;
|
||||
import dan200.computercraft.client.turtle.TurtleUpgradeModels;
|
||||
import dan200.computercraft.client.turtle.TurtleUpgradeModelManager;
|
||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||
import dan200.computercraft.shared.turtle.blocks.TurtleBlockEntity;
|
||||
import dan200.computercraft.shared.util.Holiday;
|
||||
@ -79,7 +79,7 @@ public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBloc
|
||||
|
||||
// Render the turtle
|
||||
var colour = turtle.getColour();
|
||||
var overlay = TurtleOverlayManager.getOverlay(Minecraft.getInstance().getModelManager(), turtle.getOverlay());
|
||||
var overlay = TurtleOverlayManager.get(Minecraft.getInstance().getModelManager(), turtle.getOverlay());
|
||||
|
||||
if (colour == -1) {
|
||||
renderModel(transform, buffers, lightmapCoord, overlayLight, turtle.getFamily() == ComputerFamily.NORMAL ? NORMAL_TURTLE_MODEL : ADVANCED_TURTLE_MODEL, null);
|
||||
@ -103,7 +103,7 @@ public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBloc
|
||||
}
|
||||
|
||||
private void renderUpgrade(PoseStack transform, MultiBufferSource buffers, int lightmapCoord, int overlayLight, TurtleBlockEntity turtle, TurtleSide side, float f) {
|
||||
var upgrade = turtle.getUpgrade(side);
|
||||
var upgrade = turtle.getAccess().getUpgradeWithData(side);
|
||||
if (upgrade == null) return;
|
||||
transform.pushPose();
|
||||
|
||||
@ -112,8 +112,8 @@ public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBloc
|
||||
transform.mulPose(Axis.XN.rotationDegrees(toolAngle));
|
||||
transform.translate(0.0f, -0.5f, -0.5f);
|
||||
|
||||
TurtleUpgradeModels.getModeller(upgrade).renderForLevel(upgrade, turtle.getAccess(), side, turtle.getAccess().getUpgradeData(side), transform, buffers, lightmapCoord, overlayLight);
|
||||
|
||||
TurtleUpgradeModelManager.get(Minecraft.getInstance().getModelManager(), upgrade.holder())
|
||||
.renderForLevel(upgrade, side, turtle.getAccess(), transform, buffers, lightmapCoord, overlayLight);
|
||||
|
||||
transform.popPose();
|
||||
}
|
||||
|
@ -1,89 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.client.turtle;
|
||||
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.api.client.StandaloneModel;
|
||||
import dan200.computercraft.api.client.turtle.TurtleUpgradeModel;
|
||||
import dan200.computercraft.api.client.turtle.TurtleUpgradeModelViaStandalone;
|
||||
import dan200.computercraft.api.turtle.TurtleSide;
|
||||
import dan200.computercraft.shared.ModRegistry;
|
||||
import dan200.computercraft.shared.turtle.upgrades.TurtleModem;
|
||||
import dan200.computercraft.shared.util.DataComponentUtil;
|
||||
import net.minecraft.client.resources.model.ModelBaker;
|
||||
import net.minecraft.core.component.DataComponentPatch;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* A {@link TurtleUpgradeModel} for modems, providing different models depending on if the modem is on/off.
|
||||
*
|
||||
* @param normal The models for a normal wireless modem.
|
||||
* @param advanced The models for an advanced/ender modem.
|
||||
*/
|
||||
public record TurtleModemModel(
|
||||
ModemModels<StandaloneModel> normal, ModemModels<StandaloneModel> advanced
|
||||
) implements TurtleUpgradeModelViaStandalone<TurtleModem> {
|
||||
public static final TurtleUpgradeModel.Unbaked<TurtleModem> UNBAKED = new Unbaked();
|
||||
|
||||
@Override
|
||||
public StandaloneModel getModel(TurtleModem modem, TurtleSide side, DataComponentPatch data) {
|
||||
var active = DataComponentUtil.isPresent(data, ModRegistry.DataComponents.ON.get(), x -> x);
|
||||
|
||||
var models = modem.advanced() ? advanced() : normal();
|
||||
return side == TurtleSide.LEFT
|
||||
? (active ? models.leftOnModel() : models.leftOffModel())
|
||||
: (active ? models.rightOnModel() : models.rightOffModel());
|
||||
}
|
||||
|
||||
private static final class Unbaked implements TurtleUpgradeModel.Unbaked<TurtleModem> {
|
||||
@Override
|
||||
public TurtleUpgradeModel<TurtleModem> bake(ModelBaker baker) {
|
||||
return new TurtleModemModel(
|
||||
ModemModels.NORMAL.map(x -> StandaloneModel.of(x, baker)),
|
||||
ModemModels.ADVANCED.map(x -> StandaloneModel.of(x, baker))
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resolveDependencies(Resolver resolver) {
|
||||
ModemModels.NORMAL.forEach(resolver::markDependency);
|
||||
ModemModels.ADVANCED.forEach(resolver::markDependency);
|
||||
}
|
||||
}
|
||||
|
||||
private record ModemModels<T>(
|
||||
T leftOffModel, T rightOffModel,
|
||||
T leftOnModel, T rightOnModel
|
||||
) {
|
||||
private static final ModemModels<ResourceLocation> NORMAL = create("normal");
|
||||
private static final ModemModels<ResourceLocation> ADVANCED = create("advanced");
|
||||
|
||||
static ModemModels<ResourceLocation> create(String type) {
|
||||
return new ModemModels<>(
|
||||
ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_modem_" + type + "_off_left"),
|
||||
ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_modem_" + type + "_off_right"),
|
||||
ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_modem_" + type + "_on_left"),
|
||||
ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_modem_" + type + "_on_right")
|
||||
);
|
||||
}
|
||||
|
||||
public void forEach(Consumer<T> out) {
|
||||
out.accept(leftOffModel());
|
||||
out.accept(rightOffModel);
|
||||
out.accept(leftOnModel());
|
||||
out.accept(rightOnModel());
|
||||
}
|
||||
|
||||
public <U> ModemModels<U> map(Function<T, U> mapper) {
|
||||
return new ModemModels<>(
|
||||
mapper.apply(leftOffModel()), mapper.apply(rightOffModel()),
|
||||
mapper.apply(leftOnModel()), mapper.apply(rightOnModel())
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@ -4,66 +4,27 @@
|
||||
|
||||
package dan200.computercraft.client.turtle;
|
||||
|
||||
import com.mojang.serialization.JsonOps;
|
||||
import dan200.computercraft.client.ClientRegistry;
|
||||
import dan200.computercraft.client.platform.ClientPlatformHelper;
|
||||
import dan200.computercraft.client.platform.ModelKey;
|
||||
import dan200.computercraft.shared.util.ResourceUtils;
|
||||
import dan200.computercraft.client.CustomModelManager;
|
||||
import net.minecraft.client.resources.model.MissingBlockModel;
|
||||
import net.minecraft.client.resources.model.ModelManager;
|
||||
import net.minecraft.resources.FileToIdConverter;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.packs.resources.ResourceManager;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
/**
|
||||
* A manager for loading {@link TurtleOverlay}s. This is responsible for {@linkplain #load(ResourceManager, Executor)
|
||||
* loading overlays from resource packs}, {@linkplain #register(ClientRegistry.RegisterExtraModels, Map) baking them}, and
|
||||
* then {@linkplain #getOverlay(ModelManager, ResourceLocation) looking them up}.
|
||||
* The model manager for {@link TurtleOverlay}s.
|
||||
*/
|
||||
public class TurtleOverlayManager {
|
||||
private static final FileToIdConverter ID_CONVERTER = FileToIdConverter.json(TurtleOverlay.SOURCE);
|
||||
|
||||
/**
|
||||
* The {@link ModelKey} for the missing turtle overlay. This is used by
|
||||
* {@link #getOverlay(ModelManager, ResourceLocation)} when an overlay does not exist.
|
||||
*/
|
||||
private static final ModelKey<TurtleOverlay> MISSING_KEY = ClientPlatformHelper.get().createModelKey(
|
||||
MissingBlockModel.LOCATION, () -> "Missing turtle overlay"
|
||||
private static final CustomModelManager<TurtleOverlay.Unbaked, TurtleOverlay> loader = new CustomModelManager<>(
|
||||
"turtle overlay", FileToIdConverter.json(TurtleOverlay.SOURCE), TurtleOverlay.CODEC,
|
||||
TurtleOverlay.Unbaked::bake,
|
||||
new TurtleOverlay.Unbaked(MissingBlockModel.LOCATION, false)
|
||||
);
|
||||
|
||||
private static final Map<ResourceLocation, ModelKey<TurtleOverlay>> modelKeys = new ConcurrentHashMap<>();
|
||||
|
||||
private static ModelKey<TurtleOverlay> getModelKey(ResourceLocation overlay) {
|
||||
return modelKeys.computeIfAbsent(overlay, o -> ClientPlatformHelper.get().createModelKey(o, () -> "Turtle overlay " + o));
|
||||
}
|
||||
|
||||
/**
|
||||
* Load our overlays from resources.
|
||||
*
|
||||
* @param resources The current resource manager.
|
||||
* @param executor The executor to schedule work on.
|
||||
* @return The map of unbaked overlay.
|
||||
*/
|
||||
public static CompletableFuture<Map<ResourceLocation, TurtleOverlay.Unbaked>> load(ResourceManager resources, Executor executor) {
|
||||
return ResourceUtils.load(resources, executor, "turtle overlay", ID_CONVERTER, JsonOps.INSTANCE, TurtleOverlay.CODEC);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register our unbaked overlay models.
|
||||
*
|
||||
* @param register The callback to register models with.
|
||||
* @param overlays The overlays to register.
|
||||
*/
|
||||
public static void register(ClientRegistry.RegisterExtraModels register, Map<ResourceLocation, TurtleOverlay.Unbaked> overlays) {
|
||||
overlays.forEach((id, overlay) -> register.register(getModelKey(id), overlay, TurtleOverlay.Unbaked::bake));
|
||||
register.register(MISSING_KEY, new TurtleOverlay.Unbaked(MissingBlockModel.LOCATION, false), TurtleOverlay.Unbaked::bake);
|
||||
public static CustomModelManager<TurtleOverlay.Unbaked, TurtleOverlay> loader() {
|
||||
return loader;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -75,14 +36,7 @@ public class TurtleOverlayManager {
|
||||
* @return The turtle overlay.
|
||||
*/
|
||||
@Contract("_, null -> null; _, !null -> !null")
|
||||
public static @Nullable TurtleOverlay getOverlay(ModelManager modelManager, @Nullable ResourceLocation id) {
|
||||
if (id == null) return null;
|
||||
|
||||
var overlay = getModelKey(id).get(modelManager);
|
||||
if (overlay != null) return overlay;
|
||||
|
||||
var missing = MISSING_KEY.get(modelManager);
|
||||
if (missing == null) throw new IllegalStateException("Rendering turtles before models are baked");
|
||||
return missing;
|
||||
public static @Nullable TurtleOverlay get(ModelManager modelManager, @Nullable ResourceLocation id) {
|
||||
return id == null ? null : loader.get(modelManager, id);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,43 @@
|
||||
// SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.client.turtle;
|
||||
|
||||
import dan200.computercraft.api.client.turtle.BasicUpgradeModel;
|
||||
import dan200.computercraft.api.client.turtle.TurtleUpgradeModel;
|
||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
||||
import dan200.computercraft.client.CustomModelManager;
|
||||
import net.minecraft.client.resources.model.MissingBlockModel;
|
||||
import net.minecraft.client.resources.model.ModelManager;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.resources.FileToIdConverter;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* The model manager for {@link TurtleUpgradeModel}s.
|
||||
*/
|
||||
public final class TurtleUpgradeModelManager {
|
||||
private static final CustomModelManager<TurtleUpgradeModel.Unbaked, TurtleUpgradeModel> loader = new CustomModelManager<>(
|
||||
"turtle upgrade", FileToIdConverter.json(TurtleUpgradeModel.SOURCE), TurtleUpgradeModel.CODEC,
|
||||
TurtleUpgradeModel.Unbaked::bake,
|
||||
BasicUpgradeModel.unbaked(MissingBlockModel.LOCATION, MissingBlockModel.LOCATION)
|
||||
);
|
||||
|
||||
public static CustomModelManager<TurtleUpgradeModel.Unbaked, TurtleUpgradeModel> loader() {
|
||||
return loader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the model for the given turtle upgrade.
|
||||
*
|
||||
* @param modelManager The model manager.
|
||||
* @param upgrade The turtle upgrade
|
||||
* @return The turtle upgrade model.
|
||||
*/
|
||||
@Contract("_, null -> null; _, !null -> !null")
|
||||
public static @Nullable TurtleUpgradeModel get(ModelManager modelManager, Holder.@Nullable Reference<ITurtleUpgrade> upgrade) {
|
||||
return upgrade == null ? null : loader.get(modelManager, upgrade.key().location());
|
||||
}
|
||||
}
|
@ -1,88 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.client.turtle;
|
||||
|
||||
import dan200.computercraft.api.client.turtle.TurtleUpgradeModel;
|
||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
||||
import dan200.computercraft.api.upgrades.UpgradeType;
|
||||
import dan200.computercraft.client.ClientRegistry;
|
||||
import dan200.computercraft.client.platform.ClientPlatformHelper;
|
||||
import dan200.computercraft.client.platform.ModelKey;
|
||||
import dan200.computercraft.shared.util.RegistryHelper;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.resources.model.MissingBlockModel;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* A registry of {@link TurtleUpgradeModel}s.
|
||||
*/
|
||||
public final class TurtleUpgradeModels {
|
||||
private static final Object fetchedLock = new Object();
|
||||
private static volatile boolean fetchedModels;
|
||||
|
||||
private static final Map<ModelKey<? extends TurtleUpgradeModel<?>>, TurtleUpgradeModel.Unbaked<?>> unbaked = new ConcurrentHashMap<>();
|
||||
private static final Map<UpgradeType<? extends ITurtleUpgrade>, ModelKey<? extends TurtleUpgradeModel<?>>> modelKeys = new ConcurrentHashMap<>();
|
||||
public static final ModelKey<TurtleUpgradeModel<ITurtleUpgrade>> missingModelKey = ClientPlatformHelper.get().createModelKey(
|
||||
MissingBlockModel.LOCATION,
|
||||
() -> "Missing turtle model"
|
||||
);
|
||||
|
||||
private TurtleUpgradeModels() {
|
||||
}
|
||||
|
||||
public static <T extends ITurtleUpgrade> void register(UpgradeType<T> type, TurtleUpgradeModel.Unbaked<? super T> modeller) {
|
||||
if (fetchedModels) {
|
||||
throw new IllegalStateException(String.format(
|
||||
"Turtle upgrade type %s must be registered before models are baked.",
|
||||
RegistryHelper.getKeyOrThrow(RegistryHelper.getRegistry(ITurtleUpgrade.typeRegistry()), type)
|
||||
));
|
||||
}
|
||||
|
||||
if (unbaked.putIfAbsent(getModelKey(type), modeller) != null) {
|
||||
throw new IllegalStateException("Modeller already registered for serialiser");
|
||||
}
|
||||
}
|
||||
|
||||
public static void fetch(Runnable action) {
|
||||
if (fetchedModels) return;
|
||||
synchronized (fetchedLock) {
|
||||
if (fetchedModels) return;
|
||||
action.run();
|
||||
fetchedModels = true;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <T extends ITurtleUpgrade> ModelKey<TurtleUpgradeModel<? super T>> getModelKey(UpgradeType<T> type) {
|
||||
return (ModelKey<TurtleUpgradeModel<? super T>>) modelKeys.computeIfAbsent(type, t -> {
|
||||
var id = RegistryHelper.getKeyOrThrow(RegistryHelper.getRegistry(ITurtleUpgrade.typeRegistry()), t);
|
||||
return ClientPlatformHelper.get().createModelKey(
|
||||
RegistryHelper.getKeyOrThrow(RegistryHelper.getRegistry(ITurtleUpgrade.typeRegistry()), t),
|
||||
() -> "Turtle upgrade " + id
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
public static <T extends ITurtleUpgrade> TurtleUpgradeModel<? super T> getModeller(T upgrade) {
|
||||
var modelManager = Minecraft.getInstance().getModelManager();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
var model = getModelKey((UpgradeType<T>) upgrade.getType()).get(modelManager);
|
||||
if (model != null) return model;
|
||||
|
||||
var missing = missingModelKey.get(modelManager);
|
||||
if (missing == null) throw new IllegalStateException("Rendering turtles before models are baked");
|
||||
return missing;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static void bake(ClientRegistry.RegisterExtraModels register) {
|
||||
unbaked.forEach((id, model) ->
|
||||
register.register((ModelKey<TurtleUpgradeModel<?>>) id, model, TurtleUpgradeModel.Unbaked::bake));
|
||||
register.register(missingModelKey, TurtleUpgradeModel.sided(MissingBlockModel.LOCATION, MissingBlockModel.LOCATION), TurtleUpgradeModel.Unbaked::bake);
|
||||
}
|
||||
}
|
@ -5,6 +5,7 @@
|
||||
package dan200.computercraft.data;
|
||||
|
||||
import com.mojang.serialization.Codec;
|
||||
import dan200.computercraft.api.client.turtle.TurtleUpgradeModel;
|
||||
import dan200.computercraft.api.pocket.IPocketUpgrade;
|
||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
||||
import dan200.computercraft.client.gui.GuiSprites;
|
||||
@ -52,7 +53,7 @@ public final class DataProviders {
|
||||
var fullRegistryPatch = RegistryPatchGenerator.createLookup(
|
||||
generator.registries(),
|
||||
Util.make(new RegistrySetBuilder(), builder -> {
|
||||
builder.add(ITurtleUpgrade.REGISTRY, TurtleUpgradeProvider::addUpgrades);
|
||||
builder.add(ITurtleUpgrade.REGISTRY, TurtleUpgradeProvider::register);
|
||||
builder.add(IPocketUpgrade.REGISTRY, PocketUpgradeProvider::addUpgrades);
|
||||
}));
|
||||
var fullRegistries = fullRegistryPatch.thenApply(RegistrySetBuilder.PatchedRegistries::full);
|
||||
@ -89,6 +90,7 @@ public final class DataProviders {
|
||||
});
|
||||
|
||||
generator.addFromCodec("Turtle overlays", PackOutput.Target.RESOURCE_PACK, TurtleOverlay.SOURCE, TurtleOverlay.CODEC, TurtleOverlays::register);
|
||||
generator.addFromCodec("Turtle upgrade models", PackOutput.Target.RESOURCE_PACK, TurtleUpgradeModel.SOURCE, TurtleUpgradeModel.CODEC, TurtleUpgradeProvider::addModels);
|
||||
|
||||
generator.addModels(BlockModelProvider::addBlockModels, ItemModelProvider::addItemModels);
|
||||
}
|
||||
|
@ -6,6 +6,10 @@ package dan200.computercraft.data;
|
||||
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.api.ComputerCraftTags;
|
||||
import dan200.computercraft.api.client.turtle.BasicUpgradeModel;
|
||||
import dan200.computercraft.api.client.turtle.ItemUpgradeModel;
|
||||
import dan200.computercraft.api.client.turtle.SelectUpgradeModel;
|
||||
import dan200.computercraft.api.client.turtle.TurtleUpgradeModel;
|
||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
||||
import dan200.computercraft.shared.ModRegistry;
|
||||
import dan200.computercraft.shared.turtle.upgrades.TurtleCraftingTable;
|
||||
@ -17,21 +21,21 @@ import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.Items;
|
||||
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import static dan200.computercraft.api.turtle.TurtleToolBuilder.tool;
|
||||
|
||||
class TurtleUpgradeProvider {
|
||||
public static void addUpgrades(BootstrapContext<ITurtleUpgrade> upgrades) {
|
||||
upgrades.register(id("speaker"), new TurtleSpeaker(new ItemStack(ModRegistry.Items.SPEAKER.get())));
|
||||
upgrades.register(vanilla("crafting_table"), new TurtleCraftingTable(new ItemStack(Items.CRAFTING_TABLE)));
|
||||
upgrades.register(id("wireless_modem_normal"), new TurtleModem(new ItemStack(ModRegistry.Items.WIRELESS_MODEM_NORMAL.get()), false));
|
||||
upgrades.register(id("wireless_modem_advanced"), new TurtleModem(new ItemStack(ModRegistry.Items.WIRELESS_MODEM_ADVANCED.get()), true));
|
||||
private static final ResourceKey<ITurtleUpgrade> SPEAKER = id("speaker");
|
||||
private static final ResourceKey<ITurtleUpgrade> CRAFTING_TABLE = vanilla("crafting_table");
|
||||
private static final ResourceKey<ITurtleUpgrade> WIRELESS_MODEM_NORMAL = id("wireless_modem_normal");
|
||||
private static final ResourceKey<ITurtleUpgrade> WIRELESS_MODEM_ADVANCED = id("wireless_modem_advanced");
|
||||
|
||||
tool(vanilla("diamond_axe").location(), Items.DIAMOND_AXE).damageMultiplier(6.0f).register(upgrades);
|
||||
tool(vanilla("diamond_pickaxe"), Items.DIAMOND_PICKAXE).register(upgrades);
|
||||
tool(vanilla("diamond_hoe"), Items.DIAMOND_HOE).breakable(ComputerCraftTags.Blocks.TURTLE_HOE_BREAKABLE).register(upgrades);
|
||||
tool(vanilla("diamond_shovel"), Items.DIAMOND_SHOVEL).breakable(ComputerCraftTags.Blocks.TURTLE_SHOVEL_BREAKABLE).register(upgrades);
|
||||
tool(vanilla("diamond_sword"), Items.DIAMOND_SWORD).breakable(ComputerCraftTags.Blocks.TURTLE_SWORD_BREAKABLE).damageMultiplier(9.0f).register(upgrades);
|
||||
}
|
||||
private static final ResourceKey<ITurtleUpgrade> DIAMOND_AXE = vanilla("diamond_axe");
|
||||
private static final ResourceKey<ITurtleUpgrade> DIAMOND_PICKAXE = vanilla("diamond_pickaxe");
|
||||
private static final ResourceKey<ITurtleUpgrade> DIAMOND_HOE = vanilla("diamond_hoe");
|
||||
private static final ResourceKey<ITurtleUpgrade> DIAMOND_SHOVEL = vanilla("diamond_shovel");
|
||||
private static final ResourceKey<ITurtleUpgrade> DIAMOND_SWORD = vanilla("diamond_sword");
|
||||
|
||||
private static ResourceKey<ITurtleUpgrade> id(String id) {
|
||||
return ITurtleUpgrade.createKey(ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, id));
|
||||
@ -41,4 +45,52 @@ class TurtleUpgradeProvider {
|
||||
// Naughty, please don't do this. Mostly here for some semblance of backwards compatibility.
|
||||
return ITurtleUpgrade.createKey(ResourceLocation.fromNamespaceAndPath("minecraft", id));
|
||||
}
|
||||
|
||||
public static void register(BootstrapContext<ITurtleUpgrade> upgrades) {
|
||||
upgrades.register(SPEAKER, new TurtleSpeaker(new ItemStack(ModRegistry.Items.SPEAKER.get())));
|
||||
upgrades.register(CRAFTING_TABLE, new TurtleCraftingTable(new ItemStack(Items.CRAFTING_TABLE)));
|
||||
upgrades.register(WIRELESS_MODEM_NORMAL, new TurtleModem(new ItemStack(ModRegistry.Items.WIRELESS_MODEM_NORMAL.get()), false));
|
||||
upgrades.register(WIRELESS_MODEM_ADVANCED, new TurtleModem(new ItemStack(ModRegistry.Items.WIRELESS_MODEM_ADVANCED.get()), true));
|
||||
|
||||
tool(DIAMOND_AXE, Items.DIAMOND_AXE).damageMultiplier(6.0f).register(upgrades);
|
||||
tool(DIAMOND_PICKAXE, Items.DIAMOND_PICKAXE).register(upgrades);
|
||||
tool(DIAMOND_HOE, Items.DIAMOND_HOE).breakable(ComputerCraftTags.Blocks.TURTLE_HOE_BREAKABLE).register(upgrades);
|
||||
tool(DIAMOND_SHOVEL, Items.DIAMOND_SHOVEL).breakable(ComputerCraftTags.Blocks.TURTLE_SHOVEL_BREAKABLE).register(upgrades);
|
||||
tool(DIAMOND_SWORD, Items.DIAMOND_SWORD).breakable(ComputerCraftTags.Blocks.TURTLE_SWORD_BREAKABLE).damageMultiplier(9.0f).register(upgrades);
|
||||
}
|
||||
|
||||
public static void addModels(BiConsumer<ResourceLocation, TurtleUpgradeModel.Unbaked> out) {
|
||||
out.accept(SPEAKER.location(), BasicUpgradeModel.unbaked(
|
||||
ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_speaker_left"),
|
||||
ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_speaker_right")
|
||||
));
|
||||
out.accept(CRAFTING_TABLE.location(), BasicUpgradeModel.unbaked(
|
||||
ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_crafting_table_left"),
|
||||
ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_crafting_table_right")
|
||||
));
|
||||
|
||||
out.accept(WIRELESS_MODEM_NORMAL.location(), createModemModel("normal"));
|
||||
out.accept(WIRELESS_MODEM_ADVANCED.location(), createModemModel("advanced"));
|
||||
|
||||
out.accept(DIAMOND_AXE.location(), ItemUpgradeModel.unbaked());
|
||||
out.accept(DIAMOND_PICKAXE.location(), ItemUpgradeModel.unbaked());
|
||||
out.accept(DIAMOND_HOE.location(), ItemUpgradeModel.unbaked());
|
||||
out.accept(DIAMOND_SHOVEL.location(), ItemUpgradeModel.unbaked());
|
||||
out.accept(DIAMOND_SWORD.location(), ItemUpgradeModel.unbaked());
|
||||
}
|
||||
|
||||
private static TurtleUpgradeModel.Unbaked createModemModel(String type) {
|
||||
return SelectUpgradeModel.onComponent(ModRegistry.DataComponents.ON.get())
|
||||
.when(false, createBaseModemModel(type, "off"))
|
||||
.when(true, createBaseModemModel(type, "on"))
|
||||
.fallback(createBaseModemModel(type, "off"))
|
||||
.create();
|
||||
}
|
||||
|
||||
private static TurtleUpgradeModel.Unbaked createBaseModemModel(String type, String state) {
|
||||
return BasicUpgradeModel.unbaked(
|
||||
ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_modem_" + type + "_" + state + "_left"),
|
||||
ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_modem_" + type + "_" + state + "_right")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ import dan200.computercraft.shared.peripheral.monitor.MonitorEdgeState;
|
||||
import dan200.computercraft.shared.peripheral.printer.PrinterBlock;
|
||||
import dan200.computercraft.shared.turtle.blocks.TurtleBlock;
|
||||
import dan200.computercraft.shared.util.DirectionUtil;
|
||||
import net.minecraft.client.color.item.Dye;
|
||||
import net.minecraft.client.data.models.BlockModelGenerators;
|
||||
import net.minecraft.client.data.models.MultiVariant;
|
||||
import net.minecraft.client.data.models.blockstates.ConditionBuilder;
|
||||
@ -216,7 +217,7 @@ public class BlockModelProvider {
|
||||
generators.itemModelOutput.accept(block.asItem(), ItemModelUtils.composite(
|
||||
ItemModelUtils.conditional(
|
||||
new HasComponent(DataComponents.DYED_COLOR, false),
|
||||
ItemModelUtils.plainModel(TurtleBlockEntityRenderer.COLOUR_TURTLE_MODEL),
|
||||
ItemModelUtils.tintedModel(TurtleBlockEntityRenderer.COLOUR_TURTLE_MODEL, new Dye(-1)),
|
||||
ItemModelUtils.plainModel(model)
|
||||
),
|
||||
new TurtleUpgradeModel.Unbaked(TurtleSide.LEFT, model),
|
||||
|
@ -0,0 +1,3 @@
|
||||
{
|
||||
"type": "computercraft:item"
|
||||
}
|
@ -11,7 +11,7 @@ import net.minecraft.world.item.ItemStack;
|
||||
// @start region=body
|
||||
public class ExampleTurtleUpgrade extends AbstractTurtleUpgrade {
|
||||
public ExampleTurtleUpgrade(ItemStack stack) {
|
||||
super(TurtleUpgradeType.PERIPHERAL, "example", stack);
|
||||
super(TurtleUpgradeType.PERIPHERAL, "upgrade.examplemod.example_turtle_upgrade.adjective", stack);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -2,6 +2,8 @@ package com.example.examplemod.data;
|
||||
|
||||
import com.example.examplemod.ExampleMod;
|
||||
import com.example.examplemod.ExampleTurtleUpgrade;
|
||||
import dan200.computercraft.api.client.turtle.ItemUpgradeModel;
|
||||
import dan200.computercraft.api.client.turtle.TurtleUpgradeModel;
|
||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
||||
import net.minecraft.Util;
|
||||
import net.minecraft.core.HolderLookup;
|
||||
@ -13,18 +15,19 @@ import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.Items;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
/**
|
||||
* Extends the bootstrap registries with our {@linkplain ExampleTurtleUpgrade example turtle upgrade}.
|
||||
*/
|
||||
// @start region=body
|
||||
public class TurtleUpgradeProvider {
|
||||
// Define our upgrade ids.
|
||||
private static final ResourceLocation EXAMPLE_TURTLE_UPGRADE = ResourceLocation.fromNamespaceAndPath(ExampleMod.MOD_ID, "example_turtle_upgrade");
|
||||
|
||||
// Register our turtle upgrades.
|
||||
public static void addUpgrades(BootstrapContext<ITurtleUpgrade> upgrades) {
|
||||
upgrades.register(
|
||||
ITurtleUpgrade.createKey(ResourceLocation.fromNamespaceAndPath(ExampleMod.MOD_ID, "example_turtle_upgrade")),
|
||||
new ExampleTurtleUpgrade(new ItemStack(Items.COMPASS))
|
||||
);
|
||||
upgrades.register(ITurtleUpgrade.createKey(EXAMPLE_TURTLE_UPGRADE), new ExampleTurtleUpgrade(new ItemStack(Items.COMPASS)));
|
||||
}
|
||||
|
||||
// Set up the dynamic registries to contain our turtle upgrades.
|
||||
@ -33,5 +36,10 @@ public class TurtleUpgradeProvider {
|
||||
builder.add(ITurtleUpgrade.REGISTRY, TurtleUpgradeProvider::addUpgrades);
|
||||
}));
|
||||
}
|
||||
|
||||
// Register our turtle models.
|
||||
public static void addUpgradeModels(BiConsumer<ResourceLocation, TurtleUpgradeModel.Unbaked> models) {
|
||||
models.accept(EXAMPLE_TURTLE_UPGRADE, ItemUpgradeModel.unbaked());
|
||||
}
|
||||
}
|
||||
// @end region=body
|
||||
|
@ -0,0 +1,5 @@
|
||||
{
|
||||
"type": "computercraft:sided",
|
||||
"left": "computercraft:block/turtle_speaker_left",
|
||||
"right": "computercraft:block/turtle_speaker_right"
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
{
|
||||
"type": "computercraft:select",
|
||||
"cases": [
|
||||
{
|
||||
"model": {
|
||||
"type": "computercraft:sided",
|
||||
"left": "computercraft:block/turtle_modem_advanced_off_left",
|
||||
"right": "computercraft:block/turtle_modem_advanced_off_right"
|
||||
},
|
||||
"when": [false]
|
||||
},
|
||||
{
|
||||
"model": {
|
||||
"type": "computercraft:sided",
|
||||
"left": "computercraft:block/turtle_modem_advanced_on_left",
|
||||
"right": "computercraft:block/turtle_modem_advanced_on_right"
|
||||
},
|
||||
"when": [true]
|
||||
}
|
||||
],
|
||||
"fallback": {
|
||||
"type": "computercraft:sided",
|
||||
"left": "computercraft:block/turtle_modem_advanced_off_left",
|
||||
"right": "computercraft:block/turtle_modem_advanced_off_right"
|
||||
},
|
||||
"property": "computercraft:on"
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
{
|
||||
"type": "computercraft:select",
|
||||
"cases": [
|
||||
{
|
||||
"model": {
|
||||
"type": "computercraft:sided",
|
||||
"left": "computercraft:block/turtle_modem_normal_off_left",
|
||||
"right": "computercraft:block/turtle_modem_normal_off_right"
|
||||
},
|
||||
"when": [false]
|
||||
},
|
||||
{
|
||||
"model": {
|
||||
"type": "computercraft:sided",
|
||||
"left": "computercraft:block/turtle_modem_normal_on_left",
|
||||
"right": "computercraft:block/turtle_modem_normal_on_right"
|
||||
},
|
||||
"when": [true]
|
||||
}
|
||||
],
|
||||
"fallback": {
|
||||
"type": "computercraft:sided",
|
||||
"left": "computercraft:block/turtle_modem_normal_off_left",
|
||||
"right": "computercraft:block/turtle_modem_normal_off_right"
|
||||
},
|
||||
"property": "computercraft:on"
|
||||
}
|
@ -6,7 +6,11 @@
|
||||
"type": "minecraft:condition",
|
||||
"component": "minecraft:dyed_color",
|
||||
"on_false": {"type": "minecraft:model", "model": "computercraft:block/turtle_advanced"},
|
||||
"on_true": {"type": "minecraft:model", "model": "computercraft:block/turtle_colour"},
|
||||
"on_true": {
|
||||
"type": "minecraft:model",
|
||||
"model": "computercraft:block/turtle_colour",
|
||||
"tints": [{"type": "minecraft:dye", "default": -1}]
|
||||
},
|
||||
"property": "minecraft:has_component"
|
||||
},
|
||||
{"type": "computercraft:turtle/upgrade", "side": "left", "transforms": "computercraft:block/turtle_advanced"},
|
||||
|
@ -6,7 +6,11 @@
|
||||
"type": "minecraft:condition",
|
||||
"component": "minecraft:dyed_color",
|
||||
"on_false": {"type": "minecraft:model", "model": "computercraft:block/turtle_normal"},
|
||||
"on_true": {"type": "minecraft:model", "model": "computercraft:block/turtle_colour"},
|
||||
"on_true": {
|
||||
"type": "minecraft:model",
|
||||
"model": "computercraft:block/turtle_colour",
|
||||
"tints": [{"type": "minecraft:dye", "default": -1}]
|
||||
},
|
||||
"property": "minecraft:has_component"
|
||||
},
|
||||
{"type": "computercraft:turtle/upgrade", "side": "left", "transforms": "computercraft:block/turtle_normal"},
|
||||
|
@ -0,0 +1,5 @@
|
||||
{
|
||||
"type": "computercraft:sided",
|
||||
"left": "computercraft:block/turtle_crafting_table_left",
|
||||
"right": "computercraft:block/turtle_crafting_table_right"
|
||||
}
|
@ -0,0 +1 @@
|
||||
{"type": "computercraft:item"}
|
@ -0,0 +1 @@
|
||||
{"type": "computercraft:item"}
|
@ -0,0 +1 @@
|
||||
{"type": "computercraft:item"}
|
@ -0,0 +1 @@
|
||||
{"type": "computercraft:item"}
|
@ -0,0 +1 @@
|
||||
{"type": "computercraft:item"}
|
@ -56,7 +56,7 @@ public class ResourceUtils {
|
||||
var result = codec.parse(ops, JsonParser.parseReader(reader))
|
||||
.ifError(e -> LOG.error("Couldn't parse {} '{}' from pack '{}': {}", kind, id, resource.sourcePackId(), e.message()))
|
||||
.result().orElse(null);
|
||||
return result == null ? null : new Pair<>(id, result);
|
||||
return result == null ? null : Pair.of(id, result);
|
||||
} catch (Exception exception) {
|
||||
LOG.error("Failed to open {} {} from pack '{}'", kind, resource, resource.sourcePackId(), exception);
|
||||
return null;
|
||||
|
@ -4,11 +4,11 @@
|
||||
|
||||
package dan200.computercraft.api.client;
|
||||
|
||||
import com.mojang.serialization.MapCodec;
|
||||
import dan200.computercraft.api.client.turtle.RegisterTurtleUpgradeModel;
|
||||
import dan200.computercraft.api.client.turtle.TurtleUpgradeModel;
|
||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
||||
import dan200.computercraft.api.upgrades.UpgradeType;
|
||||
import dan200.computercraft.impl.client.FabricComputerCraftAPIClientService;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
/**
|
||||
* The Fabric-specific entrypoint for ComputerCraft's client-side API.
|
||||
@ -27,12 +27,11 @@ public final class FabricComputerCraftAPIClient {
|
||||
* <p>
|
||||
* This method may be used as a {@link RegisterTurtleUpgradeModel}, for convenient use in multi-loader code.
|
||||
*
|
||||
* @param type The turtle upgrade type.
|
||||
* @param model The upgrade model.
|
||||
* @param <T> The type of the turtle upgrade.
|
||||
* @param id The id used for this type of upgrade model.
|
||||
* @param codec The codec used to read/decode an upgrade model.
|
||||
*/
|
||||
public static <T extends ITurtleUpgrade> void registerTurtleUpgradeModeller(UpgradeType<T> type, TurtleUpgradeModel.Unbaked<? super T> model) {
|
||||
getInstance().registerTurtleUpgradeModeller(type, model);
|
||||
public static void registerTurtleUpgradeModeller(ResourceLocation id, MapCodec<? extends TurtleUpgradeModel.Unbaked> codec) {
|
||||
getInstance().registerTurtleUpgradeModeller(id, codec);
|
||||
}
|
||||
|
||||
private static FabricComputerCraftAPIClientService getInstance() {
|
||||
|
@ -4,12 +4,10 @@
|
||||
|
||||
package dan200.computercraft.impl.client;
|
||||
|
||||
import com.mojang.serialization.MapCodec;
|
||||
import dan200.computercraft.api.client.turtle.TurtleUpgradeModel;
|
||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
||||
import dan200.computercraft.api.upgrades.UpgradeType;
|
||||
import dan200.computercraft.impl.Services;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Backing interface for CC's client-side API.
|
||||
@ -17,25 +15,10 @@ import org.jspecify.annotations.Nullable;
|
||||
* Do <strong>NOT</strong> directly reference this class. It exists for internal use by the API.
|
||||
*/
|
||||
@ApiStatus.Internal
|
||||
public interface FabricComputerCraftAPIClientService {
|
||||
public interface FabricComputerCraftAPIClientService extends ComputerCraftAPIClientService {
|
||||
static FabricComputerCraftAPIClientService get() {
|
||||
var instance = Instance.INSTANCE;
|
||||
return instance == null ? Services.raise(FabricComputerCraftAPIClientService.class, Instance.ERROR) : instance;
|
||||
return (FabricComputerCraftAPIClientService) ComputerCraftAPIClientService.get();
|
||||
}
|
||||
|
||||
<T extends ITurtleUpgrade> void registerTurtleUpgradeModeller(UpgradeType<T> type, TurtleUpgradeModel.Unbaked<? super T> model);
|
||||
|
||||
final class Instance {
|
||||
static final @Nullable FabricComputerCraftAPIClientService INSTANCE;
|
||||
static final @Nullable Throwable ERROR;
|
||||
|
||||
static {
|
||||
var helper = Services.tryLoad(FabricComputerCraftAPIClientService.class);
|
||||
INSTANCE = helper.instance();
|
||||
ERROR = helper.error();
|
||||
}
|
||||
|
||||
private Instance() {
|
||||
}
|
||||
}
|
||||
void registerTurtleUpgradeModeller(ResourceLocation id, MapCodec<? extends TurtleUpgradeModel.Unbaked> codec);
|
||||
}
|
||||
|
@ -5,15 +5,26 @@
|
||||
package dan200.computercraft.impl.client;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.mojang.serialization.Codec;
|
||||
import com.mojang.serialization.MapCodec;
|
||||
import dan200.computercraft.api.client.turtle.TurtleUpgradeModel;
|
||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
||||
import dan200.computercraft.api.upgrades.UpgradeType;
|
||||
import dan200.computercraft.client.turtle.TurtleUpgradeModels;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.util.ExtraCodecs;
|
||||
|
||||
@AutoService(FabricComputerCraftAPIClientService.class)
|
||||
import java.util.function.Function;
|
||||
|
||||
@AutoService(ComputerCraftAPIClientService.class)
|
||||
public final class FabricComputerCraftAPIClientImpl implements FabricComputerCraftAPIClientService {
|
||||
private static final ExtraCodecs.LateBoundIdMapper<ResourceLocation, MapCodec<? extends TurtleUpgradeModel.Unbaked>> TURTLE_UPGRADE_MODELS = new ExtraCodecs.LateBoundIdMapper<>();
|
||||
private static final Codec<TurtleUpgradeModel.Unbaked> TURTLE_UPGRADE_CODEC = TURTLE_UPGRADE_MODELS.codec(ResourceLocation.CODEC).dispatch(TurtleUpgradeModel.Unbaked::type, Function.identity());
|
||||
|
||||
@Override
|
||||
public <T extends ITurtleUpgrade> void registerTurtleUpgradeModeller(UpgradeType<T> type, TurtleUpgradeModel.Unbaked<? super T> modeller) {
|
||||
TurtleUpgradeModels.register(type, modeller);
|
||||
public Codec<TurtleUpgradeModel.Unbaked> getTurtleUpgradeModelCodec() {
|
||||
return TURTLE_UPGRADE_CODEC;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerTurtleUpgradeModeller(ResourceLocation id, MapCodec<? extends TurtleUpgradeModel.Unbaked> codec) {
|
||||
TURTLE_UPGRADE_MODELS.put(id, codec);
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +0,0 @@
|
||||
package com.example.examplemod;
|
||||
|
||||
import dan200.computercraft.api.client.FabricComputerCraftAPIClient;
|
||||
import dan200.computercraft.api.client.turtle.TurtleUpgradeModel;
|
||||
import net.fabricmc.api.ClientModInitializer;
|
||||
|
||||
public class FabricExampleModClient implements ClientModInitializer {
|
||||
@Override
|
||||
public void onInitializeClient() {
|
||||
// @start region=turtle_model
|
||||
FabricComputerCraftAPIClient.registerTurtleUpgradeModeller(ExampleMod.EXAMPLE_TURTLE_UPGRADE, TurtleUpgradeModel.flatItem());
|
||||
// @end region=turtle_model
|
||||
}
|
||||
}
|
@ -1,15 +1,20 @@
|
||||
package com.example.examplemod;
|
||||
|
||||
import com.example.examplemod.data.TurtleUpgradeProvider;
|
||||
import dan200.computercraft.api.client.turtle.TurtleUpgradeModel;
|
||||
import net.fabricmc.fabric.api.datagen.v1.DataGeneratorEntrypoint;
|
||||
import net.fabricmc.fabric.api.datagen.v1.FabricDataGenerator;
|
||||
import net.fabricmc.fabric.api.datagen.v1.FabricDataOutput;
|
||||
import net.fabricmc.fabric.api.datagen.v1.provider.FabricCodecDataProvider;
|
||||
import net.fabricmc.fabric.api.datagen.v1.provider.FabricDynamicRegistryProvider;
|
||||
import net.fabricmc.fabric.api.event.registry.DynamicRegistries;
|
||||
import net.minecraft.core.HolderLookup;
|
||||
import net.minecraft.core.RegistrySetBuilder;
|
||||
import net.minecraft.data.PackOutput;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
/**
|
||||
* Data generators for our Fabric example mod.
|
||||
@ -25,6 +30,17 @@ public class FabricExampleModDataGenerator implements DataGeneratorEntrypoint {
|
||||
private static void addTurtleUpgrades(FabricDataGenerator.Pack pack, CompletableFuture<HolderLookup.Provider> registries) {
|
||||
var fullRegistryPatch = TurtleUpgradeProvider.makeUpgradeRegistry(registries);
|
||||
pack.addProvider((FabricDataOutput output) -> new AutomaticDynamicRegistryProvider(output, fullRegistryPatch));
|
||||
pack.addProvider((FabricDataOutput output) -> new FabricCodecDataProvider<>(output, registries, PackOutput.Target.RESOURCE_PACK, TurtleUpgradeModel.SOURCE, TurtleUpgradeModel.CODEC) {
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Turtle upgrade models";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configure(BiConsumer<ResourceLocation, TurtleUpgradeModel.Unbaked> out, HolderLookup.Provider provider) {
|
||||
TurtleUpgradeProvider.addUpgradeModels(out);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -4,9 +4,8 @@
|
||||
|
||||
package dan200.computercraft.api.client.turtle;
|
||||
|
||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
||||
import dan200.computercraft.api.turtle.TurtleUpgradeType;
|
||||
import dan200.computercraft.api.upgrades.UpgradeType;
|
||||
import com.mojang.serialization.MapCodec;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.neoforged.bus.api.Event;
|
||||
import net.neoforged.fml.event.IModBusEvent;
|
||||
import net.neoforged.fml.event.lifecycle.FMLClientSetupEvent;
|
||||
@ -14,8 +13,7 @@ import net.neoforged.fml.event.lifecycle.FMLCommonSetupEvent;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
/**
|
||||
* This event is fired to register {@link TurtleUpgradeModel}s for a mod's {@linkplain TurtleUpgradeType turtle
|
||||
* upgrades}.
|
||||
* This event is fired to register additional {@link TurtleUpgradeModel}s.
|
||||
* <p>
|
||||
* This event is fired during the initial mod construction. Registries will be frozen, but mods may not be fully
|
||||
* initialised at this point (i.e. {@link FMLCommonSetupEvent} or {@link FMLClientSetupEvent} may not have been
|
||||
@ -30,10 +28,10 @@ public class RegisterTurtleModelEvent extends Event implements IModBusEvent, Reg
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* {@inheritDoc}The codec used to read/decode an upgrade model.
|
||||
*/
|
||||
@Override
|
||||
public <T extends ITurtleUpgrade> void register(UpgradeType<T> type, TurtleUpgradeModel.Unbaked<? super T> modeller) {
|
||||
dispatch.register(type, modeller);
|
||||
public void register(ResourceLocation id, MapCodec<? extends TurtleUpgradeModel.Unbaked> model) {
|
||||
dispatch.register(id, model);
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,6 @@ import dan200.computercraft.api.client.turtle.RegisterTurtleModelEvent;
|
||||
import dan200.computercraft.client.platform.ForgeModelKey;
|
||||
import dan200.computercraft.client.platform.ModelKey;
|
||||
import dan200.computercraft.client.render.ExtendedItemFrameRenderState;
|
||||
import dan200.computercraft.client.turtle.TurtleUpgradeModels;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.entity.ItemFrameRenderer;
|
||||
import net.minecraft.client.resources.model.ModelBaker;
|
||||
@ -19,7 +18,6 @@ import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.util.context.ContextKey;
|
||||
import net.neoforged.api.distmarker.Dist;
|
||||
import net.neoforged.bus.api.SubscribeEvent;
|
||||
import net.neoforged.fml.ModLoader;
|
||||
import net.neoforged.fml.common.EventBusSubscriber;
|
||||
import net.neoforged.fml.event.lifecycle.FMLClientSetupEvent;
|
||||
import net.neoforged.neoforge.client.event.*;
|
||||
@ -44,8 +42,6 @@ public final class ForgeClientRegistry {
|
||||
|
||||
@SubscribeEvent
|
||||
public static void registerModels(ModelEvent.RegisterStandalone event) {
|
||||
TurtleUpgradeModels.fetch(() -> ModLoader.postEvent(new RegisterTurtleModelEvent(TurtleUpgradeModels::register)));
|
||||
|
||||
// Load resources
|
||||
Queue<Runnable> tasks = new ArrayDeque<>();
|
||||
var state = ClientRegistry.gatherExtraModels(Minecraft.getInstance().getResourceManager(), tasks::add);
|
||||
|
@ -0,0 +1,26 @@
|
||||
// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.impl.client;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.mojang.serialization.Codec;
|
||||
import com.mojang.serialization.MapCodec;
|
||||
import dan200.computercraft.api.client.turtle.RegisterTurtleModelEvent;
|
||||
import dan200.computercraft.api.client.turtle.TurtleUpgradeModel;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.util.ExtraCodecs;
|
||||
import net.neoforged.fml.ModLoader;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
@AutoService(ComputerCraftAPIClientService.class)
|
||||
public final class ForgeComputerCraftAPIClientImpl implements ComputerCraftAPIClientService {
|
||||
@Override
|
||||
public Codec<TurtleUpgradeModel.Unbaked> getTurtleUpgradeModelCodec() {
|
||||
var idMapper = new ExtraCodecs.LateBoundIdMapper<ResourceLocation, MapCodec<? extends TurtleUpgradeModel.Unbaked>>();
|
||||
ModLoader.postEvent(new RegisterTurtleModelEvent(idMapper::put));
|
||||
return idMapper.codec(ResourceLocation.CODEC).dispatch(TurtleUpgradeModel.Unbaked::type, Function.identity());
|
||||
}
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
package com.example.examplemod;
|
||||
|
||||
import dan200.computercraft.api.client.turtle.RegisterTurtleModelEvent;
|
||||
import dan200.computercraft.api.client.turtle.TurtleUpgradeModel;
|
||||
import net.neoforged.api.distmarker.Dist;
|
||||
import net.neoforged.bus.api.SubscribeEvent;
|
||||
import net.neoforged.fml.common.EventBusSubscriber;
|
||||
|
||||
/**
|
||||
* The client-side entry point for the Forge version of our example mod.
|
||||
*/
|
||||
@EventBusSubscriber(modid = ExampleMod.MOD_ID, value = Dist.CLIENT, bus = EventBusSubscriber.Bus.MOD)
|
||||
public class ForgeExampleModClient {
|
||||
// @start region=turtle_model
|
||||
@SubscribeEvent
|
||||
public static void onRegisterTurtleModellers(RegisterTurtleModelEvent event) {
|
||||
event.register(ExampleMod.EXAMPLE_TURTLE_UPGRADE, TurtleUpgradeModel.flatItem());
|
||||
}
|
||||
// @end region=turtle_model
|
||||
}
|
@ -1,11 +1,14 @@
|
||||
package com.example.examplemod;
|
||||
|
||||
import com.example.examplemod.data.TurtleUpgradeProvider;
|
||||
import dan200.computercraft.api.client.turtle.TurtleUpgradeModel;
|
||||
import net.minecraft.core.HolderLookup;
|
||||
import net.minecraft.data.DataGenerator;
|
||||
import net.minecraft.data.PackOutput;
|
||||
import net.neoforged.bus.api.SubscribeEvent;
|
||||
import net.neoforged.fml.common.EventBusSubscriber;
|
||||
import net.neoforged.neoforge.common.data.DatapackBuiltinEntriesProvider;
|
||||
import net.neoforged.neoforge.common.data.JsonCodecProvider;
|
||||
import net.neoforged.neoforge.data.event.GatherDataEvent;
|
||||
|
||||
import java.util.Set;
|
||||
@ -17,7 +20,7 @@ import java.util.concurrent.CompletableFuture;
|
||||
@EventBusSubscriber(bus = EventBusSubscriber.Bus.MOD)
|
||||
public class ForgeExampleModDataGenerator {
|
||||
@SubscribeEvent
|
||||
public static void gather(GatherDataEvent event) {
|
||||
public static void gather(GatherDataEvent.Client event) {
|
||||
var pack = event.getGenerator().getVanillaPack(true);
|
||||
addTurtleUpgrades(pack, event.getLookupProvider());
|
||||
}
|
||||
@ -26,6 +29,12 @@ public class ForgeExampleModDataGenerator {
|
||||
private static void addTurtleUpgrades(DataGenerator.PackGenerator pack, CompletableFuture<HolderLookup.Provider> registries) {
|
||||
var fullRegistryPatch = TurtleUpgradeProvider.makeUpgradeRegistry(registries);
|
||||
pack.addProvider(o -> new DatapackBuiltinEntriesProvider(o, fullRegistryPatch, Set.of(ExampleMod.MOD_ID)));
|
||||
pack.addProvider(o -> new JsonCodecProvider<>(o, PackOutput.Target.RESOURCE_PACK, TurtleUpgradeModel.SOURCE, TurtleUpgradeModel.CODEC, registries, ExampleMod.MOD_ID) {
|
||||
@Override
|
||||
protected void gather() {
|
||||
TurtleUpgradeProvider.addUpgradeModels(this::unconditional);
|
||||
}
|
||||
});
|
||||
}
|
||||
// @end region=turtle_upgrades
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user