1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2024-06-17 02:40:06 +00:00

Render enchanted upgrades with a glint (#1532)

This commit is contained in:
Jonathan Coates 2023-07-23 11:18:22 +01:00 committed by GitHub
parent 940f59b116
commit 57a944fd90
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 765 additions and 106 deletions

View File

@ -69,8 +69,8 @@ default Collection<ResourceLocation> getDependencies() {
}
/**
* A basic {@link TurtleUpgradeModeller} which renders using the upgrade's {@linkplain ITurtleUpgrade#getCraftingItem()
* crafting item}.
* A basic {@link TurtleUpgradeModeller} which renders using the upgrade's {@linkplain ITurtleUpgrade#getUpgradeItem(CompoundTag)}
* 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.
@ -80,7 +80,7 @@ default Collection<ResourceLocation> getDependencies() {
*/
@SuppressWarnings("unchecked")
static <T extends ITurtleUpgrade> TurtleUpgradeModeller<T> flatItem() {
return (TurtleUpgradeModeller<T>) TurtleUpgradeModellers.FLAT_ITEM;
return (TurtleUpgradeModeller<T>) TurtleUpgradeModellers.UPGRADE_ITEM;
}
/**

View File

@ -6,13 +6,20 @@
import com.mojang.math.Transformation;
import dan200.computercraft.api.client.TransformedModel;
import dan200.computercraft.api.turtle.ITurtleAccess;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.impl.client.ClientPlatformHelper;
import net.minecraft.client.Minecraft;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.item.ItemStack;
import org.joml.Matrix4f;
import javax.annotation.Nullable;
class TurtleUpgradeModellers {
private static final Transformation leftTransform = getMatrixFor(-0.40625f);
private static final Transformation rightTransform = getMatrixFor(0.40625f);
private static final Transformation leftTransform = getMatrixFor(-0.4065f);
private static final Transformation rightTransform = getMatrixFor(0.4065f);
private static Transformation getMatrixFor(float offset) {
var matrix = new Matrix4f();
@ -26,6 +33,23 @@ private static Transformation getMatrixFor(float offset) {
return new Transformation(matrix);
}
static final TurtleUpgradeModeller<ITurtleUpgrade> FLAT_ITEM = (upgrade, turtle, side) ->
TransformedModel.of(upgrade.getCraftingItem(), side == TurtleSide.LEFT ? leftTransform : rightTransform);
static final TurtleUpgradeModeller<ITurtleUpgrade> UPGRADE_ITEM = new UpgradeItemModeller();
private static class UpgradeItemModeller implements TurtleUpgradeModeller<ITurtleUpgrade> {
@Override
public TransformedModel getModel(ITurtleUpgrade upgrade, @Nullable ITurtleAccess turtle, TurtleSide side) {
return getModel(turtle == null ? upgrade.getCraftingItem() : upgrade.getUpgradeItem(turtle.getUpgradeNBTData(side)), side);
}
@Override
public TransformedModel getModel(ITurtleUpgrade upgrade, CompoundTag data, TurtleSide side) {
return getModel(upgrade.getUpgradeItem(data), side);
}
private TransformedModel getModel(ItemStack stack, TurtleSide side) {
var model = Minecraft.getInstance().getItemRenderer().getItemModelShaper().getItemModel(stack);
if (stack.hasFoil()) model = ClientPlatformHelper.get().createdFoiledModel(model);
return new TransformedModel(model, side == TurtleSide.LEFT ? leftTransform : rightTransform);
}
}
}

View File

@ -5,6 +5,7 @@
package dan200.computercraft.impl.client;
import dan200.computercraft.impl.Services;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.ModelManager;
import net.minecraft.client.resources.model.ModelResourceLocation;
@ -24,6 +25,15 @@ public interface ClientPlatformHelper {
*/
BakedModel getModel(ModelManager manager, ResourceLocation location);
/**
* Wrap this model in a version which renders a foil/enchantment glint.
*
* @param model The model to wrap.
* @return The wrapped model.
* @see RenderType#glint()
*/
BakedModel createdFoiledModel(BakedModel model);
static ClientPlatformHelper get() {
var instance = Instance.INSTANCE;
return instance == null ? Services.raise(ClientPlatformHelper.class, Instance.ERROR) : instance;

View File

@ -27,10 +27,9 @@
* This is typically used with a {@link BakedModel} subclass - see the loader-specific projects.
*/
public class ModelTransformer {
@SuppressWarnings("MutablePublicArray") // It's not nice, but is efficient.
public static final int[] INVERSE_ORDER = new int[]{ 3, 2, 1, 0 };
private static final int[] INVERSE_ORDER = new int[]{ 3, 2, 1, 0 };
public static final int STRIDE = DefaultVertexFormat.BLOCK.getIntegerSize();
private static final int STRIDE = DefaultVertexFormat.BLOCK.getIntegerSize();
private static final int POS_OFFSET = findOffset(DefaultVertexFormat.BLOCK, DefaultVertexFormat.ELEMENT_POSITION);
protected final Matrix4f transformation;
@ -62,7 +61,7 @@ private BakedQuad transformQuad(BakedQuad quad) {
for (var i = 0; i < 4; i++) {
var inStart = STRIDE * i;
// Reverse the order of the quads if we're inverting
var outStart = STRIDE * (invert ? INVERSE_ORDER[i] : i);
var outStart = getVertexOffset(i, invert);
System.arraycopy(inputData, inStart, outputData, outStart, STRIDE);
// Apply the matrix to our position
@ -86,6 +85,10 @@ private BakedQuad transformQuad(BakedQuad quad) {
return new BakedQuad(outputData, quad.getTintIndex(), direction, quad.getSprite(), quad.isShade());
}
public static int getVertexOffset(int vertex, boolean invert) {
return (invert ? ModelTransformer.INVERSE_ORDER[vertex] : vertex) * ModelTransformer.STRIDE;
}
private record TransformedQuads(List<BakedQuad> original, List<BakedQuad> transformed) {
}

View File

@ -4,8 +4,13 @@
package dan200.computercraft.client.platform;
import com.mojang.blaze3d.vertex.PoseStack;
import dan200.computercraft.shared.network.NetworkMessage;
import dan200.computercraft.shared.network.server.ServerNetworkContext;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.resources.model.BakedModel;
import javax.annotation.Nullable;
public interface ClientPlatformHelper extends dan200.computercraft.impl.client.ClientPlatformHelper {
static ClientPlatformHelper get() {
@ -18,4 +23,16 @@ static ClientPlatformHelper get() {
* @param message The message to send.
*/
void sendToServer(NetworkMessage<ServerNetworkContext> message);
/**
* Render a {@link BakedModel}, using any loader-specific hooks.
*
* @param transform The current matrix transformation to apply.
* @param buffers The current pool of render buffers.
* @param model The model to draw.
* @param lightmapCoord The current packed lightmap coordinate.
* @param overlayLight The current overlay light.
* @param tints Block colour tints to apply to the model.
*/
void renderBakedModel(PoseStack transform, MultiBufferSource buffers, BakedModel model, int lightmapCoord, int overlayLight, @Nullable int[] tints);
}

View File

@ -0,0 +1,102 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.client.render;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import dan200.computercraft.client.model.turtle.ModelTransformer;
import dan200.computercraft.client.platform.ClientPlatformHelper;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.block.model.BakedQuad;
import net.minecraft.client.renderer.entity.ItemRenderer;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.world.item.ItemStack;
import org.joml.Vector4f;
import javax.annotation.Nullable;
import java.util.List;
/**
* Utilities for rendering {@link BakedModel}s and {@link BakedQuad}s.
*/
public final class ModelRenderer {
private ModelRenderer() {
}
/**
* Render a list of {@linkplain BakedQuad quads} to a buffer.
* <p>
* This is not intended to be used directly, but instead by {@link ClientPlatformHelper#renderBakedModel(PoseStack, MultiBufferSource, BakedModel, int, int, int[])}. The
* implementation here is pretty similar to {@link ItemRenderer#renderQuadList(PoseStack, VertexConsumer, List, ItemStack, int, int)},
* but supports inverted quads (i.e. those with a negative scale).
*
* @param transform The current matrix transformation to apply.
* @param buffer The buffer to draw to.
* @param quads The quads to draw.
* @param lightmapCoord The current packed lightmap coordinate.
* @param overlayLight The current overlay light.
* @param tints Block colour tints to apply to the model.
*/
public static void renderQuads(PoseStack transform, VertexConsumer buffer, List<BakedQuad> quads, int lightmapCoord, int overlayLight, @Nullable int[] tints) {
var matrix = transform.last();
var inverted = matrix.pose().determinant() < 0;
for (var bakedquad : quads) {
var tint = -1;
if (tints != null && bakedquad.isTinted()) {
var idx = bakedquad.getTintIndex();
if (idx >= 0 && idx < tints.length) tint = tints[bakedquad.getTintIndex()];
}
var r = (float) (tint >> 16 & 255) / 255.0F;
var g = (float) (tint >> 8 & 255) / 255.0F;
var b = (float) (tint & 255) / 255.0F;
putBulkQuad(buffer, matrix, bakedquad, r, g, b, lightmapCoord, overlayLight, inverted);
}
}
/**
* A version of {@link VertexConsumer#putBulkData(PoseStack.Pose, BakedQuad, float, float, float, int, int)} which
* will reverse vertex order when the matrix is inverted.
*
* @param buffer The buffer to draw to.
* @param pose The current matrix stack.
* @param quad The quad to draw.
* @param red The red tint of this quad.
* @param green The green tint of this quad.
* @param blue The blue tint of this quad.
* @param lightmapCoord The lightmap coordinate
* @param overlayLight The overlay light.
*/
private static void putBulkQuad(VertexConsumer buffer, PoseStack.Pose pose, BakedQuad quad, float red, float green, float blue, int lightmapCoord, int overlayLight, boolean invert) {
var matrix = pose.pose();
// It's a little dubious to transform using this matrix rather than the normal matrix. This mirrors the logic in
// Direction.rotate (so not out of nowhere!), but is a little suspicious.
var dirNormal = quad.getDirection().getNormal();
var vector = new Vector4f();
matrix.transform(dirNormal.getX(), dirNormal.getY(), dirNormal.getZ(), 0.0f, vector).normalize();
float normalX = vector.x(), normalY = vector.y(), normalZ = vector.z();
var vertices = quad.getVertices();
for (var vertex = 0; vertex < 4; vertex++) {
var i = ModelTransformer.getVertexOffset(vertex, invert);
var x = Float.intBitsToFloat(vertices[i]);
var y = Float.intBitsToFloat(vertices[i + 1]);
var z = Float.intBitsToFloat(vertices[i + 2]);
matrix.transform(x, y, z, 1, vector);
var u = Float.intBitsToFloat(vertices[i + 4]);
var v = Float.intBitsToFloat(vertices[i + 5]);
buffer.vertex(
vector.x(), vector.y(), vector.z(),
red, green, blue, 1.0F, u, v, overlayLight, lightmapCoord,
normalX, normalY, normalZ
);
}
}
}

View File

@ -5,36 +5,28 @@
package dan200.computercraft.client.render;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import com.mojang.math.Axis;
import com.mojang.math.Transformation;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.client.model.turtle.ModelTransformer;
import dan200.computercraft.client.platform.ClientPlatformHelper;
import dan200.computercraft.client.turtle.TurtleUpgradeModellers;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.turtle.blocks.TurtleBlockEntity;
import dan200.computercraft.shared.util.DirectionUtil;
import dan200.computercraft.shared.util.Holiday;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.Font;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.Sheets;
import net.minecraft.client.renderer.block.model.BakedQuad;
import net.minecraft.client.renderer.blockentity.BlockEntityRenderDispatcher;
import net.minecraft.client.renderer.blockentity.BlockEntityRenderer;
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.RandomSource;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.HitResult;
import org.joml.Vector4f;
import javax.annotation.Nullable;
import java.util.List;
public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBlockEntity> {
private static final ModelResourceLocation NORMAL_TURTLE_MODEL = new ModelResourceLocation(ComputerCraftAPI.MOD_ID, "turtle_normal", "inventory");
@ -42,8 +34,6 @@ public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBloc
private static final ResourceLocation COLOUR_TURTLE_MODEL = new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_colour");
private static final ResourceLocation ELF_OVERLAY_MODEL = new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_elf_overlay");
private final RandomSource random = RandomSource.create(0);
private final BlockEntityRenderDispatcher renderer;
private final Font font;
@ -109,23 +99,22 @@ public void render(TurtleBlockEntity turtle, float partialTicks, PoseStack trans
var family = turtle.getFamily();
var overlay = turtle.getOverlay();
var buffer = buffers.getBuffer(Sheets.translucentCullBlockSheet());
renderModel(transform, buffer, lightmapCoord, overlayLight, getTurtleModel(family, colour != -1), colour == -1 ? null : new int[]{ colour });
renderModel(transform, buffers, lightmapCoord, overlayLight, getTurtleModel(family, colour != -1), colour == -1 ? null : new int[]{ colour });
// Render the overlay
var overlayModel = getTurtleOverlayModel(overlay, Holiday.getCurrent() == Holiday.CHRISTMAS);
if (overlayModel != null) {
renderModel(transform, buffer, lightmapCoord, overlayLight, overlayModel, null);
renderModel(transform, buffers, lightmapCoord, overlayLight, overlayModel, null);
}
// Render the upgrades
renderUpgrade(transform, buffer, lightmapCoord, overlayLight, turtle, TurtleSide.LEFT, partialTicks);
renderUpgrade(transform, buffer, lightmapCoord, overlayLight, turtle, TurtleSide.RIGHT, partialTicks);
renderUpgrade(transform, buffers, lightmapCoord, overlayLight, turtle, TurtleSide.LEFT, partialTicks);
renderUpgrade(transform, buffers, lightmapCoord, overlayLight, turtle, TurtleSide.RIGHT, partialTicks);
transform.popPose();
}
private void renderUpgrade(PoseStack transform, VertexConsumer renderer, int lightmapCoord, int overlayLight, TurtleBlockEntity turtle, TurtleSide side, float f) {
private void renderUpgrade(PoseStack transform, MultiBufferSource buffers, int lightmapCoord, int overlayLight, TurtleBlockEntity turtle, TurtleSide side, float f) {
var upgrade = turtle.getUpgrade(side);
if (upgrade == null) return;
transform.pushPose();
@ -136,16 +125,15 @@ private void renderUpgrade(PoseStack transform, VertexConsumer renderer, int lig
transform.translate(0.0f, -0.5f, -0.5f);
var model = TurtleUpgradeModellers.getModel(upgrade, turtle.getAccess(), side);
pushPoseFromTransformation(transform, model.getMatrix());
renderModel(transform, renderer, lightmapCoord, overlayLight, model.getModel(), null);
transform.popPose();
applyTransformation(transform, model.getMatrix());
renderModel(transform, buffers, lightmapCoord, overlayLight, model.getModel(), null);
transform.popPose();
}
private void renderModel(PoseStack transform, VertexConsumer renderer, int lightmapCoord, int overlayLight, ResourceLocation modelLocation, @Nullable int[] tints) {
private void renderModel(PoseStack transform, MultiBufferSource buffers, int lightmapCoord, int overlayLight, ResourceLocation modelLocation, @Nullable int[] tints) {
var modelManager = Minecraft.getInstance().getItemRenderer().getItemModelShaper().getModelManager();
renderModel(transform, renderer, lightmapCoord, overlayLight, ClientPlatformHelper.get().getModel(modelManager, modelLocation), tints);
renderModel(transform, buffers, lightmapCoord, overlayLight, ClientPlatformHelper.get().getModel(modelManager, modelLocation), tints);
}
/**
@ -159,80 +147,11 @@ private void renderModel(PoseStack transform, VertexConsumer renderer, int light
* @param tints Tints for the quads, as an array of RGB values.
* @see net.minecraft.client.renderer.block.ModelBlockRenderer#renderModel
*/
private void renderModel(PoseStack transform, VertexConsumer renderer, int lightmapCoord, int overlayLight, BakedModel model, @Nullable int[] tints) {
for (var facing : DirectionUtil.FACINGS) {
random.setSeed(42);
renderQuads(transform, renderer, lightmapCoord, overlayLight, model.getQuads(null, facing, random), tints);
}
random.setSeed(42);
renderQuads(transform, renderer, lightmapCoord, overlayLight, model.getQuads(null, null, random), tints);
private void renderModel(PoseStack transform, MultiBufferSource renderer, int lightmapCoord, int overlayLight, BakedModel model, @Nullable int[] tints) {
ClientPlatformHelper.get().renderBakedModel(transform, renderer, model, lightmapCoord, overlayLight, tints);
}
private static void renderQuads(PoseStack transform, VertexConsumer buffer, int lightmapCoord, int overlayLight, List<BakedQuad> quads, @Nullable int[] tints) {
var matrix = transform.last();
var inverted = matrix.pose().determinant() < 0;
for (var bakedquad : quads) {
var tint = -1;
if (tints != null && bakedquad.isTinted()) {
var idx = bakedquad.getTintIndex();
if (idx >= 0 && idx < tints.length) tint = tints[bakedquad.getTintIndex()];
}
var r = (float) (tint >> 16 & 255) / 255.0F;
var g = (float) (tint >> 8 & 255) / 255.0F;
var b = (float) (tint & 255) / 255.0F;
if (inverted) {
putBulkQuadInvert(buffer, matrix, bakedquad, r, g, b, lightmapCoord, overlayLight);
} else {
buffer.putBulkData(matrix, bakedquad, r, g, b, lightmapCoord, overlayLight);
}
}
}
/**
* A version of {@link VertexConsumer#putBulkData(PoseStack.Pose, BakedQuad, float, float, float, int, int)} for
* when the matrix is inverted.
*
* @param buffer The buffer to draw to.
* @param pose The current matrix stack.
* @param quad The quad to draw.
* @param red The red tint of this quad.
* @param green The green tint of this quad.
* @param blue The blue tint of this quad.
* @param lightmapCoord The lightmap coordinate
* @param overlayLight The overlay light.
*/
private static void putBulkQuadInvert(VertexConsumer buffer, PoseStack.Pose pose, BakedQuad quad, float red, float green, float blue, int lightmapCoord, int overlayLight) {
var matrix = pose.pose();
// It's a little dubious to transform using this matrix rather than the normal matrix. This mirrors the logic in
// Direction.rotate (so not out of nowhere!), but is a little suspicious.
var dirNormal = quad.getDirection().getNormal();
var normal = matrix.transform(new Vector4f(dirNormal.getX(), dirNormal.getY(), dirNormal.getZ(), 0.0f)).normalize();
var vertices = quad.getVertices();
for (var vertex : ModelTransformer.INVERSE_ORDER) {
var i = vertex * ModelTransformer.STRIDE;
var x = Float.intBitsToFloat(vertices[i]);
var y = Float.intBitsToFloat(vertices[i + 1]);
var z = Float.intBitsToFloat(vertices[i + 2]);
var transformed = matrix.transform(new Vector4f(x, y, z, 1));
var u = Float.intBitsToFloat(vertices[i + 4]);
var v = Float.intBitsToFloat(vertices[i + 5]);
buffer.vertex(
transformed.x(), transformed.y(), transformed.z(),
red, green, blue, 1.0F, u, v, overlayLight, lightmapCoord,
normal.x(), normal.y(), normal.z()
);
}
}
private static void pushPoseFromTransformation(PoseStack stack, Transformation transformation) {
stack.pushPose();
private static void applyTransformation(PoseStack stack, Transformation transformation) {
var trans = transformation.getTranslation();
stack.translate(trans.x(), trans.y(), trans.z());

View File

@ -100,12 +100,12 @@ public CompoundTag getUpgradeData(ItemStack stack) {
public ItemStack getUpgradeItem(CompoundTag upgradeData) {
// Copy upgrade data back to the item.
var item = super.getUpgradeItem(upgradeData).copy();
item.setTag(upgradeData.contains(TAG_ITEM_TAG, TAG_COMPOUND) ? upgradeData.getCompound(TAG_ITEM_TAG).copy() : null);
item.setTag(upgradeData.contains(TAG_ITEM_TAG, TAG_COMPOUND) ? upgradeData.getCompound(TAG_ITEM_TAG) : null);
return item;
}
private ItemStack getToolStack(ITurtleAccess turtle, TurtleSide side) {
return getUpgradeItem(turtle.getUpgradeNBTData(side));
return getUpgradeItem(turtle.getUpgradeNBTData(side)).copy();
}
private void setToolStack(ITurtleAccess turtle, TurtleSide side, ItemStack stack) {

View File

@ -0,0 +1,56 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.shared.util;
import org.jetbrains.annotations.Nullable;
import java.util.AbstractList;
import java.util.Iterator;
import java.util.List;
/**
* A list which prepends a single value to another list.
*
* @param <T> The type of item in the list.
*/
public final class ConsList<T> extends AbstractList<T> {
private final T head;
private final List<T> tail;
public ConsList(T head, List<T> tail) {
this.head = head;
this.tail = tail;
}
@Override
public T get(int index) {
return index == 0 ? head : tail.get(index - 1);
}
@Override
public int size() {
return 1 + tail.size();
}
@Override
public Iterator<T> iterator() {
return new Iterator<>() {
private @Nullable Iterator<T> tailIterator;
@Override
public boolean hasNext() {
return tailIterator == null || tailIterator.hasNext();
}
@Override
public T next() {
if (tailIterator != null) return tailIterator.next();
tailIterator = tail.iterator();
return head;
}
};
}
}

View File

@ -0,0 +1,37 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.shared.util;
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
* A couple of trivial tests for {@link ConsList}, mostly as a quick safety check.
*/
public class ConsListTest {
@Test
public void testGet() {
var list = new ConsList<>(1, List.of(2, 3, 4));
assertEquals(1, list.get(0));
assertEquals(2, list.get(1));
assertEquals(4, list.get(3));
}
@Test
public void testSize() {
var list = new ConsList<>(1, List.of(2, 3, 4));
assertEquals(4, list.size());
}
@Test
public void testIterator() {
var list = new ConsList<>(1, List.of(2, 3, 4));
assertArrayEquals(new Integer[]{ 1, 2, 3, 4 }, list.toArray(Integer[]::new));
}
}

View File

@ -623,6 +623,24 @@ fun Peripheral_change(helper: GameTestHelper) = helper.sequence {
}
// TODO: Turtle sucking from items
/**
* Render turtles as an item.
*/
@ClientGameTest
fun Render_turtle_items(helper: GameTestHelper) = helper.sequence {
thenExecute { helper.positionAtArmorStand() }
thenScreenshot()
}
/**
* Render turtles as a block entity.
*/
@ClientGameTest
fun Render_turtle_blocks(helper: GameTestHelper) = helper.sequence {
thenExecute { helper.positionAtArmorStand() }
thenScreenshot()
}
}
private val LuaTaskContext.turtle get() = getApi<TurtleAPI>()

View File

@ -0,0 +1,140 @@
{
DataVersion: 3337,
size: [5, 5, 5],
data: [
{pos: [0, 0, 0], state: "minecraft:polished_andesite"},
{pos: [0, 0, 1], state: "minecraft:polished_andesite"},
{pos: [0, 0, 2], state: "minecraft:polished_andesite"},
{pos: [0, 0, 3], state: "minecraft:polished_andesite"},
{pos: [0, 0, 4], state: "minecraft:polished_andesite"},
{pos: [1, 0, 0], state: "minecraft:polished_andesite"},
{pos: [1, 0, 1], state: "minecraft:polished_andesite"},
{pos: [1, 0, 2], state: "minecraft:polished_andesite"},
{pos: [1, 0, 3], state: "minecraft:polished_andesite"},
{pos: [1, 0, 4], state: "minecraft:polished_andesite"},
{pos: [2, 0, 0], state: "minecraft:polished_andesite"},
{pos: [2, 0, 1], state: "minecraft:polished_andesite"},
{pos: [2, 0, 2], state: "minecraft:polished_andesite"},
{pos: [2, 0, 3], state: "minecraft:polished_andesite"},
{pos: [2, 0, 4], state: "minecraft:polished_andesite"},
{pos: [3, 0, 0], state: "minecraft:polished_andesite"},
{pos: [3, 0, 1], state: "minecraft:polished_andesite"},
{pos: [3, 0, 2], state: "minecraft:polished_andesite"},
{pos: [3, 0, 3], state: "minecraft:polished_andesite"},
{pos: [3, 0, 4], state: "minecraft:polished_andesite"},
{pos: [4, 0, 0], state: "minecraft:polished_andesite"},
{pos: [4, 0, 1], state: "minecraft:polished_andesite"},
{pos: [4, 0, 2], state: "minecraft:polished_andesite"},
{pos: [4, 0, 3], state: "minecraft:polished_andesite"},
{pos: [4, 0, 4], state: "minecraft:polished_andesite"},
{pos: [0, 1, 0], state: "minecraft:air"},
{pos: [0, 1, 1], state: "minecraft:air"},
{pos: [0, 1, 2], state: "minecraft:air"},
{pos: [0, 1, 3], state: "minecraft:air"},
{pos: [0, 1, 4], state: "minecraft:air"},
{pos: [1, 1, 0], state: "minecraft:air"},
{pos: [1, 1, 1], state: "minecraft:air"},
{pos: [1, 1, 2], state: "minecraft:air"},
{pos: [1, 1, 3], state: "computercraft:turtle_normal{facing:east,waterlogged:false}", nbt: {ComputerId: 8, Fuel: 0, Items: [], LeftUpgrade: "cctest:netherite_pickaxe", LeftUpgradeNbt: {Tag: {Damage: 0, Enchantments: [{id: "minecraft:efficiency", lvl: 5s}], RepairCost: 1}}, On: 1b, Owner: {LowerId: -7931330074873442406L, Name: "Player848", UpperId: 7430876841693101418L}, Slot: 0, id: "computercraft:turtle_normal"}},
{pos: [1, 1, 4], state: "minecraft:air"},
{pos: [2, 1, 0], state: "minecraft:air"},
{pos: [2, 1, 1], state: "minecraft:air"},
{pos: [2, 1, 2], state: "minecraft:air"},
{pos: [2, 1, 3], state: "minecraft:air"},
{pos: [2, 1, 4], state: "minecraft:air"},
{pos: [3, 1, 0], state: "minecraft:air"},
{pos: [3, 1, 1], state: "minecraft:air"},
{pos: [3, 1, 2], state: "minecraft:air"},
{pos: [3, 1, 3], state: "computercraft:turtle_normal{facing:west,waterlogged:false}", nbt: {ComputerId: 8, Fuel: 0, Items: [], Label: "Dinnerbone", On: 1b, Owner: {LowerId: -7931330074873442406L, Name: "Player848", UpperId: 7430876841693101418L}, RightUpgrade: "minecraft:diamond_pickaxe", RightUpgradeNbt: {Tag: {Damage: 0}}, Slot: 0, id: "computercraft:turtle_normal"}},
{pos: [3, 1, 4], state: "minecraft:air"},
{pos: [4, 1, 0], state: "minecraft:air"},
{pos: [4, 1, 1], state: "minecraft:air"},
{pos: [4, 1, 2], state: "minecraft:air"},
{pos: [4, 1, 3], state: "minecraft:air"},
{pos: [4, 1, 4], state: "minecraft:air"},
{pos: [0, 2, 0], state: "minecraft:air"},
{pos: [0, 2, 1], state: "minecraft:air"},
{pos: [0, 2, 2], state: "minecraft:air"},
{pos: [0, 2, 3], state: "minecraft:air"},
{pos: [0, 2, 4], state: "minecraft:air"},
{pos: [1, 2, 0], state: "minecraft:air"},
{pos: [1, 2, 1], state: "minecraft:air"},
{pos: [1, 2, 2], state: "minecraft:air"},
{pos: [1, 2, 3], state: "minecraft:air"},
{pos: [1, 2, 4], state: "minecraft:air"},
{pos: [2, 2, 0], state: "minecraft:air"},
{pos: [2, 2, 1], state: "minecraft:air"},
{pos: [2, 2, 2], state: "minecraft:air"},
{pos: [2, 2, 3], state: "minecraft:air"},
{pos: [2, 2, 4], state: "minecraft:air"},
{pos: [3, 2, 0], state: "minecraft:air"},
{pos: [3, 2, 1], state: "minecraft:air"},
{pos: [3, 2, 2], state: "minecraft:air"},
{pos: [3, 2, 3], state: "minecraft:air"},
{pos: [3, 2, 4], state: "minecraft:air"},
{pos: [4, 2, 0], state: "minecraft:air"},
{pos: [4, 2, 1], state: "minecraft:air"},
{pos: [4, 2, 2], state: "minecraft:air"},
{pos: [4, 2, 3], state: "minecraft:air"},
{pos: [4, 2, 4], state: "minecraft:air"},
{pos: [0, 3, 0], state: "minecraft:air"},
{pos: [0, 3, 1], state: "minecraft:air"},
{pos: [0, 3, 2], state: "minecraft:air"},
{pos: [0, 3, 3], state: "minecraft:air"},
{pos: [0, 3, 4], state: "minecraft:air"},
{pos: [1, 3, 0], state: "minecraft:air"},
{pos: [1, 3, 1], state: "minecraft:air"},
{pos: [1, 3, 2], state: "minecraft:air"},
{pos: [1, 3, 3], state: "computercraft:turtle_normal{facing:east,waterlogged:false}", nbt: {ComputerId: 8, Fuel: 0, Items: [], Label: "Dinnerbone", LeftUpgrade: "cctest:netherite_pickaxe", LeftUpgradeNbt: {Tag: {Damage: 0, Enchantments: [{id: "minecraft:efficiency", lvl: 5s}], RepairCost: 1}}, On: 1b, Owner: {LowerId: -7931330074873442406L, Name: "Player848", UpperId: 7430876841693101418L}, Slot: 0, id: "computercraft:turtle_normal"}},
{pos: [1, 3, 4], state: "minecraft:air"},
{pos: [2, 3, 0], state: "minecraft:air"},
{pos: [2, 3, 1], state: "minecraft:air"},
{pos: [2, 3, 2], state: "minecraft:air"},
{pos: [2, 3, 3], state: "minecraft:air"},
{pos: [2, 3, 4], state: "minecraft:air"},
{pos: [3, 3, 0], state: "minecraft:air"},
{pos: [3, 3, 1], state: "minecraft:air"},
{pos: [3, 3, 2], state: "minecraft:air"},
{pos: [3, 3, 3], state: "computercraft:turtle_normal{facing:west,waterlogged:false}", nbt: {ComputerId: 8, Fuel: 0, Items: [], On: 1b, Owner: {LowerId: -7931330074873442406L, Name: "Player848", UpperId: 7430876841693101418L}, RightUpgrade: "minecraft:diamond_pickaxe", RightUpgradeNbt: {Tag: {Damage: 0}}, Slot: 0, id: "computercraft:turtle_normal"}},
{pos: [3, 3, 4], state: "minecraft:air"},
{pos: [4, 3, 0], state: "minecraft:air"},
{pos: [4, 3, 1], state: "minecraft:air"},
{pos: [4, 3, 2], state: "minecraft:air"},
{pos: [4, 3, 3], state: "minecraft:air"},
{pos: [4, 3, 4], state: "minecraft:air"},
{pos: [0, 4, 0], state: "minecraft:air"},
{pos: [0, 4, 1], state: "minecraft:air"},
{pos: [0, 4, 2], state: "minecraft:air"},
{pos: [0, 4, 3], state: "minecraft:air"},
{pos: [0, 4, 4], state: "minecraft:air"},
{pos: [1, 4, 0], state: "minecraft:air"},
{pos: [1, 4, 1], state: "minecraft:air"},
{pos: [1, 4, 2], state: "minecraft:air"},
{pos: [1, 4, 3], state: "minecraft:air"},
{pos: [1, 4, 4], state: "minecraft:air"},
{pos: [2, 4, 0], state: "minecraft:air"},
{pos: [2, 4, 1], state: "minecraft:air"},
{pos: [2, 4, 2], state: "minecraft:air"},
{pos: [2, 4, 3], state: "minecraft:air"},
{pos: [2, 4, 4], state: "minecraft:air"},
{pos: [3, 4, 0], state: "minecraft:air"},
{pos: [3, 4, 1], state: "minecraft:air"},
{pos: [3, 4, 2], state: "minecraft:air"},
{pos: [3, 4, 3], state: "minecraft:air"},
{pos: [3, 4, 4], state: "minecraft:air"},
{pos: [4, 4, 0], state: "minecraft:air"},
{pos: [4, 4, 1], state: "minecraft:air"},
{pos: [4, 4, 2], state: "minecraft:air"},
{pos: [4, 4, 3], state: "minecraft:air"},
{pos: [4, 4, 4], state: "minecraft:air"}
],
entities: [
{blockPos: [2, 1, 0], pos: [2.52408694606693d, 1.0d, 0.6487863808268131d], nbt: {AbsorptionAmount: 0.0f, Air: 300s, ArmorItems: [{}, {}, {}, {}], Attributes: [{Base: 0.699999988079071d, Name: "minecraft:generic.movement_speed"}], Brain: {memories: {}}, CustomName: '{"text":"turtle_test.render_turtle_blocks"}', DeathTime: 0s, DisabledSlots: 0, FallDistance: 0.0f, FallFlying: 0b, Fire: -1s, HandItems: [{}, {}], Health: 20.0f, HurtByTimestamp: 0, HurtTime: 0s, Invisible: 1b, Invulnerable: 0b, Marker: 1b, Motion: [0.0d, 0.0d, 0.0d], NoBasePlate: 0b, OnGround: 0b, PortalCooldown: 0, Pos: [124.52408694606693d, -58.0d, 50.64878638082681d], Pose: {}, Rotation: [0.14965993f, 4.066999f], ShowArms: 0b, Small: 0b, UUID: [I; 755114464, 1289439453, -2088751844, 985194758], id: "minecraft:armor_stand"}}
],
palette: [
"minecraft:polished_andesite",
"minecraft:air",
"computercraft:turtle_normal{facing:east,waterlogged:false}",
"computercraft:turtle_normal{facing:west,waterlogged:false}"
]
}

View File

@ -0,0 +1,142 @@
{
DataVersion: 3337,
size: [5, 5, 5],
data: [
{pos: [0, 0, 0], state: "minecraft:polished_andesite"},
{pos: [0, 0, 1], state: "minecraft:polished_andesite"},
{pos: [0, 0, 2], state: "minecraft:polished_andesite"},
{pos: [0, 0, 3], state: "minecraft:polished_andesite"},
{pos: [0, 0, 4], state: "minecraft:polished_andesite"},
{pos: [1, 0, 0], state: "minecraft:polished_andesite"},
{pos: [1, 0, 1], state: "minecraft:polished_andesite"},
{pos: [1, 0, 2], state: "minecraft:polished_andesite"},
{pos: [1, 0, 3], state: "minecraft:polished_andesite"},
{pos: [1, 0, 4], state: "minecraft:polished_andesite"},
{pos: [2, 0, 0], state: "minecraft:polished_andesite"},
{pos: [2, 0, 1], state: "minecraft:polished_andesite"},
{pos: [2, 0, 2], state: "minecraft:polished_andesite"},
{pos: [2, 0, 3], state: "minecraft:polished_andesite"},
{pos: [2, 0, 4], state: "minecraft:polished_andesite"},
{pos: [3, 0, 0], state: "minecraft:polished_andesite"},
{pos: [3, 0, 1], state: "minecraft:polished_andesite"},
{pos: [3, 0, 2], state: "minecraft:polished_andesite"},
{pos: [3, 0, 3], state: "minecraft:polished_andesite"},
{pos: [3, 0, 4], state: "minecraft:polished_andesite"},
{pos: [4, 0, 0], state: "minecraft:polished_andesite"},
{pos: [4, 0, 1], state: "minecraft:polished_andesite"},
{pos: [4, 0, 2], state: "minecraft:polished_andesite"},
{pos: [4, 0, 3], state: "minecraft:polished_andesite"},
{pos: [4, 0, 4], state: "minecraft:polished_andesite"},
{pos: [0, 1, 0], state: "minecraft:air"},
{pos: [0, 1, 1], state: "minecraft:air"},
{pos: [0, 1, 2], state: "minecraft:air"},
{pos: [0, 1, 3], state: "minecraft:air"},
{pos: [0, 1, 4], state: "minecraft:air"},
{pos: [1, 1, 0], state: "minecraft:air"},
{pos: [1, 1, 1], state: "minecraft:air"},
{pos: [1, 1, 2], state: "minecraft:air"},
{pos: [1, 1, 3], state: "minecraft:air"},
{pos: [1, 1, 4], state: "minecraft:air"},
{pos: [2, 1, 0], state: "minecraft:air"},
{pos: [2, 1, 1], state: "minecraft:air"},
{pos: [2, 1, 2], state: "minecraft:air"},
{pos: [2, 1, 3], state: "minecraft:air"},
{pos: [2, 1, 4], state: "minecraft:air"},
{pos: [3, 1, 0], state: "minecraft:air"},
{pos: [3, 1, 1], state: "minecraft:air"},
{pos: [3, 1, 2], state: "minecraft:air"},
{pos: [3, 1, 3], state: "minecraft:air"},
{pos: [3, 1, 4], state: "minecraft:air"},
{pos: [4, 1, 0], state: "minecraft:air"},
{pos: [4, 1, 1], state: "minecraft:air"},
{pos: [4, 1, 2], state: "minecraft:air"},
{pos: [4, 1, 3], state: "minecraft:air"},
{pos: [4, 1, 4], state: "minecraft:air"},
{pos: [0, 2, 0], state: "minecraft:air"},
{pos: [0, 2, 1], state: "minecraft:air"},
{pos: [0, 2, 2], state: "minecraft:air"},
{pos: [0, 2, 3], state: "minecraft:air"},
{pos: [0, 2, 4], state: "minecraft:air"},
{pos: [1, 2, 0], state: "minecraft:air"},
{pos: [1, 2, 1], state: "minecraft:air"},
{pos: [1, 2, 2], state: "minecraft:air"},
{pos: [1, 2, 3], state: "minecraft:air"},
{pos: [1, 2, 4], state: "minecraft:air"},
{pos: [2, 2, 0], state: "minecraft:air"},
{pos: [2, 2, 1], state: "minecraft:air"},
{pos: [2, 2, 2], state: "minecraft:air"},
{pos: [2, 2, 3], state: "minecraft:air"},
{pos: [2, 2, 4], state: "minecraft:air"},
{pos: [3, 2, 0], state: "minecraft:air"},
{pos: [3, 2, 1], state: "minecraft:air"},
{pos: [3, 2, 2], state: "minecraft:air"},
{pos: [3, 2, 3], state: "minecraft:air"},
{pos: [3, 2, 4], state: "minecraft:air"},
{pos: [4, 2, 0], state: "minecraft:air"},
{pos: [4, 2, 1], state: "minecraft:air"},
{pos: [4, 2, 2], state: "minecraft:air"},
{pos: [4, 2, 3], state: "minecraft:air"},
{pos: [4, 2, 4], state: "minecraft:air"},
{pos: [0, 3, 0], state: "minecraft:air"},
{pos: [0, 3, 1], state: "minecraft:air"},
{pos: [0, 3, 2], state: "minecraft:air"},
{pos: [0, 3, 3], state: "minecraft:air"},
{pos: [0, 3, 4], state: "minecraft:air"},
{pos: [1, 3, 0], state: "minecraft:air"},
{pos: [1, 3, 1], state: "minecraft:air"},
{pos: [1, 3, 2], state: "minecraft:air"},
{pos: [1, 3, 3], state: "minecraft:air"},
{pos: [1, 3, 4], state: "minecraft:air"},
{pos: [2, 3, 0], state: "minecraft:air"},
{pos: [2, 3, 1], state: "minecraft:air"},
{pos: [2, 3, 2], state: "minecraft:air"},
{pos: [2, 3, 3], state: "minecraft:air"},
{pos: [2, 3, 4], state: "minecraft:air"},
{pos: [3, 3, 0], state: "minecraft:air"},
{pos: [3, 3, 1], state: "minecraft:air"},
{pos: [3, 3, 2], state: "minecraft:air"},
{pos: [3, 3, 3], state: "minecraft:air"},
{pos: [3, 3, 4], state: "minecraft:air"},
{pos: [4, 3, 0], state: "minecraft:air"},
{pos: [4, 3, 1], state: "minecraft:air"},
{pos: [4, 3, 2], state: "minecraft:air"},
{pos: [4, 3, 3], state: "minecraft:air"},
{pos: [4, 3, 4], state: "minecraft:air"},
{pos: [0, 4, 0], state: "minecraft:air"},
{pos: [0, 4, 1], state: "minecraft:air"},
{pos: [0, 4, 2], state: "minecraft:air"},
{pos: [0, 4, 3], state: "minecraft:air"},
{pos: [0, 4, 4], state: "minecraft:air"},
{pos: [1, 4, 0], state: "minecraft:air"},
{pos: [1, 4, 1], state: "minecraft:air"},
{pos: [1, 4, 2], state: "minecraft:air"},
{pos: [1, 4, 3], state: "minecraft:air"},
{pos: [1, 4, 4], state: "minecraft:air"},
{pos: [2, 4, 0], state: "minecraft:air"},
{pos: [2, 4, 1], state: "minecraft:air"},
{pos: [2, 4, 2], state: "minecraft:air"},
{pos: [2, 4, 3], state: "minecraft:air"},
{pos: [2, 4, 4], state: "minecraft:air"},
{pos: [3, 4, 0], state: "minecraft:air"},
{pos: [3, 4, 1], state: "minecraft:air"},
{pos: [3, 4, 2], state: "minecraft:air"},
{pos: [3, 4, 3], state: "minecraft:air"},
{pos: [3, 4, 4], state: "minecraft:air"},
{pos: [4, 4, 0], state: "minecraft:air"},
{pos: [4, 4, 1], state: "minecraft:air"},
{pos: [4, 4, 2], state: "minecraft:air"},
{pos: [4, 4, 3], state: "minecraft:air"},
{pos: [4, 4, 4], state: "minecraft:air"}
],
entities: [
{blockPos: [2, 1, 0], pos: [2.5d, 1.0d, 0.5d], nbt: {AbsorptionAmount: 0.0f, Air: 300s, ArmorItems: [{}, {}, {}, {}], Attributes: [{Base: 0.699999988079071d, Name: "minecraft:generic.movement_speed"}], Brain: {memories: {}}, CustomName: '{"text":"turtle_test.render_turtle_items"}', DeathTime: 0s, DisabledSlots: 0, FallDistance: 0.0f, FallFlying: 0b, Fire: -1s, HandItems: [{}, {}], Health: 20.0f, HurtByTimestamp: 0, HurtTime: 0s, Invisible: 1b, Invulnerable: 0b, Marker: 1b, Motion: [0.0d, 0.0d, 0.0d], NoBasePlate: 0b, OnGround: 0b, PortalCooldown: 0, Pos: [125.5d, -58.0d, 53.6501934495752d], Pose: {}, Rotation: [0.14965993f, 4.066999f], ShowArms: 0b, Small: 0b, UUID: [I; -1678989666, 1780632657, -1267321893, 665166246], id: "minecraft:armor_stand"}},
{blockPos: [3, 1, 3], pos: [3.5d, 1.5d, 3.5d], nbt: {Air: 300s, FallDistance: 0.0f, Fire: 0s, Invulnerable: 0b, Motion: [0.0d, 0.0d, 0.0d], OnGround: 0b, PortalCooldown: 0, Pos: [126.5d, -57.5d, 56.5d], Rotation: [-90.0f, 0.0f], UUID: [I; 671334450, -268547745, -1360971514, -649716242], billboard: "fixed", glow_color_override: -1, height: 0.0f, id: "minecraft:item_display", interpolation_duration: 0, item: {Count: 1b, id: "computercraft:turtle_normal", tag: {ComputerId: 8, Fuel: 0, Items: [], display: {Name: '{"text":"Dinnerbone"}'}, On: 1b, RightUpgrade: "minecraft:diamond_pickaxe", RightUpgradeNbt: {Tag: {Damage: 0}}}}, item_display: "none", shadow_radius: 0.0f, shadow_strength: 1.0f, transformation: {left_rotation: [0.0f, 0.0f, 0.0f, 1.0f], right_rotation: [0.0f, 0.0f, 0.0f, 1.0f], scale: [1.0f, 1.0f, 1.0f], translation: [0.0f, 0.0f, 0.0f]}, view_range: 1.0f, width: 0.0f}},
{blockPos: [3, 3, 3], pos: [3.5d, 3.5d, 3.5d], nbt: {Air: 300s, FallDistance: 0.0f, Fire: 0s, Invulnerable: 0b, Motion: [0.0d, 0.0d, 0.0d], OnGround: 0b, PortalCooldown: 0, Pos: [126.5d, -57.5d, 56.5d], Rotation: [-90.0f, 0.0f], UUID: [I; 671334422, -268542345, -1362491514, -649716242], billboard: "fixed", glow_color_override: -1, height: 0.0f, id: "minecraft:item_display", interpolation_duration: 0, item: {Count: 1b, id: "computercraft:turtle_normal", tag: {ComputerId: 8, Fuel: 0, Items: [], On: 1b, RightUpgrade: "minecraft:diamond_pickaxe", RightUpgradeNbt: {Tag: {Damage: 0}}}}, item_display: "none", shadow_radius: 0.0f, shadow_strength: 1.0f, transformation: {left_rotation: [0.0f, 0.0f, 0.0f, 1.0f], right_rotation: [0.0f, 0.0f, 0.0f, 1.0f], scale: [1.0f, 1.0f, 1.0f], translation: [0.0f, 0.0f, 0.0f]}, view_range: 1.0f, width: 0.0f}},
{blockPos: [1, 1, 3], pos: [1.5d, 1.5d, 3.5d], nbt: {Air: 300s, FallDistance: 0.0f, Fire: 0s, Invulnerable: 0b, Motion: [0.0d, 0.0d, 0.0d], OnGround: 0b, PortalCooldown: 0, Pos: [126.5d, -57.5d, 56.5d], Rotation: [90.0f, 0.0f], UUID: [I; 625334450, -268647745, -1360971514, -649724242], billboard: "fixed", glow_color_override: -1, height: 0.0f, id: "minecraft:item_display", interpolation_duration: 0, item: {Count: 1b, id: "computercraft:turtle_normal", tag: {ComputerId: 8, Fuel: 0, Items: [], On: 1b, LeftUpgrade: "cctest:netherite_pickaxe", LeftUpgradeNbt: {Tag: {Damage: 0, Enchantments: [{id: "minecraft:efficiency", lvl: 5s}], RepairCost: 1}}}}, item_display: "none", shadow_radius: 0.0f, shadow_strength: 1.0f, transformation: {left_rotation: [0.0f, 0.0f, 0.0f, 1.0f], right_rotation: [0.0f, 0.0f, 0.0f, 1.0f], scale: [1.0f, 1.0f, 1.0f], translation: [0.0f, 0.0f, 0.0f]}, view_range: 1.0f, width: 0.0f}},
{blockPos: [1, 3, 3], pos: [1.5d, 3.5d, 3.5d], nbt: {Air: 300s, FallDistance: 0.0f, Fire: 0s, Invulnerable: 0b, Motion: [0.0d, 0.0d, 0.0d], OnGround: 0b, PortalCooldown: 0, Pos: [126.5d, -57.5d, 56.5d], Rotation: [90.0f, 0.0f], UUID: [I; 675334422, -268542245, -1362491514, -649755242], billboard: "fixed", glow_color_override: -1, height: 0.0f, id: "minecraft:item_display", interpolation_duration: 0, item: {Count: 1b, id: "computercraft:turtle_normal", tag: {ComputerId: 8, Fuel: 0, Items: [], display: {Name: '{"text":"Dinnerbone"}'}, On: 1b, LeftUpgrade: "cctest:netherite_pickaxe", LeftUpgradeNbt: {Tag: {Damage: 0, Enchantments: [{id: "minecraft:efficiency", lvl: 5s}], RepairCost: 1}}}}, item_display: "none", shadow_radius: 0.0f, shadow_strength: 1.0f, transformation: {left_rotation: [0.0f, 0.0f, 0.0f, 1.0f], right_rotation: [0.0f, 0.0f, 0.0f, 1.0f], scale: [1.0f, 1.0f, 1.0f], translation: [0.0f, 0.0f, 0.0f]}, view_range: 1.0f, width: 0.0f}}
],
palette: [
"minecraft:polished_andesite",
"minecraft:air"
]
}

View File

@ -0,0 +1,79 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.client.model;
import dan200.computercraft.core.util.Nullability;
import net.fabricmc.fabric.api.renderer.v1.Renderer;
import net.fabricmc.fabric.api.renderer.v1.RendererAccess;
import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial;
import net.fabricmc.fabric.api.renderer.v1.mesh.MutableQuadView;
import net.fabricmc.fabric.api.renderer.v1.model.ForwardingBakedModel;
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext;
import net.fabricmc.fabric.api.util.TriState;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.core.BlockPos;
import net.minecraft.util.RandomSource;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.BlockAndTintGetter;
import net.minecraft.world.level.block.state.BlockState;
import javax.annotation.Nullable;
import java.util.function.Supplier;
/**
* A model wrapper which applies a {@link RenderMaterial#glint() glint}/foil to the original model.
*/
public final class FoiledModel extends ForwardingBakedModel implements RenderContext.QuadTransform {
private final @Nullable Renderer renderer = RendererAccess.INSTANCE.getRenderer();
private @Nullable RenderMaterial lastMaterial, lastFoiledMaterial;
public FoiledModel(BakedModel model) {
wrapped = model;
}
@Override
public boolean isVanillaAdapter() {
return false;
}
@Override
public void emitBlockQuads(BlockAndTintGetter blockView, BlockState state, BlockPos pos, Supplier<RandomSource> randomSupplier, RenderContext context) {
context.pushTransform(this);
super.emitBlockQuads(blockView, state, pos, randomSupplier, context);
context.popTransform();
}
@Override
public void emitItemQuads(ItemStack stack, Supplier<RandomSource> randomSupplier, RenderContext context) {
context.pushTransform(this);
super.emitItemQuads(stack, randomSupplier, context);
context.popTransform();
}
@Override
public boolean equals(Object obj) {
return this == obj || (obj instanceof FoiledModel other && wrapped.equals(other.wrapped));
}
@Override
public int hashCode() {
return wrapped.hashCode() ^ 1;
}
@Override
public boolean transform(MutableQuadView quad) {
if (renderer == null) return true;
var material = quad.material();
if (material == lastMaterial) {
quad.material(Nullability.assertNonNull(lastFoiledMaterial));
} else {
lastMaterial = material;
quad.material(lastFoiledMaterial = renderer.materialFinder().copyFrom(material).glint(TriState.TRUE).find());
}
return true;
}
}

View File

@ -5,16 +5,28 @@
package dan200.computercraft.client.platform;
import com.google.auto.service.AutoService;
import com.mojang.blaze3d.vertex.PoseStack;
import dan200.computercraft.client.model.FoiledModel;
import dan200.computercraft.client.render.ModelRenderer;
import dan200.computercraft.shared.network.NetworkMessage;
import dan200.computercraft.shared.network.server.ServerNetworkContext;
import dan200.computercraft.shared.platform.NetworkHandler;
import net.fabricmc.fabric.api.renderer.v1.model.ModelHelper;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.Sheets;
import net.minecraft.client.renderer.entity.ItemRenderer;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.ModelManager;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.RandomSource;
import javax.annotation.Nullable;
@AutoService(dan200.computercraft.impl.client.ClientPlatformHelper.class)
public class ClientPlatformHelperImpl implements ClientPlatformHelper {
private static final RandomSource random = RandomSource.create(0);
@Override
public void sendToServer(NetworkMessage<ServerNetworkContext> message) {
Minecraft.getInstance().player.connection.send(NetworkHandler.encodeServer(message));
@ -25,4 +37,22 @@ public BakedModel getModel(ModelManager manager, ResourceLocation location) {
var model = manager.getModel(location);
return model == null ? manager.getMissingModel() : model;
}
@Override
public BakedModel createdFoiledModel(BakedModel model) {
return new FoiledModel(model);
}
@Override
public void renderBakedModel(PoseStack transform, MultiBufferSource buffers, BakedModel model, int lightmapCoord, int overlayLight, @Nullable int[] tints) {
// Unfortunately we can't call Fabric's emitItemQuads here, as there's no way to obtain a RenderContext via the
// API. Instead, we special case our FoiledModel, and just render everything else normally.
var buffer = ItemRenderer.getFoilBuffer(buffers, Sheets.translucentCullBlockSheet(), true, model instanceof FoiledModel);
for (var faceIdx = 0; faceIdx <= ModelHelper.NULL_FACE_ID; faceIdx++) {
var face = ModelHelper.faceFromIndex(faceIdx);
random.setSeed(42);
ModelRenderer.renderQuads(transform, buffer, model.getQuads(null, face, random), lightmapCoord, overlayLight, tints);
}
}
}

View File

@ -0,0 +1,51 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.client.model;
import dan200.computercraft.shared.util.ConsList;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.block.model.BakedQuad;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.core.Direction;
import net.minecraft.util.RandomSource;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraftforge.client.model.BakedModelWrapper;
import net.minecraftforge.client.model.data.ModelData;
import org.jetbrains.annotations.Nullable;
import java.util.List;
/**
* A model wrapper which applies a glint/foil to the original model.
*/
public final class FoiledModel extends BakedModelWrapper<BakedModel> {
public FoiledModel(BakedModel model) {
super(model);
}
@Override
public List<BakedQuad> getQuads(@Nullable BlockState state, @Nullable Direction side, RandomSource rand, ModelData extraData, @Nullable RenderType renderType) {
return renderType == RenderType.glint()
? super.getQuads(state, side, rand, extraData, null)
: super.getQuads(state, side, rand, extraData, renderType);
}
@Override
public List<RenderType> getRenderTypes(ItemStack itemStack, boolean fabulous) {
return new ConsList<>(fabulous ? RenderType.glintDirect() : RenderType.glint(), super.getRenderTypes(itemStack, fabulous));
}
@Override
public boolean equals(Object obj) {
return this == obj || (obj instanceof FoiledModel other && originalModel.equals(other.originalModel));
}
@Override
public int hashCode() {
return originalModel.hashCode() ^ 1;
}
}

View File

@ -5,22 +5,53 @@
package dan200.computercraft.client.platform;
import com.google.auto.service.AutoService;
import com.mojang.blaze3d.vertex.PoseStack;
import dan200.computercraft.client.model.FoiledModel;
import dan200.computercraft.client.render.ModelRenderer;
import dan200.computercraft.shared.network.NetworkMessage;
import dan200.computercraft.shared.network.server.ServerNetworkContext;
import dan200.computercraft.shared.platform.NetworkHandler;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.ModelManager;
import net.minecraft.core.Direction;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.RandomSource;
import net.minecraft.world.item.ItemStack;
import net.minecraftforge.client.model.data.ModelData;
import javax.annotation.Nullable;
import java.util.Arrays;
@AutoService(dan200.computercraft.impl.client.ClientPlatformHelper.class)
public class ClientPlatformHelperImpl implements ClientPlatformHelper {
private static final RandomSource random = RandomSource.create(0);
private static final Direction[] directions = Arrays.copyOf(Direction.values(), 7);
@Override
public BakedModel getModel(ModelManager manager, ResourceLocation location) {
return manager.getModel(location);
}
@Override
public BakedModel createdFoiledModel(BakedModel model) {
return new FoiledModel(model);
}
@Override
public void sendToServer(NetworkMessage<ServerNetworkContext> message) {
NetworkHandler.sendToServer(message);
}
@Override
public void renderBakedModel(PoseStack transform, MultiBufferSource buffers, BakedModel model, int lightmapCoord, int overlayLight, @Nullable int[] tints) {
for (var renderType : model.getRenderTypes(ItemStack.EMPTY, true)) {
var buffer = buffers.getBuffer(renderType);
for (var face : directions) {
random.setSeed(42);
var quads = model.getQuads(null, face, random, ModelData.EMPTY, renderType);
ModelRenderer.renderQuads(transform, buffer, quads, lightmapCoord, overlayLight, tints);
}
}
}
}