diff --git a/gradle.properties b/gradle.properties index 3ad02043f..b0d7e85fb 100644 --- a/gradle.properties +++ b/gradle.properties @@ -15,4 +15,4 @@ isUnstable=true modVersion=1.115.1 # Minecraft properties: We want to configure this here so we can read it in settings.gradle -mcVersion=1.21.4 +mcVersion=1.21.5 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d782bfa38..72a768c5d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -7,19 +7,19 @@ # Minecraft # MC version is specified in gradle.properties, as we need that in settings.gradle. # Remember to update corresponding versions in fabric.mod.json/neoforge.mods.toml -fabric-api = "0.118.0+1.21.4" +fabric-api = "0.120.0+1.21.5" fabric-loader = "0.16.10" -neoForge = "21.4.101-beta" +neoForge = "21.5.49-beta" neoMergeTool = "2.0.0" mixin = "0.8.5" -parchment = "2024.12.07" -parchmentMc = "1.21.4" -yarn = "1.21.4+build.1" +parchment = "2025.04.19" +parchmentMc = "1.21.5" +yarn = "1.21.5+build.1" # Core dependencies (these versions are tied to the version Minecraft uses) fastutil = "8.5.15" guava = "33.3.1-jre" -netty = "4.1.115.Final" +netty = "4.1.118.Final" slf4j = "2.0.16" # Core dependencies (independent of Minecraft) @@ -38,14 +38,14 @@ nightConfig = "3.8.1" # Minecraft mods emi = "1.1.7+1.21" fabricPermissions = "0.3.3" -iris-fabric = "1.8.8+1.21.4-fabric" -iris-forge = "1.8.8+1.21.4-neoforge" +iris-fabric = "1.8.11+1.21.5-fabric" +iris-forge = "1.8.11+1.21.5-neoforge" jei = "19.8.2.99" modmenu = "13.0.2" moreRed = "6.0.0.3" rei = "18.0.800" -sodium-fabric = "mc1.21.4-0.6.10-fabric" -sodium-forge = "mc1.21.4-0.6.10-neoforge" +sodium-fabric = "mc1.21.5-0.6.12-fabric" +sodium-forge = "mc1.21.5-0.6.12-neoforge" mixinExtra = "0.3.5" create-forge = "6.0.0-6" create-fabric = "0.5.1-f-build.1467+mc1.20.1" @@ -69,7 +69,7 @@ ideaExt = "1.1.7" illuaminate = "0.1.0-83-g1131f68" lwjgl = "3.3.3" minotaur = "2.8.7" -modDevGradle = "2.0.78" +modDevGradle = "2.0.82" nullAway = "0.12.4" shadow = "8.3.1" spotless = "7.0.2" diff --git a/projects/common-api/src/client/java/dan200/computercraft/api/client/StandaloneModel.java b/projects/common-api/src/client/java/dan200/computercraft/api/client/StandaloneModel.java new file mode 100644 index 000000000..008ed08ce --- /dev/null +++ b/projects/common-api/src/client/java/dan200/computercraft/api/client/StandaloneModel.java @@ -0,0 +1,153 @@ +// SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers +// +// SPDX-License-Identifier: MPL-2.0 + +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; +import net.minecraft.client.renderer.item.ItemStackRenderState; +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; +import net.minecraft.world.item.ItemDisplayContext; +import net.minecraft.world.item.ItemStack; +import org.joml.Vector3f; +import org.jspecify.annotations.Nullable; + +import java.util.List; +import java.util.function.Supplier; + +/** + * A standalone model. + *

+ * This is very similar to vanilla's {@link BlockModelWrapper}, but suitable for use in both {@link ItemModel}s and + * block models. This is primarily intended for use with {@link TurtleUpgradeModel}s. + */ +public final class StandaloneModel { + private final List quads; + private final boolean useBlockLight; + private final TextureAtlasSprite particleIcon; + private final RenderType renderType; + private final Supplier extents; + + /** + * Construct a new {@link StandaloneModel}. + * + * @param quads The list of quads which form this model. + * @param usesBlockLight Whether this uses block lighting. See {@link ItemStackRenderState.LayerRenderState#setUsesBlockLight(boolean)}. + * @param particleIcon The sprite for the model's particles. See {@link ItemStackRenderState.LayerRenderState#setParticleIcon(TextureAtlasSprite)}. + * @param renderType The render type for this model. + */ + public StandaloneModel(List quads, boolean usesBlockLight, TextureAtlasSprite particleIcon, RenderType renderType) { + this.quads = quads; + this.useBlockLight = usesBlockLight; + this.particleIcon = particleIcon; + this.renderType = renderType; + this.extents = Suppliers.memoize(() -> BlockModelWrapper.computeExtents(quads)); + } + + /** + * Load a model from a {@link ModelBaker} and bake it. + * + * @param model The model id to load. + * @param baker The model baker. + * @return The baked {@link StandaloneModel}. + */ + public static StandaloneModel of(ResourceLocation model, ModelBaker baker) { + return of(baker.getModel(model), baker); + } + + /** + * Bake a {@link ResolvedModel} into a {@link StandaloneModel}. + * + * @param model The resolved model. + * @param baker The model baker. + * @return The baked {@link StandaloneModel}. + */ + public static StandaloneModel of(ResolvedModel model, ModelBaker baker) { + var slots = model.getTopTextureSlots(); + return new StandaloneModel( + model.bakeTopGeometry(slots, baker, BlockModelRotation.X0_Y0).getAll(), + model.getTopGuiLight().lightLikeBlock(), + model.resolveParticleSprite(slots, baker), + Sheets.translucentItemSheet() + ); + } + + /** + * Set up an {@link ItemStackRenderState.LayerRenderState} to render this model. + * + * @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); + layer.setRenderType(renderType); + layer.setUsesBlockLight(useBlockLight); + layer.setParticleIcon(particleIcon); + layer.prepareQuadList().addAll(quads); + } + + /** + * Render the model directly. + * + * @param transform The current pose stack transformations. + * @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); + } + + /** + * Render the model directly. + * + * @param transform The current pose stack transformations. + * @param buffers The buffer source to use for rendering. + * @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(); + var buffer = buffers.getBuffer(renderType); + for (var quad : quads) { + float r, g, b, a; + var idx = quad.tintIndex(); + if (tints != null && idx >= 0 && idx < tints.length) { + var tint = tints[idx]; + r = ARGB.red(tint) / 255.0f; + g = ARGB.green(tint) / 255.0f; + b = ARGB.blue(tint) / 255.0f; + a = ARGB.alpha(tint) / 255.0f; + } else { + r = g = b = a = 1.0f; + } + + buffer.putBulkData(pose, quad, r, g, b, a, light, overlay); + } + } +} diff --git a/projects/common-api/src/client/java/dan200/computercraft/api/client/TransformedModel.java b/projects/common-api/src/client/java/dan200/computercraft/api/client/TransformedModel.java deleted file mode 100644 index 7e94488f5..000000000 --- a/projects/common-api/src/client/java/dan200/computercraft/api/client/TransformedModel.java +++ /dev/null @@ -1,43 +0,0 @@ -// SPDX-FileCopyrightText: 2020 The CC: Tweaked Developers -// -// SPDX-License-Identifier: MPL-2.0 - - -package dan200.computercraft.api.client; - -import com.mojang.math.Transformation; -import dan200.computercraft.impl.client.ClientPlatformHelper; -import net.minecraft.client.Minecraft; -import net.minecraft.client.resources.model.BakedModel; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.item.ItemStack; - -/** - * A model to render, combined with a transformation matrix to apply. - */ -public sealed interface TransformedModel permits TransformedModel.Baked, TransformedModel.Item { - record Baked(BakedModel model) implements TransformedModel { - } - - record Item(ItemStack stack, Transformation transformation) implements TransformedModel { - } - - static TransformedModel of(BakedModel model) { - return new TransformedModel.Baked(model); - } - - /** - * Look up a model in the model bakery and construct a {@link TransformedModel} with no transformation. - * - * @param location The location of the model to load. - * @return The new {@link TransformedModel} instance. - */ - static TransformedModel of(ResourceLocation location) { - var modelManager = Minecraft.getInstance().getModelManager(); - return of(ClientPlatformHelper.get().getModel(modelManager, location)); - } - - static TransformedModel of(ItemStack item, Transformation transform) { - return new TransformedModel.Item(item, transform); - } -} diff --git a/projects/common-api/src/client/java/dan200/computercraft/api/client/turtle/ItemUpgradeModel.java b/projects/common-api/src/client/java/dan200/computercraft/api/client/turtle/ItemUpgradeModel.java new file mode 100644 index 000000000..190bf5361 --- /dev/null +++ b/projects/common-api/src/client/java/dan200/computercraft/api/client/turtle/ItemUpgradeModel.java @@ -0,0 +1,102 @@ +// 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.math.Axis; +import com.mojang.math.Transformation; +import dan200.computercraft.api.turtle.ITurtleAccess; +import dan200.computercraft.api.turtle.ITurtleUpgrade; +import dan200.computercraft.api.turtle.TurtleSide; +import net.minecraft.client.Minecraft; +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.special.SpecialModelRenderer; +import net.minecraft.client.resources.model.ModelBaker; +import net.minecraft.core.component.DataComponentPatch; +import net.minecraft.util.Mth; +import net.minecraft.world.item.ItemDisplayContext; +import net.minecraft.world.item.ItemStack; +import org.joml.Matrix4f; +import org.jspecify.annotations.Nullable; + +final class ItemUpgradeModel implements TurtleUpgradeModel { + static final TurtleUpgradeModel.Unbaked UNBAKED = new Unbaked(); + static final TurtleUpgradeModel INSTANCE = new ItemUpgradeModel<>(); + + private static final TransformedRenderer LEFT = computeRenderer(TurtleSide.LEFT); + private static final TransformedRenderer RIGHT = computeRenderer(TurtleSide.RIGHT); + + private ItemUpgradeModel() { + } + + @Override + public void renderForItem(T upgrade, TurtleSide side, DataComponentPatch data, ItemStackRenderState renderer, ItemModelResolver resolver, ItemTransform transform, int seed) { + var childState = new ItemStackRenderState(); + resolver.updateForTopItem(childState, upgrade.getUpgradeItem(data), ItemDisplayContext.NONE, null, null, seed); + if (!childState.isEmpty()) { + var layer = renderer.newLayer(); + layer.setTransform(transform); + layer.setupSpecialModel(getRenderer(side), childState); + } + } + + @Override + public void renderForLevel(T upgrade, ITurtleAccess turtle, TurtleSide side, DataComponentPatch data, 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 + ); + } + + private static final class Unbaked implements TurtleUpgradeModel.Unbaked { + @Override + public TurtleUpgradeModel bake(ModelBaker baker) { + return INSTANCE; + } + + @Override + public void resolveDependencies(Resolver resolver) { + } + } + + private static TransformedRenderer computeRenderer(TurtleSide side) { + var pose = new Matrix4f(); + pose.translate(0.5f, 0.5f, 0.5f); + pose.rotate(Axis.YN.rotationDegrees(90f)); + pose.rotate(Axis.ZP.rotationDegrees(90f)); + pose.translate(0.0f, 0.0f, side == TurtleSide.RIGHT ? -0.4065f : 0.4065f); + return new TransformedRenderer(new Transformation(pose)); + } + + private static TransformedRenderer getRenderer(TurtleSide side) { + return switch (side) { + case LEFT -> LEFT; + case RIGHT -> RIGHT; + }; + } + + private record TransformedRenderer(Transformation transform) implements SpecialModelRenderer { + @Override + public void render( + @Nullable ItemStackRenderState state, ItemDisplayContext itemDisplayContext, PoseStack poseStack, + MultiBufferSource multiBufferSource, int overlay, int light, boolean bl + ) { + if (state == null) return; + poseStack.pushPose(); + poseStack.mulPose(transform.getMatrix()); + state.render(poseStack, multiBufferSource, overlay, light); + poseStack.popPose(); + } + + @Override + public @Nullable ItemStackRenderState extractArgument(ItemStack itemStack) { + return null; + } + } +} diff --git a/projects/common-api/src/client/java/dan200/computercraft/api/client/turtle/RegisterTurtleUpgradeModeller.java b/projects/common-api/src/client/java/dan200/computercraft/api/client/turtle/RegisterTurtleUpgradeModel.java similarity index 64% rename from projects/common-api/src/client/java/dan200/computercraft/api/client/turtle/RegisterTurtleUpgradeModeller.java rename to projects/common-api/src/client/java/dan200/computercraft/api/client/turtle/RegisterTurtleUpgradeModel.java index ede3e7675..3100d2b5a 100644 --- a/projects/common-api/src/client/java/dan200/computercraft/api/client/turtle/RegisterTurtleUpgradeModeller.java +++ b/projects/common-api/src/client/java/dan200/computercraft/api/client/turtle/RegisterTurtleUpgradeModel.java @@ -8,19 +8,19 @@ import dan200.computercraft.api.turtle.ITurtleUpgrade; import dan200.computercraft.api.upgrades.UpgradeType; /** - * A functional interface to register a {@link TurtleUpgradeModeller} for a class of turtle upgrades. + * A functional interface to register a {@link TurtleUpgradeModel} for a class of turtle upgrades. *

* This interface is largely intended to be used from multi-loader code, to allow sharing registration code between * multiple loaders. */ @FunctionalInterface -public interface RegisterTurtleUpgradeModeller { +public interface RegisterTurtleUpgradeModel { /** - * Register a {@link TurtleUpgradeModeller}. + * Register a {@link TurtleUpgradeModel}. * - * @param type The turtle upgrade type. - * @param modeller The upgrade modeller. - * @param The type of the turtle upgrade. + * @param type The turtle upgrade type. + * @param mode The unbaked upgrade model. + * @param The type of the turtle upgrade. */ - void register(UpgradeType type, TurtleUpgradeModeller modeller); + void register(UpgradeType type, TurtleUpgradeModel.Unbaked mode); } diff --git a/projects/common-api/src/client/java/dan200/computercraft/api/client/turtle/SidedUpgradeModel.java b/projects/common-api/src/client/java/dan200/computercraft/api/client/turtle/SidedUpgradeModel.java new file mode 100644 index 000000000..4020607d2 --- /dev/null +++ b/projects/common-api/src/client/java/dan200/computercraft/api/client/turtle/SidedUpgradeModel.java @@ -0,0 +1,40 @@ +// SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers +// +// SPDX-License-Identifier: MPL-2.0 + +package dan200.computercraft.api.client.turtle; + +import dan200.computercraft.api.client.StandaloneModel; +import dan200.computercraft.api.turtle.ITurtleUpgrade; +import dan200.computercraft.api.turtle.TurtleSide; +import net.minecraft.client.resources.model.ModelBaker; +import net.minecraft.core.component.DataComponentPatch; +import net.minecraft.resources.ResourceLocation; + +record SidedUpgradeModel( + StandaloneModel left, StandaloneModel right +) implements TurtleUpgradeModelViaStandalone { + @Override + public StandaloneModel getModel(T upgrade, TurtleSide side, DataComponentPatch data) { + return switch (side) { + case LEFT -> left(); + case RIGHT -> right(); + }; + } + + record Unbaked( + ResourceLocation left, ResourceLocation right + ) implements TurtleUpgradeModel.Unbaked { + + @Override + public TurtleUpgradeModel bake(ModelBaker baker) { + return new SidedUpgradeModel<>(StandaloneModel.of(left(), baker), StandaloneModel.of(right(), baker)); + } + + @Override + public void resolveDependencies(Resolver resolver) { + resolver.markDependency(left()); + resolver.markDependency(right()); + } + } +} diff --git a/projects/common-api/src/client/java/dan200/computercraft/api/client/turtle/TurtleUpgradeModel.java b/projects/common-api/src/client/java/dan200/computercraft/api/client/turtle/TurtleUpgradeModel.java new file mode 100644 index 000000000..786c288b4 --- /dev/null +++ b/projects/common-api/src/client/java/dan200/computercraft/api/client/turtle/TurtleUpgradeModel.java @@ -0,0 +1,107 @@ +// 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.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.block.model.ItemTransform; +import net.minecraft.client.renderer.item.ItemModel; +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; + +/** + * The model for a {@link ITurtleUpgrade}. + *

+ * Use {@code dan200.computercraft.api.client.FabricComputerCraftAPIClient#registerTurtleUpgradeModeller} to register a + * modeller on Fabric and {@code dan200.computercraft.api.client.turtle.RegisterTurtleModellersEvent} to register one + * on Forge. + * + *

Example

+ *

Fabric

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

Forge

+ * {@snippet class=com.example.examplemod.FabricExampleModClient region=turtle_model} + * + * @param The type of turtle upgrade this modeller applies to. + * @see RegisterTurtleUpgradeModel For multi-loader registration support. + */ +public interface TurtleUpgradeModel { + /** + * 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 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); + + /** + * 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 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); + + /** + * An unbaked turtle model. Much like other unbaked models (e.g. {@link ItemModel.Unbaked}), this should resolve + * any dependencies and returned the fully-resolved model. + * + * @param The type of turtle upgrade for this model. + */ + interface Unbaked extends ResolvableModel { + TurtleUpgradeModel bake(ModelBaker baker); + } + + /** + * A basic {@link TurtleUpgradeModel} which renders using the upgrade's {@linkplain ITurtleUpgrade#getUpgradeItem(DataComponentPatch) + * upgrade item}. + *

+ * This uses appropriate transformations for "flat" items, namely those extending the {@literal minecraft:item/generated} + * model type. It will not appear correct for 3D models with additional depth, such as blocks. + * + * @param The type of the turtle upgrade. + * @return The constructed modeller. + */ + static TurtleUpgradeModel.Unbaked flatItem() { + return ItemUpgradeModel.UNBAKED; + } + + /** + * Construct a {@link TurtleUpgradeModel} which has a single model for the left and right side. + * + * @param left The model to use on the left. + * @param right The model to use on the right. + * @param The type of the turtle upgrade. + * @return The constructed modeller. + */ + static TurtleUpgradeModel.Unbaked sided(ResourceLocation left, ResourceLocation right) { + return new SidedUpgradeModel.Unbaked<>(left, right); + } +} diff --git a/projects/common-api/src/client/java/dan200/computercraft/api/client/turtle/TurtleUpgradeModelViaStandalone.java b/projects/common-api/src/client/java/dan200/computercraft/api/client/turtle/TurtleUpgradeModelViaStandalone.java new file mode 100644 index 000000000..81ea70ad6 --- /dev/null +++ b/projects/common-api/src/client/java/dan200/computercraft/api/client/turtle/TurtleUpgradeModelViaStandalone.java @@ -0,0 +1,32 @@ +// SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers +// +// SPDX-License-Identifier: MPL-2.0 + +package dan200.computercraft.api.client.turtle; + +import com.mojang.blaze3d.vertex.PoseStack; +import dan200.computercraft.api.client.StandaloneModel; +import dan200.computercraft.api.turtle.ITurtleAccess; +import dan200.computercraft.api.turtle.ITurtleUpgrade; +import dan200.computercraft.api.turtle.TurtleSide; +import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.client.renderer.block.model.ItemTransform; +import net.minecraft.client.renderer.item.ItemModelResolver; +import net.minecraft.client.renderer.item.ItemStackRenderState; +import net.minecraft.core.component.DataComponentPatch; + +public interface TurtleUpgradeModelViaStandalone extends TurtleUpgradeModel { + StandaloneModel getModel(T upgrade, TurtleSide side, DataComponentPatch data); + + @Override + default void renderForItem(T upgrade, TurtleSide side, DataComponentPatch data, ItemStackRenderState renderer, ItemModelResolver resolver, ItemTransform transform, int seed) { + var layer = renderer.newLayer(); + layer.setTransform(transform); + getModel(upgrade, side, data).setupItemLayer(layer); + } + + @Override + default void renderForLevel(T upgrade, ITurtleAccess turtle, TurtleSide side, DataComponentPatch data, PoseStack transform, MultiBufferSource buffers, int light, int overlay) { + getModel(upgrade, side, data).render(transform, buffers, light, overlay); + } +} diff --git a/projects/common-api/src/client/java/dan200/computercraft/api/client/turtle/TurtleUpgradeModeller.java b/projects/common-api/src/client/java/dan200/computercraft/api/client/turtle/TurtleUpgradeModeller.java deleted file mode 100644 index 347d0314f..000000000 --- a/projects/common-api/src/client/java/dan200/computercraft/api/client/turtle/TurtleUpgradeModeller.java +++ /dev/null @@ -1,99 +0,0 @@ -// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers -// -// SPDX-License-Identifier: MPL-2.0 - -package dan200.computercraft.api.client.turtle; - -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 net.minecraft.client.resources.model.UnbakedModel; -import net.minecraft.core.component.DataComponentPatch; -import net.minecraft.resources.ResourceLocation; -import org.jspecify.annotations.Nullable; - -import java.util.stream.Stream; - -/** - * Provides models for a {@link ITurtleUpgrade}. - *

- * Use {@code dan200.computercraft.api.client.FabricComputerCraftAPIClient#registerTurtleUpgradeModeller} to register a - * modeller on Fabric and {@code dan200.computercraft.api.client.turtle.RegisterTurtleModellersEvent} to register one - * on Forge. - * - *

Example

- *

Fabric

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

Forge

- * {@snippet class=com.example.examplemod.FabricExampleModClient region=turtle_modellers} - * - * @param The type of turtle upgrade this modeller applies to. - * @see RegisterTurtleUpgradeModeller For multi-loader registration support. - */ -public interface TurtleUpgradeModeller { - /** - * Obtain the model to be used when rendering a turtle peripheral. - *

- * When the current turtle is {@literal null}, this function should be constant for a given upgrade, side and data. - * - * @param upgrade The upgrade that you're getting the model for. - * @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. - * @return The model that you wish to be used to render your upgrade. - */ - TransformedModel getModel(T upgrade, @Nullable ITurtleAccess turtle, TurtleSide side, DataComponentPatch data); - - /** - * Get the models that this turtle modeller depends on. - *

- * Models included in this stream will be loaded and baked alongside item and block models, and so may be referenced - * by {@link TransformedModel#of(ResourceLocation)}. You do not need to override this method if you will load models - * by other means. - * - * @return A list of models that this modeller depends on. - * @see UnbakedModel#resolveDependencies(UnbakedModel.Resolver) - */ - default Stream getDependencies() { - return Stream.of(); - } - - /** - * A basic {@link TurtleUpgradeModeller} which renders using the upgrade's {@linkplain ITurtleUpgrade#getUpgradeItem(DataComponentPatch)} - * upgrade item}. - *

- * This uses appropriate transformations for "flat" items, namely those extending the {@literal minecraft:item/generated} - * model type. It will not appear correct for 3D models with additional depth, such as blocks. - * - * @param The type of the turtle upgrade. - * @return The constructed modeller. - */ - @SuppressWarnings("unchecked") - static TurtleUpgradeModeller flatItem() { - return (TurtleUpgradeModeller) TurtleUpgradeModellers.UPGRADE_ITEM; - } - - /** - * Construct a {@link TurtleUpgradeModeller} which has a single model for the left and right side. - * - * @param left The model to use on the left. - * @param right The model to use on the right. - * @param The type of the turtle upgrade. - * @return The constructed modeller. - */ - static TurtleUpgradeModeller sided(ResourceLocation left, ResourceLocation right) { - return new TurtleUpgradeModeller<>() { - @Override - public TransformedModel getModel(T upgrade, @Nullable ITurtleAccess turtle, TurtleSide side, DataComponentPatch data) { - return TransformedModel.of(side == TurtleSide.LEFT ? left : right); - } - - @Override - public Stream getDependencies() { - return Stream.of(left, right); - } - }; - } -} diff --git a/projects/common-api/src/client/java/dan200/computercraft/api/client/turtle/TurtleUpgradeModellers.java b/projects/common-api/src/client/java/dan200/computercraft/api/client/turtle/TurtleUpgradeModellers.java deleted file mode 100644 index 8612ffb23..000000000 --- a/projects/common-api/src/client/java/dan200/computercraft/api/client/turtle/TurtleUpgradeModellers.java +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers -// -// SPDX-License-Identifier: MPL-2.0 - -package dan200.computercraft.api.client.turtle; - -import com.mojang.math.Axis; -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 net.minecraft.core.component.DataComponentPatch; -import org.joml.Matrix4f; -import org.jspecify.annotations.Nullable; - -final class TurtleUpgradeModellers { - private static final Transformation leftTransform = getMatrixFor(TurtleSide.LEFT); - private static final Transformation rightTransform = getMatrixFor(TurtleSide.RIGHT); - - private static Transformation getMatrixFor(TurtleSide side) { - var pose = new Matrix4f(); - pose.translate(0.5f, 0.5f, 0.5f); - pose.rotate(Axis.YN.rotationDegrees(90f)); - pose.rotate(Axis.ZP.rotationDegrees(90f)); - pose.translate(0.0f, 0.0f, side == TurtleSide.RIGHT ? -0.4065f : 0.4065f); - return new Transformation(pose); - } - - static final TurtleUpgradeModeller UPGRADE_ITEM = new UpgradeItemModeller(); - - private static final class UpgradeItemModeller implements TurtleUpgradeModeller { - @Override - public TransformedModel getModel(ITurtleUpgrade upgrade, @Nullable ITurtleAccess turtle, TurtleSide side, DataComponentPatch data) { - return TransformedModel.of(upgrade.getUpgradeItem(data), side == TurtleSide.LEFT ? leftTransform : rightTransform); - } - } -} diff --git a/projects/common-api/src/client/java/dan200/computercraft/impl/client/ClientPlatformHelper.java b/projects/common-api/src/client/java/dan200/computercraft/impl/client/ClientPlatformHelper.java deleted file mode 100644 index c36fe919b..000000000 --- a/projects/common-api/src/client/java/dan200/computercraft/impl/client/ClientPlatformHelper.java +++ /dev/null @@ -1,43 +0,0 @@ -// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers -// -// SPDX-License-Identifier: MPL-2.0 - -package dan200.computercraft.impl.client; - -import dan200.computercraft.impl.Services; -import net.minecraft.client.resources.model.BakedModel; -import net.minecraft.client.resources.model.ModelManager; -import net.minecraft.resources.ResourceLocation; -import org.jetbrains.annotations.ApiStatus; -import org.jspecify.annotations.Nullable; - -@ApiStatus.Internal -public interface ClientPlatformHelper { - /** - * Get a model from a resource. - * - * @param manager The model manager. - * @param resourceLocation The model resourceLocation. - * @return The baked model. - */ - BakedModel getModel(ModelManager manager, ResourceLocation resourceLocation); - - static ClientPlatformHelper get() { - var instance = Instance.INSTANCE; - return instance == null ? Services.raise(ClientPlatformHelper.class, Instance.ERROR) : instance; - } - - final class Instance { - static final @Nullable ClientPlatformHelper INSTANCE; - static final @Nullable Throwable ERROR; - - static { - var helper = Services.tryLoad(ClientPlatformHelper.class); - INSTANCE = helper.instance(); - ERROR = helper.error(); - } - - private Instance() { - } - } -} diff --git a/projects/common-api/src/main/java/dan200/computercraft/api/turtle/ITurtleUpgrade.java b/projects/common-api/src/main/java/dan200/computercraft/api/turtle/ITurtleUpgrade.java index 51988eaac..d15fe4e46 100644 --- a/projects/common-api/src/main/java/dan200/computercraft/api/turtle/ITurtleUpgrade.java +++ b/projects/common-api/src/main/java/dan200/computercraft/api/turtle/ITurtleUpgrade.java @@ -53,13 +53,13 @@ import java.util.function.Function; * *

Rendering the upgrade

* Next, we need to register a model for our upgrade. This is done by registering a - * {@link dan200.computercraft.api.client.turtle.TurtleUpgradeModeller} for your upgrade type. + * {@link dan200.computercraft.api.client.turtle.TurtleUpgradeModel} for your upgrade type. * *

Fabric

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

Forge

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

Registering the upgrade itself

* Upgrades themselves are loaded from datapacks when a level is loaded. In order to register our new upgrade, we must diff --git a/projects/common/src/client/java/dan200/computercraft/client/ClientHooks.java b/projects/common/src/client/java/dan200/computercraft/client/ClientHooks.java index 8c3055daf..8b87f0611 100644 --- a/projects/common/src/client/java/dan200/computercraft/client/ClientHooks.java +++ b/projects/common/src/client/java/dan200/computercraft/client/ClientHooks.java @@ -39,7 +39,6 @@ import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.HitResult; -import org.jspecify.annotations.Nullable; import java.util.function.Consumer; @@ -137,20 +136,20 @@ public final class ClientHooks { if (upgrade != null) out.accept(String.format("Upgrade[%s]: %s", side, upgrade.holder().key().location())); } - public static @Nullable BlockState getBlockBreakingState(BlockState state, BlockPos pos) { + public static BlockState getBlockBreakingState(BlockState state, BlockPos pos) { // Only apply to cables which have both a cable and modem if (state.getBlock() != ModRegistry.Blocks.CABLE.get() || !state.getValue(CableBlock.CABLE) || state.getValue(CableBlock.MODEM) == CableModemVariant.None ) { - return null; + return state; } var hit = Minecraft.getInstance().hitResult; - if (hit == null || hit.getType() != HitResult.Type.BLOCK) return null; + if (hit == null || hit.getType() != HitResult.Type.BLOCK) return state; var hitPos = ((BlockHitResult) hit).getBlockPos(); - if (!hitPos.equals(pos)) return null; + if (!hitPos.equals(pos)) return state; return WorldUtil.isVecInside(CableShapes.getModemShape(state), hit.getLocation().subtract(pos.getX(), pos.getY(), pos.getZ())) ? state.getBlock().defaultBlockState().setValue(CableBlock.MODEM, state.getValue(CableBlock.MODEM)) diff --git a/projects/common/src/client/java/dan200/computercraft/client/ClientRegistry.java b/projects/common/src/client/java/dan200/computercraft/client/ClientRegistry.java index f9ad68044..cf2b47633 100644 --- a/projects/common/src/client/java/dan200/computercraft/client/ClientRegistry.java +++ b/projects/common/src/client/java/dan200/computercraft/client/ClientRegistry.java @@ -6,19 +6,21 @@ package dan200.computercraft.client; import com.mojang.serialization.MapCodec; import dan200.computercraft.api.ComputerCraftAPI; -import dan200.computercraft.api.client.turtle.RegisterTurtleUpgradeModeller; -import dan200.computercraft.api.client.turtle.TurtleUpgradeModeller; +import dan200.computercraft.api.client.StandaloneModel; +import dan200.computercraft.api.client.turtle.RegisterTurtleUpgradeModel; +import dan200.computercraft.api.client.turtle.TurtleUpgradeModel; import dan200.computercraft.client.gui.*; import dan200.computercraft.client.item.colour.PocketComputerLight; import dan200.computercraft.client.item.model.TurtleOverlayModel; -import dan200.computercraft.client.item.model.TurtleUpgradeModel; import dan200.computercraft.client.item.properties.PocketComputerStateProperty; import dan200.computercraft.client.item.properties.TurtleShowElfOverlay; +import dan200.computercraft.client.platform.ClientPlatformHelper; +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.TurtleModemModeller; -import dan200.computercraft.client.turtle.TurtleUpgradeModellers; +import dan200.computercraft.client.turtle.TurtleModemModel; +import dan200.computercraft.client.turtle.TurtleUpgradeModels; import dan200.computercraft.shared.ModRegistry; import dan200.computercraft.shared.computer.inventory.AbstractComputerMenu; import dan200.computercraft.shared.turtle.TurtleOverlay; @@ -32,14 +34,18 @@ import net.minecraft.client.renderer.blockentity.BlockEntityRenderers; import net.minecraft.client.renderer.item.ItemModel; import net.minecraft.client.renderer.item.properties.conditional.ConditionalItemModelProperty; import net.minecraft.client.renderer.item.properties.select.SelectItemModelProperty; +import net.minecraft.client.resources.model.MissingBlockModel; +import net.minecraft.client.resources.model.ModelManager; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.packs.resources.PreparableReloadListener; import net.minecraft.world.inventory.AbstractContainerMenu; import net.minecraft.world.inventory.MenuType; import java.util.Collection; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; import java.util.function.BiConsumer; -import java.util.function.Consumer; /** * Registers client-side objects, such as {@link BlockEntityRendererProvider}s and @@ -53,6 +59,19 @@ public final class ClientRegistry { private ClientRegistry() { } + private static final Map> models = new ConcurrentHashMap<>(); + + public static ModelKey getModel(ResourceLocation model) { + return models.computeIfAbsent(model, m -> ClientPlatformHelper.get().createModelKey(m, m::toString)); + } + + public static StandaloneModel getModel(ModelManager manager, ResourceLocation modelId) { + var model = getModel(modelId).get(manager); + if (model != null) return model; + + return Objects.requireNonNull(getModel(MissingBlockModel.LOCATION).get(manager)); + } + /** * Register any client-side objects which don't have to be done on the main thread. */ @@ -78,17 +97,17 @@ public final class ClientRegistry { > void register(MenuType type, MenuScreens.ScreenConstructor factory); } - public static void registerTurtleModellers(RegisterTurtleUpgradeModeller register) { - register.register(ModRegistry.TurtleUpgradeTypes.SPEAKER.get(), TurtleUpgradeModeller.sided( + 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(), TurtleUpgradeModeller.sided( + 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(), new TurtleModemModeller()); - register.register(ModRegistry.TurtleUpgradeTypes.TOOL.get(), TurtleUpgradeModeller.flatItem()); + register.register(ModRegistry.TurtleUpgradeTypes.WIRELESS_MODEM.get(), TurtleModemModel.UNBAKED); + register.register(ModRegistry.TurtleUpgradeTypes.TOOL.get(), TurtleUpgradeModel.flatItem()); } public static void registerReloadListeners(BiConsumer register, Minecraft minecraft) { @@ -100,17 +119,22 @@ public final class ClientRegistry { TurtleBlockEntityRenderer.NORMAL_TURTLE_MODEL, TurtleBlockEntityRenderer.ADVANCED_TURTLE_MODEL, TurtleBlockEntityRenderer.COLOUR_TURTLE_MODEL, + MissingBlockModel.LOCATION, }; - public static void registerExtraModels(Consumer register, Collection extraModels) { - for (var model : EXTRA_MODELS) register.accept(model); - extraModels.forEach(register); - TurtleUpgradeModellers.getDependencies().forEach(register); + public static void registerExtraModels( + BiConsumer, ResourceLocation> registerBasic, + BiConsumer>, TurtleUpgradeModel.Unbaked> registerTurtle, + Collection extraModels + ) { + for (var model : EXTRA_MODELS) registerBasic.accept(getModel(model), model); + for (var model : extraModels) registerBasic.accept(getModel(model), model); + TurtleUpgradeModels.bake(registerTurtle); } public static void registerItemModels(BiConsumer> register) { register.accept(TurtleOverlayModel.ID, TurtleOverlayModel.CODEC); - register.accept(TurtleUpgradeModel.ID, TurtleUpgradeModel.CODEC); + register.accept(dan200.computercraft.client.item.model.TurtleUpgradeModel.ID, dan200.computercraft.client.item.model.TurtleUpgradeModel.CODEC); } public static void registerItemColours(BiConsumer> register) { diff --git a/projects/common/src/client/java/dan200/computercraft/client/gui/NoTermComputerScreen.java b/projects/common/src/client/java/dan200/computercraft/client/gui/NoTermComputerScreen.java index fa79f4d2f..f5626967b 100644 --- a/projects/common/src/client/java/dan200/computercraft/client/gui/NoTermComputerScreen.java +++ b/projects/common/src/client/java/dan200/computercraft/client/gui/NoTermComputerScreen.java @@ -72,8 +72,8 @@ public class NoTermComputerScreen extends Screen public boolean mouseScrolled(double mouseX, double mouseY, double scrollX, double scrollY) { var direction = scrollHandler.onMouseScroll(scrollX, scrollY); var inventory = Objects.requireNonNull(minecraft().player).getInventory(); - inventory.setSelectedHotbarSlot(ScrollWheelHandler.getNextScrollWheelSelection( - direction.y == 0 ? -direction.x : direction.y, inventory.selected, Inventory.getSelectionSize() + inventory.setSelectedSlot(ScrollWheelHandler.getNextScrollWheelSelection( + direction.y == 0 ? -direction.x : direction.y, inventory.getSelectedSlot(), Inventory.getSelectionSize() )); return super.mouseScrolled(mouseX, mouseY, scrollX, scrollY); diff --git a/projects/common/src/client/java/dan200/computercraft/client/integration/IrisShaderMod.java b/projects/common/src/client/java/dan200/computercraft/client/integration/IrisShaderMod.java index fc2952a42..85581bb84 100644 --- a/projects/common/src/client/java/dan200/computercraft/client/integration/IrisShaderMod.java +++ b/projects/common/src/client/java/dan200/computercraft/client/integration/IrisShaderMod.java @@ -38,7 +38,7 @@ public class IrisShaderMod implements ShaderMod.Provider { : super.getQuadEmitter(vertexCount, makeBuffer); } - private static final class IrisQuadEmitter implements DirectFixedWidthFontRenderer.QuadEmitter { + private static final class IrisQuadEmitter extends DirectFixedWidthFontRenderer.QuadEmitter { private final IrisTextVertexSink sink; private @Nullable ByteBuffer buffer; diff --git a/projects/common/src/client/java/dan200/computercraft/client/integration/ShaderMod.java b/projects/common/src/client/java/dan200/computercraft/client/integration/ShaderMod.java index 122f32489..6285229cc 100644 --- a/projects/common/src/client/java/dan200/computercraft/client/integration/ShaderMod.java +++ b/projects/common/src/client/java/dan200/computercraft/client/integration/ShaderMod.java @@ -5,7 +5,6 @@ package dan200.computercraft.client.integration; import com.mojang.blaze3d.vertex.ByteBufferBuilder; -import com.mojang.blaze3d.vertex.VertexBuffer; import dan200.computercraft.client.render.text.DirectFixedWidthFontRenderer; import java.util.Optional; @@ -29,7 +28,7 @@ public class ShaderMod { } /** - * Get an appropriate quad emitter for use with {@link VertexBuffer} and {@link DirectFixedWidthFontRenderer} . + * Get an appropriate quad emitter for use with a vertex buffer and {@link DirectFixedWidthFontRenderer} . * * @param vertexCount The number of vertices. * @param buffer A function to allocate a temporary buffer. diff --git a/projects/common/src/client/java/dan200/computercraft/client/item/model/BakedModelWithTransform.java b/projects/common/src/client/java/dan200/computercraft/client/item/model/BakedModelWithTransform.java deleted file mode 100644 index 091214925..000000000 --- a/projects/common/src/client/java/dan200/computercraft/client/item/model/BakedModelWithTransform.java +++ /dev/null @@ -1,32 +0,0 @@ -// SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers -// -// SPDX-License-Identifier: MPL-2.0 - -package dan200.computercraft.client.item.model; - -import net.minecraft.client.renderer.Sheets; -import net.minecraft.client.renderer.block.model.ItemTransforms; -import net.minecraft.client.renderer.item.ItemStackRenderState; -import net.minecraft.client.resources.model.BakedModel; -import net.minecraft.client.resources.model.DelegateBakedModel; - -/** - * A {@link BakedModel} that wraps another model, but providing different {@link ItemTransforms}. - */ -class BakedModelWithTransform extends DelegateBakedModel { - private final ItemTransforms transforms; - - BakedModelWithTransform(BakedModel bakedModel, ItemTransforms transforms) { - super(bakedModel); - this.transforms = transforms; - } - - static void addLayer(ItemStackRenderState state, BakedModel model, ItemTransforms transforms) { - state.newLayer().setupBlockModel(new BakedModelWithTransform(model, transforms), Sheets.translucentItemSheet()); - } - - @Override - public ItemTransforms getTransforms() { - return transforms; - } -} diff --git a/projects/common/src/client/java/dan200/computercraft/client/item/model/TurtleOverlayModel.java b/projects/common/src/client/java/dan200/computercraft/client/item/model/TurtleOverlayModel.java index 0c658434b..6b748aecb 100644 --- a/projects/common/src/client/java/dan200/computercraft/client/item/model/TurtleOverlayModel.java +++ b/projects/common/src/client/java/dan200/computercraft/client/item/model/TurtleOverlayModel.java @@ -7,7 +7,7 @@ package dan200.computercraft.client.item.model; import com.mojang.serialization.MapCodec; import com.mojang.serialization.codecs.RecordCodecBuilder; import dan200.computercraft.api.ComputerCraftAPI; -import dan200.computercraft.client.platform.ClientPlatformHelper; +import dan200.computercraft.client.ClientRegistry; import dan200.computercraft.shared.turtle.TurtleOverlay; import dan200.computercraft.shared.turtle.items.TurtleItem; import net.minecraft.client.Minecraft; @@ -39,8 +39,9 @@ public record TurtleOverlayModel(ItemTransforms transforms) implements ItemModel var overlay = TurtleItem.getOverlay(stack); if (overlay == null) return; - var model = ClientPlatformHelper.get().getModel(Minecraft.getInstance().getModelManager(), overlay.model()); - BakedModelWithTransform.addLayer(state, model, transforms()); + var layer = state.newLayer(); + ClientRegistry.getModel(Minecraft.getInstance().getModelManager(), overlay.model()).setupItemLayer(layer); + layer.setTransform(transforms().getTransform(context)); } public record Unbaked(ResourceLocation base) implements ItemModel.Unbaked { @@ -51,7 +52,7 @@ public record TurtleOverlayModel(ItemTransforms transforms) implements ItemModel @Override public ItemModel bake(BakingContext bakingContext) { - return new TurtleOverlayModel(bakingContext.bake(base).getTransforms()); + return new TurtleOverlayModel(bakingContext.blockModelBaker().getModel(base).getTopTransforms()); } @Override diff --git a/projects/common/src/client/java/dan200/computercraft/client/item/model/TurtleUpgradeModel.java b/projects/common/src/client/java/dan200/computercraft/client/item/model/TurtleUpgradeModel.java index 6d520d60a..6f92b63dd 100644 --- a/projects/common/src/client/java/dan200/computercraft/client/item/model/TurtleUpgradeModel.java +++ b/projects/common/src/client/java/dan200/computercraft/client/item/model/TurtleUpgradeModel.java @@ -4,23 +4,17 @@ package dan200.computercraft.client.item.model; -import com.mojang.blaze3d.vertex.PoseStack; -import com.mojang.math.Transformation; import com.mojang.serialization.MapCodec; import com.mojang.serialization.codecs.RecordCodecBuilder; import dan200.computercraft.api.ComputerCraftAPI; -import dan200.computercraft.api.client.TransformedModel; -import dan200.computercraft.api.client.turtle.TurtleUpgradeModeller; import dan200.computercraft.api.turtle.TurtleSide; -import dan200.computercraft.client.turtle.TurtleUpgradeModellers; +import dan200.computercraft.client.turtle.TurtleUpgradeModels; import dan200.computercraft.shared.turtle.items.TurtleItem; import net.minecraft.client.multiplayer.ClientLevel; -import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.client.renderer.block.model.ItemTransforms; import net.minecraft.client.renderer.item.ItemModel; import net.minecraft.client.renderer.item.ItemModelResolver; import net.minecraft.client.renderer.item.ItemStackRenderState; -import net.minecraft.client.renderer.special.SpecialModelRenderer; -import net.minecraft.client.resources.model.BakedModel; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.item.ItemDisplayContext; @@ -28,12 +22,12 @@ import net.minecraft.world.item.ItemStack; import org.jspecify.annotations.Nullable; /** - * An {@link ItemModel} that renders a turtle upgrade, using its {@link TurtleUpgradeModeller}. + * An {@link ItemModel} that renders a turtle upgrade, using its {@link dan200.computercraft.api.client.turtle.TurtleUpgradeModel}. * * @param side The side the upgrade resides on. * @param base The base model. Only used to provide item transforms. */ -public record TurtleUpgradeModel(TurtleSide side, BakedModel base) implements ItemModel { +public record TurtleUpgradeModel(TurtleSide side, ItemTransforms base) implements ItemModel { public static final ResourceLocation ID = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "turtle/upgrade"); public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group( TurtleSide.CODEC.fieldOf("side").forGetter(Unbaked::side), @@ -41,21 +35,11 @@ public record TurtleUpgradeModel(TurtleSide side, BakedModel base) implements It ).apply(instance, Unbaked::new)); @Override - public void update(ItemStackRenderState state, ItemStack stack, ItemModelResolver resolver, ItemDisplayContext context, @Nullable ClientLevel level, @Nullable LivingEntity holder, int light) { + public void update(ItemStackRenderState state, ItemStack stack, ItemModelResolver resolver, ItemDisplayContext context, @Nullable ClientLevel level, @Nullable LivingEntity holder, int seed) { var upgrade = TurtleItem.getUpgradeWithData(stack, side); if (upgrade == null) return; - switch (TurtleUpgradeModellers.getModel(upgrade.upgrade(), upgrade.data(), side)) { - case TransformedModel.Item model -> { - var childState = new ItemStackRenderState(); - resolver.updateForTopItem(childState, model.stack(), ItemDisplayContext.NONE, false, level, null, 0); - if (!childState.isEmpty()) { - state.newLayer().setupSpecialModel(new TransformedRenderer(childState, model.transformation()), null, base); - } - } - case TransformedModel.Baked baked -> - BakedModelWithTransform.addLayer(state, baked.model(), base.getTransforms()); - } + TurtleUpgradeModels.getModeller(upgrade.upgrade()).renderForItem(upgrade.upgrade(), side, upgrade.data(), state, resolver, base.getTransform(context), seed); } public record Unbaked(TurtleSide side, ResourceLocation base) implements ItemModel.Unbaked { @@ -66,29 +50,12 @@ public record TurtleUpgradeModel(TurtleSide side, BakedModel base) implements It @Override public ItemModel bake(BakingContext bakingContext) { - return new TurtleUpgradeModel(side, bakingContext.bake(base)); + return new TurtleUpgradeModel(side, bakingContext.blockModelBaker().getModel(base).getTopTransforms()); } @Override public void resolveDependencies(Resolver resolver) { - resolver.resolve(base); - } - } - - private record TransformedRenderer( - ItemStackRenderState state, Transformation transform - ) implements SpecialModelRenderer { - @Override - public void render(@Nullable Void object, ItemDisplayContext itemDisplayContext, PoseStack poseStack, MultiBufferSource multiBufferSource, int overlay, int light, boolean bl) { - poseStack.pushPose(); - poseStack.mulPose(transform.getMatrix()); - state.render(poseStack, multiBufferSource, overlay, light); - poseStack.popPose(); - } - - @Override - public @Nullable Void extractArgument(ItemStack itemStack) { - return null; + resolver.markDependency(base); } } } diff --git a/projects/common/src/client/java/dan200/computercraft/client/item/properties/PocketComputerStateProperty.java b/projects/common/src/client/java/dan200/computercraft/client/item/properties/PocketComputerStateProperty.java index 10f88bbd1..c92303f56 100644 --- a/projects/common/src/client/java/dan200/computercraft/client/item/properties/PocketComputerStateProperty.java +++ b/projects/common/src/client/java/dan200/computercraft/client/item/properties/PocketComputerStateProperty.java @@ -4,6 +4,7 @@ package dan200.computercraft.client.item.properties; +import com.mojang.serialization.Codec; import com.mojang.serialization.MapCodec; import dan200.computercraft.api.ComputerCraftAPI; import dan200.computercraft.client.pocket.ClientPocketComputers; @@ -38,6 +39,11 @@ public final class PocketComputerStateProperty implements SelectItemModelPropert return computer == null ? ComputerState.OFF : computer.getState(); } + @Override + public Codec valueCodec() { + return ComputerState.CODEC; + } + @Override public Type, ComputerState> type() { return TYPE; diff --git a/projects/common/src/client/java/dan200/computercraft/client/platform/ClientPlatformHelper.java b/projects/common/src/client/java/dan200/computercraft/client/platform/ClientPlatformHelper.java index 03981432b..badf19207 100644 --- a/projects/common/src/client/java/dan200/computercraft/client/platform/ClientPlatformHelper.java +++ b/projects/common/src/client/java/dan200/computercraft/client/platform/ClientPlatformHelper.java @@ -4,25 +4,40 @@ package dan200.computercraft.client.platform; -import com.mojang.blaze3d.vertex.PoseStack; -import net.minecraft.client.renderer.MultiBufferSource; -import net.minecraft.client.resources.model.BakedModel; +import dan200.computercraft.impl.Services; +import net.minecraft.client.resources.model.ModelDebugName; +import net.minecraft.resources.ResourceLocation; +import org.jetbrains.annotations.Contract; import org.jspecify.annotations.Nullable; -public interface ClientPlatformHelper extends dan200.computercraft.impl.client.ClientPlatformHelper { +public interface ClientPlatformHelper { static ClientPlatformHelper get() { - return (ClientPlatformHelper) dan200.computercraft.impl.client.ClientPlatformHelper.get(); + var instance = Instance.INSTANCE; + return instance == null ? Services.raise(ClientPlatformHelper.class, Instance.ERROR) : instance; } /** - * Render a {@link BakedModel}, using any loader-specific hooks. + * Create a new unique {@link ModelKey}. * - * @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. + * @param id An identifier for this model key. + * @param name The debug name for this model key. + * @param The type of baked model. + * @return The newly created model key. */ - void renderBakedModel(PoseStack transform, MultiBufferSource buffers, BakedModel model, int lightmapCoord, int overlayLight, int @Nullable [] tints); + @Contract("_, _ -> new") + ModelKey createModelKey(ResourceLocation id, ModelDebugName name); + + final class Instance { + static final @Nullable ClientPlatformHelper INSTANCE; + static final @Nullable Throwable ERROR; + + static { + var helper = Services.tryLoad(ClientPlatformHelper.class); + INSTANCE = helper.instance(); + ERROR = helper.error(); + } + + private Instance() { + } + } } diff --git a/projects/common/src/client/java/dan200/computercraft/client/platform/ModelKey.java b/projects/common/src/client/java/dan200/computercraft/client/platform/ModelKey.java new file mode 100644 index 000000000..59e993665 --- /dev/null +++ b/projects/common/src/client/java/dan200/computercraft/client/platform/ModelKey.java @@ -0,0 +1,24 @@ +// SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers +// +// SPDX-License-Identifier: MPL-2.0 + +package dan200.computercraft.client.platform; + +import net.minecraft.client.resources.model.ModelManager; +import org.jspecify.annotations.Nullable; + +/** + * A key used to identify extra/standalone models. + * + * @param The type of baked model. + */ +public interface ModelKey { + /** + * Lookup this model key in the model manager. + * + * @param manager The model manager. + * @return The loaded model, or {@code null} if not available. + */ + @Nullable + T get(ModelManager manager); +} diff --git a/projects/common/src/client/java/dan200/computercraft/client/render/CustomLecternRenderer.java b/projects/common/src/client/java/dan200/computercraft/client/render/CustomLecternRenderer.java index 099cb37d0..5ed4e1a96 100644 --- a/projects/common/src/client/java/dan200/computercraft/client/render/CustomLecternRenderer.java +++ b/projects/common/src/client/java/dan200/computercraft/client/render/CustomLecternRenderer.java @@ -19,7 +19,6 @@ import dan200.computercraft.shared.media.items.PrintoutItem; import dan200.computercraft.shared.pocket.items.PocketComputerItem; import net.minecraft.client.renderer.MultiBufferSource; import net.minecraft.client.renderer.RenderType; -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.renderer.blockentity.LecternRenderer; @@ -40,19 +39,16 @@ import static dan200.computercraft.client.render.text.FixedWidthFontRenderer.FON public class CustomLecternRenderer implements BlockEntityRenderer { private static final int POCKET_TERMINAL_RENDER_DISTANCE = 32; - private final BlockEntityRenderDispatcher berDispatcher; private final LecternPrintoutModel printoutModel; private final LecternPocketModel pocketModel; public CustomLecternRenderer(BlockEntityRendererProvider.Context context) { - berDispatcher = context.getBlockEntityRenderDispatcher(); - printoutModel = new LecternPrintoutModel(); pocketModel = new LecternPocketModel(); } @Override - public void render(CustomLecternBlockEntity lectern, float partialTick, PoseStack poseStack, MultiBufferSource buffer, int packedLight, int packedOverlay) { + public void render(CustomLecternBlockEntity lectern, float partialTick, PoseStack poseStack, MultiBufferSource buffer, int packedLight, int packedOverlay, Vec3 camera) { poseStack.pushPose(); poseStack.translate(0.5f, 1.0625f, 0.5f); poseStack.mulPose(Axis.YP.rotationDegrees(-lectern.getBlockState().getValue(LecternBlock.FACING).getClockWise().toYRot())); @@ -83,7 +79,7 @@ public class CustomLecternRenderer implements BlockEntityRenderer - * This is not intended to be used directly, but instead by {@link ClientPlatformHelper#renderBakedModel(PoseStack, MultiBufferSource, BakedModel, int, int, int[])}. The - * implementation here is identical to {@link ItemRenderer#renderQuadList(PoseStack, VertexConsumer, List, int[], int, int)}. - * - * @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 quads, int lightmapCoord, int overlayLight, int @Nullable [] tints) { - var matrix = transform.last(); - - for (var bakedquad : quads) { - float r = 1.0f, g = 1.0f, b = 1.0f, a = 1.0f; - if (tints != null && bakedquad.isTinted()) { - var idx = bakedquad.getTintIndex(); - if (idx >= 0 && idx < tints.length) { - var tint = tints[bakedquad.getTintIndex()]; - r = ARGB.red(tint) / 255.0f; - g = ARGB.green(tint) / 255.0f; - b = ARGB.blue(tint) / 255.0f; - a = ARGB.alpha(tint) / 255.0f; - } - } - - buffer.putBulkData(matrix, bakedquad, r, g, b, a, lightmapCoord, overlayLight); - } - } -} diff --git a/projects/common/src/client/java/dan200/computercraft/client/render/TurtleBlockEntityRenderer.java b/projects/common/src/client/java/dan200/computercraft/client/render/TurtleBlockEntityRenderer.java index 9b0de24d8..203986f88 100644 --- a/projects/common/src/client/java/dan200/computercraft/client/render/TurtleBlockEntityRenderer.java +++ b/projects/common/src/client/java/dan200/computercraft/client/render/TurtleBlockEntityRenderer.java @@ -7,10 +7,9 @@ package dan200.computercraft.client.render; import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.math.Axis; import dan200.computercraft.api.ComputerCraftAPI; -import dan200.computercraft.api.client.TransformedModel; import dan200.computercraft.api.turtle.TurtleSide; -import dan200.computercraft.client.platform.ClientPlatformHelper; -import dan200.computercraft.client.turtle.TurtleUpgradeModellers; +import dan200.computercraft.client.ClientRegistry; +import dan200.computercraft.client.turtle.TurtleUpgradeModels; import dan200.computercraft.shared.computer.core.ComputerFamily; import dan200.computercraft.shared.turtle.TurtleOverlay; import dan200.computercraft.shared.turtle.blocks.TurtleBlockEntity; @@ -21,14 +20,12 @@ import net.minecraft.client.renderer.MultiBufferSource; 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.resources.ResourceLocation; import net.minecraft.util.ARGB; import net.minecraft.util.CommonColors; -import net.minecraft.util.Mth; -import net.minecraft.world.item.ItemDisplayContext; import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.HitResult; +import net.minecraft.world.phys.Vec3; import org.jspecify.annotations.Nullable; public class TurtleBlockEntityRenderer implements BlockEntityRenderer { @@ -45,7 +42,7 @@ public class TurtleBlockEntityRenderer implements BlockEntityRenderer { - transform.mulPose(model.transformation().getMatrix()); - transform.mulPose(Axis.YP.rotation(Mth.PI)); - Minecraft.getInstance().getItemRenderer().renderStatic( - model.stack(), ItemDisplayContext.FIXED, lightmapCoord, overlayLight, transform, buffers, turtle.getLevel(), 0 - ); - } - - case TransformedModel.Baked model -> - renderModel(transform, buffers, lightmapCoord, overlayLight, model.model(), null); - } + TurtleUpgradeModels.getModeller(upgrade).renderForLevel(upgrade, turtle.getAccess(), side, turtle.getAccess().getUpgradeData(side), transform, buffers, lightmapCoord, overlayLight); transform.popPose(); @@ -133,21 +119,7 @@ public class TurtleBlockEntityRenderer implements BlockEntityRenderer { /** @@ -45,7 +48,7 @@ public class MonitorBlockEntityRenderer implements BlockEntityRenderer - DirectFixedWidthFontRenderer.drawTerminalBackground(sink, 0, 0, terminal, yMargin, yMargin, xMargin, xMargin)); + DirectFixedWidthFontRenderer.drawTerminalForeground(sink, 0, 0, terminal); + var vertexCountAfterForeground = sink.vertexCount(); - renderToBuffer(foregroundBuffer, size + 4, sink -> { - DirectFixedWidthFontRenderer.drawTerminalForeground(sink, 0, 0, terminal); - // If the cursor is visible, we append it to the end of our buffer. When rendering, we can either - // render n or n+1 quads and so toggle the cursor on and off. - DirectFixedWidthFontRenderer.drawCursor(sink, 0, 0, terminal); - }); + DirectFixedWidthFontRenderer.drawCursor(sink, 0, 0, terminal); + var vertexCountAfterCursor = sink.vertexCount(); + + if (vertexCountAfterCursor > maxVertexCount) { + throw new IllegalStateException("Drew too many vertices. Expected " + maxVertexCount + ", drew " + vertexCountAfterCursor); + } + + try (var result = backingBufferBuilder.build()) { + if (result == null) { + // If we have nothing to draw, just mark it as empty. We'll skip drawing in drawWithShader. + renderState.indexAfterBackground = renderState.indexAfterForeground = renderState.indexAfterCursor = 0; + } else { + renderState.register(); + + var commandEncoder = RenderSystem.getDevice().createCommandEncoder(); + + var resultBuffer = result.byteBuffer(); + if (resultBuffer.limit() / sink.format().getVertexSize() != vertexCountAfterCursor) { + throw new IllegalStateException("Mismatched vertex count"); + } + + if (renderState.vertexBuffer == null || resultBuffer.limit() > renderState.vertexBuffer.size()) { + if (renderState.vertexBuffer != null) { + renderState.vertexBuffer.close(); + renderState.vertexBuffer = null; + } + renderState.vertexBuffer = RenderSystem.getDevice().createBuffer( + () -> "Monitor at " + monitor.getOrigin().getBlockPos(), + BufferType.VERTICES, BufferUsage.STATIC_WRITE, resultBuffer + ); + } else if (!renderState.vertexBuffer.isClosed()) { + commandEncoder.writeToBuffer(renderState.vertexBuffer, resultBuffer, 0); + } + } + } + + var mode = FixedWidthFontRenderer.TERMINAL_TEXT.mode(); + renderState.indexAfterBackground = mode.indexCount(vertexCountAfterBackground); + renderState.indexAfterForeground = mode.indexCount(vertexCountAfterForeground); + renderState.indexAfterCursor = mode.indexCount(vertexCountAfterCursor); } + if (renderState.indexAfterCursor == 0) return; + // Our VBO renders coordinates in monitor-space rather than world space. A full sized monitor (8x6) will // use positions from (0, 0) to (164*FONT_WIDTH, 81*FONT_HEIGHT) = (984, 729). This is far outside the // normal render distance (~200), and the edges of the monitor fade out due to fog. @@ -152,69 +192,52 @@ public class MonitorBlockEntityRenderer implements BlockEntityRenderer draw) { - var sink = ShaderMod.get().getQuadEmitter(size, backingBufferBuilder); - draw.accept(sink); + private static void drawWithShader(MonitorRenderState renderState, RenderType renderType, int indexOffset, int indexCount) { + if (renderState.vertexBuffer == null) { + throw new IllegalStateException("MonitorRenderState has not been initialised"); + } + if (indexCount == 0) return; - var result = backingBufferBuilder.build(); - if (result == null) { - // If we have nothing to draw, just mark it as empty. We'll skip drawing in drawWithShader. - vbo.indexCount = 0; - return; + renderType.setupRenderState(); + + var autoStorageBuffer = RenderSystem.getSequentialBuffer(renderType.mode()); + var indexBuffer = autoStorageBuffer.getBuffer(indexOffset + indexCount); + + try (var renderPass = RenderSystem.getDevice().createCommandEncoder().createRenderPass( + renderType.getRenderTarget().getColorTexture(), OptionalInt.empty(), renderType.getRenderTarget().getDepthTexture(), OptionalDouble.empty() + )) { + renderPass.setPipeline(renderType.getRenderPipeline()); + renderPass.setVertexBuffer(0, renderState.vertexBuffer); + renderPass.setIndexBuffer(indexBuffer, autoStorageBuffer.type()); + + for (var j = 0; j < 12; j++) { + var gpuTexture = RenderSystem.getShaderTexture(j); + if (gpuTexture != null) renderPass.bindSampler("Sampler" + j, gpuTexture); + } + + renderPass.drawIndexed(indexOffset, indexCount); } - var buffer = result.byteBuffer(); - var vertices = buffer.limit() / sink.format().getVertexSize(); - - vbo.bind(); - vbo.upload(new MeshData(result, new MeshData.DrawState( - sink.format(), - vertices, FixedWidthFontRenderer.TERMINAL_TEXT.mode().indexCount(vertices), - FixedWidthFontRenderer.TERMINAL_TEXT.mode(), VertexFormat.IndexType.least(vertices) - ))); - } - - private static void drawWithShader(VertexBuffer buffer, Matrix4f modelView, Matrix4f projection, @Nullable CompiledShaderProgram compiledShaderProgram, int indicies) { - var originalIndexCount = buffer.indexCount; - if (originalIndexCount == 0) return; - - try { - buffer.indexCount = indicies; - buffer.drawWithShader(modelView, projection, compiledShaderProgram); - } finally { - buffer.indexCount = originalIndexCount; - } + renderType.clearRenderState(); } @Override diff --git a/projects/common/src/client/java/dan200/computercraft/client/render/monitor/MonitorRenderState.java b/projects/common/src/client/java/dan200/computercraft/client/render/monitor/MonitorRenderState.java index a94f28817..d771161ea 100644 --- a/projects/common/src/client/java/dan200/computercraft/client/render/monitor/MonitorRenderState.java +++ b/projects/common/src/client/java/dan200/computercraft/client/render/monitor/MonitorRenderState.java @@ -5,8 +5,7 @@ package dan200.computercraft.client.render.monitor; import com.google.errorprone.annotations.concurrent.GuardedBy; -import com.mojang.blaze3d.buffers.BufferUsage; -import com.mojang.blaze3d.vertex.VertexBuffer; +import com.mojang.blaze3d.buffers.GpuBuffer; import dan200.computercraft.shared.peripheral.monitor.ClientMonitor; import net.minecraft.core.BlockPos; import org.jspecify.annotations.Nullable; @@ -21,53 +20,39 @@ import java.util.Set; * This is automatically cleared by {@link dan200.computercraft.shared.peripheral.monitor.MonitorBlockEntity} when the * entity is unloaded on the client side (see {@link MonitorRenderState#close()}). */ -public class MonitorRenderState implements ClientMonitor.RenderState { +public final class MonitorRenderState implements ClientMonitor.RenderState { @GuardedBy("allMonitors") private static final Set allMonitors = new HashSet<>(); - public long lastRenderFrame = -1; - public @Nullable BlockPos lastRenderPos = null; + long lastRenderFrame = -1; + @Nullable + BlockPos lastRenderPos = null; - public @Nullable VertexBuffer backgroundBuffer; - public @Nullable VertexBuffer foregroundBuffer; + @Nullable + GpuBuffer vertexBuffer; - /** - * Create the appropriate buffer if needed. - * - * @return If a buffer was created. This will return {@code false} if we already have an appropriate buffer, - * or this mode does not require one. - */ - public boolean createBuffer() { - if (backgroundBuffer != null) return false; + int indexAfterBackground; + int indexAfterForeground; + int indexAfterCursor; - deleteBuffers(); - backgroundBuffer = new VertexBuffer(BufferUsage.STATIC_WRITE); - foregroundBuffer = new VertexBuffer(BufferUsage.STATIC_WRITE); - addMonitor(); - return true; - } + void register() { + if (vertexBuffer != null) return; - private void addMonitor() { synchronized (allMonitors) { allMonitors.add(this); } } private void deleteBuffers() { - if (backgroundBuffer != null) { - backgroundBuffer.close(); - backgroundBuffer = null; - } - - if (foregroundBuffer != null) { - foregroundBuffer.close(); - foregroundBuffer = null; + if (vertexBuffer != null) { + vertexBuffer.close(); + vertexBuffer = null; } } @Override public void close() { - if (backgroundBuffer != null) { + if (vertexBuffer != null) { synchronized (allMonitors) { allMonitors.remove(this); } diff --git a/projects/common/src/client/java/dan200/computercraft/client/render/text/DirectFixedWidthFontRenderer.java b/projects/common/src/client/java/dan200/computercraft/client/render/text/DirectFixedWidthFontRenderer.java index 6bb344868..50c865d88 100644 --- a/projects/common/src/client/java/dan200/computercraft/client/render/text/DirectFixedWidthFontRenderer.java +++ b/projects/common/src/client/java/dan200/computercraft/client/render/text/DirectFixedWidthFontRenderer.java @@ -33,7 +33,7 @@ import static org.lwjgl.system.MemoryUtil.*; *

* Note this is almost an exact copy of {@link FixedWidthFontRenderer}. While the code duplication is unfortunate, * it is measurably faster than introducing polymorphism into {@link FixedWidthFontRenderer}. - * + *

* IMPORTANT: When making changes to this class, please check if you need to make the same changes to * {@link FixedWidthFontRenderer}. */ @@ -156,21 +156,30 @@ public final class DirectFixedWidthFontRenderer { } } - public static int getVertexCount(Terminal terminal) { - return (terminal.getHeight() + 2) * (terminal.getWidth() + 2) * 2; - } - private static void quad(QuadEmitter buffer, float x1, float y1, float x2, float y2, float z, int colour, float u1, float v1, float u2, float v2) { + buffer.vertexCount += 4; buffer.quad(x1, y1, x2, y2, z, colour, u1, v1, u2, v2); } - public interface QuadEmitter { - VertexFormat format(); + public abstract static class QuadEmitter { + private int vertexCount; - void quad(float x1, float y1, float x2, float y2, float z, int colour, float u1, float v1, float u2, float v2); + public abstract VertexFormat format(); + + protected abstract void quad(float x1, float y1, float x2, float y2, float z, int colour, float u1, float v1, float u2, float v2); + + public int vertexCount() { + return vertexCount; + } } - public record ByteBufferEmitter(ByteBufferBuilder builder) implements QuadEmitter { + public static final class ByteBufferEmitter extends QuadEmitter { + private final ByteBufferBuilder builder; + + public ByteBufferEmitter(ByteBufferBuilder builder) { + this.builder = builder; + } + @Override public VertexFormat format() { return TERMINAL_TEXT.format(); diff --git a/projects/common/src/client/java/dan200/computercraft/client/render/text/FixedWidthFontRenderer.java b/projects/common/src/client/java/dan200/computercraft/client/render/text/FixedWidthFontRenderer.java index 02ea2a86f..fd36ae8ae 100644 --- a/projects/common/src/client/java/dan200/computercraft/client/render/text/FixedWidthFontRenderer.java +++ b/projects/common/src/client/java/dan200/computercraft/client/render/text/FixedWidthFontRenderer.java @@ -40,6 +40,8 @@ public final class FixedWidthFontRenderer { */ public static final RenderType TERMINAL_TEXT = RenderType.text(FONT); + public static final RenderType TERMINAL_TEXT_OFFSET = RenderType.textPolygonOffset(FONT); + public static final int FONT_HEIGHT = 9; public static final int FONT_WIDTH = 6; static final float WIDTH = 256.0f; diff --git a/projects/common/src/client/java/dan200/computercraft/client/turtle/TurtleModemModel.java b/projects/common/src/client/java/dan200/computercraft/client/turtle/TurtleModemModel.java new file mode 100644 index 000000000..6735dc7b6 --- /dev/null +++ b/projects/common/src/client/java/dan200/computercraft/client/turtle/TurtleModemModel.java @@ -0,0 +1,89 @@ +// 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 normal, ModemModels advanced +) implements TurtleUpgradeModelViaStandalone { + public static final TurtleUpgradeModel.Unbaked UNBAKED = new Unbaked(); + + @Override + public StandaloneModel getModel(TurtleModem modem, TurtleSide side, DataComponentPatch data) { + var active = DataComponentUtil.isPresent(data, ModRegistry.DataComponents.ON.get(), x -> x); + + var models = modem.advanced() ? advanced() : normal(); + return side == TurtleSide.LEFT + ? (active ? models.leftOnModel() : models.leftOffModel()) + : (active ? models.rightOnModel() : models.rightOffModel()); + } + + private static final class Unbaked implements TurtleUpgradeModel.Unbaked { + @Override + public TurtleUpgradeModel bake(ModelBaker baker) { + return new TurtleModemModel( + ModemModels.NORMAL.map(x -> StandaloneModel.of(x, baker)), + ModemModels.ADVANCED.map(x -> StandaloneModel.of(x, baker)) + ); + } + + @Override + public void resolveDependencies(Resolver resolver) { + ModemModels.NORMAL.forEach(resolver::markDependency); + ModemModels.ADVANCED.forEach(resolver::markDependency); + } + } + + private record ModemModels( + T leftOffModel, T rightOffModel, + T leftOnModel, T rightOnModel + ) { + private static final ModemModels NORMAL = create("normal"); + private static final ModemModels ADVANCED = create("advanced"); + + static ModemModels create(String type) { + return new ModemModels<>( + ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_modem_" + type + "_off_left"), + ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_modem_" + type + "_off_right"), + ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_modem_" + type + "_on_left"), + ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_modem_" + type + "_on_right") + ); + } + + public void forEach(Consumer out) { + out.accept(leftOffModel()); + out.accept(rightOffModel); + out.accept(leftOnModel()); + out.accept(rightOnModel()); + } + + public ModemModels map(Function mapper) { + return new ModemModels<>( + mapper.apply(leftOffModel()), mapper.apply(rightOffModel()), + mapper.apply(leftOnModel()), mapper.apply(rightOnModel()) + ); + } + } +} diff --git a/projects/common/src/client/java/dan200/computercraft/client/turtle/TurtleModemModeller.java b/projects/common/src/client/java/dan200/computercraft/client/turtle/TurtleModemModeller.java deleted file mode 100644 index 141e42572..000000000 --- a/projects/common/src/client/java/dan200/computercraft/client/turtle/TurtleModemModeller.java +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. -// -// SPDX-License-Identifier: LicenseRef-CCPL - -package dan200.computercraft.client.turtle; - -import dan200.computercraft.api.ComputerCraftAPI; -import dan200.computercraft.api.client.TransformedModel; -import dan200.computercraft.api.client.turtle.TurtleUpgradeModeller; -import dan200.computercraft.api.turtle.ITurtleAccess; -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.core.component.DataComponentPatch; -import net.minecraft.resources.ResourceLocation; -import org.jspecify.annotations.Nullable; - -import java.util.stream.Stream; - -/** - * A {@link TurtleUpgradeModeller} for modems, providing different models depending on if the modem is on/off. - */ -public class TurtleModemModeller implements TurtleUpgradeModeller { - @Override - public TransformedModel getModel(TurtleModem upgrade, @Nullable ITurtleAccess turtle, TurtleSide side, DataComponentPatch data) { - var active = DataComponentUtil.isPresent(data, ModRegistry.DataComponents.ON.get(), x -> x); - - var models = upgrade.advanced() ? ModemModels.ADVANCED : ModemModels.NORMAL; - return side == TurtleSide.LEFT - ? TransformedModel.of(active ? models.leftOnModel() : models.leftOffModel()) - : TransformedModel.of(active ? models.rightOnModel() : models.rightOffModel()); - } - - @Override - public Stream getDependencies() { - return Stream.of(ModemModels.NORMAL, ModemModels.ADVANCED).flatMap(ModemModels::getDependencies); - } - - private record ModemModels( - ResourceLocation leftOffModel, ResourceLocation rightOffModel, - ResourceLocation leftOnModel, ResourceLocation rightOnModel - ) { - private static final ModemModels NORMAL = create("normal"); - private static final ModemModels ADVANCED = create("advanced"); - - public static ModemModels create(String type) { - return new ModemModels( - ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_modem_" + type + "_off_left"), - ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_modem_" + type + "_off_right"), - ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_modem_" + type + "_on_left"), - ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_modem_" + type + "_on_right") - ); - } - - public Stream getDependencies() { - return Stream.of(leftOffModel, rightOffModel, leftOnModel, rightOnModel); - } - } -} diff --git a/projects/common/src/client/java/dan200/computercraft/client/turtle/TurtleUpgradeModellers.java b/projects/common/src/client/java/dan200/computercraft/client/turtle/TurtleUpgradeModellers.java deleted file mode 100644 index 6d974549a..000000000 --- a/projects/common/src/client/java/dan200/computercraft/client/turtle/TurtleUpgradeModellers.java +++ /dev/null @@ -1,66 +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.TransformedModel; -import dan200.computercraft.api.client.turtle.TurtleUpgradeModeller; -import dan200.computercraft.api.turtle.ITurtleAccess; -import dan200.computercraft.api.turtle.ITurtleUpgrade; -import dan200.computercraft.api.turtle.TurtleSide; -import dan200.computercraft.api.upgrades.UpgradeType; -import dan200.computercraft.shared.util.RegistryHelper; -import net.minecraft.client.Minecraft; -import net.minecraft.core.component.DataComponentPatch; -import net.minecraft.resources.ResourceLocation; - -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.stream.Stream; - -/** - * A registry of {@link TurtleUpgradeModeller}s. - */ -public final class TurtleUpgradeModellers { - private static final TurtleUpgradeModeller NULL_TURTLE_MODELLER = (upgrade, turtle, side, data) -> - TransformedModel.of(Minecraft.getInstance().getModelManager().getMissingModel()); - - private static final Map, TurtleUpgradeModeller> turtleModels = new ConcurrentHashMap<>(); - private static volatile boolean fetchedModels; - - private TurtleUpgradeModellers() { - } - - public static void register(UpgradeType type, TurtleUpgradeModeller 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 (turtleModels.putIfAbsent(type, modeller) != null) { - throw new IllegalStateException("Modeller already registered for serialiser"); - } - } - - public static TransformedModel getModel(ITurtleUpgrade upgrade, ITurtleAccess access, TurtleSide side) { - return getModeller(upgrade).getModel(upgrade, access, side, access.getUpgradeData(side)); - } - - public static TransformedModel getModel(ITurtleUpgrade upgrade, DataComponentPatch data, TurtleSide side) { - return getModeller(upgrade).getModel(upgrade, null, side, data); - } - - @SuppressWarnings("unchecked") - private static TurtleUpgradeModeller getModeller(T upgrade) { - var modeller = turtleModels.get(upgrade.getType()); - return (TurtleUpgradeModeller) (modeller == null ? NULL_TURTLE_MODELLER : modeller); - } - - public static Stream getDependencies() { - fetchedModels = true; - return turtleModels.values().stream().flatMap(TurtleUpgradeModeller::getDependencies); - } -} diff --git a/projects/common/src/client/java/dan200/computercraft/client/turtle/TurtleUpgradeModels.java b/projects/common/src/client/java/dan200/computercraft/client/turtle/TurtleUpgradeModels.java new file mode 100644 index 000000000..b231f09ed --- /dev/null +++ b/projects/common/src/client/java/dan200/computercraft/client/turtle/TurtleUpgradeModels.java @@ -0,0 +1,86 @@ +// 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.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; +import java.util.function.BiConsumer; + +/** + * A registry of {@link TurtleUpgradeModel}s. + */ +public final class TurtleUpgradeModels { + private static final Object fetchedLock = new Object(); + private static volatile boolean fetchedModels; + + private static final Map>, TurtleUpgradeModel.Unbaked> unbaked = new ConcurrentHashMap<>(); + private static final Map, ModelKey>> modelKeys = new ConcurrentHashMap<>(); + public static final ModelKey> missingModelKey = ClientPlatformHelper.get().createModelKey( + MissingBlockModel.LOCATION, + () -> "Missing turtle model" + ); + + private TurtleUpgradeModels() { + } + + public static void register(UpgradeType type, TurtleUpgradeModel.Unbaked modeller) { + if (fetchedModels) { + throw new IllegalStateException(String.format( + "Turtle upgrade type %s must be registered before models are baked.", + RegistryHelper.getKeyOrThrow(RegistryHelper.getRegistry(ITurtleUpgrade.typeRegistry()), type) + )); + } + + if (unbaked.putIfAbsent(getModelKey(type), modeller) != null) { + throw new IllegalStateException("Modeller already registered for serialiser"); + } + } + + public static void fetch(Runnable action) { + if (fetchedModels) return; + synchronized (fetchedLock) { + if (fetchedModels) return; + action.run(); + fetchedModels = true; + } + } + + @SuppressWarnings("unchecked") + private static ModelKey> getModelKey(UpgradeType type) { + return (ModelKey>) modelKeys.computeIfAbsent(type, t -> { + var id = RegistryHelper.getKeyOrThrow(RegistryHelper.getRegistry(ITurtleUpgrade.typeRegistry()), t); + return ClientPlatformHelper.get().createModelKey( + RegistryHelper.getKeyOrThrow(RegistryHelper.getRegistry(ITurtleUpgrade.typeRegistry()), t), + () -> "Turtle upgrade " + id + ); + }); + } + + public static TurtleUpgradeModel getModeller(T upgrade) { + var modelManager = Minecraft.getInstance().getModelManager(); + + @SuppressWarnings("unchecked") + var model = getModelKey((UpgradeType) upgrade.getType()).get(modelManager); + if (model != null) return model; + + var missing = missingModelKey.get(modelManager); + if (missing == null) throw new IllegalStateException("Rendering turtles before models are baked"); + return missing; + } + + public static void bake(BiConsumer>, TurtleUpgradeModel.Unbaked> baker) { + unbaked.forEach(baker); + baker.accept(missingModelKey, TurtleUpgradeModel.sided(MissingBlockModel.LOCATION, MissingBlockModel.LOCATION)); + } +} diff --git a/projects/common/src/client/java/dan200/computercraft/mixin/client/BlockRenderDispatcherMixin.java b/projects/common/src/client/java/dan200/computercraft/mixin/client/BlockRenderDispatcherMixin.java new file mode 100644 index 000000000..08fbc8887 --- /dev/null +++ b/projects/common/src/client/java/dan200/computercraft/mixin/client/BlockRenderDispatcherMixin.java @@ -0,0 +1,30 @@ +// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers +// +// SPDX-License-Identifier: MPL-2.0 + +package dan200.computercraft.mixin.client; + +import com.llamalad7.mixinextras.sugar.Local; +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.VertexConsumer; +import dan200.computercraft.client.ClientHooks; +import net.minecraft.client.renderer.block.BlockRenderDispatcher; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.BlockAndTintGetter; +import net.minecraft.world.level.block.state.BlockState; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.ModifyVariable; + +/** + * Provides custom block breaking progress for modems, so it only applies to the current part. + * + * @see BlockRenderDispatcher#renderBreakingTexture(BlockState, BlockPos, BlockAndTintGetter, PoseStack, VertexConsumer) + */ +@Mixin(BlockRenderDispatcher.class) +public class BlockRenderDispatcherMixin { + @ModifyVariable(method = "renderBreakingTexture", at = @At("HEAD")) + public BlockState renderBlockDamage(BlockState state, @Local BlockPos pos) { + return ClientHooks.getBlockBreakingState(state, pos); + } +} diff --git a/projects/forge/src/client/resources/computercraft-client.forge.mixins.json b/projects/common/src/client/resources/computercraft-client.mixins.json similarity index 100% rename from projects/forge/src/client/resources/computercraft-client.forge.mixins.json rename to projects/common/src/client/resources/computercraft-client.mixins.json diff --git a/projects/common/src/datagen/java/dan200/computercraft/data/client/BlockModelProvider.java b/projects/common/src/datagen/java/dan200/computercraft/data/client/BlockModelProvider.java index 792dac94e..27a264770 100644 --- a/projects/common/src/datagen/java/dan200/computercraft/data/client/BlockModelProvider.java +++ b/projects/common/src/datagen/java/dan200/computercraft/data/client/BlockModelProvider.java @@ -4,6 +4,7 @@ package dan200.computercraft.data.client; +import com.mojang.math.Quadrant; import dan200.computercraft.api.ComputerCraftAPI; import dan200.computercraft.api.turtle.TurtleSide; import dan200.computercraft.client.item.model.TurtleOverlayModel; @@ -24,8 +25,13 @@ import dan200.computercraft.shared.turtle.TurtleOverlay; import dan200.computercraft.shared.turtle.blocks.TurtleBlock; import dan200.computercraft.shared.util.DirectionUtil; import net.minecraft.client.data.models.BlockModelGenerators; -import net.minecraft.client.data.models.blockstates.*; +import net.minecraft.client.data.models.MultiVariant; +import net.minecraft.client.data.models.blockstates.ConditionBuilder; +import net.minecraft.client.data.models.blockstates.MultiPartGenerator; +import net.minecraft.client.data.models.blockstates.MultiVariantGenerator; +import net.minecraft.client.data.models.blockstates.PropertyDispatch; import net.minecraft.client.data.models.model.*; +import net.minecraft.client.renderer.block.model.VariantMutator; import net.minecraft.client.renderer.item.EmptyModel; import net.minecraft.client.renderer.item.properties.conditional.HasComponent; import net.minecraft.core.Direction; @@ -33,7 +39,6 @@ import net.minecraft.core.component.DataComponents; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Blocks; -import net.minecraft.world.level.block.state.properties.BlockStateProperties; import net.minecraft.world.level.block.state.properties.BooleanProperty; import net.minecraft.world.level.block.state.properties.Property; @@ -43,6 +48,7 @@ import java.util.Optional; import java.util.function.BiFunction; import java.util.function.Function; +import static net.minecraft.client.data.models.BlockModelGenerators.*; import static net.minecraft.client.data.models.model.ModelLocationUtils.getModelLocation; import static net.minecraft.client.data.models.model.TextureMapping.getBlockTexture; @@ -116,15 +122,14 @@ public class BlockModelProvider { registerTurtleModem(generators, "block/turtle_modem_advanced", "block/wireless_modem_advanced_face"); generators.blockStateOutput.accept( - BlockModelGenerators.createSimpleBlock(ModRegistry.Blocks.LECTERN.get(), getModelLocation(Blocks.LECTERN)) + createSimpleBlock(ModRegistry.Blocks.LECTERN.get(), plainVariant(getModelLocation(Blocks.LECTERN))) .with(createHorizontalFacingDispatch()) ); } private static void registerDiskDrive(BlockModelGenerators generators) { var diskDrive = ModRegistry.Blocks.DISK_DRIVE.get(); - generators.blockStateOutput.accept(MultiVariantGenerator.multiVariant(diskDrive) - .with(createHorizontalFacingDispatch()) + generators.blockStateOutput.accept(MultiVariantGenerator.dispatch(diskDrive) .with(createModelDispatch(DiskDriveBlock.STATE, value -> { var textureSuffix = switch (value) { case EMPTY -> "_front"; @@ -137,14 +142,14 @@ public class BlockModelProvider { generators.modelOutput ); })) + .with(createHorizontalFacingDispatch()) ); generators.registerSimpleItemModel(diskDrive, getModelLocation(diskDrive, "_empty")); } private static void registerPrinter(BlockModelGenerators generators) { var printer = ModRegistry.Blocks.PRINTER.get(); - generators.blockStateOutput.accept(MultiVariantGenerator.multiVariant(printer) - .with(createHorizontalFacingDispatch()) + generators.blockStateOutput.accept(MultiVariantGenerator.dispatch(printer) .with(createModelDispatch(PrinterBlock.TOP, PrinterBlock.BOTTOM, (top, bottom) -> { String model, texture; if (top && bottom) { @@ -165,13 +170,13 @@ public class BlockModelProvider { generators.modelOutput ); })) + .with(createHorizontalFacingDispatch()) ); generators.registerSimpleItemModel(printer, getModelLocation(printer, "_empty")); } private static void registerComputer(BlockModelGenerators generators, ComputerBlock block) { - generators.blockStateOutput.accept(MultiVariantGenerator.multiVariant(block) - .with(createHorizontalFacingDispatch()) + generators.blockStateOutput.accept(MultiVariantGenerator.dispatch(block) .with(createModelDispatch(ComputerBlock.STATE, state -> switch (state) { case OFF -> ModelTemplates.CUBE_ORIENTABLE.createWithSuffix( block, "_" + state.getSerializedName(), @@ -184,6 +189,7 @@ public class BlockModelProvider { generators.modelOutput ); })) + .with(createHorizontalFacingDispatch()) ); generators.registerSimpleItemModel(block, getModelLocation(block, "_blinking")); } @@ -193,7 +199,7 @@ public class BlockModelProvider { var particleModel = ModelTemplates.PARTICLE_ONLY.createWithSuffix( block, "_particle", TextureMapping.particle(getBlockTexture(block, "_front")), generators.modelOutput ); - generators.blockStateOutput.accept(BlockModelGenerators.createSimpleBlock(block, particleModel)); + generators.blockStateOutput.accept(createSimpleBlock(block, plainVariant(particleModel))); // We then register the full model for use in items and the BE renderer. var model = TURTLE.create(block, new TextureMapping() @@ -224,17 +230,17 @@ public class BlockModelProvider { } private static void registerWirelessModem(BlockModelGenerators generators, WirelessModemBlock block) { - generators.blockStateOutput.accept(MultiVariantGenerator.multiVariant(block) - .with(createFacingDispatch()) + generators.blockStateOutput.accept(MultiVariantGenerator.dispatch(block) .with(createModelDispatch(WirelessModemBlock.ON, on -> modemModel(generators, getModelLocation(block, on ? "_on" : "_off"), getBlockTexture(block, "_face" + (on ? "_on" : ""))) - ))); + )) + .with(createFacingDispatch())); generators.registerSimpleItemModel(block, getModelLocation(block, "_off")); } private static void registerWiredModems(BlockModelGenerators generators) { var fullBlock = ModRegistry.Blocks.WIRED_MODEM_FULL.get(); - generators.blockStateOutput.accept(MultiVariantGenerator.multiVariant(fullBlock) + generators.blockStateOutput.accept(MultiVariantGenerator.dispatch(fullBlock) .with(createModelDispatch(WiredModemFullBlock.MODEM_ON, WiredModemFullBlock.PERIPHERAL_ON, (on, peripheral) -> { var suffix = (on ? "_on" : "_off") + (peripheral ? "_peripheral" : ""); var faceTexture = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/wired_modem_face" + (peripheral ? "_peripheral" : "") + (on ? "_on" : "")); @@ -281,10 +287,10 @@ public class BlockModelProvider { monitorModel(generators, block, "_u", 22, 5, 0, 38); monitorModel(generators, block, "_ud", 21, 6, 0, 37); - generators.blockStateOutput.accept(MultiVariantGenerator.multiVariant(block) + generators.blockStateOutput.accept(MultiVariantGenerator.dispatch(block) + .with(createModelDispatch(MonitorBlock.STATE, edge -> getModelLocation(block, edge == MonitorEdgeState.NONE ? "" : "_" + edge.getSerializedName()))) .with(createHorizontalFacingDispatch()) .with(createVerticalFacingDispatch(MonitorBlock.ORIENTATION)) - .with(createModelDispatch(MonitorBlock.STATE, edge -> getModelLocation(block, edge == MonitorEdgeState.NONE ? "" : "_" + edge.getSerializedName()))) ); generators.registerSimpleItemModel(block, monitorModel(generators, block, "_item", 15, 4, 0, 32)); } @@ -308,55 +314,54 @@ public class BlockModelProvider { var coreFacing = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/cable_core_facing"); // Up/Down generator.with( - Condition.or( + or( cableNoNeighbour(Direction.NORTH, Direction.SOUTH, Direction.EAST, Direction.WEST).term(CableBlock.UP, true), cableNoNeighbour(Direction.NORTH, Direction.SOUTH, Direction.EAST, Direction.WEST).term(CableBlock.DOWN, true) ), - Variant.variant().with(VariantProperties.MODEL, coreFacing).with(VariantProperties.X_ROT, VariantProperties.Rotation.R90) + plainVariant(coreFacing).with(VariantMutator.X_ROT.withValue(Quadrant.R90)) ); // North/South and no neighbours generator.with( - Condition.or( + or( cableNoNeighbour(Direction.UP, Direction.DOWN, Direction.NORTH, Direction.SOUTH, Direction.EAST, Direction.WEST), cableNoNeighbour(Direction.UP, Direction.DOWN, Direction.EAST, Direction.WEST).term(CableBlock.NORTH, true), cableNoNeighbour(Direction.UP, Direction.DOWN, Direction.EAST, Direction.WEST).term(CableBlock.SOUTH, true) ), - Variant.variant().with(VariantProperties.MODEL, coreFacing).with(VariantProperties.Y_ROT, VariantProperties.Rotation.R0) + plainVariant(coreFacing).with(VariantMutator.Y_ROT.withValue(Quadrant.R0)) ); // East/West generator.with( - Condition.or( + or( cableNoNeighbour(Direction.NORTH, Direction.SOUTH, Direction.UP, Direction.DOWN).term(CableBlock.EAST, true), cableNoNeighbour(Direction.NORTH, Direction.SOUTH, Direction.UP, Direction.DOWN).term(CableBlock.WEST, true) ), - Variant.variant().with(VariantProperties.MODEL, coreFacing).with(VariantProperties.Y_ROT, VariantProperties.Rotation.R90) + plainVariant(coreFacing).with(VariantMutator.Y_ROT.withValue(Quadrant.R90)) ); // Find all other possibilities and emit a "solid" core which doesn't have a facing direction. var core = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/cable_core_any"); - List rightAngles = new ArrayList<>(); + List rightAngles = new ArrayList<>(); for (var i = 0; i < DirectionUtil.FACINGS.length; i++) { for (var j = i; j < DirectionUtil.FACINGS.length; j++) { if (DirectionUtil.FACINGS[i].getAxis() == DirectionUtil.FACINGS[j].getAxis()) continue; - rightAngles.add(new Condition.TerminalCondition() + rightAngles.add(condition() .term(CableBlock.CABLE, true).term(CABLE_DIRECTIONS[i], true).term(CABLE_DIRECTIONS[j], true) ); } } - generator.with(Condition.or(rightAngles.toArray(new Condition[0])), Variant.variant().with(VariantProperties.MODEL, core)); + generator.with(or(rightAngles.toArray(new ConditionBuilder[0])), plainVariant(core)); // Then emit the actual cable arms var arm = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/cable_arm"); for (var direction : DirectionUtil.FACINGS) { generator.with( - new Condition.TerminalCondition().term(CABLE_DIRECTIONS[direction.ordinal()], true), - Variant.variant() - .with(VariantProperties.MODEL, arm) - .with(VariantProperties.X_ROT, toXAngle(direction.getOpposite())) - .with(VariantProperties.Y_ROT, toYAngle(direction.getOpposite())) + condition().term(CABLE_DIRECTIONS[direction.ordinal()], true), + plainVariant(arm) + .with(VariantMutator.X_ROT.withValue(toXAngle(direction.getOpposite()))) + .with(VariantMutator.Y_ROT.withValue(toYAngle(direction.getOpposite()))) ); } @@ -366,11 +371,10 @@ public class BlockModelProvider { for (var peripheral : BOOLEANS) { var suffix = (on ? "_on" : "_off") + (peripheral ? "_peripheral" : ""); generator.with( - new Condition.TerminalCondition().term(CableBlock.MODEM, CableModemVariant.from(direction, on, peripheral)), - Variant.variant() - .with(VariantProperties.MODEL, ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/wired_modem" + suffix)) - .with(VariantProperties.X_ROT, toXAngle(direction)) - .with(VariantProperties.Y_ROT, toYAngle(direction)) + condition().term(CableBlock.MODEM, CableModemVariant.from(direction, on, peripheral)), + plainVariant(ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/wired_modem" + suffix)) + .with(VariantMutator.X_ROT.withValue(toXAngle(direction.getOpposite()))) + .with(VariantMutator.Y_ROT.withValue(toYAngle(direction.getOpposite()))) ); } } @@ -390,8 +394,8 @@ public class BlockModelProvider { private static final BooleanProperty[] CABLE_DIRECTIONS = { CableBlock.DOWN, CableBlock.UP, CableBlock.NORTH, CableBlock.SOUTH, CableBlock.WEST, CableBlock.EAST }; private static final boolean[] BOOLEANS = new boolean[]{ false, true }; - private static Condition.TerminalCondition cableNoNeighbour(Direction... directions) { - var condition = new Condition.TerminalCondition().term(CableBlock.CABLE, true); + private static ConditionBuilder cableNoNeighbour(Direction... directions) { + var condition = condition().term(CableBlock.CABLE, true); for (var direction : directions) condition.term(CABLE_DIRECTIONS[direction.ordinal()], false); return condition; } @@ -418,66 +422,60 @@ public class BlockModelProvider { generators.registerSimpleItemModel(block, ModelLocationUtils.getModelLocation(block)); } - private static VariantProperties.Rotation toXAngle(Direction direction) { + private static Quadrant toXAngle(Direction direction) { return switch (direction) { - default -> VariantProperties.Rotation.R0; - case UP -> VariantProperties.Rotation.R270; - case DOWN -> VariantProperties.Rotation.R90; + default -> Quadrant.R0; + case UP -> Quadrant.R270; + case DOWN -> Quadrant.R90; }; } - private static VariantProperties.Rotation toYAngle(Direction direction) { + private static Quadrant toYAngle(Direction direction) { return switch (direction) { - default -> VariantProperties.Rotation.R0; - case NORTH -> VariantProperties.Rotation.R0; - case SOUTH -> VariantProperties.Rotation.R180; - case EAST -> VariantProperties.Rotation.R90; - case WEST -> VariantProperties.Rotation.R270; + default -> Quadrant.R0; + case NORTH -> Quadrant.R0; + case SOUTH -> Quadrant.R180; + case EAST -> Quadrant.R90; + case WEST -> Quadrant.R270; }; } - private static PropertyDispatch createHorizontalFacingDispatch() { - var dispatch = PropertyDispatch.property(BlockStateProperties.HORIZONTAL_FACING); + private static PropertyDispatch createHorizontalFacingDispatch() { + /*var dispatch = PropertyDispatch.modify(BlockStateProperties.HORIZONTAL_FACING); for (var direction : BlockStateProperties.HORIZONTAL_FACING.getPossibleValues()) { dispatch.select(direction, Variant.variant().with(VariantProperties.Y_ROT, toYAngle(direction))); } - return dispatch; + return dispatch;*/ + return BlockModelGenerators.ROTATION_HORIZONTAL_FACING; } - private static PropertyDispatch createVerticalFacingDispatch(Property property) { - var dispatch = PropertyDispatch.property(property); + private static PropertyDispatch createVerticalFacingDispatch(Property property) { + var dispatch = PropertyDispatch.modify(property); for (var direction : property.getPossibleValues()) { - dispatch.select(direction, Variant.variant().with(VariantProperties.X_ROT, toXAngle(direction))); + dispatch.select(direction, VariantMutator.X_ROT.withValue(toXAngle(direction))); } return dispatch; } - private static PropertyDispatch createFacingDispatch() { - var dispatch = PropertyDispatch.property(BlockStateProperties.FACING); - for (var direction : BlockStateProperties.FACING.getPossibleValues()) { - dispatch.select(direction, Variant.variant() - .with(VariantProperties.Y_ROT, toYAngle(direction)) - .with(VariantProperties.X_ROT, toXAngle(direction)) - ); - } - return dispatch; + private static PropertyDispatch createFacingDispatch() { + return BlockModelGenerators.ROTATION_FACING; } - private static > PropertyDispatch createModelDispatch(Property property, Function makeModel) { - var variant = PropertyDispatch.property(property); + private static > PropertyDispatch createModelDispatch(Property property, Function makeModel) { + var variant = PropertyDispatch.initial(property); for (var value : property.getPossibleValues()) { - variant.select(value, Variant.variant().with(VariantProperties.MODEL, makeModel.apply(value))); + variant.select(value, plainVariant(makeModel.apply(value))); } return variant; } - private static , U extends Comparable> PropertyDispatch createModelDispatch( + private static , U extends Comparable> PropertyDispatch createModelDispatch( Property propertyT, Property propertyU, BiFunction makeModel ) { - var variant = PropertyDispatch.properties(propertyT, propertyU); + var variant = PropertyDispatch.initial(propertyT, propertyU); for (var valueT : propertyT.getPossibleValues()) { for (var valueU : propertyU.getPossibleValues()) { - variant.select(valueT, valueU, Variant.variant().with(VariantProperties.MODEL, makeModel.apply(valueT, valueU))); + variant.select(valueT, valueU, plainVariant(makeModel.apply(valueT, valueU))); } } return variant; diff --git a/projects/common/src/examples/java/com/example/examplemod/peripheral/BrewingStandPeripheral.java b/projects/common/src/examples/java/com/example/examplemod/peripheral/BrewingStandPeripheral.java index 54a32131d..b9e5dc1c4 100644 --- a/projects/common/src/examples/java/com/example/examplemod/peripheral/BrewingStandPeripheral.java +++ b/projects/common/src/examples/java/com/example/examplemod/peripheral/BrewingStandPeripheral.java @@ -28,7 +28,7 @@ public class BrewingStandPeripheral implements IPeripheral { @LuaFunction public final int getFuel() { // Don't do it this way! Use an access widener/transformer to access the "fuel" field instead. - return brewingStand.saveWithoutMetadata(brewingStand.getLevel().registryAccess()).getInt("Fuel"); + return brewingStand.saveWithoutMetadata(brewingStand.getLevel().registryAccess()).getByteOr("Fuel", (byte) 0); } @Override diff --git a/projects/common/src/examples/java/com/example/examplemod/peripheral/FurnacePeripheral.java b/projects/common/src/examples/java/com/example/examplemod/peripheral/FurnacePeripheral.java index e09ba7f1c..75f7ea26e 100644 --- a/projects/common/src/examples/java/com/example/examplemod/peripheral/FurnacePeripheral.java +++ b/projects/common/src/examples/java/com/example/examplemod/peripheral/FurnacePeripheral.java @@ -23,7 +23,7 @@ public class FurnacePeripheral implements GenericPeripheral { @LuaFunction(mainThread = true) public int getBurnTime(AbstractFurnaceBlockEntity furnace) { // Don't do it this way! Use an access widener/transformer to access the "litTime" field instead. - return furnace.saveWithoutMetadata(furnace.getLevel().registryAccess()).getInt("BurnTime"); + return furnace.saveWithoutMetadata(furnace.getLevel().registryAccess()).getShortOr("lit_time_remaining", (short) 0); } } // @end region=body diff --git a/projects/common/src/generated/resources/assets/computercraft/blockstates/cable.json b/projects/common/src/generated/resources/assets/computercraft/blockstates/cable.json index a440c3e2c..2c00e3b80 100644 --- a/projects/common/src/generated/resources/assets/computercraft/blockstates/cable.json +++ b/projects/common/src/generated/resources/assets/computercraft/blockstates/cable.json @@ -10,7 +10,7 @@ } }, { - "apply": {"model": "computercraft:block/cable_core_facing", "y": 0}, + "apply": {"model": "computercraft:block/cable_core_facing"}, "when": { "OR": [ { @@ -55,70 +55,70 @@ ] } }, - {"apply": {"model": "computercraft:block/cable_arm", "x": 270, "y": 0}, "when": {"down": "true"}}, - {"apply": {"model": "computercraft:block/cable_arm", "x": 90, "y": 0}, "when": {"up": "true"}}, - {"apply": {"model": "computercraft:block/cable_arm", "x": 0, "y": 180}, "when": {"north": "true"}}, - {"apply": {"model": "computercraft:block/cable_arm", "x": 0, "y": 0}, "when": {"south": "true"}}, - {"apply": {"model": "computercraft:block/cable_arm", "x": 0, "y": 90}, "when": {"west": "true"}}, - {"apply": {"model": "computercraft:block/cable_arm", "x": 0, "y": 270}, "when": {"east": "true"}}, - {"apply": {"model": "computercraft:block/wired_modem_off", "x": 90, "y": 0}, "when": {"modem": "down_off"}}, + {"apply": {"model": "computercraft:block/cable_arm", "x": 270}, "when": {"down": "true"}}, + {"apply": {"model": "computercraft:block/cable_arm", "x": 90}, "when": {"up": "true"}}, + {"apply": {"model": "computercraft:block/cable_arm", "y": 180}, "when": {"north": "true"}}, + {"apply": {"model": "computercraft:block/cable_arm"}, "when": {"south": "true"}}, + {"apply": {"model": "computercraft:block/cable_arm", "y": 90}, "when": {"west": "true"}}, + {"apply": {"model": "computercraft:block/cable_arm", "y": 270}, "when": {"east": "true"}}, + {"apply": {"model": "computercraft:block/wired_modem_off", "x": 270}, "when": {"modem": "down_off"}}, { - "apply": {"model": "computercraft:block/wired_modem_off_peripheral", "x": 90, "y": 0}, + "apply": {"model": "computercraft:block/wired_modem_off_peripheral", "x": 270}, "when": {"modem": "down_off_peripheral"} }, - {"apply": {"model": "computercraft:block/wired_modem_on", "x": 90, "y": 0}, "when": {"modem": "down_on"}}, + {"apply": {"model": "computercraft:block/wired_modem_on", "x": 270}, "when": {"modem": "down_on"}}, { - "apply": {"model": "computercraft:block/wired_modem_on_peripheral", "x": 90, "y": 0}, + "apply": {"model": "computercraft:block/wired_modem_on_peripheral", "x": 270}, "when": {"modem": "down_on_peripheral"} }, - {"apply": {"model": "computercraft:block/wired_modem_off", "x": 270, "y": 0}, "when": {"modem": "up_off"}}, + {"apply": {"model": "computercraft:block/wired_modem_off", "x": 90}, "when": {"modem": "up_off"}}, { - "apply": {"model": "computercraft:block/wired_modem_off_peripheral", "x": 270, "y": 0}, + "apply": {"model": "computercraft:block/wired_modem_off_peripheral", "x": 90}, "when": {"modem": "up_off_peripheral"} }, - {"apply": {"model": "computercraft:block/wired_modem_on", "x": 270, "y": 0}, "when": {"modem": "up_on"}}, + {"apply": {"model": "computercraft:block/wired_modem_on", "x": 90}, "when": {"modem": "up_on"}}, { - "apply": {"model": "computercraft:block/wired_modem_on_peripheral", "x": 270, "y": 0}, + "apply": {"model": "computercraft:block/wired_modem_on_peripheral", "x": 90}, "when": {"modem": "up_on_peripheral"} }, - {"apply": {"model": "computercraft:block/wired_modem_off", "x": 0, "y": 0}, "when": {"modem": "north_off"}}, + {"apply": {"model": "computercraft:block/wired_modem_off", "y": 180}, "when": {"modem": "north_off"}}, { - "apply": {"model": "computercraft:block/wired_modem_off_peripheral", "x": 0, "y": 0}, + "apply": {"model": "computercraft:block/wired_modem_off_peripheral", "y": 180}, "when": {"modem": "north_off_peripheral"} }, - {"apply": {"model": "computercraft:block/wired_modem_on", "x": 0, "y": 0}, "when": {"modem": "north_on"}}, + {"apply": {"model": "computercraft:block/wired_modem_on", "y": 180}, "when": {"modem": "north_on"}}, { - "apply": {"model": "computercraft:block/wired_modem_on_peripheral", "x": 0, "y": 0}, + "apply": {"model": "computercraft:block/wired_modem_on_peripheral", "y": 180}, "when": {"modem": "north_on_peripheral"} }, - {"apply": {"model": "computercraft:block/wired_modem_off", "x": 0, "y": 180}, "when": {"modem": "south_off"}}, + {"apply": {"model": "computercraft:block/wired_modem_off"}, "when": {"modem": "south_off"}}, { - "apply": {"model": "computercraft:block/wired_modem_off_peripheral", "x": 0, "y": 180}, + "apply": {"model": "computercraft:block/wired_modem_off_peripheral"}, "when": {"modem": "south_off_peripheral"} }, - {"apply": {"model": "computercraft:block/wired_modem_on", "x": 0, "y": 180}, "when": {"modem": "south_on"}}, + {"apply": {"model": "computercraft:block/wired_modem_on"}, "when": {"modem": "south_on"}}, { - "apply": {"model": "computercraft:block/wired_modem_on_peripheral", "x": 0, "y": 180}, + "apply": {"model": "computercraft:block/wired_modem_on_peripheral"}, "when": {"modem": "south_on_peripheral"} }, - {"apply": {"model": "computercraft:block/wired_modem_off", "x": 0, "y": 270}, "when": {"modem": "west_off"}}, + {"apply": {"model": "computercraft:block/wired_modem_off", "y": 90}, "when": {"modem": "west_off"}}, { - "apply": {"model": "computercraft:block/wired_modem_off_peripheral", "x": 0, "y": 270}, + "apply": {"model": "computercraft:block/wired_modem_off_peripheral", "y": 90}, "when": {"modem": "west_off_peripheral"} }, - {"apply": {"model": "computercraft:block/wired_modem_on", "x": 0, "y": 270}, "when": {"modem": "west_on"}}, + {"apply": {"model": "computercraft:block/wired_modem_on", "y": 90}, "when": {"modem": "west_on"}}, { - "apply": {"model": "computercraft:block/wired_modem_on_peripheral", "x": 0, "y": 270}, + "apply": {"model": "computercraft:block/wired_modem_on_peripheral", "y": 90}, "when": {"modem": "west_on_peripheral"} }, - {"apply": {"model": "computercraft:block/wired_modem_off", "x": 0, "y": 90}, "when": {"modem": "east_off"}}, + {"apply": {"model": "computercraft:block/wired_modem_off", "y": 270}, "when": {"modem": "east_off"}}, { - "apply": {"model": "computercraft:block/wired_modem_off_peripheral", "x": 0, "y": 90}, + "apply": {"model": "computercraft:block/wired_modem_off_peripheral", "y": 270}, "when": {"modem": "east_off_peripheral"} }, - {"apply": {"model": "computercraft:block/wired_modem_on", "x": 0, "y": 90}, "when": {"modem": "east_on"}}, + {"apply": {"model": "computercraft:block/wired_modem_on", "y": 270}, "when": {"modem": "east_on"}}, { - "apply": {"model": "computercraft:block/wired_modem_on_peripheral", "x": 0, "y": 90}, + "apply": {"model": "computercraft:block/wired_modem_on_peripheral", "y": 270}, "when": {"modem": "east_on_peripheral"} } ] diff --git a/projects/common/src/generated/resources/assets/computercraft/blockstates/computer_advanced.json b/projects/common/src/generated/resources/assets/computercraft/blockstates/computer_advanced.json index 5f5960311..a13c25e88 100644 --- a/projects/common/src/generated/resources/assets/computercraft/blockstates/computer_advanced.json +++ b/projects/common/src/generated/resources/assets/computercraft/blockstates/computer_advanced.json @@ -3,9 +3,9 @@ "facing=east,state=blinking": {"model": "computercraft:block/computer_advanced_blinking", "y": 90}, "facing=east,state=off": {"model": "computercraft:block/computer_advanced_off", "y": 90}, "facing=east,state=on": {"model": "computercraft:block/computer_advanced_on", "y": 90}, - "facing=north,state=blinking": {"model": "computercraft:block/computer_advanced_blinking", "y": 0}, - "facing=north,state=off": {"model": "computercraft:block/computer_advanced_off", "y": 0}, - "facing=north,state=on": {"model": "computercraft:block/computer_advanced_on", "y": 0}, + "facing=north,state=blinking": {"model": "computercraft:block/computer_advanced_blinking"}, + "facing=north,state=off": {"model": "computercraft:block/computer_advanced_off"}, + "facing=north,state=on": {"model": "computercraft:block/computer_advanced_on"}, "facing=south,state=blinking": {"model": "computercraft:block/computer_advanced_blinking", "y": 180}, "facing=south,state=off": {"model": "computercraft:block/computer_advanced_off", "y": 180}, "facing=south,state=on": {"model": "computercraft:block/computer_advanced_on", "y": 180}, diff --git a/projects/common/src/generated/resources/assets/computercraft/blockstates/computer_command.json b/projects/common/src/generated/resources/assets/computercraft/blockstates/computer_command.json index 2077c3fbf..07560bc5f 100644 --- a/projects/common/src/generated/resources/assets/computercraft/blockstates/computer_command.json +++ b/projects/common/src/generated/resources/assets/computercraft/blockstates/computer_command.json @@ -3,9 +3,9 @@ "facing=east,state=blinking": {"model": "computercraft:block/computer_command_blinking", "y": 90}, "facing=east,state=off": {"model": "computercraft:block/computer_command_off", "y": 90}, "facing=east,state=on": {"model": "computercraft:block/computer_command_on", "y": 90}, - "facing=north,state=blinking": {"model": "computercraft:block/computer_command_blinking", "y": 0}, - "facing=north,state=off": {"model": "computercraft:block/computer_command_off", "y": 0}, - "facing=north,state=on": {"model": "computercraft:block/computer_command_on", "y": 0}, + "facing=north,state=blinking": {"model": "computercraft:block/computer_command_blinking"}, + "facing=north,state=off": {"model": "computercraft:block/computer_command_off"}, + "facing=north,state=on": {"model": "computercraft:block/computer_command_on"}, "facing=south,state=blinking": {"model": "computercraft:block/computer_command_blinking", "y": 180}, "facing=south,state=off": {"model": "computercraft:block/computer_command_off", "y": 180}, "facing=south,state=on": {"model": "computercraft:block/computer_command_on", "y": 180}, diff --git a/projects/common/src/generated/resources/assets/computercraft/blockstates/computer_normal.json b/projects/common/src/generated/resources/assets/computercraft/blockstates/computer_normal.json index 534584f1d..25ffc3938 100644 --- a/projects/common/src/generated/resources/assets/computercraft/blockstates/computer_normal.json +++ b/projects/common/src/generated/resources/assets/computercraft/blockstates/computer_normal.json @@ -3,9 +3,9 @@ "facing=east,state=blinking": {"model": "computercraft:block/computer_normal_blinking", "y": 90}, "facing=east,state=off": {"model": "computercraft:block/computer_normal_off", "y": 90}, "facing=east,state=on": {"model": "computercraft:block/computer_normal_on", "y": 90}, - "facing=north,state=blinking": {"model": "computercraft:block/computer_normal_blinking", "y": 0}, - "facing=north,state=off": {"model": "computercraft:block/computer_normal_off", "y": 0}, - "facing=north,state=on": {"model": "computercraft:block/computer_normal_on", "y": 0}, + "facing=north,state=blinking": {"model": "computercraft:block/computer_normal_blinking"}, + "facing=north,state=off": {"model": "computercraft:block/computer_normal_off"}, + "facing=north,state=on": {"model": "computercraft:block/computer_normal_on"}, "facing=south,state=blinking": {"model": "computercraft:block/computer_normal_blinking", "y": 180}, "facing=south,state=off": {"model": "computercraft:block/computer_normal_off", "y": 180}, "facing=south,state=on": {"model": "computercraft:block/computer_normal_on", "y": 180}, diff --git a/projects/common/src/generated/resources/assets/computercraft/blockstates/disk_drive.json b/projects/common/src/generated/resources/assets/computercraft/blockstates/disk_drive.json index ea03105fb..e1bb92d2b 100644 --- a/projects/common/src/generated/resources/assets/computercraft/blockstates/disk_drive.json +++ b/projects/common/src/generated/resources/assets/computercraft/blockstates/disk_drive.json @@ -3,9 +3,9 @@ "facing=east,state=empty": {"model": "computercraft:block/disk_drive_empty", "y": 90}, "facing=east,state=full": {"model": "computercraft:block/disk_drive_full", "y": 90}, "facing=east,state=invalid": {"model": "computercraft:block/disk_drive_invalid", "y": 90}, - "facing=north,state=empty": {"model": "computercraft:block/disk_drive_empty", "y": 0}, - "facing=north,state=full": {"model": "computercraft:block/disk_drive_full", "y": 0}, - "facing=north,state=invalid": {"model": "computercraft:block/disk_drive_invalid", "y": 0}, + "facing=north,state=empty": {"model": "computercraft:block/disk_drive_empty"}, + "facing=north,state=full": {"model": "computercraft:block/disk_drive_full"}, + "facing=north,state=invalid": {"model": "computercraft:block/disk_drive_invalid"}, "facing=south,state=empty": {"model": "computercraft:block/disk_drive_empty", "y": 180}, "facing=south,state=full": {"model": "computercraft:block/disk_drive_full", "y": 180}, "facing=south,state=invalid": {"model": "computercraft:block/disk_drive_invalid", "y": 180}, diff --git a/projects/common/src/generated/resources/assets/computercraft/blockstates/lectern.json b/projects/common/src/generated/resources/assets/computercraft/blockstates/lectern.json index c760753f3..a5ec50c9d 100644 --- a/projects/common/src/generated/resources/assets/computercraft/blockstates/lectern.json +++ b/projects/common/src/generated/resources/assets/computercraft/blockstates/lectern.json @@ -1,7 +1,7 @@ { "variants": { "facing=east": {"model": "minecraft:block/lectern", "y": 90}, - "facing=north": {"model": "minecraft:block/lectern", "y": 0}, + "facing=north": {"model": "minecraft:block/lectern"}, "facing=south": {"model": "minecraft:block/lectern", "y": 180}, "facing=west": {"model": "minecraft:block/lectern", "y": 270} } diff --git a/projects/common/src/generated/resources/assets/computercraft/blockstates/monitor_advanced.json b/projects/common/src/generated/resources/assets/computercraft/blockstates/monitor_advanced.json index 7f85fcc59..ceebe2fb3 100644 --- a/projects/common/src/generated/resources/assets/computercraft/blockstates/monitor_advanced.json +++ b/projects/common/src/generated/resources/assets/computercraft/blockstates/monitor_advanced.json @@ -20,26 +20,22 @@ "facing=east,orientation=down,state=rud": {"model": "computercraft:block/monitor_advanced_rud", "x": 90, "y": 90}, "facing=east,orientation=down,state=u": {"model": "computercraft:block/monitor_advanced_u", "x": 90, "y": 90}, "facing=east,orientation=down,state=ud": {"model": "computercraft:block/monitor_advanced_ud", "x": 90, "y": 90}, - "facing=east,orientation=north,state=d": {"model": "computercraft:block/monitor_advanced_d", "x": 0, "y": 90}, - "facing=east,orientation=north,state=l": {"model": "computercraft:block/monitor_advanced_l", "x": 0, "y": 90}, - "facing=east,orientation=north,state=ld": {"model": "computercraft:block/monitor_advanced_ld", "x": 0, "y": 90}, - "facing=east,orientation=north,state=lr": {"model": "computercraft:block/monitor_advanced_lr", "x": 0, "y": 90}, - "facing=east,orientation=north,state=lrd": {"model": "computercraft:block/monitor_advanced_lrd", "x": 0, "y": 90}, - "facing=east,orientation=north,state=lru": {"model": "computercraft:block/monitor_advanced_lru", "x": 0, "y": 90}, - "facing=east,orientation=north,state=lrud": { - "model": "computercraft:block/monitor_advanced_lrud", - "x": 0, - "y": 90 - }, - "facing=east,orientation=north,state=lu": {"model": "computercraft:block/monitor_advanced_lu", "x": 0, "y": 90}, - "facing=east,orientation=north,state=lud": {"model": "computercraft:block/monitor_advanced_lud", "x": 0, "y": 90}, - "facing=east,orientation=north,state=none": {"model": "computercraft:block/monitor_advanced", "x": 0, "y": 90}, - "facing=east,orientation=north,state=r": {"model": "computercraft:block/monitor_advanced_r", "x": 0, "y": 90}, - "facing=east,orientation=north,state=rd": {"model": "computercraft:block/monitor_advanced_rd", "x": 0, "y": 90}, - "facing=east,orientation=north,state=ru": {"model": "computercraft:block/monitor_advanced_ru", "x": 0, "y": 90}, - "facing=east,orientation=north,state=rud": {"model": "computercraft:block/monitor_advanced_rud", "x": 0, "y": 90}, - "facing=east,orientation=north,state=u": {"model": "computercraft:block/monitor_advanced_u", "x": 0, "y": 90}, - "facing=east,orientation=north,state=ud": {"model": "computercraft:block/monitor_advanced_ud", "x": 0, "y": 90}, + "facing=east,orientation=north,state=d": {"model": "computercraft:block/monitor_advanced_d", "y": 90}, + "facing=east,orientation=north,state=l": {"model": "computercraft:block/monitor_advanced_l", "y": 90}, + "facing=east,orientation=north,state=ld": {"model": "computercraft:block/monitor_advanced_ld", "y": 90}, + "facing=east,orientation=north,state=lr": {"model": "computercraft:block/monitor_advanced_lr", "y": 90}, + "facing=east,orientation=north,state=lrd": {"model": "computercraft:block/monitor_advanced_lrd", "y": 90}, + "facing=east,orientation=north,state=lru": {"model": "computercraft:block/monitor_advanced_lru", "y": 90}, + "facing=east,orientation=north,state=lrud": {"model": "computercraft:block/monitor_advanced_lrud", "y": 90}, + "facing=east,orientation=north,state=lu": {"model": "computercraft:block/monitor_advanced_lu", "y": 90}, + "facing=east,orientation=north,state=lud": {"model": "computercraft:block/monitor_advanced_lud", "y": 90}, + "facing=east,orientation=north,state=none": {"model": "computercraft:block/monitor_advanced", "y": 90}, + "facing=east,orientation=north,state=r": {"model": "computercraft:block/monitor_advanced_r", "y": 90}, + "facing=east,orientation=north,state=rd": {"model": "computercraft:block/monitor_advanced_rd", "y": 90}, + "facing=east,orientation=north,state=ru": {"model": "computercraft:block/monitor_advanced_ru", "y": 90}, + "facing=east,orientation=north,state=rud": {"model": "computercraft:block/monitor_advanced_rud", "y": 90}, + "facing=east,orientation=north,state=u": {"model": "computercraft:block/monitor_advanced_u", "y": 90}, + "facing=east,orientation=north,state=ud": {"model": "computercraft:block/monitor_advanced_ud", "y": 90}, "facing=east,orientation=up,state=d": {"model": "computercraft:block/monitor_advanced_d", "x": 270, "y": 90}, "facing=east,orientation=up,state=l": {"model": "computercraft:block/monitor_advanced_l", "x": 270, "y": 90}, "facing=east,orientation=up,state=ld": {"model": "computercraft:block/monitor_advanced_ld", "x": 270, "y": 90}, @@ -56,62 +52,54 @@ "facing=east,orientation=up,state=rud": {"model": "computercraft:block/monitor_advanced_rud", "x": 270, "y": 90}, "facing=east,orientation=up,state=u": {"model": "computercraft:block/monitor_advanced_u", "x": 270, "y": 90}, "facing=east,orientation=up,state=ud": {"model": "computercraft:block/monitor_advanced_ud", "x": 270, "y": 90}, - "facing=north,orientation=down,state=d": {"model": "computercraft:block/monitor_advanced_d", "x": 90, "y": 0}, - "facing=north,orientation=down,state=l": {"model": "computercraft:block/monitor_advanced_l", "x": 90, "y": 0}, - "facing=north,orientation=down,state=ld": {"model": "computercraft:block/monitor_advanced_ld", "x": 90, "y": 0}, - "facing=north,orientation=down,state=lr": {"model": "computercraft:block/monitor_advanced_lr", "x": 90, "y": 0}, - "facing=north,orientation=down,state=lrd": {"model": "computercraft:block/monitor_advanced_lrd", "x": 90, "y": 0}, - "facing=north,orientation=down,state=lru": {"model": "computercraft:block/monitor_advanced_lru", "x": 90, "y": 0}, - "facing=north,orientation=down,state=lrud": { - "model": "computercraft:block/monitor_advanced_lrud", - "x": 90, - "y": 0 - }, - "facing=north,orientation=down,state=lu": {"model": "computercraft:block/monitor_advanced_lu", "x": 90, "y": 0}, - "facing=north,orientation=down,state=lud": {"model": "computercraft:block/monitor_advanced_lud", "x": 90, "y": 0}, - "facing=north,orientation=down,state=none": {"model": "computercraft:block/monitor_advanced", "x": 90, "y": 0}, - "facing=north,orientation=down,state=r": {"model": "computercraft:block/monitor_advanced_r", "x": 90, "y": 0}, - "facing=north,orientation=down,state=rd": {"model": "computercraft:block/monitor_advanced_rd", "x": 90, "y": 0}, - "facing=north,orientation=down,state=ru": {"model": "computercraft:block/monitor_advanced_ru", "x": 90, "y": 0}, - "facing=north,orientation=down,state=rud": {"model": "computercraft:block/monitor_advanced_rud", "x": 90, "y": 0}, - "facing=north,orientation=down,state=u": {"model": "computercraft:block/monitor_advanced_u", "x": 90, "y": 0}, - "facing=north,orientation=down,state=ud": {"model": "computercraft:block/monitor_advanced_ud", "x": 90, "y": 0}, - "facing=north,orientation=north,state=d": {"model": "computercraft:block/monitor_advanced_d", "x": 0, "y": 0}, - "facing=north,orientation=north,state=l": {"model": "computercraft:block/monitor_advanced_l", "x": 0, "y": 0}, - "facing=north,orientation=north,state=ld": {"model": "computercraft:block/monitor_advanced_ld", "x": 0, "y": 0}, - "facing=north,orientation=north,state=lr": {"model": "computercraft:block/monitor_advanced_lr", "x": 0, "y": 0}, - "facing=north,orientation=north,state=lrd": {"model": "computercraft:block/monitor_advanced_lrd", "x": 0, "y": 0}, - "facing=north,orientation=north,state=lru": {"model": "computercraft:block/monitor_advanced_lru", "x": 0, "y": 0}, - "facing=north,orientation=north,state=lrud": { - "model": "computercraft:block/monitor_advanced_lrud", - "x": 0, - "y": 0 - }, - "facing=north,orientation=north,state=lu": {"model": "computercraft:block/monitor_advanced_lu", "x": 0, "y": 0}, - "facing=north,orientation=north,state=lud": {"model": "computercraft:block/monitor_advanced_lud", "x": 0, "y": 0}, - "facing=north,orientation=north,state=none": {"model": "computercraft:block/monitor_advanced", "x": 0, "y": 0}, - "facing=north,orientation=north,state=r": {"model": "computercraft:block/monitor_advanced_r", "x": 0, "y": 0}, - "facing=north,orientation=north,state=rd": {"model": "computercraft:block/monitor_advanced_rd", "x": 0, "y": 0}, - "facing=north,orientation=north,state=ru": {"model": "computercraft:block/monitor_advanced_ru", "x": 0, "y": 0}, - "facing=north,orientation=north,state=rud": {"model": "computercraft:block/monitor_advanced_rud", "x": 0, "y": 0}, - "facing=north,orientation=north,state=u": {"model": "computercraft:block/monitor_advanced_u", "x": 0, "y": 0}, - "facing=north,orientation=north,state=ud": {"model": "computercraft:block/monitor_advanced_ud", "x": 0, "y": 0}, - "facing=north,orientation=up,state=d": {"model": "computercraft:block/monitor_advanced_d", "x": 270, "y": 0}, - "facing=north,orientation=up,state=l": {"model": "computercraft:block/monitor_advanced_l", "x": 270, "y": 0}, - "facing=north,orientation=up,state=ld": {"model": "computercraft:block/monitor_advanced_ld", "x": 270, "y": 0}, - "facing=north,orientation=up,state=lr": {"model": "computercraft:block/monitor_advanced_lr", "x": 270, "y": 0}, - "facing=north,orientation=up,state=lrd": {"model": "computercraft:block/monitor_advanced_lrd", "x": 270, "y": 0}, - "facing=north,orientation=up,state=lru": {"model": "computercraft:block/monitor_advanced_lru", "x": 270, "y": 0}, - "facing=north,orientation=up,state=lrud": {"model": "computercraft:block/monitor_advanced_lrud", "x": 270, "y": 0}, - "facing=north,orientation=up,state=lu": {"model": "computercraft:block/monitor_advanced_lu", "x": 270, "y": 0}, - "facing=north,orientation=up,state=lud": {"model": "computercraft:block/monitor_advanced_lud", "x": 270, "y": 0}, - "facing=north,orientation=up,state=none": {"model": "computercraft:block/monitor_advanced", "x": 270, "y": 0}, - "facing=north,orientation=up,state=r": {"model": "computercraft:block/monitor_advanced_r", "x": 270, "y": 0}, - "facing=north,orientation=up,state=rd": {"model": "computercraft:block/monitor_advanced_rd", "x": 270, "y": 0}, - "facing=north,orientation=up,state=ru": {"model": "computercraft:block/monitor_advanced_ru", "x": 270, "y": 0}, - "facing=north,orientation=up,state=rud": {"model": "computercraft:block/monitor_advanced_rud", "x": 270, "y": 0}, - "facing=north,orientation=up,state=u": {"model": "computercraft:block/monitor_advanced_u", "x": 270, "y": 0}, - "facing=north,orientation=up,state=ud": {"model": "computercraft:block/monitor_advanced_ud", "x": 270, "y": 0}, + "facing=north,orientation=down,state=d": {"model": "computercraft:block/monitor_advanced_d", "x": 90}, + "facing=north,orientation=down,state=l": {"model": "computercraft:block/monitor_advanced_l", "x": 90}, + "facing=north,orientation=down,state=ld": {"model": "computercraft:block/monitor_advanced_ld", "x": 90}, + "facing=north,orientation=down,state=lr": {"model": "computercraft:block/monitor_advanced_lr", "x": 90}, + "facing=north,orientation=down,state=lrd": {"model": "computercraft:block/monitor_advanced_lrd", "x": 90}, + "facing=north,orientation=down,state=lru": {"model": "computercraft:block/monitor_advanced_lru", "x": 90}, + "facing=north,orientation=down,state=lrud": {"model": "computercraft:block/monitor_advanced_lrud", "x": 90}, + "facing=north,orientation=down,state=lu": {"model": "computercraft:block/monitor_advanced_lu", "x": 90}, + "facing=north,orientation=down,state=lud": {"model": "computercraft:block/monitor_advanced_lud", "x": 90}, + "facing=north,orientation=down,state=none": {"model": "computercraft:block/monitor_advanced", "x": 90}, + "facing=north,orientation=down,state=r": {"model": "computercraft:block/monitor_advanced_r", "x": 90}, + "facing=north,orientation=down,state=rd": {"model": "computercraft:block/monitor_advanced_rd", "x": 90}, + "facing=north,orientation=down,state=ru": {"model": "computercraft:block/monitor_advanced_ru", "x": 90}, + "facing=north,orientation=down,state=rud": {"model": "computercraft:block/monitor_advanced_rud", "x": 90}, + "facing=north,orientation=down,state=u": {"model": "computercraft:block/monitor_advanced_u", "x": 90}, + "facing=north,orientation=down,state=ud": {"model": "computercraft:block/monitor_advanced_ud", "x": 90}, + "facing=north,orientation=north,state=d": {"model": "computercraft:block/monitor_advanced_d"}, + "facing=north,orientation=north,state=l": {"model": "computercraft:block/monitor_advanced_l"}, + "facing=north,orientation=north,state=ld": {"model": "computercraft:block/monitor_advanced_ld"}, + "facing=north,orientation=north,state=lr": {"model": "computercraft:block/monitor_advanced_lr"}, + "facing=north,orientation=north,state=lrd": {"model": "computercraft:block/monitor_advanced_lrd"}, + "facing=north,orientation=north,state=lru": {"model": "computercraft:block/monitor_advanced_lru"}, + "facing=north,orientation=north,state=lrud": {"model": "computercraft:block/monitor_advanced_lrud"}, + "facing=north,orientation=north,state=lu": {"model": "computercraft:block/monitor_advanced_lu"}, + "facing=north,orientation=north,state=lud": {"model": "computercraft:block/monitor_advanced_lud"}, + "facing=north,orientation=north,state=none": {"model": "computercraft:block/monitor_advanced"}, + "facing=north,orientation=north,state=r": {"model": "computercraft:block/monitor_advanced_r"}, + "facing=north,orientation=north,state=rd": {"model": "computercraft:block/monitor_advanced_rd"}, + "facing=north,orientation=north,state=ru": {"model": "computercraft:block/monitor_advanced_ru"}, + "facing=north,orientation=north,state=rud": {"model": "computercraft:block/monitor_advanced_rud"}, + "facing=north,orientation=north,state=u": {"model": "computercraft:block/monitor_advanced_u"}, + "facing=north,orientation=north,state=ud": {"model": "computercraft:block/monitor_advanced_ud"}, + "facing=north,orientation=up,state=d": {"model": "computercraft:block/monitor_advanced_d", "x": 270}, + "facing=north,orientation=up,state=l": {"model": "computercraft:block/monitor_advanced_l", "x": 270}, + "facing=north,orientation=up,state=ld": {"model": "computercraft:block/monitor_advanced_ld", "x": 270}, + "facing=north,orientation=up,state=lr": {"model": "computercraft:block/monitor_advanced_lr", "x": 270}, + "facing=north,orientation=up,state=lrd": {"model": "computercraft:block/monitor_advanced_lrd", "x": 270}, + "facing=north,orientation=up,state=lru": {"model": "computercraft:block/monitor_advanced_lru", "x": 270}, + "facing=north,orientation=up,state=lrud": {"model": "computercraft:block/monitor_advanced_lrud", "x": 270}, + "facing=north,orientation=up,state=lu": {"model": "computercraft:block/monitor_advanced_lu", "x": 270}, + "facing=north,orientation=up,state=lud": {"model": "computercraft:block/monitor_advanced_lud", "x": 270}, + "facing=north,orientation=up,state=none": {"model": "computercraft:block/monitor_advanced", "x": 270}, + "facing=north,orientation=up,state=r": {"model": "computercraft:block/monitor_advanced_r", "x": 270}, + "facing=north,orientation=up,state=rd": {"model": "computercraft:block/monitor_advanced_rd", "x": 270}, + "facing=north,orientation=up,state=ru": {"model": "computercraft:block/monitor_advanced_ru", "x": 270}, + "facing=north,orientation=up,state=rud": {"model": "computercraft:block/monitor_advanced_rud", "x": 270}, + "facing=north,orientation=up,state=u": {"model": "computercraft:block/monitor_advanced_u", "x": 270}, + "facing=north,orientation=up,state=ud": {"model": "computercraft:block/monitor_advanced_ud", "x": 270}, "facing=south,orientation=down,state=d": {"model": "computercraft:block/monitor_advanced_d", "x": 90, "y": 180}, "facing=south,orientation=down,state=l": {"model": "computercraft:block/monitor_advanced_l", "x": 90, "y": 180}, "facing=south,orientation=down,state=ld": {"model": "computercraft:block/monitor_advanced_ld", "x": 90, "y": 180}, @@ -148,42 +136,22 @@ }, "facing=south,orientation=down,state=u": {"model": "computercraft:block/monitor_advanced_u", "x": 90, "y": 180}, "facing=south,orientation=down,state=ud": {"model": "computercraft:block/monitor_advanced_ud", "x": 90, "y": 180}, - "facing=south,orientation=north,state=d": {"model": "computercraft:block/monitor_advanced_d", "x": 0, "y": 180}, - "facing=south,orientation=north,state=l": {"model": "computercraft:block/monitor_advanced_l", "x": 0, "y": 180}, - "facing=south,orientation=north,state=ld": {"model": "computercraft:block/monitor_advanced_ld", "x": 0, "y": 180}, - "facing=south,orientation=north,state=lr": {"model": "computercraft:block/monitor_advanced_lr", "x": 0, "y": 180}, - "facing=south,orientation=north,state=lrd": { - "model": "computercraft:block/monitor_advanced_lrd", - "x": 0, - "y": 180 - }, - "facing=south,orientation=north,state=lru": { - "model": "computercraft:block/monitor_advanced_lru", - "x": 0, - "y": 180 - }, - "facing=south,orientation=north,state=lrud": { - "model": "computercraft:block/monitor_advanced_lrud", - "x": 0, - "y": 180 - }, - "facing=south,orientation=north,state=lu": {"model": "computercraft:block/monitor_advanced_lu", "x": 0, "y": 180}, - "facing=south,orientation=north,state=lud": { - "model": "computercraft:block/monitor_advanced_lud", - "x": 0, - "y": 180 - }, - "facing=south,orientation=north,state=none": {"model": "computercraft:block/monitor_advanced", "x": 0, "y": 180}, - "facing=south,orientation=north,state=r": {"model": "computercraft:block/monitor_advanced_r", "x": 0, "y": 180}, - "facing=south,orientation=north,state=rd": {"model": "computercraft:block/monitor_advanced_rd", "x": 0, "y": 180}, - "facing=south,orientation=north,state=ru": {"model": "computercraft:block/monitor_advanced_ru", "x": 0, "y": 180}, - "facing=south,orientation=north,state=rud": { - "model": "computercraft:block/monitor_advanced_rud", - "x": 0, - "y": 180 - }, - "facing=south,orientation=north,state=u": {"model": "computercraft:block/monitor_advanced_u", "x": 0, "y": 180}, - "facing=south,orientation=north,state=ud": {"model": "computercraft:block/monitor_advanced_ud", "x": 0, "y": 180}, + "facing=south,orientation=north,state=d": {"model": "computercraft:block/monitor_advanced_d", "y": 180}, + "facing=south,orientation=north,state=l": {"model": "computercraft:block/monitor_advanced_l", "y": 180}, + "facing=south,orientation=north,state=ld": {"model": "computercraft:block/monitor_advanced_ld", "y": 180}, + "facing=south,orientation=north,state=lr": {"model": "computercraft:block/monitor_advanced_lr", "y": 180}, + "facing=south,orientation=north,state=lrd": {"model": "computercraft:block/monitor_advanced_lrd", "y": 180}, + "facing=south,orientation=north,state=lru": {"model": "computercraft:block/monitor_advanced_lru", "y": 180}, + "facing=south,orientation=north,state=lrud": {"model": "computercraft:block/monitor_advanced_lrud", "y": 180}, + "facing=south,orientation=north,state=lu": {"model": "computercraft:block/monitor_advanced_lu", "y": 180}, + "facing=south,orientation=north,state=lud": {"model": "computercraft:block/monitor_advanced_lud", "y": 180}, + "facing=south,orientation=north,state=none": {"model": "computercraft:block/monitor_advanced", "y": 180}, + "facing=south,orientation=north,state=r": {"model": "computercraft:block/monitor_advanced_r", "y": 180}, + "facing=south,orientation=north,state=rd": {"model": "computercraft:block/monitor_advanced_rd", "y": 180}, + "facing=south,orientation=north,state=ru": {"model": "computercraft:block/monitor_advanced_ru", "y": 180}, + "facing=south,orientation=north,state=rud": {"model": "computercraft:block/monitor_advanced_rud", "y": 180}, + "facing=south,orientation=north,state=u": {"model": "computercraft:block/monitor_advanced_u", "y": 180}, + "facing=south,orientation=north,state=ud": {"model": "computercraft:block/monitor_advanced_ud", "y": 180}, "facing=south,orientation=up,state=d": {"model": "computercraft:block/monitor_advanced_d", "x": 270, "y": 180}, "facing=south,orientation=up,state=l": {"model": "computercraft:block/monitor_advanced_l", "x": 270, "y": 180}, "facing=south,orientation=up,state=ld": {"model": "computercraft:block/monitor_advanced_ld", "x": 270, "y": 180}, @@ -224,26 +192,22 @@ "facing=west,orientation=down,state=rud": {"model": "computercraft:block/monitor_advanced_rud", "x": 90, "y": 270}, "facing=west,orientation=down,state=u": {"model": "computercraft:block/monitor_advanced_u", "x": 90, "y": 270}, "facing=west,orientation=down,state=ud": {"model": "computercraft:block/monitor_advanced_ud", "x": 90, "y": 270}, - "facing=west,orientation=north,state=d": {"model": "computercraft:block/monitor_advanced_d", "x": 0, "y": 270}, - "facing=west,orientation=north,state=l": {"model": "computercraft:block/monitor_advanced_l", "x": 0, "y": 270}, - "facing=west,orientation=north,state=ld": {"model": "computercraft:block/monitor_advanced_ld", "x": 0, "y": 270}, - "facing=west,orientation=north,state=lr": {"model": "computercraft:block/monitor_advanced_lr", "x": 0, "y": 270}, - "facing=west,orientation=north,state=lrd": {"model": "computercraft:block/monitor_advanced_lrd", "x": 0, "y": 270}, - "facing=west,orientation=north,state=lru": {"model": "computercraft:block/monitor_advanced_lru", "x": 0, "y": 270}, - "facing=west,orientation=north,state=lrud": { - "model": "computercraft:block/monitor_advanced_lrud", - "x": 0, - "y": 270 - }, - "facing=west,orientation=north,state=lu": {"model": "computercraft:block/monitor_advanced_lu", "x": 0, "y": 270}, - "facing=west,orientation=north,state=lud": {"model": "computercraft:block/monitor_advanced_lud", "x": 0, "y": 270}, - "facing=west,orientation=north,state=none": {"model": "computercraft:block/monitor_advanced", "x": 0, "y": 270}, - "facing=west,orientation=north,state=r": {"model": "computercraft:block/monitor_advanced_r", "x": 0, "y": 270}, - "facing=west,orientation=north,state=rd": {"model": "computercraft:block/monitor_advanced_rd", "x": 0, "y": 270}, - "facing=west,orientation=north,state=ru": {"model": "computercraft:block/monitor_advanced_ru", "x": 0, "y": 270}, - "facing=west,orientation=north,state=rud": {"model": "computercraft:block/monitor_advanced_rud", "x": 0, "y": 270}, - "facing=west,orientation=north,state=u": {"model": "computercraft:block/monitor_advanced_u", "x": 0, "y": 270}, - "facing=west,orientation=north,state=ud": {"model": "computercraft:block/monitor_advanced_ud", "x": 0, "y": 270}, + "facing=west,orientation=north,state=d": {"model": "computercraft:block/monitor_advanced_d", "y": 270}, + "facing=west,orientation=north,state=l": {"model": "computercraft:block/monitor_advanced_l", "y": 270}, + "facing=west,orientation=north,state=ld": {"model": "computercraft:block/monitor_advanced_ld", "y": 270}, + "facing=west,orientation=north,state=lr": {"model": "computercraft:block/monitor_advanced_lr", "y": 270}, + "facing=west,orientation=north,state=lrd": {"model": "computercraft:block/monitor_advanced_lrd", "y": 270}, + "facing=west,orientation=north,state=lru": {"model": "computercraft:block/monitor_advanced_lru", "y": 270}, + "facing=west,orientation=north,state=lrud": {"model": "computercraft:block/monitor_advanced_lrud", "y": 270}, + "facing=west,orientation=north,state=lu": {"model": "computercraft:block/monitor_advanced_lu", "y": 270}, + "facing=west,orientation=north,state=lud": {"model": "computercraft:block/monitor_advanced_lud", "y": 270}, + "facing=west,orientation=north,state=none": {"model": "computercraft:block/monitor_advanced", "y": 270}, + "facing=west,orientation=north,state=r": {"model": "computercraft:block/monitor_advanced_r", "y": 270}, + "facing=west,orientation=north,state=rd": {"model": "computercraft:block/monitor_advanced_rd", "y": 270}, + "facing=west,orientation=north,state=ru": {"model": "computercraft:block/monitor_advanced_ru", "y": 270}, + "facing=west,orientation=north,state=rud": {"model": "computercraft:block/monitor_advanced_rud", "y": 270}, + "facing=west,orientation=north,state=u": {"model": "computercraft:block/monitor_advanced_u", "y": 270}, + "facing=west,orientation=north,state=ud": {"model": "computercraft:block/monitor_advanced_ud", "y": 270}, "facing=west,orientation=up,state=d": {"model": "computercraft:block/monitor_advanced_d", "x": 270, "y": 270}, "facing=west,orientation=up,state=l": {"model": "computercraft:block/monitor_advanced_l", "x": 270, "y": 270}, "facing=west,orientation=up,state=ld": {"model": "computercraft:block/monitor_advanced_ld", "x": 270, "y": 270}, diff --git a/projects/common/src/generated/resources/assets/computercraft/blockstates/monitor_normal.json b/projects/common/src/generated/resources/assets/computercraft/blockstates/monitor_normal.json index 6c966368b..931774193 100644 --- a/projects/common/src/generated/resources/assets/computercraft/blockstates/monitor_normal.json +++ b/projects/common/src/generated/resources/assets/computercraft/blockstates/monitor_normal.json @@ -16,22 +16,22 @@ "facing=east,orientation=down,state=rud": {"model": "computercraft:block/monitor_normal_rud", "x": 90, "y": 90}, "facing=east,orientation=down,state=u": {"model": "computercraft:block/monitor_normal_u", "x": 90, "y": 90}, "facing=east,orientation=down,state=ud": {"model": "computercraft:block/monitor_normal_ud", "x": 90, "y": 90}, - "facing=east,orientation=north,state=d": {"model": "computercraft:block/monitor_normal_d", "x": 0, "y": 90}, - "facing=east,orientation=north,state=l": {"model": "computercraft:block/monitor_normal_l", "x": 0, "y": 90}, - "facing=east,orientation=north,state=ld": {"model": "computercraft:block/monitor_normal_ld", "x": 0, "y": 90}, - "facing=east,orientation=north,state=lr": {"model": "computercraft:block/monitor_normal_lr", "x": 0, "y": 90}, - "facing=east,orientation=north,state=lrd": {"model": "computercraft:block/monitor_normal_lrd", "x": 0, "y": 90}, - "facing=east,orientation=north,state=lru": {"model": "computercraft:block/monitor_normal_lru", "x": 0, "y": 90}, - "facing=east,orientation=north,state=lrud": {"model": "computercraft:block/monitor_normal_lrud", "x": 0, "y": 90}, - "facing=east,orientation=north,state=lu": {"model": "computercraft:block/monitor_normal_lu", "x": 0, "y": 90}, - "facing=east,orientation=north,state=lud": {"model": "computercraft:block/monitor_normal_lud", "x": 0, "y": 90}, - "facing=east,orientation=north,state=none": {"model": "computercraft:block/monitor_normal", "x": 0, "y": 90}, - "facing=east,orientation=north,state=r": {"model": "computercraft:block/monitor_normal_r", "x": 0, "y": 90}, - "facing=east,orientation=north,state=rd": {"model": "computercraft:block/monitor_normal_rd", "x": 0, "y": 90}, - "facing=east,orientation=north,state=ru": {"model": "computercraft:block/monitor_normal_ru", "x": 0, "y": 90}, - "facing=east,orientation=north,state=rud": {"model": "computercraft:block/monitor_normal_rud", "x": 0, "y": 90}, - "facing=east,orientation=north,state=u": {"model": "computercraft:block/monitor_normal_u", "x": 0, "y": 90}, - "facing=east,orientation=north,state=ud": {"model": "computercraft:block/monitor_normal_ud", "x": 0, "y": 90}, + "facing=east,orientation=north,state=d": {"model": "computercraft:block/monitor_normal_d", "y": 90}, + "facing=east,orientation=north,state=l": {"model": "computercraft:block/monitor_normal_l", "y": 90}, + "facing=east,orientation=north,state=ld": {"model": "computercraft:block/monitor_normal_ld", "y": 90}, + "facing=east,orientation=north,state=lr": {"model": "computercraft:block/monitor_normal_lr", "y": 90}, + "facing=east,orientation=north,state=lrd": {"model": "computercraft:block/monitor_normal_lrd", "y": 90}, + "facing=east,orientation=north,state=lru": {"model": "computercraft:block/monitor_normal_lru", "y": 90}, + "facing=east,orientation=north,state=lrud": {"model": "computercraft:block/monitor_normal_lrud", "y": 90}, + "facing=east,orientation=north,state=lu": {"model": "computercraft:block/monitor_normal_lu", "y": 90}, + "facing=east,orientation=north,state=lud": {"model": "computercraft:block/monitor_normal_lud", "y": 90}, + "facing=east,orientation=north,state=none": {"model": "computercraft:block/monitor_normal", "y": 90}, + "facing=east,orientation=north,state=r": {"model": "computercraft:block/monitor_normal_r", "y": 90}, + "facing=east,orientation=north,state=rd": {"model": "computercraft:block/monitor_normal_rd", "y": 90}, + "facing=east,orientation=north,state=ru": {"model": "computercraft:block/monitor_normal_ru", "y": 90}, + "facing=east,orientation=north,state=rud": {"model": "computercraft:block/monitor_normal_rud", "y": 90}, + "facing=east,orientation=north,state=u": {"model": "computercraft:block/monitor_normal_u", "y": 90}, + "facing=east,orientation=north,state=ud": {"model": "computercraft:block/monitor_normal_ud", "y": 90}, "facing=east,orientation=up,state=d": {"model": "computercraft:block/monitor_normal_d", "x": 270, "y": 90}, "facing=east,orientation=up,state=l": {"model": "computercraft:block/monitor_normal_l", "x": 270, "y": 90}, "facing=east,orientation=up,state=ld": {"model": "computercraft:block/monitor_normal_ld", "x": 270, "y": 90}, @@ -48,54 +48,54 @@ "facing=east,orientation=up,state=rud": {"model": "computercraft:block/monitor_normal_rud", "x": 270, "y": 90}, "facing=east,orientation=up,state=u": {"model": "computercraft:block/monitor_normal_u", "x": 270, "y": 90}, "facing=east,orientation=up,state=ud": {"model": "computercraft:block/monitor_normal_ud", "x": 270, "y": 90}, - "facing=north,orientation=down,state=d": {"model": "computercraft:block/monitor_normal_d", "x": 90, "y": 0}, - "facing=north,orientation=down,state=l": {"model": "computercraft:block/monitor_normal_l", "x": 90, "y": 0}, - "facing=north,orientation=down,state=ld": {"model": "computercraft:block/monitor_normal_ld", "x": 90, "y": 0}, - "facing=north,orientation=down,state=lr": {"model": "computercraft:block/monitor_normal_lr", "x": 90, "y": 0}, - "facing=north,orientation=down,state=lrd": {"model": "computercraft:block/monitor_normal_lrd", "x": 90, "y": 0}, - "facing=north,orientation=down,state=lru": {"model": "computercraft:block/monitor_normal_lru", "x": 90, "y": 0}, - "facing=north,orientation=down,state=lrud": {"model": "computercraft:block/monitor_normal_lrud", "x": 90, "y": 0}, - "facing=north,orientation=down,state=lu": {"model": "computercraft:block/monitor_normal_lu", "x": 90, "y": 0}, - "facing=north,orientation=down,state=lud": {"model": "computercraft:block/monitor_normal_lud", "x": 90, "y": 0}, - "facing=north,orientation=down,state=none": {"model": "computercraft:block/monitor_normal", "x": 90, "y": 0}, - "facing=north,orientation=down,state=r": {"model": "computercraft:block/monitor_normal_r", "x": 90, "y": 0}, - "facing=north,orientation=down,state=rd": {"model": "computercraft:block/monitor_normal_rd", "x": 90, "y": 0}, - "facing=north,orientation=down,state=ru": {"model": "computercraft:block/monitor_normal_ru", "x": 90, "y": 0}, - "facing=north,orientation=down,state=rud": {"model": "computercraft:block/monitor_normal_rud", "x": 90, "y": 0}, - "facing=north,orientation=down,state=u": {"model": "computercraft:block/monitor_normal_u", "x": 90, "y": 0}, - "facing=north,orientation=down,state=ud": {"model": "computercraft:block/monitor_normal_ud", "x": 90, "y": 0}, - "facing=north,orientation=north,state=d": {"model": "computercraft:block/monitor_normal_d", "x": 0, "y": 0}, - "facing=north,orientation=north,state=l": {"model": "computercraft:block/monitor_normal_l", "x": 0, "y": 0}, - "facing=north,orientation=north,state=ld": {"model": "computercraft:block/monitor_normal_ld", "x": 0, "y": 0}, - "facing=north,orientation=north,state=lr": {"model": "computercraft:block/monitor_normal_lr", "x": 0, "y": 0}, - "facing=north,orientation=north,state=lrd": {"model": "computercraft:block/monitor_normal_lrd", "x": 0, "y": 0}, - "facing=north,orientation=north,state=lru": {"model": "computercraft:block/monitor_normal_lru", "x": 0, "y": 0}, - "facing=north,orientation=north,state=lrud": {"model": "computercraft:block/monitor_normal_lrud", "x": 0, "y": 0}, - "facing=north,orientation=north,state=lu": {"model": "computercraft:block/monitor_normal_lu", "x": 0, "y": 0}, - "facing=north,orientation=north,state=lud": {"model": "computercraft:block/monitor_normal_lud", "x": 0, "y": 0}, - "facing=north,orientation=north,state=none": {"model": "computercraft:block/monitor_normal", "x": 0, "y": 0}, - "facing=north,orientation=north,state=r": {"model": "computercraft:block/monitor_normal_r", "x": 0, "y": 0}, - "facing=north,orientation=north,state=rd": {"model": "computercraft:block/monitor_normal_rd", "x": 0, "y": 0}, - "facing=north,orientation=north,state=ru": {"model": "computercraft:block/monitor_normal_ru", "x": 0, "y": 0}, - "facing=north,orientation=north,state=rud": {"model": "computercraft:block/monitor_normal_rud", "x": 0, "y": 0}, - "facing=north,orientation=north,state=u": {"model": "computercraft:block/monitor_normal_u", "x": 0, "y": 0}, - "facing=north,orientation=north,state=ud": {"model": "computercraft:block/monitor_normal_ud", "x": 0, "y": 0}, - "facing=north,orientation=up,state=d": {"model": "computercraft:block/monitor_normal_d", "x": 270, "y": 0}, - "facing=north,orientation=up,state=l": {"model": "computercraft:block/monitor_normal_l", "x": 270, "y": 0}, - "facing=north,orientation=up,state=ld": {"model": "computercraft:block/monitor_normal_ld", "x": 270, "y": 0}, - "facing=north,orientation=up,state=lr": {"model": "computercraft:block/monitor_normal_lr", "x": 270, "y": 0}, - "facing=north,orientation=up,state=lrd": {"model": "computercraft:block/monitor_normal_lrd", "x": 270, "y": 0}, - "facing=north,orientation=up,state=lru": {"model": "computercraft:block/monitor_normal_lru", "x": 270, "y": 0}, - "facing=north,orientation=up,state=lrud": {"model": "computercraft:block/monitor_normal_lrud", "x": 270, "y": 0}, - "facing=north,orientation=up,state=lu": {"model": "computercraft:block/monitor_normal_lu", "x": 270, "y": 0}, - "facing=north,orientation=up,state=lud": {"model": "computercraft:block/monitor_normal_lud", "x": 270, "y": 0}, - "facing=north,orientation=up,state=none": {"model": "computercraft:block/monitor_normal", "x": 270, "y": 0}, - "facing=north,orientation=up,state=r": {"model": "computercraft:block/monitor_normal_r", "x": 270, "y": 0}, - "facing=north,orientation=up,state=rd": {"model": "computercraft:block/monitor_normal_rd", "x": 270, "y": 0}, - "facing=north,orientation=up,state=ru": {"model": "computercraft:block/monitor_normal_ru", "x": 270, "y": 0}, - "facing=north,orientation=up,state=rud": {"model": "computercraft:block/monitor_normal_rud", "x": 270, "y": 0}, - "facing=north,orientation=up,state=u": {"model": "computercraft:block/monitor_normal_u", "x": 270, "y": 0}, - "facing=north,orientation=up,state=ud": {"model": "computercraft:block/monitor_normal_ud", "x": 270, "y": 0}, + "facing=north,orientation=down,state=d": {"model": "computercraft:block/monitor_normal_d", "x": 90}, + "facing=north,orientation=down,state=l": {"model": "computercraft:block/monitor_normal_l", "x": 90}, + "facing=north,orientation=down,state=ld": {"model": "computercraft:block/monitor_normal_ld", "x": 90}, + "facing=north,orientation=down,state=lr": {"model": "computercraft:block/monitor_normal_lr", "x": 90}, + "facing=north,orientation=down,state=lrd": {"model": "computercraft:block/monitor_normal_lrd", "x": 90}, + "facing=north,orientation=down,state=lru": {"model": "computercraft:block/monitor_normal_lru", "x": 90}, + "facing=north,orientation=down,state=lrud": {"model": "computercraft:block/monitor_normal_lrud", "x": 90}, + "facing=north,orientation=down,state=lu": {"model": "computercraft:block/monitor_normal_lu", "x": 90}, + "facing=north,orientation=down,state=lud": {"model": "computercraft:block/monitor_normal_lud", "x": 90}, + "facing=north,orientation=down,state=none": {"model": "computercraft:block/monitor_normal", "x": 90}, + "facing=north,orientation=down,state=r": {"model": "computercraft:block/monitor_normal_r", "x": 90}, + "facing=north,orientation=down,state=rd": {"model": "computercraft:block/monitor_normal_rd", "x": 90}, + "facing=north,orientation=down,state=ru": {"model": "computercraft:block/monitor_normal_ru", "x": 90}, + "facing=north,orientation=down,state=rud": {"model": "computercraft:block/monitor_normal_rud", "x": 90}, + "facing=north,orientation=down,state=u": {"model": "computercraft:block/monitor_normal_u", "x": 90}, + "facing=north,orientation=down,state=ud": {"model": "computercraft:block/monitor_normal_ud", "x": 90}, + "facing=north,orientation=north,state=d": {"model": "computercraft:block/monitor_normal_d"}, + "facing=north,orientation=north,state=l": {"model": "computercraft:block/monitor_normal_l"}, + "facing=north,orientation=north,state=ld": {"model": "computercraft:block/monitor_normal_ld"}, + "facing=north,orientation=north,state=lr": {"model": "computercraft:block/monitor_normal_lr"}, + "facing=north,orientation=north,state=lrd": {"model": "computercraft:block/monitor_normal_lrd"}, + "facing=north,orientation=north,state=lru": {"model": "computercraft:block/monitor_normal_lru"}, + "facing=north,orientation=north,state=lrud": {"model": "computercraft:block/monitor_normal_lrud"}, + "facing=north,orientation=north,state=lu": {"model": "computercraft:block/monitor_normal_lu"}, + "facing=north,orientation=north,state=lud": {"model": "computercraft:block/monitor_normal_lud"}, + "facing=north,orientation=north,state=none": {"model": "computercraft:block/monitor_normal"}, + "facing=north,orientation=north,state=r": {"model": "computercraft:block/monitor_normal_r"}, + "facing=north,orientation=north,state=rd": {"model": "computercraft:block/monitor_normal_rd"}, + "facing=north,orientation=north,state=ru": {"model": "computercraft:block/monitor_normal_ru"}, + "facing=north,orientation=north,state=rud": {"model": "computercraft:block/monitor_normal_rud"}, + "facing=north,orientation=north,state=u": {"model": "computercraft:block/monitor_normal_u"}, + "facing=north,orientation=north,state=ud": {"model": "computercraft:block/monitor_normal_ud"}, + "facing=north,orientation=up,state=d": {"model": "computercraft:block/monitor_normal_d", "x": 270}, + "facing=north,orientation=up,state=l": {"model": "computercraft:block/monitor_normal_l", "x": 270}, + "facing=north,orientation=up,state=ld": {"model": "computercraft:block/monitor_normal_ld", "x": 270}, + "facing=north,orientation=up,state=lr": {"model": "computercraft:block/monitor_normal_lr", "x": 270}, + "facing=north,orientation=up,state=lrd": {"model": "computercraft:block/monitor_normal_lrd", "x": 270}, + "facing=north,orientation=up,state=lru": {"model": "computercraft:block/monitor_normal_lru", "x": 270}, + "facing=north,orientation=up,state=lrud": {"model": "computercraft:block/monitor_normal_lrud", "x": 270}, + "facing=north,orientation=up,state=lu": {"model": "computercraft:block/monitor_normal_lu", "x": 270}, + "facing=north,orientation=up,state=lud": {"model": "computercraft:block/monitor_normal_lud", "x": 270}, + "facing=north,orientation=up,state=none": {"model": "computercraft:block/monitor_normal", "x": 270}, + "facing=north,orientation=up,state=r": {"model": "computercraft:block/monitor_normal_r", "x": 270}, + "facing=north,orientation=up,state=rd": {"model": "computercraft:block/monitor_normal_rd", "x": 270}, + "facing=north,orientation=up,state=ru": {"model": "computercraft:block/monitor_normal_ru", "x": 270}, + "facing=north,orientation=up,state=rud": {"model": "computercraft:block/monitor_normal_rud", "x": 270}, + "facing=north,orientation=up,state=u": {"model": "computercraft:block/monitor_normal_u", "x": 270}, + "facing=north,orientation=up,state=ud": {"model": "computercraft:block/monitor_normal_ud", "x": 270}, "facing=south,orientation=down,state=d": {"model": "computercraft:block/monitor_normal_d", "x": 90, "y": 180}, "facing=south,orientation=down,state=l": {"model": "computercraft:block/monitor_normal_l", "x": 90, "y": 180}, "facing=south,orientation=down,state=ld": {"model": "computercraft:block/monitor_normal_ld", "x": 90, "y": 180}, @@ -116,26 +116,22 @@ "facing=south,orientation=down,state=rud": {"model": "computercraft:block/monitor_normal_rud", "x": 90, "y": 180}, "facing=south,orientation=down,state=u": {"model": "computercraft:block/monitor_normal_u", "x": 90, "y": 180}, "facing=south,orientation=down,state=ud": {"model": "computercraft:block/monitor_normal_ud", "x": 90, "y": 180}, - "facing=south,orientation=north,state=d": {"model": "computercraft:block/monitor_normal_d", "x": 0, "y": 180}, - "facing=south,orientation=north,state=l": {"model": "computercraft:block/monitor_normal_l", "x": 0, "y": 180}, - "facing=south,orientation=north,state=ld": {"model": "computercraft:block/monitor_normal_ld", "x": 0, "y": 180}, - "facing=south,orientation=north,state=lr": {"model": "computercraft:block/monitor_normal_lr", "x": 0, "y": 180}, - "facing=south,orientation=north,state=lrd": {"model": "computercraft:block/monitor_normal_lrd", "x": 0, "y": 180}, - "facing=south,orientation=north,state=lru": {"model": "computercraft:block/monitor_normal_lru", "x": 0, "y": 180}, - "facing=south,orientation=north,state=lrud": { - "model": "computercraft:block/monitor_normal_lrud", - "x": 0, - "y": 180 - }, - "facing=south,orientation=north,state=lu": {"model": "computercraft:block/monitor_normal_lu", "x": 0, "y": 180}, - "facing=south,orientation=north,state=lud": {"model": "computercraft:block/monitor_normal_lud", "x": 0, "y": 180}, - "facing=south,orientation=north,state=none": {"model": "computercraft:block/monitor_normal", "x": 0, "y": 180}, - "facing=south,orientation=north,state=r": {"model": "computercraft:block/monitor_normal_r", "x": 0, "y": 180}, - "facing=south,orientation=north,state=rd": {"model": "computercraft:block/monitor_normal_rd", "x": 0, "y": 180}, - "facing=south,orientation=north,state=ru": {"model": "computercraft:block/monitor_normal_ru", "x": 0, "y": 180}, - "facing=south,orientation=north,state=rud": {"model": "computercraft:block/monitor_normal_rud", "x": 0, "y": 180}, - "facing=south,orientation=north,state=u": {"model": "computercraft:block/monitor_normal_u", "x": 0, "y": 180}, - "facing=south,orientation=north,state=ud": {"model": "computercraft:block/monitor_normal_ud", "x": 0, "y": 180}, + "facing=south,orientation=north,state=d": {"model": "computercraft:block/monitor_normal_d", "y": 180}, + "facing=south,orientation=north,state=l": {"model": "computercraft:block/monitor_normal_l", "y": 180}, + "facing=south,orientation=north,state=ld": {"model": "computercraft:block/monitor_normal_ld", "y": 180}, + "facing=south,orientation=north,state=lr": {"model": "computercraft:block/monitor_normal_lr", "y": 180}, + "facing=south,orientation=north,state=lrd": {"model": "computercraft:block/monitor_normal_lrd", "y": 180}, + "facing=south,orientation=north,state=lru": {"model": "computercraft:block/monitor_normal_lru", "y": 180}, + "facing=south,orientation=north,state=lrud": {"model": "computercraft:block/monitor_normal_lrud", "y": 180}, + "facing=south,orientation=north,state=lu": {"model": "computercraft:block/monitor_normal_lu", "y": 180}, + "facing=south,orientation=north,state=lud": {"model": "computercraft:block/monitor_normal_lud", "y": 180}, + "facing=south,orientation=north,state=none": {"model": "computercraft:block/monitor_normal", "y": 180}, + "facing=south,orientation=north,state=r": {"model": "computercraft:block/monitor_normal_r", "y": 180}, + "facing=south,orientation=north,state=rd": {"model": "computercraft:block/monitor_normal_rd", "y": 180}, + "facing=south,orientation=north,state=ru": {"model": "computercraft:block/monitor_normal_ru", "y": 180}, + "facing=south,orientation=north,state=rud": {"model": "computercraft:block/monitor_normal_rud", "y": 180}, + "facing=south,orientation=north,state=u": {"model": "computercraft:block/monitor_normal_u", "y": 180}, + "facing=south,orientation=north,state=ud": {"model": "computercraft:block/monitor_normal_ud", "y": 180}, "facing=south,orientation=up,state=d": {"model": "computercraft:block/monitor_normal_d", "x": 270, "y": 180}, "facing=south,orientation=up,state=l": {"model": "computercraft:block/monitor_normal_l", "x": 270, "y": 180}, "facing=south,orientation=up,state=ld": {"model": "computercraft:block/monitor_normal_ld", "x": 270, "y": 180}, @@ -168,22 +164,22 @@ "facing=west,orientation=down,state=rud": {"model": "computercraft:block/monitor_normal_rud", "x": 90, "y": 270}, "facing=west,orientation=down,state=u": {"model": "computercraft:block/monitor_normal_u", "x": 90, "y": 270}, "facing=west,orientation=down,state=ud": {"model": "computercraft:block/monitor_normal_ud", "x": 90, "y": 270}, - "facing=west,orientation=north,state=d": {"model": "computercraft:block/monitor_normal_d", "x": 0, "y": 270}, - "facing=west,orientation=north,state=l": {"model": "computercraft:block/monitor_normal_l", "x": 0, "y": 270}, - "facing=west,orientation=north,state=ld": {"model": "computercraft:block/monitor_normal_ld", "x": 0, "y": 270}, - "facing=west,orientation=north,state=lr": {"model": "computercraft:block/monitor_normal_lr", "x": 0, "y": 270}, - "facing=west,orientation=north,state=lrd": {"model": "computercraft:block/monitor_normal_lrd", "x": 0, "y": 270}, - "facing=west,orientation=north,state=lru": {"model": "computercraft:block/monitor_normal_lru", "x": 0, "y": 270}, - "facing=west,orientation=north,state=lrud": {"model": "computercraft:block/monitor_normal_lrud", "x": 0, "y": 270}, - "facing=west,orientation=north,state=lu": {"model": "computercraft:block/monitor_normal_lu", "x": 0, "y": 270}, - "facing=west,orientation=north,state=lud": {"model": "computercraft:block/monitor_normal_lud", "x": 0, "y": 270}, - "facing=west,orientation=north,state=none": {"model": "computercraft:block/monitor_normal", "x": 0, "y": 270}, - "facing=west,orientation=north,state=r": {"model": "computercraft:block/monitor_normal_r", "x": 0, "y": 270}, - "facing=west,orientation=north,state=rd": {"model": "computercraft:block/monitor_normal_rd", "x": 0, "y": 270}, - "facing=west,orientation=north,state=ru": {"model": "computercraft:block/monitor_normal_ru", "x": 0, "y": 270}, - "facing=west,orientation=north,state=rud": {"model": "computercraft:block/monitor_normal_rud", "x": 0, "y": 270}, - "facing=west,orientation=north,state=u": {"model": "computercraft:block/monitor_normal_u", "x": 0, "y": 270}, - "facing=west,orientation=north,state=ud": {"model": "computercraft:block/monitor_normal_ud", "x": 0, "y": 270}, + "facing=west,orientation=north,state=d": {"model": "computercraft:block/monitor_normal_d", "y": 270}, + "facing=west,orientation=north,state=l": {"model": "computercraft:block/monitor_normal_l", "y": 270}, + "facing=west,orientation=north,state=ld": {"model": "computercraft:block/monitor_normal_ld", "y": 270}, + "facing=west,orientation=north,state=lr": {"model": "computercraft:block/monitor_normal_lr", "y": 270}, + "facing=west,orientation=north,state=lrd": {"model": "computercraft:block/monitor_normal_lrd", "y": 270}, + "facing=west,orientation=north,state=lru": {"model": "computercraft:block/monitor_normal_lru", "y": 270}, + "facing=west,orientation=north,state=lrud": {"model": "computercraft:block/monitor_normal_lrud", "y": 270}, + "facing=west,orientation=north,state=lu": {"model": "computercraft:block/monitor_normal_lu", "y": 270}, + "facing=west,orientation=north,state=lud": {"model": "computercraft:block/monitor_normal_lud", "y": 270}, + "facing=west,orientation=north,state=none": {"model": "computercraft:block/monitor_normal", "y": 270}, + "facing=west,orientation=north,state=r": {"model": "computercraft:block/monitor_normal_r", "y": 270}, + "facing=west,orientation=north,state=rd": {"model": "computercraft:block/monitor_normal_rd", "y": 270}, + "facing=west,orientation=north,state=ru": {"model": "computercraft:block/monitor_normal_ru", "y": 270}, + "facing=west,orientation=north,state=rud": {"model": "computercraft:block/monitor_normal_rud", "y": 270}, + "facing=west,orientation=north,state=u": {"model": "computercraft:block/monitor_normal_u", "y": 270}, + "facing=west,orientation=north,state=ud": {"model": "computercraft:block/monitor_normal_ud", "y": 270}, "facing=west,orientation=up,state=d": {"model": "computercraft:block/monitor_normal_d", "x": 270, "y": 270}, "facing=west,orientation=up,state=l": {"model": "computercraft:block/monitor_normal_l", "x": 270, "y": 270}, "facing=west,orientation=up,state=ld": {"model": "computercraft:block/monitor_normal_ld", "x": 270, "y": 270}, diff --git a/projects/common/src/generated/resources/assets/computercraft/blockstates/printer.json b/projects/common/src/generated/resources/assets/computercraft/blockstates/printer.json index 2166b5f04..027934678 100644 --- a/projects/common/src/generated/resources/assets/computercraft/blockstates/printer.json +++ b/projects/common/src/generated/resources/assets/computercraft/blockstates/printer.json @@ -2,16 +2,16 @@ "variants": { "bottom=false,facing=east,top=false": {"model": "computercraft:block/printer_empty", "y": 90}, "bottom=false,facing=east,top=true": {"model": "computercraft:block/printer_top_full", "y": 90}, - "bottom=false,facing=north,top=false": {"model": "computercraft:block/printer_empty", "y": 0}, - "bottom=false,facing=north,top=true": {"model": "computercraft:block/printer_top_full", "y": 0}, + "bottom=false,facing=north,top=false": {"model": "computercraft:block/printer_empty"}, + "bottom=false,facing=north,top=true": {"model": "computercraft:block/printer_top_full"}, "bottom=false,facing=south,top=false": {"model": "computercraft:block/printer_empty", "y": 180}, "bottom=false,facing=south,top=true": {"model": "computercraft:block/printer_top_full", "y": 180}, "bottom=false,facing=west,top=false": {"model": "computercraft:block/printer_empty", "y": 270}, "bottom=false,facing=west,top=true": {"model": "computercraft:block/printer_top_full", "y": 270}, "bottom=true,facing=east,top=false": {"model": "computercraft:block/printer_bottom_full", "y": 90}, "bottom=true,facing=east,top=true": {"model": "computercraft:block/printer_both_full", "y": 90}, - "bottom=true,facing=north,top=false": {"model": "computercraft:block/printer_bottom_full", "y": 0}, - "bottom=true,facing=north,top=true": {"model": "computercraft:block/printer_both_full", "y": 0}, + "bottom=true,facing=north,top=false": {"model": "computercraft:block/printer_bottom_full"}, + "bottom=true,facing=north,top=true": {"model": "computercraft:block/printer_both_full"}, "bottom=true,facing=south,top=false": {"model": "computercraft:block/printer_bottom_full", "y": 180}, "bottom=true,facing=south,top=true": {"model": "computercraft:block/printer_both_full", "y": 180}, "bottom=true,facing=west,top=false": {"model": "computercraft:block/printer_bottom_full", "y": 270}, diff --git a/projects/common/src/generated/resources/assets/computercraft/blockstates/wireless_modem_advanced.json b/projects/common/src/generated/resources/assets/computercraft/blockstates/wireless_modem_advanced.json index 9f029e6da..e70054318 100644 --- a/projects/common/src/generated/resources/assets/computercraft/blockstates/wireless_modem_advanced.json +++ b/projects/common/src/generated/resources/assets/computercraft/blockstates/wireless_modem_advanced.json @@ -1,16 +1,16 @@ { "variants": { - "facing=down,on=false": {"model": "computercraft:block/wireless_modem_advanced_off", "x": 90, "y": 0}, - "facing=down,on=true": {"model": "computercraft:block/wireless_modem_advanced_on", "x": 90, "y": 0}, - "facing=east,on=false": {"model": "computercraft:block/wireless_modem_advanced_off", "x": 0, "y": 90}, - "facing=east,on=true": {"model": "computercraft:block/wireless_modem_advanced_on", "x": 0, "y": 90}, - "facing=north,on=false": {"model": "computercraft:block/wireless_modem_advanced_off", "x": 0, "y": 0}, - "facing=north,on=true": {"model": "computercraft:block/wireless_modem_advanced_on", "x": 0, "y": 0}, - "facing=south,on=false": {"model": "computercraft:block/wireless_modem_advanced_off", "x": 0, "y": 180}, - "facing=south,on=true": {"model": "computercraft:block/wireless_modem_advanced_on", "x": 0, "y": 180}, - "facing=up,on=false": {"model": "computercraft:block/wireless_modem_advanced_off", "x": 270, "y": 0}, - "facing=up,on=true": {"model": "computercraft:block/wireless_modem_advanced_on", "x": 270, "y": 0}, - "facing=west,on=false": {"model": "computercraft:block/wireless_modem_advanced_off", "x": 0, "y": 270}, - "facing=west,on=true": {"model": "computercraft:block/wireless_modem_advanced_on", "x": 0, "y": 270} + "facing=down,on=false": {"model": "computercraft:block/wireless_modem_advanced_off", "x": 90}, + "facing=down,on=true": {"model": "computercraft:block/wireless_modem_advanced_on", "x": 90}, + "facing=east,on=false": {"model": "computercraft:block/wireless_modem_advanced_off", "y": 90}, + "facing=east,on=true": {"model": "computercraft:block/wireless_modem_advanced_on", "y": 90}, + "facing=north,on=false": {"model": "computercraft:block/wireless_modem_advanced_off"}, + "facing=north,on=true": {"model": "computercraft:block/wireless_modem_advanced_on"}, + "facing=south,on=false": {"model": "computercraft:block/wireless_modem_advanced_off", "y": 180}, + "facing=south,on=true": {"model": "computercraft:block/wireless_modem_advanced_on", "y": 180}, + "facing=up,on=false": {"model": "computercraft:block/wireless_modem_advanced_off", "x": 270}, + "facing=up,on=true": {"model": "computercraft:block/wireless_modem_advanced_on", "x": 270}, + "facing=west,on=false": {"model": "computercraft:block/wireless_modem_advanced_off", "y": 270}, + "facing=west,on=true": {"model": "computercraft:block/wireless_modem_advanced_on", "y": 270} } } diff --git a/projects/common/src/generated/resources/assets/computercraft/blockstates/wireless_modem_normal.json b/projects/common/src/generated/resources/assets/computercraft/blockstates/wireless_modem_normal.json index 09623ddbf..6faadf248 100644 --- a/projects/common/src/generated/resources/assets/computercraft/blockstates/wireless_modem_normal.json +++ b/projects/common/src/generated/resources/assets/computercraft/blockstates/wireless_modem_normal.json @@ -1,16 +1,16 @@ { "variants": { - "facing=down,on=false": {"model": "computercraft:block/wireless_modem_normal_off", "x": 90, "y": 0}, - "facing=down,on=true": {"model": "computercraft:block/wireless_modem_normal_on", "x": 90, "y": 0}, - "facing=east,on=false": {"model": "computercraft:block/wireless_modem_normal_off", "x": 0, "y": 90}, - "facing=east,on=true": {"model": "computercraft:block/wireless_modem_normal_on", "x": 0, "y": 90}, - "facing=north,on=false": {"model": "computercraft:block/wireless_modem_normal_off", "x": 0, "y": 0}, - "facing=north,on=true": {"model": "computercraft:block/wireless_modem_normal_on", "x": 0, "y": 0}, - "facing=south,on=false": {"model": "computercraft:block/wireless_modem_normal_off", "x": 0, "y": 180}, - "facing=south,on=true": {"model": "computercraft:block/wireless_modem_normal_on", "x": 0, "y": 180}, - "facing=up,on=false": {"model": "computercraft:block/wireless_modem_normal_off", "x": 270, "y": 0}, - "facing=up,on=true": {"model": "computercraft:block/wireless_modem_normal_on", "x": 270, "y": 0}, - "facing=west,on=false": {"model": "computercraft:block/wireless_modem_normal_off", "x": 0, "y": 270}, - "facing=west,on=true": {"model": "computercraft:block/wireless_modem_normal_on", "x": 0, "y": 270} + "facing=down,on=false": {"model": "computercraft:block/wireless_modem_normal_off", "x": 90}, + "facing=down,on=true": {"model": "computercraft:block/wireless_modem_normal_on", "x": 90}, + "facing=east,on=false": {"model": "computercraft:block/wireless_modem_normal_off", "y": 90}, + "facing=east,on=true": {"model": "computercraft:block/wireless_modem_normal_on", "y": 90}, + "facing=north,on=false": {"model": "computercraft:block/wireless_modem_normal_off"}, + "facing=north,on=true": {"model": "computercraft:block/wireless_modem_normal_on"}, + "facing=south,on=false": {"model": "computercraft:block/wireless_modem_normal_off", "y": 180}, + "facing=south,on=true": {"model": "computercraft:block/wireless_modem_normal_on", "y": 180}, + "facing=up,on=false": {"model": "computercraft:block/wireless_modem_normal_off", "x": 270}, + "facing=up,on=true": {"model": "computercraft:block/wireless_modem_normal_on", "x": 270}, + "facing=west,on=false": {"model": "computercraft:block/wireless_modem_normal_off", "y": 270}, + "facing=west,on=true": {"model": "computercraft:block/wireless_modem_normal_on", "y": 270} } } diff --git a/projects/common/src/main/java/dan200/computercraft/mixin/V1460Mixin.java b/projects/common/src/main/java/dan200/computercraft/mixin/V1460Mixin.java index 5a5f57af9..427d93cd2 100644 --- a/projects/common/src/main/java/dan200/computercraft/mixin/V1460Mixin.java +++ b/projects/common/src/main/java/dan200/computercraft/mixin/V1460Mixin.java @@ -44,7 +44,8 @@ class V1460Mixin { // Disk drives contain a single item schema.register(map, "computercraft:disk_drive", () -> DSL.optionalFields( - "Item", References.ITEM_STACK.in(schema) + "Item", References.ITEM_STACK.in(schema), + "CustomName", References.TEXT_COMPONENT.in(schema) )); } diff --git a/projects/common/src/main/java/dan200/computercraft/shared/CommonHooks.java b/projects/common/src/main/java/dan200/computercraft/shared/CommonHooks.java index 2acdf06ee..d48989ab3 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/CommonHooks.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/CommonHooks.java @@ -13,7 +13,6 @@ import dan200.computercraft.shared.lectern.CustomLecternBlock; import dan200.computercraft.shared.peripheral.monitor.MonitorWatcher; import dan200.computercraft.shared.util.DropConsumer; import dan200.computercraft.shared.util.TickScheduler; -import net.minecraft.core.component.DataComponentType; import net.minecraft.core.component.DataComponents; import net.minecraft.core.registries.Registries; import net.minecraft.network.chat.Component; @@ -29,7 +28,7 @@ import net.minecraft.world.InteractionResult; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.*; -import net.minecraft.world.item.component.TooltipProvider; +import net.minecraft.world.item.component.TooltipDisplay; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.LecternBlock; @@ -170,18 +169,12 @@ public final class CommonHooks { public static void onItemTooltip(ItemStack stack, Item.TooltipContext context, TooltipFlag flags, List out) { var appender = new TooltipAppender(out); - addToTooltip(stack, ModRegistry.DataComponents.PRINTOUT.get(), context, appender, flags); - addToTooltip(stack, ModRegistry.DataComponents.TREASURE_DISK.get(), context, appender, flags); - // Disk and computer IDs require some conditional logic, so we don't bother using TooltipProvider. - - var diskId = stack.get(ModRegistry.DataComponents.DISK_ID.get()); - if (diskId != null && flags.isAdvanced()) diskId.addToTooltip("gui.computercraft.tooltip.disk_id", appender); - - var computerId = stack.get(ModRegistry.DataComponents.COMPUTER_ID.get()); - if (computerId != null && (flags.isAdvanced() || !stack.has(DataComponents.CUSTOM_NAME))) { - computerId.addToTooltip("gui.computercraft.tooltip.computer_id", appender); - } + var display = stack.getOrDefault(DataComponents.TOOLTIP_DISPLAY, TooltipDisplay.DEFAULT); + stack.addToTooltip(ModRegistry.DataComponents.PRINTOUT.get(), context, display, appender, flags); + stack.addToTooltip(ModRegistry.DataComponents.TREASURE_DISK.get(), context, display, appender, flags); + stack.addToTooltip(ModRegistry.DataComponents.COMPUTER_ID.get(), context, display, appender, flags); + stack.addToTooltip(ModRegistry.DataComponents.DISK_ID.get(), context, display, appender, flags); } /** @@ -200,9 +193,4 @@ public final class CommonHooks { out.add(index++, component); } } - - private static void addToTooltip(ItemStack stack, DataComponentType component, Item.TooltipContext context, Consumer out, TooltipFlag flags) { - var provider = stack.get(component); - if (provider != null) provider.addToTooltip(context, out, flags); - } } diff --git a/projects/common/src/main/java/dan200/computercraft/shared/ModRegistry.java b/projects/common/src/main/java/dan200/computercraft/shared/ModRegistry.java index 9b21d0270..db010f211 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/ModRegistry.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/ModRegistry.java @@ -19,7 +19,6 @@ import dan200.computercraft.api.turtle.ITurtleUpgrade; import dan200.computercraft.api.upgrades.UpgradeBase; import dan200.computercraft.api.upgrades.UpgradeData; import dan200.computercraft.api.upgrades.UpgradeType; -import dan200.computercraft.core.util.Colour; import dan200.computercraft.impl.PocketUpgrades; import dan200.computercraft.shared.command.UserLevel; import dan200.computercraft.shared.command.arguments.ComputerArgumentType; @@ -113,7 +112,7 @@ import net.minecraft.resources.ResourceLocation; import net.minecraft.world.flag.FeatureFlags; import net.minecraft.world.inventory.MenuType; import net.minecraft.world.item.*; -import net.minecraft.world.item.component.DyedItemColor; +import net.minecraft.world.item.component.TooltipDisplay; import net.minecraft.world.item.crafting.CustomRecipe; import net.minecraft.world.item.crafting.Recipe; import net.minecraft.world.item.crafting.RecipeSerializer; @@ -274,6 +273,13 @@ public final class ModRegistry { return new Item.Properties(); } + private static Item.Properties dyeableProperties() { + return properties().component( + net.minecraft.core.component.DataComponents.TOOLTIP_DISPLAY, + TooltipDisplay.DEFAULT.withHidden(net.minecraft.core.component.DataComponents.DYED_COLOR, true) + ); + } + private static RegistryEntry register(String name, Function build, Supplier properties) { return REGISTRY.register(name, () -> build.apply(properties.get().setId(ResourceKey.create(Registries.ITEM, ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, name))))); } @@ -282,26 +288,26 @@ public final class ModRegistry { return register(name, build, () -> properties); } - private static RegistryEntry ofBlock(RegistryEntry parent, BiFunction supplier) { - return register(parent.id().getPath(), p -> supplier.apply(parent.get(), p), properties().useBlockDescriptionPrefix()); + private static RegistryEntry ofBlock(RegistryEntry parent, BiFunction supplier, Item.Properties properties) { + return register(parent.id().getPath(), p -> supplier.apply(parent.get(), p), properties.useBlockDescriptionPrefix()); } - public static final RegistryEntry COMPUTER_NORMAL = ofBlock(Blocks.COMPUTER_NORMAL, BlockItem::new); - public static final RegistryEntry COMPUTER_ADVANCED = ofBlock(Blocks.COMPUTER_ADVANCED, BlockItem::new); - public static final RegistryEntry COMPUTER_COMMAND = ofBlock(Blocks.COMPUTER_COMMAND, GameMasterBlockItem::new); + public static final RegistryEntry COMPUTER_NORMAL = ofBlock(Blocks.COMPUTER_NORMAL, BlockItem::new, properties()); + public static final RegistryEntry COMPUTER_ADVANCED = ofBlock(Blocks.COMPUTER_ADVANCED, BlockItem::new, properties()); + public static final RegistryEntry COMPUTER_COMMAND = ofBlock(Blocks.COMPUTER_COMMAND, GameMasterBlockItem::new, properties()); public static final RegistryEntry POCKET_COMPUTER_NORMAL = register("pocket_computer_normal", - p -> new PocketComputerItem(p, ComputerFamily.NORMAL), properties().stacksTo(1)); + p -> new PocketComputerItem(p, ComputerFamily.NORMAL), dyeableProperties()); public static final RegistryEntry POCKET_COMPUTER_ADVANCED = register("pocket_computer_advanced", - p -> new PocketComputerItem(p, ComputerFamily.ADVANCED), properties().stacksTo(1)); + p -> new PocketComputerItem(p, ComputerFamily.ADVANCED), dyeableProperties()); - public static final RegistryEntry TURTLE_NORMAL = ofBlock(Blocks.TURTLE_NORMAL, TurtleItem::new); - public static final RegistryEntry TURTLE_ADVANCED = ofBlock(Blocks.TURTLE_ADVANCED, TurtleItem::new); + public static final RegistryEntry TURTLE_NORMAL = ofBlock(Blocks.TURTLE_NORMAL, TurtleItem::new, dyeableProperties()); + public static final RegistryEntry TURTLE_ADVANCED = ofBlock(Blocks.TURTLE_ADVANCED, TurtleItem::new, dyeableProperties()); public static final RegistryEntry DISK = - register("disk", DiskItem::new, properties().stacksTo(1)); + register("disk", DiskItem::new, dyeableProperties().stacksTo(1)); public static final RegistryEntry TREASURE_DISK = - register("treasure_disk", DiskItem::new, properties().stacksTo(1)); + register("treasure_disk", DiskItem::new, dyeableProperties().stacksTo(1)); private static Item.Properties printoutProperties() { return properties().stacksTo(1).component(DataComponents.PRINTOUT.get(), PrintoutData.EMPTY); @@ -314,15 +320,15 @@ public final class ModRegistry { public static final RegistryEntry PRINTED_BOOK = register("printed_book", PrintoutItem::new, Items::printoutProperties); - public static final RegistryEntry SPEAKER = ofBlock(Blocks.SPEAKER, BlockItem::new); - public static final RegistryEntry DISK_DRIVE = ofBlock(Blocks.DISK_DRIVE, BlockItem::new); - public static final RegistryEntry PRINTER = ofBlock(Blocks.PRINTER, BlockItem::new); - public static final RegistryEntry MONITOR_NORMAL = ofBlock(Blocks.MONITOR_NORMAL, BlockItem::new); - public static final RegistryEntry MONITOR_ADVANCED = ofBlock(Blocks.MONITOR_ADVANCED, BlockItem::new); - public static final RegistryEntry WIRELESS_MODEM_NORMAL = ofBlock(Blocks.WIRELESS_MODEM_NORMAL, BlockItem::new); - public static final RegistryEntry WIRELESS_MODEM_ADVANCED = ofBlock(Blocks.WIRELESS_MODEM_ADVANCED, BlockItem::new); - public static final RegistryEntry WIRED_MODEM_FULL = ofBlock(Blocks.WIRED_MODEM_FULL, BlockItem::new); - public static final RegistryEntry REDSTONE_RELAY = ofBlock(Blocks.REDSTONE_RELAY, BlockItem::new); + public static final RegistryEntry SPEAKER = ofBlock(Blocks.SPEAKER, BlockItem::new, properties()); + public static final RegistryEntry DISK_DRIVE = ofBlock(Blocks.DISK_DRIVE, BlockItem::new, properties()); + public static final RegistryEntry PRINTER = ofBlock(Blocks.PRINTER, BlockItem::new, properties()); + public static final RegistryEntry MONITOR_NORMAL = ofBlock(Blocks.MONITOR_NORMAL, BlockItem::new, properties()); + public static final RegistryEntry MONITOR_ADVANCED = ofBlock(Blocks.MONITOR_ADVANCED, BlockItem::new, properties()); + public static final RegistryEntry WIRELESS_MODEM_NORMAL = ofBlock(Blocks.WIRELESS_MODEM_NORMAL, BlockItem::new, properties()); + public static final RegistryEntry WIRELESS_MODEM_ADVANCED = ofBlock(Blocks.WIRELESS_MODEM_ADVANCED, BlockItem::new, properties()); + public static final RegistryEntry WIRED_MODEM_FULL = ofBlock(Blocks.WIRED_MODEM_FULL, BlockItem::new, properties()); + public static final RegistryEntry REDSTONE_RELAY = ofBlock(Blocks.REDSTONE_RELAY, BlockItem::new, properties()); public static final RegistryEntry CABLE = register("cable", p -> new CableBlockItem.Cable(Blocks.CABLE.get(), p), properties().useBlockDescriptionPrefix()); @@ -340,8 +346,8 @@ public final class ModRegistry { /** * The id of a computer. */ - public static final RegistryEntry> COMPUTER_ID = register("computer_id", b -> b - .persistent(NonNegativeId.CODEC).networkSynchronized(NonNegativeId.STREAM_CODEC) + public static final RegistryEntry> COMPUTER_ID = register("computer_id", b -> b + .persistent(NonNegativeId.Computer.CODEC).networkSynchronized(NonNegativeId.Computer.STREAM_CODEC) ); /** @@ -422,8 +428,8 @@ public final class ModRegistry { /** * The id of a disk. */ - public static final RegistryEntry> DISK_ID = register("disk_id", b -> b - .persistent(NonNegativeId.CODEC).networkSynchronized(NonNegativeId.STREAM_CODEC) + public static final RegistryEntry> DISK_ID = register("disk_id", b -> b + .persistent(NonNegativeId.Disk.CODEC).networkSynchronized(NonNegativeId.Disk.STREAM_CODEC) ); /** @@ -597,8 +603,8 @@ public final class ModRegistry { out.accept(Items.PRINTED_BOOK.get()); out.accept(Items.DISK_DRIVE.get()); - for (var colour = 0; colour < 16; colour++) { - out.accept(DataComponentUtil.createStack(Items.DISK.get(), net.minecraft.core.component.DataComponents.DYED_COLOR, new DyedItemColor(Colour.VALUES[colour].getHex(), false))); + for (var colour : DyeColor.values()) { + out.accept(DataComponentUtil.createDyedStack(Items.DISK.get(), colour.getTextureDiffuseColor())); } }) .build()); diff --git a/projects/common/src/main/java/dan200/computercraft/shared/command/builder/HelpingArgumentBuilder.java b/projects/common/src/main/java/dan200/computercraft/shared/command/builder/HelpingArgumentBuilder.java index 81f60cc0f..aa058c48c 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/command/builder/HelpingArgumentBuilder.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/command/builder/HelpingArgumentBuilder.java @@ -182,10 +182,7 @@ public final class HelpingArgumentBuilder extends LiteralArgumentBuilder s - .withClickEvent(new ClickEvent(ClickEvent.Action.COPY_TO_CLIPBOARD, text)) - .withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.translatable("gui.computercraft.tooltip.copy"))) + .withClickEvent(new ClickEvent.CopyToClipboard(text)) + .withHoverEvent(new HoverEvent.ShowText(Component.translatable("gui.computercraft.tooltip.copy"))) ); } diff --git a/projects/common/src/main/java/dan200/computercraft/shared/common/ClearColourRecipe.java b/projects/common/src/main/java/dan200/computercraft/shared/common/ClearColourRecipe.java index 98436c763..7b7b2a8fc 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/common/ClearColourRecipe.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/common/ClearColourRecipe.java @@ -6,7 +6,6 @@ package dan200.computercraft.shared.common; import dan200.computercraft.api.ComputerCraftTags; import dan200.computercraft.shared.ModRegistry; -import dan200.computercraft.shared.util.DataComponentUtil; import net.minecraft.core.HolderLookup; import net.minecraft.core.NonNullList; import net.minecraft.core.component.DataComponents; @@ -60,7 +59,9 @@ public final class ClearColourRecipe extends CustomRecipe { if (colourable.isEmpty()) return ItemStack.EMPTY; - return DataComponentUtil.createResult(colourable, DataComponents.DYED_COLOR, null); + var result = colourable.copyWithCount(1); + result.remove(DataComponents.DYED_COLOR); + return result; } @Override diff --git a/projects/common/src/main/java/dan200/computercraft/shared/common/ColourableRecipe.java b/projects/common/src/main/java/dan200/computercraft/shared/common/ColourableRecipe.java index cda3a7e21..ed01b3821 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/common/ColourableRecipe.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/common/ColourableRecipe.java @@ -10,9 +10,7 @@ import dan200.computercraft.shared.util.ColourTracker; import dan200.computercraft.shared.util.ColourUtils; import dan200.computercraft.shared.util.DataComponentUtil; import net.minecraft.core.HolderLookup; -import net.minecraft.core.component.DataComponents; import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.component.DyedItemColor; import net.minecraft.world.item.crafting.CraftingBookCategory; import net.minecraft.world.item.crafting.CraftingInput; import net.minecraft.world.item.crafting.CustomRecipe; @@ -66,8 +64,7 @@ public final class ColourableRecipe extends CustomRecipe { return colourable.isEmpty() ? ItemStack.EMPTY - : DataComponentUtil.createResult(colourable, DataComponents.DYED_COLOR, new DyedItemColor(tracker.getColour(), false)); - + : DataComponentUtil.setDyeColour(colourable.copyWithCount(1), tracker.getColour()); } @Override diff --git a/projects/common/src/main/java/dan200/computercraft/shared/common/HorizontalContainerBlock.java b/projects/common/src/main/java/dan200/computercraft/shared/common/HorizontalContainerBlock.java index 99cb268a3..f08758c1b 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/common/HorizontalContainerBlock.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/common/HorizontalContainerBlock.java @@ -6,7 +6,6 @@ package dan200.computercraft.shared.common; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; -import net.minecraft.world.Containers; import net.minecraft.world.InteractionResult; import net.minecraft.world.entity.player.Player; import net.minecraft.world.inventory.AbstractContainerMenu; @@ -60,12 +59,6 @@ public abstract class HorizontalContainerBlock extends BaseEntityBlock { return InteractionResult.CONSUME; } - @Override - protected final void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean isMoving) { - Containers.dropContentsOnDestroy(state, newState, level, pos); - super.onRemove(state, level, pos, newState, isMoving); - } - @Override protected final boolean hasAnalogOutputSignal(BlockState pState) { return true; diff --git a/projects/common/src/main/java/dan200/computercraft/shared/computer/blocks/AbstractComputerBlockEntity.java b/projects/common/src/main/java/dan200/computercraft/shared/computer/blocks/AbstractComputerBlockEntity.java index 4b7d22b00..d93a782d7 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/computer/blocks/AbstractComputerBlockEntity.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/computer/blocks/AbstractComputerBlockEntity.java @@ -21,10 +21,10 @@ import dan200.computercraft.shared.util.*; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.core.HolderLookup; +import net.minecraft.core.component.DataComponentGetter; import net.minecraft.core.component.DataComponentMap; import net.minecraft.core.component.DataComponents; import net.minecraft.nbt.CompoundTag; -import net.minecraft.nbt.Tag; import net.minecraft.network.chat.Component; import net.minecraft.world.Container; import net.minecraft.world.LockCode; @@ -166,16 +166,16 @@ public abstract class AbstractComputerBlockEntity extends BlockEntity implements protected void loadServer(CompoundTag nbt, HolderLookup.Provider registries) { // Load ID, label and power state - computerID = nbt.contains(NBT_ID) ? nbt.getInt(NBT_ID) : -1; - label = nbt.contains(NBT_LABEL) ? nbt.getString(NBT_LABEL) : null; - storageCapacity = nbt.contains(NBT_CAPACITY, Tag.TAG_ANY_NUMERIC) ? nbt.getLong(NBT_CAPACITY) : -1; - on = startOn = nbt.getBoolean(NBT_ON); + computerID = nbt.getIntOr(NBT_ID, -1); + label = nbt.getStringOr(NBT_LABEL, null); + storageCapacity = nbt.getLongOr(NBT_CAPACITY, -1); + on = startOn = nbt.getBooleanOr(NBT_ON, false); lockCode = LockCode.fromTag(nbt, registries); } @Override - protected void applyImplicitComponents(DataComponentInput component) { + protected void applyImplicitComponents(DataComponentGetter component) { super.applyImplicitComponents(component); label = DataComponentUtil.getCustomName(component.get(DataComponents.CUSTOM_NAME)); computerID = NonNegativeId.getId(component.get(ModRegistry.DataComponents.COMPUTER_ID.get())); @@ -197,7 +197,7 @@ public abstract class AbstractComputerBlockEntity extends BlockEntity implements */ @OverridingMethodsMustInvokeSuper protected void collectSafeComponents(DataComponentMap.Builder builder) { - builder.set(ModRegistry.DataComponents.COMPUTER_ID.get(), NonNegativeId.of(computerID)); + builder.set(ModRegistry.DataComponents.COMPUTER_ID.get(), computerID < 0 ? null : new NonNegativeId.Computer(computerID)); builder.set(DataComponents.CUSTOM_NAME, label == null ? null : Component.literal(label)); builder.set(ModRegistry.DataComponents.STORAGE_CAPACITY.get(), storageCapacity > 0 ? new StorageCapacity(storageCapacity) : null); } diff --git a/projects/common/src/main/java/dan200/computercraft/shared/computer/terminal/NetworkedTerminal.java b/projects/common/src/main/java/dan200/computercraft/shared/computer/terminal/NetworkedTerminal.java index e4b670ad3..2ce63f807 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/computer/terminal/NetworkedTerminal.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/computer/terminal/NetworkedTerminal.java @@ -96,30 +96,30 @@ public class NetworkedTerminal extends Terminal { } public synchronized void readFromNBT(CompoundTag nbt) { - cursorX = nbt.getInt("term_cursorX"); - cursorY = nbt.getInt("term_cursorY"); - cursorBlink = nbt.getBoolean("term_cursorBlink"); - cursorColour = nbt.getInt("term_textColour"); - cursorBackgroundColour = nbt.getInt("term_bgColour"); + cursorX = nbt.getIntOr("term_cursorX", 0); + cursorY = nbt.getIntOr("term_cursorY", 0); + cursorBlink = nbt.getBooleanOr("term_cursorBlink", false); + cursorColour = nbt.getIntOr("term_textColour", 0); + cursorBackgroundColour = nbt.getIntOr("term_bgColour", 0); for (var n = 0; n < height; n++) { text[n].fill(' '); if (nbt.contains("term_text_" + n)) { - text[n].write(nbt.getString("term_text_" + n)); + text[n].write(nbt.getStringOr("term_text_" + n, "")); } textColour[n].fill(BASE_16.charAt(cursorColour)); if (nbt.contains("term_textColour_" + n)) { - textColour[n].write(nbt.getString("term_textColour_" + n)); + textColour[n].write(nbt.getStringOr("term_textColour_" + n, "")); } backgroundColour[n].fill(BASE_16.charAt(cursorBackgroundColour)); if (nbt.contains("term_textBgColour_" + n)) { - backgroundColour[n].write(nbt.getString("term_textBgColour_" + n)); + backgroundColour[n].write(nbt.getStringOr("term_textBgColour_" + n, "")); } } if (nbt.contains("term_palette")) { - var rgb8 = nbt.getIntArray("term_palette"); - if (rgb8.length == Palette.PALETTE_SIZE) { + var rgb8 = nbt.getIntArray("term_palette").orElse(null); + if (rgb8 != null && rgb8.length == Palette.PALETTE_SIZE) { for (var i = 0; i < Palette.PALETTE_SIZE; i++) { var colours = Palette.decodeRGB8(rgb8[i]); palette.setColour(i, colours[0], colours[1], colours[2]); diff --git a/projects/common/src/main/java/dan200/computercraft/shared/details/ItemDetails.java b/projects/common/src/main/java/dan200/computercraft/shared/details/ItemDetails.java index dfcfd8caa..001cff0f0 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/details/ItemDetails.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/details/ItemDetails.java @@ -61,7 +61,7 @@ public class ItemDetails { if (!enchants.isEmpty()) data.put("enchantments", enchants); var unbreakable = stack.get(DataComponents.UNBREAKABLE); - if (unbreakable != null && unbreakable.showInTooltip()) data.put("unbreakable", true); + if (unbreakable != null) data.put("unbreakable", true); } /** diff --git a/projects/common/src/main/java/dan200/computercraft/shared/lectern/CustomLecternBlock.java b/projects/common/src/main/java/dan200/computercraft/shared/lectern/CustomLecternBlock.java index 696fe73a3..b06782d5b 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/lectern/CustomLecternBlock.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/lectern/CustomLecternBlock.java @@ -112,18 +112,7 @@ public class CustomLecternBlock extends LecternBlock { super.tick(state, level, pos, random); } - @Override - public void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean isMoving) { - if (state.is(newState.getBlock())) return; - - if (level.getBlockEntity(pos) instanceof CustomLecternBlockEntity lectern) { - dropItem(level, pos, state, lectern.getItem().copy()); - } - - super.onRemove(state, level, pos, newState, isMoving); - } - - private static void dropItem(Level level, BlockPos pos, BlockState state, ItemStack stack) { + static void dropItem(Level level, BlockPos pos, BlockState state, ItemStack stack) { if (stack.isEmpty()) return; var direction = state.getValue(FACING); diff --git a/projects/common/src/main/java/dan200/computercraft/shared/lectern/CustomLecternBlockEntity.java b/projects/common/src/main/java/dan200/computercraft/shared/lectern/CustomLecternBlockEntity.java index f49f1d7c4..e968d3964 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/lectern/CustomLecternBlockEntity.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/lectern/CustomLecternBlockEntity.java @@ -16,7 +16,6 @@ import dan200.computercraft.shared.util.BlockEntityHelpers; import net.minecraft.core.BlockPos; import net.minecraft.core.HolderLookup; import net.minecraft.nbt.CompoundTag; -import net.minecraft.nbt.Tag; import net.minecraft.network.protocol.Packet; import net.minecraft.network.protocol.game.ClientGamePacketListener; import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket; @@ -34,6 +33,8 @@ import net.minecraft.world.level.block.state.BlockState; import java.util.AbstractList; import java.util.List; +import static dan200.computercraft.shared.lectern.CustomLecternBlock.dropItem; + /** * The block entity for our {@link CustomLecternBlock}. * @@ -100,12 +101,17 @@ public final class CustomLecternBlockEntity extends BlockEntity { if (getLevel() != null) LecternBlock.signalPageChange(getLevel(), getBlockPos(), getBlockState()); } + @Override + public void preRemoveSideEffects(BlockPos pos, BlockState state) { + if (level != null) dropItem(level, pos, state, getItem().copy()); + } + @Override public void loadAdditional(CompoundTag tag, HolderLookup.Provider registries) { super.loadAdditional(tag, registries); - item = tag.contains(NBT_ITEM, Tag.TAG_COMPOUND) ? ItemStack.parseOptional(registries, tag.getCompound(NBT_ITEM)) : ItemStack.EMPTY; - page = tag.getInt(NBT_PAGE); + item = tag.getCompound(NBT_ITEM).flatMap(x -> ItemStack.parse(registries, x)).orElse(ItemStack.EMPTY); + page = tag.getIntOr(NBT_PAGE, 0); itemChanged(); } diff --git a/projects/common/src/main/java/dan200/computercraft/shared/media/MountMedia.java b/projects/common/src/main/java/dan200/computercraft/shared/media/MountMedia.java index 677d61242..b80b93741 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/media/MountMedia.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/media/MountMedia.java @@ -10,6 +10,7 @@ import dan200.computercraft.api.media.IMedia; import dan200.computercraft.shared.ModRegistry; import dan200.computercraft.shared.config.ConfigSpec; import dan200.computercraft.shared.util.DataComponentUtil; +import dan200.computercraft.shared.util.IDAssigner; import dan200.computercraft.shared.util.NonNegativeId; import dan200.computercraft.shared.util.StorageCapacity; import net.minecraft.core.HolderLookup; @@ -18,25 +19,28 @@ import net.minecraft.server.level.ServerLevel; import net.minecraft.world.item.ItemStack; import org.jspecify.annotations.Nullable; +import java.util.function.IntFunction; import java.util.function.Supplier; /** * Media that provides a {@link Mount}. + * + * @param The type of media (disk/computer) we're mounting. */ -public final class MountMedia implements IMedia { +public final class MountMedia implements IMedia { /** * A {@link MountMedia} implementation for {@linkplain ModRegistry.DataComponents#COMPUTER_ID computers}. */ - public static final IMedia COMPUTER = new MountMedia("computer", ModRegistry.DataComponents.COMPUTER_ID, false, ConfigSpec.computerSpaceLimit); + public static final IMedia COMPUTER = new MountMedia<>(IDAssigner.COMPUTER, ModRegistry.DataComponents.COMPUTER_ID, null, ConfigSpec.computerSpaceLimit); /** * A {@link MountMedia} implementation for {@linkplain ModRegistry.Items#DISK disks}. */ - public static final IMedia DISK = new MountMedia("disk", ModRegistry.DataComponents.DISK_ID, true, ConfigSpec.floppySpaceLimit); + public static final IMedia DISK = new MountMedia<>("disk", ModRegistry.DataComponents.DISK_ID, NonNegativeId.Disk::new, ConfigSpec.floppySpaceLimit); private final String subPath; - private final Supplier> id; - private final boolean createId; + private final Supplier> id; + private final @Nullable IntFunction createId; private final Supplier defaultCapacity; /** @@ -49,8 +53,8 @@ public final class MountMedia implements IMedia { */ public MountMedia( String subPath, - Supplier> id, - boolean createId, + Supplier> id, + @Nullable IntFunction createId, Supplier defaultCapacity ) { this.subPath = subPath; @@ -72,8 +76,8 @@ public final class MountMedia implements IMedia { @Override public @Nullable Mount createDataMount(ItemStack stack, ServerLevel level) { - var id = createId - ? NonNegativeId.getOrCreate(level.getServer(), stack, this.id.get(), subPath) + var id = createId != null + ? NonNegativeId.getOrCreate(level.getServer(), stack, this.id.get(), createId, subPath) : NonNegativeId.getId(stack.get(this.id.get())); if (id < 0) return null; diff --git a/projects/common/src/main/java/dan200/computercraft/shared/media/PrintoutMenu.java b/projects/common/src/main/java/dan200/computercraft/shared/media/PrintoutMenu.java index fb6960394..0205fdbef 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/media/PrintoutMenu.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/media/PrintoutMenu.java @@ -81,7 +81,7 @@ public class PrintoutMenu extends AbstractContainerMenu { var currentItem = currentStack.getItem(); var slot = switch (hand) { - case MAIN_HAND -> player.getInventory().selected; + case MAIN_HAND -> player.getInventory().getSelectedSlot(); case OFF_HAND -> Inventory.SLOT_OFFHAND; }; return new PrintoutMenu( diff --git a/projects/common/src/main/java/dan200/computercraft/shared/media/items/DiskItem.java b/projects/common/src/main/java/dan200/computercraft/shared/media/items/DiskItem.java index db7219e38..2113b02e8 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/media/items/DiskItem.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/media/items/DiskItem.java @@ -9,6 +9,7 @@ import net.minecraft.world.InteractionResult; import net.minecraft.world.item.Item; import net.minecraft.world.item.context.UseOnContext; + /** * An item that can be shift-right-clicked into a {@link DiskDriveBlock}. */ diff --git a/projects/common/src/main/java/dan200/computercraft/shared/media/items/PrintoutData.java b/projects/common/src/main/java/dan200/computercraft/shared/media/items/PrintoutData.java index bb650af33..24c893df1 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/media/items/PrintoutData.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/media/items/PrintoutData.java @@ -11,6 +11,7 @@ import dan200.computercraft.api.media.PrintoutContents; import dan200.computercraft.core.terminal.Terminal; import dan200.computercraft.shared.ModRegistry; import io.netty.buffer.ByteBuf; +import net.minecraft.core.component.DataComponentGetter; import net.minecraft.core.component.DataComponentHolder; import net.minecraft.network.chat.Component; import net.minecraft.network.codec.ByteBufCodecs; @@ -77,7 +78,7 @@ public record PrintoutData(String title, List lines) implements PrintoutCo ); @Override - public void addToTooltip(Item.TooltipContext context, Consumer out, TooltipFlag flag) { + public void addToTooltip(Item.TooltipContext context, Consumer out, TooltipFlag flag, DataComponentGetter components) { if (!title().isEmpty()) out.accept(Component.literal(title())); } diff --git a/projects/common/src/main/java/dan200/computercraft/shared/media/items/PrintoutItem.java b/projects/common/src/main/java/dan200/computercraft/shared/media/items/PrintoutItem.java index 4f476e83f..3700ea009 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/media/items/PrintoutItem.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/media/items/PrintoutItem.java @@ -14,6 +14,7 @@ import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.Item; import net.minecraft.world.level.Level; + public class PrintoutItem extends Item { public PrintoutItem(Properties settings) { super(settings); diff --git a/projects/common/src/main/java/dan200/computercraft/shared/media/items/TreasureDisk.java b/projects/common/src/main/java/dan200/computercraft/shared/media/items/TreasureDisk.java index 40af4d094..42c5bf0dd 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/media/items/TreasureDisk.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/media/items/TreasureDisk.java @@ -7,6 +7,7 @@ package dan200.computercraft.shared.media.items; import com.mojang.serialization.Codec; import com.mojang.serialization.codecs.RecordCodecBuilder; import dan200.computercraft.shared.ModRegistry; +import net.minecraft.core.component.DataComponentGetter; import net.minecraft.core.component.DataComponentHolder; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.chat.Component; @@ -44,7 +45,7 @@ public record TreasureDisk(String name, String path) implements TooltipProvider } @Override - public void addToTooltip(Item.TooltipContext context, Consumer out, TooltipFlag flags) { + public void addToTooltip(Item.TooltipContext context, Consumer out, TooltipFlag flags, DataComponentGetter components) { out.accept(Component.literal(name())); } } diff --git a/projects/common/src/main/java/dan200/computercraft/shared/media/recipes/DiskRecipe.java b/projects/common/src/main/java/dan200/computercraft/shared/media/recipes/DiskRecipe.java index a54067e92..baa8be346 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/media/recipes/DiskRecipe.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/media/recipes/DiskRecipe.java @@ -15,7 +15,6 @@ import dan200.computercraft.shared.util.ColourTracker; import dan200.computercraft.shared.util.ColourUtils; import dan200.computercraft.shared.util.DataComponentUtil; import net.minecraft.core.HolderLookup; -import net.minecraft.core.component.DataComponents; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.network.codec.ByteBufCodecs; @@ -24,7 +23,6 @@ import net.minecraft.world.entity.player.StackedItemContents; import net.minecraft.world.item.DyeColor; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.Items; -import net.minecraft.world.item.component.DyedItemColor; import net.minecraft.world.item.crafting.CraftingInput; import net.minecraft.world.item.crafting.Ingredient; import net.minecraft.world.item.crafting.PlacementInfo; @@ -70,15 +68,10 @@ public class DiskRecipe extends AbstractCraftingRecipe { var dyes = ColourUtils.DYES; List out = new ArrayList<>(dyes.size()); for (var i = 0; i < dyes.size(); i++) { - var tracker = new ColourTracker(); - tracker.addColour(DyeColor.byId(i)); - out.add(new ShapelessCraftingRecipeDisplay( Stream.concat(ingredients.stream(), Stream.of(Ingredient.of(BuiltInRegistries.ITEM.getOrThrow(dyes.get(i))))) .map(Ingredient::display).toList(), - new SlotDisplay.ItemStackSlotDisplay(DataComponentUtil.createStack( - ModRegistry.Items.DISK.get(), DataComponents.DYED_COLOR, new DyedItemColor(tracker.getColour(), false) - )), + new SlotDisplay.ItemStackSlotDisplay(DataComponentUtil.createDyedStack(ModRegistry.Items.DISK.get(), DyeColor.byId(i).getTextureDiffuseColor())), new SlotDisplay.ItemSlotDisplay(Items.CRAFTING_TABLE) )); } @@ -114,7 +107,7 @@ public class DiskRecipe extends AbstractCraftingRecipe { if (dye != null) tracker.addColour(dye); } - return DataComponentUtil.createStack(ModRegistry.Items.DISK.get(), DataComponents.DYED_COLOR, new DyedItemColor(tracker.hasColour() ? tracker.getColour() : Colour.BLUE.getHex(), false)); + return DataComponentUtil.createDyedStack(ModRegistry.Items.DISK.get(), tracker.getColourOr(Colour.BLUE.getHex())); } @Override diff --git a/projects/common/src/main/java/dan200/computercraft/shared/peripheral/diskdrive/DiskDriveBlockEntity.java b/projects/common/src/main/java/dan200/computercraft/shared/peripheral/diskdrive/DiskDriveBlockEntity.java index fc54a8b7d..8af9f9227 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/peripheral/diskdrive/DiskDriveBlockEntity.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/peripheral/diskdrive/DiskDriveBlockEntity.java @@ -116,7 +116,7 @@ public final class DiskDriveBlockEntity extends AbstractContainerBlockEntity imp @Override public void loadAdditional(CompoundTag nbt, HolderLookup.Provider registries) { super.loadAdditional(nbt, registries); - setDiskStack(nbt.contains(NBT_ITEM) ? ItemStack.parseOptional(registries, nbt.getCompound(NBT_ITEM)) : ItemStack.EMPTY); + setDiskStack(nbt.getCompound(NBT_ITEM).flatMap(x -> ItemStack.parse(registries, x)).orElse(ItemStack.EMPTY)); } @Override diff --git a/projects/common/src/main/java/dan200/computercraft/shared/peripheral/modem/wired/WiredModemLocalPeripheral.java b/projects/common/src/main/java/dan200/computercraft/shared/peripheral/modem/wired/WiredModemLocalPeripheral.java index 965a4cc63..29f129106 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/peripheral/modem/wired/WiredModemLocalPeripheral.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/peripheral/modem/wired/WiredModemLocalPeripheral.java @@ -12,7 +12,6 @@ import dan200.computercraft.shared.platform.ComponentAccess; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.nbt.CompoundTag; -import net.minecraft.nbt.Tag; import net.minecraft.world.level.Level; import org.jspecify.annotations.Nullable; @@ -104,11 +103,8 @@ public final class WiredModemLocalPeripheral { } public void read(CompoundTag tag, String suffix) { - id = tag.contains(NBT_PERIPHERAL_ID + suffix, Tag.TAG_ANY_NUMERIC) - ? tag.getInt(NBT_PERIPHERAL_ID + suffix) : -1; - - type = tag.contains(NBT_PERIPHERAL_TYPE + suffix, Tag.TAG_STRING) - ? tag.getString(NBT_PERIPHERAL_TYPE + suffix) : null; + id = tag.getIntOr(NBT_PERIPHERAL_ID + suffix, -1); + type = tag.getStringOr(NBT_PERIPHERAL_TYPE + suffix, null); } @Nullable diff --git a/projects/common/src/main/java/dan200/computercraft/shared/peripheral/monitor/MonitorBlock.java b/projects/common/src/main/java/dan200/computercraft/shared/peripheral/monitor/MonitorBlock.java index 162ad3318..7c3ca4df6 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/peripheral/monitor/MonitorBlock.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/peripheral/monitor/MonitorBlock.java @@ -83,15 +83,6 @@ public class MonitorBlock extends HorizontalDirectionalBlock implements EntityBl .setValue(ORIENTATION, orientation); } - @Override - protected final void onRemove(BlockState block, Level world, BlockPos pos, BlockState replace, boolean bool) { - if (block.getBlock() == replace.getBlock()) return; - - var tile = world.getBlockEntity(pos); - super.onRemove(block, world, pos, replace, bool); - if (tile instanceof MonitorBlockEntity generic) generic.destroy(); - } - @Override protected void tick(BlockState state, ServerLevel world, BlockPos pos, RandomSource rand) { var te = world.getBlockEntity(pos); diff --git a/projects/common/src/main/java/dan200/computercraft/shared/peripheral/monitor/MonitorBlockEntity.java b/projects/common/src/main/java/dan200/computercraft/shared/peripheral/monitor/MonitorBlockEntity.java index 1e8e94d96..a3e2c9d88 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/peripheral/monitor/MonitorBlockEntity.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/peripheral/monitor/MonitorBlockEntity.java @@ -57,6 +57,12 @@ public class MonitorBlockEntity extends BlockEntity { private boolean needsUpdate = false; private boolean needsValidating = false; + /** + * Whether this monitor is in the process of being removed (see {@link #preRemoveSideEffects(BlockPos, BlockState)}, + * and so should be ignored. + */ + private boolean isRemoving = false; + // MonitorWatcher state. boolean enqueued; @Nullable @@ -82,13 +88,16 @@ public class MonitorBlockEntity extends BlockEntity { @Override public void clearRemoved() { super.clearRemoved(); + isRemoving = false; needsValidating = true; // Same, tbh TickScheduler.schedule(tickToken); } - void destroy() { - // TODO: Call this before using the block - if (!getLevel().isClientSide) contractNeighbours(); + @Override + public void preRemoveSideEffects(BlockPos blockPos, BlockState blockState) { + super.preRemoveSideEffects(blockPos, blockState); + isRemoving = true; + if (level != null && !getLevel().isClientSide) contractNeighbours(); } @Override @@ -113,10 +122,10 @@ public class MonitorBlockEntity extends BlockEntity { var oldXIndex = xIndex; var oldYIndex = yIndex; - xIndex = nbt.getInt(NBT_X); - yIndex = nbt.getInt(NBT_Y); - width = nbt.getInt(NBT_WIDTH); - height = nbt.getInt(NBT_HEIGHT); + xIndex = nbt.getIntOr(NBT_X, 0); + yIndex = nbt.getIntOr(NBT_Y, 0); + width = nbt.getIntOr(NBT_WIDTH, 1); + height = nbt.getIntOr(NBT_HEIGHT, 1); if (level != null && level.isClientSide) onClientLoad(oldXIndex, oldYIndex); } @@ -245,13 +254,11 @@ public class MonitorBlockEntity extends BlockEntity { public Direction getDirection() { // Ensure we're actually a monitor block. This _should_ always be the case, but sometimes there's // fun problems with the block being missing on the client. - var state = getBlockState(); - return state.hasProperty(MonitorBlock.FACING) ? state.getValue(MonitorBlock.FACING) : Direction.NORTH; + return getBlockState().getValueOrElse(MonitorBlock.FACING, Direction.NORTH); } public Direction getOrientation() { - var state = getBlockState(); - return state.hasProperty(MonitorBlock.ORIENTATION) ? state.getValue(MonitorBlock.ORIENTATION) : Direction.NORTH; + return getBlockState().getValueOrElse(MonitorBlock.ORIENTATION, Direction.NORTH); } public Direction getFront() { @@ -286,7 +293,10 @@ public class MonitorBlockEntity extends BlockEntity { } boolean isCompatible(MonitorBlockEntity other) { - return advanced == other.advanced && getOrientation() == other.getOrientation() && getDirection() == other.getDirection(); + return !other.isRemoved() && !other.isRemoving + && advanced == other.advanced + && getOrientation() == other.getOrientation() + && getDirection() == other.getDirection(); } /** @@ -303,8 +313,7 @@ public class MonitorBlockEntity extends BlockEntity { var world = getLevel(); if (world == null || !world.isLoaded(pos)) return MonitorState.UNLOADED; - var tile = world.getBlockEntity(pos); - if (!(tile instanceof MonitorBlockEntity monitor)) return MonitorState.MISSING; + if (!(world.getBlockEntity(pos) instanceof MonitorBlockEntity monitor)) return MonitorState.MISSING; return isCompatible(monitor) ? MonitorState.present(monitor) : MonitorState.MISSING; } diff --git a/projects/common/src/main/java/dan200/computercraft/shared/peripheral/printer/PrinterBlockEntity.java b/projects/common/src/main/java/dan200/computercraft/shared/peripheral/printer/PrinterBlockEntity.java index da5f527fc..f6836ac58 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/peripheral/printer/PrinterBlockEntity.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/peripheral/printer/PrinterBlockEntity.java @@ -60,8 +60,8 @@ public final class PrinterBlockEntity extends AbstractContainerBlockEntity imple // Read page synchronized (page) { - printing = nbt.getBoolean(NBT_PRINTING); - pageTitle = nbt.getString(NBT_PAGE_TITLE); + printing = nbt.getBooleanOr(NBT_PRINTING, false); + pageTitle = nbt.getStringOr(NBT_PAGE_TITLE, ""); page.readFromNBT(nbt); } diff --git a/projects/common/src/main/java/dan200/computercraft/shared/pocket/apis/PocketAPI.java b/projects/common/src/main/java/dan200/computercraft/shared/pocket/apis/PocketAPI.java index b09bea1ac..a1117174f 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/pocket/apis/PocketAPI.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/pocket/apis/PocketAPI.java @@ -10,7 +10,8 @@ import dan200.computercraft.api.pocket.IPocketAccess; import dan200.computercraft.api.pocket.IPocketUpgrade; import dan200.computercraft.api.upgrades.UpgradeData; import dan200.computercraft.impl.PocketUpgrades; -import net.minecraft.core.NonNullList; +import net.minecraft.world.Container; +import net.minecraft.world.entity.player.Inventory; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; import org.jspecify.annotations.Nullable; @@ -69,10 +70,12 @@ public class PocketAPI implements ILuaAPI { // Attempt to find the upgrade, starting in the main segment, and then looking in the opposite // one. We start from the position the item is currently in and loop round to the start. - var newUpgrade = findUpgrade(inventory.items, inventory.selected, previousUpgrade); - if (newUpgrade == null) { - newUpgrade = findUpgrade(inventory.offhand, 0, previousUpgrade); + UpgradeData newUpgrade = null; + for (var i = 0; i < Inventory.INVENTORY_SIZE; i++) { + newUpgrade = findUpgrade(inventory, (i + inventory.getSelectedSlot()) % Inventory.INVENTORY_SIZE, previousUpgrade); + if (newUpgrade != null) break; } + if (newUpgrade == null) newUpgrade = findUpgrade(inventory, Inventory.SLOT_OFFHAND, previousUpgrade); if (newUpgrade == null) return new Object[]{ false, "Cannot find a valid upgrade" }; // Remove the current upgrade @@ -113,21 +116,18 @@ public class PocketAPI implements ILuaAPI { } } - private @Nullable UpgradeData findUpgrade(NonNullList inv, int start, @Nullable UpgradeData previous) { - for (var i = 0; i < inv.size(); i++) { - var invStack = inv.get((i + start) % inv.size()); - if (!invStack.isEmpty()) { - var newUpgrade = PocketUpgrades.instance().get(pocket.getLevel().registryAccess(), invStack); + private @Nullable UpgradeData findUpgrade(Container inv, int slot, @Nullable UpgradeData previous) { + var invStack = inv.getItem(slot); + if (invStack.isEmpty()) return null; - if (newUpgrade != null && !Objects.equals(newUpgrade, previous)) { - // Consume an item from this stack and exit the loop - invStack = invStack.copy(); - invStack.shrink(1); - inv.set((i + start) % inv.size(), invStack.isEmpty() ? ItemStack.EMPTY : invStack); + var newUpgrade = PocketUpgrades.instance().get(pocket.getLevel().registryAccess(), invStack); + if (newUpgrade != null && !Objects.equals(newUpgrade, previous)) { + // Consume an item from this stack and exit the loop + invStack = invStack.copy(); + invStack.shrink(1); + inv.setItem(slot, invStack.isEmpty() ? ItemStack.EMPTY : invStack); - return newUpgrade; - } - } + return newUpgrade; } return null; diff --git a/projects/common/src/main/java/dan200/computercraft/shared/pocket/core/PocketBrain.java b/projects/common/src/main/java/dan200/computercraft/shared/pocket/core/PocketBrain.java index 96dc50891..b619309f6 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/pocket/core/PocketBrain.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/pocket/core/PocketBrain.java @@ -12,12 +12,12 @@ import dan200.computercraft.shared.computer.core.ServerComputer; import dan200.computercraft.shared.network.client.PocketComputerDataMessage; import dan200.computercraft.shared.network.server.ServerNetworking; import dan200.computercraft.shared.pocket.items.PocketComputerItem; +import dan200.computercraft.shared.util.DataComponentUtil; import net.minecraft.core.component.DataComponentPatch; import net.minecraft.core.component.DataComponents; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.entity.Entity; import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.component.DyedItemColor; import net.minecraft.world.phys.Vec3; import org.jspecify.annotations.Nullable; @@ -93,7 +93,11 @@ public final class PocketBrain implements IPocketAccess { if (!dirty) return false; this.dirty = false; - stack.set(DataComponents.DYED_COLOR, colour == -1 ? null : new DyedItemColor(colour, false)); + if (colour == -1) { + stack.remove(DataComponents.DYED_COLOR); + } else { + DataComponentUtil.setDyeColour(stack, colour); + } PocketComputerItem.setUpgrade(stack, upgrade); return true; } diff --git a/projects/common/src/main/java/dan200/computercraft/shared/pocket/core/PocketHolder.java b/projects/common/src/main/java/dan200/computercraft/shared/pocket/core/PocketHolder.java index 2682cae95..6aa1b3787 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/pocket/core/PocketHolder.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/pocket/core/PocketHolder.java @@ -12,6 +12,8 @@ import net.minecraft.core.BlockPos; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EquipmentSlot; +import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.item.ItemEntity; import net.minecraft.world.phys.Vec3; @@ -107,6 +109,24 @@ public sealed interface PocketHolder { } } + /** + * A pocket computer in a {@link LivingEntity}'s slot. + * + * @param entity The current player. + * @param slot The slot the pocket computer is in. + */ + record LivingEntityHolder(LivingEntity entity, EquipmentSlot slot) implements EntityHolder { + @Override + public boolean isValid(ServerComputer computer) { + return entity().isAlive() && PocketComputerItem.isServerComputer(computer, entity().getItemBySlot(slot())); + } + + @Override + public void setChanged() { + // TODO: What can we do? + } + } + /** * A pocket computer in an {@link ItemEntity}. * diff --git a/projects/common/src/main/java/dan200/computercraft/shared/pocket/items/PocketComputerItem.java b/projects/common/src/main/java/dan200/computercraft/shared/pocket/items/PocketComputerItem.java index 33790c2d4..d3bf11ecb 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/pocket/items/PocketComputerItem.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/pocket/items/PocketComputerItem.java @@ -31,6 +31,8 @@ import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionResult; import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EquipmentSlot; +import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.item.ItemEntity; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.Item; @@ -102,16 +104,13 @@ public class PocketComputerItem extends Item { } @Override - public void inventoryTick(ItemStack stack, Level world, Entity entity, int compartmentSlot, boolean selected) { - // This (in vanilla at least) is only called for players. Don't bother to handle other entities. - if (world.isClientSide || !(entity instanceof ServerPlayer player)) return; - - // Find the actual slot the item exists in, aborting if it can't be found. - var slot = InventoryUtil.getInventorySlotFromCompartment(player, compartmentSlot, stack); - if (slot < 0) return; - - // If we're in the inventory, create a computer and keep it alive. - tick(stack, new PocketHolder.PlayerHolder(player, slot), false); + public void inventoryTick(ItemStack stack, ServerLevel level, Entity entity, @Nullable EquipmentSlot slot) { + if (entity instanceof ServerPlayer player) { + var invSlot = InventoryUtil.findItemInInventory(player.getInventory(), stack); + if (invSlot != -1) tick(stack, new PocketHolder.PlayerHolder(player, invSlot), false); + } else if (slot != null && entity instanceof LivingEntity living) { + tick(stack, new PocketHolder.LivingEntityHolder(living, slot), true); + } } @ForgeOverride @@ -200,7 +199,7 @@ public class PocketComputerItem extends Item { } } - var computerID = NonNegativeId.getOrCreate(level.getServer(), stack, ModRegistry.DataComponents.COMPUTER_ID.get(), IDAssigner.COMPUTER); + var computerID = NonNegativeId.getOrCreate(level.getServer(), stack, ModRegistry.DataComponents.COMPUTER_ID.get(), NonNegativeId.Computer::new, IDAssigner.COMPUTER); var brain = new PocketBrain( holder, getUpgradeWithData(stack), DyedItemColor.getOrDefault(stack, -1), ServerComputer.properties(computerID, getFamily()) diff --git a/projects/common/src/main/java/dan200/computercraft/shared/turtle/TurtleUtil.java b/projects/common/src/main/java/dan200/computercraft/shared/turtle/TurtleUtil.java index cfacf1e07..f0501269c 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/turtle/TurtleUtil.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/turtle/TurtleUtil.java @@ -7,13 +7,13 @@ package dan200.computercraft.shared.turtle; import dan200.computercraft.api.turtle.ITurtleAccess; import dan200.computercraft.shared.platform.ContainerTransfer; import dan200.computercraft.shared.platform.PlatformHelper; +import dan200.computercraft.shared.turtle.core.TurtlePlayer; import dan200.computercraft.shared.util.DropConsumer; import dan200.computercraft.shared.util.InventoryUtil; import dan200.computercraft.shared.util.WorldUtil; +import net.minecraft.world.Container; import net.minecraft.world.item.ItemStack; -import java.util.function.Function; - public class TurtleUtil { /** * Get a view of the turtle's inventory starting at the currently selected slot. This should be used when @@ -43,6 +43,10 @@ public class TurtleUtil { * @param stack The stack to store. */ public static void storeItemOrDrop(ITurtleAccess turtle, ItemStack stack) { + storeItemOrDrop(turtle, turtle.getInventory(), stack); + } + + private static void storeItemOrDrop(ITurtleAccess turtle, Container container, ItemStack stack) { if (stack.isEmpty()) return; if (turtle.isRemoved()) { WorldUtil.dropItemStack(turtle.getLevel(), turtle.getPosition(), null, stack); @@ -50,18 +54,32 @@ public class TurtleUtil { } // Put the remainder back in the turtle - var remainder = InventoryUtil.storeItemsFromOffset(turtle.getInventory(), stack, turtle.getSelectedSlot()); + var remainder = InventoryUtil.storeItemsFromOffset(container, stack, turtle.getSelectedSlot()); if (remainder.isEmpty()) return; WorldUtil.dropItemStack(turtle.getLevel(), turtle.getPosition(), turtle.getDirection().getOpposite(), remainder); } - public static Function dropConsumer(ITurtleAccess turtle) { - return stack -> turtle.isRemoved() ? stack : InventoryUtil.storeItemsFromOffset(turtle.getInventory(), stack, turtle.getSelectedSlot()); + /** + * Stop a {@link DropConsumer}, and sync the items back to the inventory. + * + * @param turtle The turtle to store drops to. + */ + public static void stopConsuming(ITurtleAccess turtle) { + for (var stack : DropConsumer.stop()) storeItemOrDrop(turtle, stack); } - public static void stopConsuming(ITurtleAccess turtle) { - var direction = turtle.isRemoved() ? null : turtle.getDirection().getOpposite(); - DropConsumer.clearAndDrop(turtle.getLevel(), turtle.getPosition(), direction); + /** + * Stop a {@link DropConsumer}, and sync the items back to the {@link TurtlePlayer} inventory. + *

+ * When using {@link TurtlePlayer#loadInventory(ITurtleAccess)}/{@link TurtlePlayer#unloadInventory(ITurtleAccess)}, + * changes to the turtle's inventory are overridden. This means items must be stored to the player's + * inventory, not the turtle's. + * + * @param turtle The turtle performing this action. + * @param player The turtle player to store items back to. + */ + public static void stopConsumingPlayer(ITurtleAccess turtle, TurtlePlayer player) { + for (var stack : DropConsumer.stop()) storeItemOrDrop(turtle, player.player().getInventory(), stack); } } diff --git a/projects/common/src/main/java/dan200/computercraft/shared/turtle/blocks/TurtleBlock.java b/projects/common/src/main/java/dan200/computercraft/shared/turtle/blocks/TurtleBlock.java index 800a1cf5f..27a58ec53 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/turtle/blocks/TurtleBlock.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/turtle/blocks/TurtleBlock.java @@ -17,7 +17,6 @@ import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.core.component.DataComponents; import net.minecraft.util.RandomSource; -import net.minecraft.world.Containers; import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionResult; import net.minecraft.world.entity.LivingEntity; @@ -122,21 +121,6 @@ public class TurtleBlock extends AbstractComputerBlock implem return super.updateShape(state, level, ticker, pos, side, otherPos, neighborState, randomSource); } - @Override - protected final void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean isMoving) { - if (state.is(newState.getBlock())) return; - - // Most blocks drop items and then remove the BE. However, if a turtle is consuming drops right now, that can - // lead to loops where it tries to insert an item back into the inventory. To prevent this, take a reference to - // the turtle BE now, remove it, and then drop the items. - var turtle = !level.isClientSide && level.getBlockEntity(pos) instanceof TurtleBlockEntity t && !t.hasMoved() - ? t : null; - - super.onRemove(state, level, pos, newState, isMoving); - - if (turtle != null) Containers.dropContents(level, pos, turtle); - } - @Override public void setPlacedBy(Level level, BlockPos pos, BlockState state, @Nullable LivingEntity entity, ItemStack stack) { super.setPlacedBy(level, pos, state, entity, stack); diff --git a/projects/common/src/main/java/dan200/computercraft/shared/turtle/blocks/TurtleBlockEntity.java b/projects/common/src/main/java/dan200/computercraft/shared/turtle/blocks/TurtleBlockEntity.java index 3ae340260..27507d4e5 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/turtle/blocks/TurtleBlockEntity.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/turtle/blocks/TurtleBlockEntity.java @@ -28,6 +28,7 @@ import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.core.HolderLookup; import net.minecraft.core.NonNullList; +import net.minecraft.core.component.DataComponentGetter; import net.minecraft.core.component.DataComponentMap; import net.minecraft.core.component.DataComponents; import net.minecraft.nbt.CompoundTag; @@ -40,6 +41,7 @@ import net.minecraft.world.entity.player.Player; import net.minecraft.world.inventory.AbstractContainerMenu; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.component.DyedItemColor; +import net.minecraft.world.item.component.TooltipDisplay; import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.phys.Vec3; @@ -119,6 +121,11 @@ public class TurtleBlockEntity extends AbstractComputerBlockEntity implements Ba protected void updateBlockState(ComputerState newState) { } + @Override + public void preRemoveSideEffects(BlockPos blockPos, BlockState blockState) { + if (!hasMoved()) super.preRemoveSideEffects(blockPos, blockState); + } + @Override public void neighborChanged() { if (moveState == MoveState.NOT_MOVED) super.neighborChanged(); @@ -157,7 +164,7 @@ public class TurtleBlockEntity extends AbstractComputerBlockEntity implements Ba } @Override - protected void applyImplicitComponents(DataComponentInput component) { + protected void applyImplicitComponents(DataComponentGetter component) { super.applyImplicitComponents(component); var colour = component.get(DataComponents.DYED_COLOR); @@ -173,7 +180,10 @@ public class TurtleBlockEntity extends AbstractComputerBlockEntity implements Ba protected void collectSafeComponents(DataComponentMap.Builder builder) { super.collectSafeComponents(builder); - builder.set(DataComponents.DYED_COLOR, brain.getColour() == -1 ? null : new DyedItemColor(brain.getColour(), false)); + if (brain.getColour() != -1) { + builder.set(DataComponents.DYED_COLOR, new DyedItemColor(brain.getColour())); + builder.set(DataComponents.TOOLTIP_DISPLAY, TooltipDisplay.DEFAULT.withHidden(DataComponents.DYED_COLOR, true)); + } builder.set(ModRegistry.DataComponents.OVERLAY.get(), brain.getOverlay()); builder.set(ModRegistry.DataComponents.FUEL.get(), brain.getFuelLevel()); builder.set(ModRegistry.DataComponents.LEFT_TURTLE_UPGRADE.get(), withPersistedData(brain.getUpgradeWithData(TurtleSide.LEFT))); @@ -300,7 +310,7 @@ public class TurtleBlockEntity extends AbstractComputerBlockEntity implements Ba @Override public void loadClient(CompoundTag nbt, HolderLookup.Provider registries) { super.loadClient(nbt, registries); - label = nbt.contains(NBT_LABEL) ? nbt.getString(NBT_LABEL) : null; + label = nbt.getStringOr(NBT_LABEL, null); brain.readDescription(nbt, registries); } diff --git a/projects/common/src/main/java/dan200/computercraft/shared/turtle/core/TurtleBrain.java b/projects/common/src/main/java/dan200/computercraft/shared/turtle/core/TurtleBrain.java index 396bd7826..73bc9d056 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/turtle/core/TurtleBrain.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/turtle/core/TurtleBrain.java @@ -32,7 +32,6 @@ import net.minecraft.core.HolderLookup; import net.minecraft.core.component.DataComponentPatch; import net.minecraft.core.particles.ParticleTypes; import net.minecraft.nbt.CompoundTag; -import net.minecraft.nbt.Tag; import net.minecraft.server.level.ServerLevel; import net.minecraft.tags.FluidTags; import net.minecraft.world.Container; @@ -133,8 +132,8 @@ public class TurtleBrain implements TurtleAccessInternal { */ private void readCommon(CompoundTag nbt, HolderLookup.Provider registries) { // Read fields - colourHex = nbt.contains(NBT_COLOUR) ? nbt.getInt(NBT_COLOUR) : -1; - fuelLevel = nbt.contains(NBT_FUEL) ? nbt.getInt(NBT_FUEL) : 0; + colourHex = nbt.getIntOr(NBT_COLOUR, -1); + fuelLevel = nbt.getIntOr(NBT_FUEL, 0); overlay = nbt.contains(NBT_OVERLAY) ? NBTUtil.decodeFrom(TurtleOverlay.CODEC, registries, nbt, NBT_OVERLAY) : null; // Read upgrades @@ -145,7 +144,7 @@ public class TurtleBrain implements TurtleAccessInternal { private void writeCommon(CompoundTag nbt, HolderLookup.Provider registries) { nbt.putInt(NBT_FUEL, fuelLevel); if (colourHex != -1) nbt.putInt(NBT_COLOUR, colourHex); - if (overlay != null) NBTUtil.encodeTo(TurtleOverlay.CODEC, registries, nbt, NBT_OVERLAY, overlay); + NBTUtil.encodeTo(TurtleOverlay.CODEC, registries, nbt, NBT_OVERLAY, overlay); // Write upgrades NBTUtil.encodeTo(TurtleUpgrades.instance().upgradeDataCodec(), registries, nbt, NBT_LEFT_UPGRADE, getUpgradeWithData(TurtleSide.LEFT)); @@ -156,14 +155,14 @@ public class TurtleBrain implements TurtleAccessInternal { readCommon(nbt, registries); // Read state - selectedSlot = nbt.getInt(NBT_SLOT); + selectedSlot = nbt.getIntOr(NBT_SLOT, 0); // Read owner - if (nbt.contains("Owner", Tag.TAG_COMPOUND)) { - var owner = nbt.getCompound("Owner"); + var owner = nbt.getCompound("Owner").orElse(null); + if (owner != null) { owningPlayer = new GameProfile( - new UUID(owner.getLong("UpperId"), owner.getLong("LowerId")), - owner.getString("Name") + new UUID(owner.getLongOr("UpperId", 0), owner.getLongOr("LowerId", 0)), + owner.getStringOr("Name", "") ); } else { owningPlayer = null; @@ -191,7 +190,7 @@ public class TurtleBrain implements TurtleAccessInternal { readCommon(nbt, registries); // Animation - var anim = TurtleAnimation.values()[nbt.getInt("Animation")]; + var anim = TurtleAnimation.values()[nbt.getIntOr("Animation", 0)]; if (anim != animation && anim != TurtleAnimation.WAIT && anim != TurtleAnimation.SHORT_WAIT && diff --git a/projects/common/src/main/java/dan200/computercraft/shared/turtle/core/TurtlePlaceCommand.java b/projects/common/src/main/java/dan200/computercraft/shared/turtle/core/TurtlePlaceCommand.java index f16de963d..05ab90827 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/turtle/core/TurtlePlaceCommand.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/turtle/core/TurtlePlaceCommand.java @@ -13,7 +13,6 @@ import dan200.computercraft.api.turtle.TurtleCommandResult; import dan200.computercraft.shared.platform.PlatformHelper; import dan200.computercraft.shared.turtle.TurtleUtil; import dan200.computercraft.shared.util.DropConsumer; -import dan200.computercraft.shared.util.InventoryUtil; import dan200.computercraft.shared.util.WorldUtil; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; @@ -106,9 +105,9 @@ public class TurtlePlaceCommand implements TurtleCommand { var hitEntity = entityHit.getEntity(); var hitPos = entityHit.getLocation(); - DropConsumer.set(hitEntity, drop -> InventoryUtil.storeItemsFromOffset(turtlePlayer.player().getInventory(), drop, 1)); + DropConsumer.set(hitEntity); var placed = PlatformHelper.get().interactWithEntity(turtlePlayer.player(), hitEntity, hitPos); - TurtleUtil.stopConsuming(turtle); + TurtleUtil.stopConsumingPlayer(turtle, turtlePlayer); return placed; } diff --git a/projects/common/src/main/java/dan200/computercraft/shared/turtle/core/TurtlePlayer.java b/projects/common/src/main/java/dan200/computercraft/shared/turtle/core/TurtlePlayer.java index 1ff756451..ff660a148 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/turtle/core/TurtlePlayer.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/turtle/core/TurtlePlayer.java @@ -134,7 +134,7 @@ public final class TurtlePlayer { public void loadInventory(ItemStack stack) { player.getInventory().clearContent(); - player.getInventory().selected = 0; + player.getInventory().setSelectedSlot(0); player.getInventory().setItem(0, stack); } @@ -145,7 +145,7 @@ public final class TurtlePlayer { var slots = turtleInventory.getContainerSize(); // Load up the fake inventory - inventory.selected = 0; + inventory.setSelectedSlot(0); for (var i = 0; i < slots; i++) { inventory.setItem(i, turtleInventory.getItem((currentSlot + i) % slots)); } @@ -160,7 +160,7 @@ public final class TurtlePlayer { var slots = turtleInventory.getContainerSize(); // Load up the fake inventory - inventory.selected = 0; + inventory.setSelectedSlot(0); for (var i = 0; i < slots; i++) { turtleInventory.setItem((currentSlot + i) % slots, inventory.getItem(i)); } diff --git a/projects/common/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleTool.java b/projects/common/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleTool.java index 1b57e4cdf..764045849 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleTool.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleTool.java @@ -175,7 +175,7 @@ public class TurtleTool extends AbstractTurtleUpgrade { var hitEntity = entityHit.getEntity(); // Start claiming entity drops - DropConsumer.set(hitEntity, TurtleUtil.dropConsumer(turtle)); + DropConsumer.set(hitEntity); // Attack the entity var result = PlatformHelper.get().canAttackEntity(player, hitEntity); @@ -285,7 +285,7 @@ public class TurtleTool extends AbstractTurtleUpgrade { if (!breakable.isSuccess()) return breakable; // And break it! - DropConsumer.set(level, blockPosition, TurtleUtil.dropConsumer(turtle)); + DropConsumer.set(level, blockPosition); var broken = !turtlePlayer.isBlockProtected(level, blockPosition) && turtlePlayer.player().gameMode.destroyBlock(blockPosition); TurtleUtil.stopConsuming(turtle); diff --git a/projects/common/src/main/java/dan200/computercraft/shared/util/ColourTracker.java b/projects/common/src/main/java/dan200/computercraft/shared/util/ColourTracker.java index e4e6acffc..6bf264649 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/util/ColourTracker.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/util/ColourTracker.java @@ -53,4 +53,8 @@ public class ColourTracker { return (avgR << 16) | (avgG << 8) | avgB; } + + public int getColourOr(int fallback) { + return hasColour() ? getColour() : fallback; + } } diff --git a/projects/common/src/main/java/dan200/computercraft/shared/util/DataComponentUtil.java b/projects/common/src/main/java/dan200/computercraft/shared/util/DataComponentUtil.java index a78d84b32..66e4c04d6 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/util/DataComponentUtil.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/util/DataComponentUtil.java @@ -10,6 +10,7 @@ import net.minecraft.core.component.DataComponentType; import net.minecraft.core.component.DataComponents; import net.minecraft.network.chat.Component; import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.component.DyedItemColor; import net.minecraft.world.level.ItemLike; import org.jetbrains.annotations.Contract; import org.jspecify.annotations.Nullable; @@ -38,14 +39,26 @@ public class DataComponentUtil { return stack; } - public static ItemStack createResult(ItemStack stack, DataComponentType type, @Nullable T value) { - return set(stack.copyWithCount(1), type, value); - } - public static ItemStack createStack(ItemLike item, DataComponentType type, @Nullable T value) { return set(new ItemStack(item), type, value); } + /** + * Create a stack dyed with a particular colour, but with the colour hidden from the tooltip. + * + * @param item The item to create the stack from. + * @param colour The stack's colour. + * @return The newly created stack. + */ + public static ItemStack createDyedStack(ItemLike item, int colour) { + return setDyeColour(new ItemStack(item), colour); + } + + public static ItemStack setDyeColour(ItemStack stack, int colour) { + stack.set(DataComponents.DYED_COLOR, new DyedItemColor(colour)); + return stack; + } + /** * Check a component is present in a {@link DataComponentPatch} and matches the supplied predicate. * diff --git a/projects/common/src/main/java/dan200/computercraft/shared/util/DropConsumer.java b/projects/common/src/main/java/dan200/computercraft/shared/util/DropConsumer.java index 8b5b2daed..3ac304e63 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/util/DropConsumer.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/util/DropConsumer.java @@ -5,7 +5,6 @@ package dan200.computercraft.shared.util; import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.item.ItemEntity; import net.minecraft.world.item.ItemStack; @@ -15,7 +14,6 @@ import org.jspecify.annotations.Nullable; import java.util.ArrayList; import java.util.List; -import java.util.function.Function; import static dan200.computercraft.core.util.Nullability.assertNonNull; @@ -23,34 +21,30 @@ public final class DropConsumer { private DropConsumer() { } - private static @Nullable Function dropConsumer; - private static @Nullable List remainingDrops; + private static @Nullable List drops; private static @Nullable Level dropWorld; private static @Nullable AABB dropBounds; private static @Nullable Entity dropEntity; - public static void set(Entity entity, Function consumer) { - dropConsumer = consumer; - remainingDrops = new ArrayList<>(); + public static void set(Entity entity) { + drops = new ArrayList<>(); dropEntity = entity; dropWorld = entity.level(); dropBounds = new AABB(entity.blockPosition()).inflate(2, 2, 2); } - public static void set(Level world, BlockPos pos, Function consumer) { - dropConsumer = consumer; - remainingDrops = new ArrayList<>(2); + public static void set(Level world, BlockPos pos) { + drops = new ArrayList<>(2); dropEntity = null; dropWorld = world; dropBounds = new AABB(pos).inflate(2, 2, 2); } - public static List clear() { - var remainingStacks = remainingDrops; + public static List stop() { + var remainingStacks = drops; if (remainingStacks == null) throw new IllegalStateException("Not currently capturing"); - dropConsumer = null; - remainingDrops = null; + drops = null; dropEntity = null; dropWorld = null; dropBounds = null; @@ -58,14 +52,8 @@ public final class DropConsumer { return remainingStacks; } - public static void clearAndDrop(Level world, BlockPos pos, @Nullable Direction direction) { - var remainingDrops = clear(); - for (var remaining : remainingDrops) WorldUtil.dropItemStack(world, pos, direction, remaining); - } - private static void handleDrops(ItemStack stack) { - var remaining = assertNonNull(dropConsumer).apply(stack); - if (!remaining.isEmpty()) assertNonNull(remainingDrops).add(remaining); + assertNonNull(drops).add(stack); } public static boolean onEntitySpawn(Entity entity) { diff --git a/projects/common/src/main/java/dan200/computercraft/shared/util/InventoryUtil.java b/projects/common/src/main/java/dan200/computercraft/shared/util/InventoryUtil.java index 717b53d62..7d442bc24 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/util/InventoryUtil.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/util/InventoryUtil.java @@ -9,12 +9,9 @@ import net.minecraft.core.Direction; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.Container; import net.minecraft.world.InteractionHand; -import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.player.Inventory; import net.minecraft.world.entity.player.Player; -import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; -import net.minecraft.world.level.Level; import net.minecraft.world.phys.EntityHitResult; import net.minecraft.world.phys.Vec3; import org.jspecify.annotations.Nullable; @@ -32,30 +29,22 @@ public final class InventoryUtil { */ public static int getHandSlot(Player player, InteractionHand hand) { return switch (hand) { - case MAIN_HAND -> player.getInventory().selected; + case MAIN_HAND -> player.getInventory().getSelectedSlot(); case OFF_HAND -> Inventory.SLOT_OFFHAND; }; } /** - * Map a slot inside a player's compartment to a slot in the full player's inventory. - *

- * {@link Inventory#tick()} passes in a slot to {@link Item#inventoryTick(ItemStack, Level, Entity, int, boolean)}. - * However, this slot corresponds to the index within the current compartment (items, armour, offhand) and not - * the actual slot. - *

- * This method searches the relevant compartments (inventory and offhand, skipping armour) for the stack, returning - * its slot if found. + * Find an item inside a container. * - * @param player The player holding the item. - * @param slot The slot inside the compartment. - * @param stack The stack being ticked. - * @return The inventory slot, or {@code -1} if the item could not be found in the inventory. + * @param container The container to search in. + * @param stack The item to find. + * @return The container slot, or {@code -1} if the item could not be found in the container. */ - public static int getInventorySlotFromCompartment(Player player, int slot, ItemStack stack) { - if (stack.isEmpty()) throw new IllegalArgumentException("Cannot search for empty stack"); - if (player.getInventory().getItem(slot) == stack) return slot; - if (player.getInventory().getItem(Inventory.SLOT_OFFHAND) == stack) return Inventory.SLOT_OFFHAND; + public static int findItemInInventory(Container container, ItemStack stack) { + for (int i = 0, size = container.getContainerSize(); i < size; i++) { + if (container.getItem(i) == stack) return i; + } return -1; } diff --git a/projects/common/src/main/java/dan200/computercraft/shared/util/NBTUtil.java b/projects/common/src/main/java/dan200/computercraft/shared/util/NBTUtil.java index b4f65ceef..911079319 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/util/NBTUtil.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/util/NBTUtil.java @@ -31,32 +31,26 @@ public final class NBTUtil { } public static @Nullable T decodeFrom(Codec codec, HolderLookup.Provider registries, CompoundTag tag, String key) { - var childTag = tag.get(key); - return childTag == null ? null : codec.parse(registries.createSerializationContext(NbtOps.INSTANCE), childTag) - .resultOrPartial(e -> LOG.warn("Failed to parse NBT: {}", e)) - .orElse(null); + return tag.read(key, codec, registries.createSerializationContext(NbtOps.INSTANCE)).orElse(null); } public static void encodeTo(Codec codec, HolderLookup.Provider registries, CompoundTag destination, String key, @Nullable T value) { - if (value == null) return; - codec.encodeStart(registries.createSerializationContext(NbtOps.INSTANCE), value) - .resultOrPartial(e -> LOG.warn("Failed to save NBT: {}", e)) - .ifPresent(x -> destination.put(key, x)); + destination.storeNullable(key, codec, registries.createSerializationContext(NbtOps.INSTANCE), value); } public static @Nullable Object toLua(@Nullable Tag tag) { if (tag == null) return null; return switch (tag.getId()) { - case Tag.TAG_BYTE, Tag.TAG_SHORT, Tag.TAG_INT, Tag.TAG_LONG -> ((NumericTag) tag).getAsLong(); - case Tag.TAG_FLOAT, Tag.TAG_DOUBLE -> ((NumericTag) tag).getAsDouble(); - case Tag.TAG_STRING -> tag.getAsString(); + case Tag.TAG_BYTE, Tag.TAG_SHORT, Tag.TAG_INT, Tag.TAG_LONG -> ((NumericTag) tag).longValue(); + case Tag.TAG_FLOAT, Tag.TAG_DOUBLE -> ((NumericTag) tag).doubleValue(); + case Tag.TAG_STRING -> ((StringTag) tag).value(); case Tag.TAG_COMPOUND -> { var compound = (CompoundTag) tag; Map map = new HashMap<>(compound.size()); - for (var key : compound.getAllKeys()) { - var value = toLua(compound.get(key)); - if (value != null) map.put(key, value); + for (var entry : compound.entrySet()) { + var value = toLua(entry.getValue()); + if (value != null) map.put(entry.getKey(), value); } yield map; } @@ -110,7 +104,7 @@ public final class NBTUtil { private static void writeTag(DataOutput output, Tag tag) throws IOException { if (tag instanceof CompoundTag compound) { - var keys = compound.getAllKeys().toArray(new String[0]); + var keys = compound.keySet().toArray(new String[0]); Arrays.sort(keys); for (var key : keys) writeNamedTag(output, key, Nullability.assertNonNull(compound.get(key))); diff --git a/projects/common/src/main/java/dan200/computercraft/shared/util/NonNegativeId.java b/projects/common/src/main/java/dan200/computercraft/shared/util/NonNegativeId.java index db8d19746..a550b34d0 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/util/NonNegativeId.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/util/NonNegativeId.java @@ -8,51 +8,101 @@ import com.mojang.serialization.Codec; import dan200.computercraft.api.ComputerCraftAPI; import io.netty.buffer.ByteBuf; import net.minecraft.ChatFormatting; +import net.minecraft.core.component.DataComponentGetter; import net.minecraft.core.component.DataComponentType; +import net.minecraft.core.component.DataComponents; import net.minecraft.network.chat.Component; import net.minecraft.network.codec.ByteBufCodecs; import net.minecraft.network.codec.StreamCodec; import net.minecraft.server.MinecraftServer; import net.minecraft.util.ExtraCodecs; +import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.TooltipFlag; +import net.minecraft.world.item.component.TooltipProvider; import org.jspecify.annotations.Nullable; import java.util.function.Consumer; +import java.util.function.IntFunction; /** * A non-negative integer id, used for computer and disk ids. * - * @param id The id of this entity. * @see dan200.computercraft.shared.ModRegistry.DataComponents#COMPUTER_ID * @see dan200.computercraft.shared.ModRegistry.DataComponents#DISK_ID */ -public record NonNegativeId(int id) { - public static final Codec CODEC = ExtraCodecs.NON_NEGATIVE_INT.xmap(NonNegativeId::new, NonNegativeId::id); +public abstract class NonNegativeId implements TooltipProvider { + private final int id; - public static final StreamCodec STREAM_CODEC = ByteBufCodecs.VAR_INT.map(NonNegativeId::new, NonNegativeId::id); - - public NonNegativeId { + protected NonNegativeId(int id) { if (id < 0) throw new IllegalArgumentException("ID must be >= 0"); + this.id = id; + } + + /** + * Get the internal id. + * + * @return The internal id. + */ + public int id() { + return id; } public static int getId(@Nullable NonNegativeId id) { return id == null ? -1 : id.id(); } - public static @Nullable NonNegativeId of(int id) { - return id >= 0 ? new NonNegativeId(id) : null; - } - - public static int getOrCreate(MinecraftServer server, ItemStack stack, DataComponentType component, String type) { + public static int getOrCreate(MinecraftServer server, ItemStack stack, DataComponentType component, IntFunction create, String type) { var id = stack.get(component); if (id != null) return id.id(); - var diskID = ComputerCraftAPI.createUniqueNumberedSaveDir(server, type); - stack.set(component, new NonNegativeId(diskID)); - return diskID; + var newId = ComputerCraftAPI.createUniqueNumberedSaveDir(server, type); + stack.set(component, create.apply(newId)); + return newId; } - public void addToTooltip(String translation, Consumer out) { + protected void addToTooltip(String translation, Consumer out) { out.accept(Component.translatable(translation, id()).withStyle(ChatFormatting.GRAY)); } + + @Override + @SuppressWarnings("EqualsGetClass") // We want to distinguish different subclasses. + public final boolean equals(Object o) { + return this == o || (o != null && getClass() == o.getClass() && id == ((NonNegativeId) o).id); + } + + @Override + public final int hashCode() { + return id; + } + + public static final class Computer extends NonNegativeId { + public static final Codec CODEC = ExtraCodecs.NON_NEGATIVE_INT.xmap(Computer::new, NonNegativeId::id); + public static final StreamCodec STREAM_CODEC = ByteBufCodecs.VAR_INT.map(Computer::new, NonNegativeId::id); + + public Computer(int id) { + super(id); + } + + @Override + public void addToTooltip(Item.TooltipContext tooltipContext, Consumer out, TooltipFlag flags, DataComponentGetter stack) { + if (flags.isAdvanced() || stack.get(DataComponents.CUSTOM_NAME) == null) { + addToTooltip("gui.computercraft.tooltip.computer_id", out); + } + } + } + + public static final class Disk extends NonNegativeId { + public static final Codec CODEC = ExtraCodecs.NON_NEGATIVE_INT.xmap(Disk::new, NonNegativeId::id); + public static final StreamCodec STREAM_CODEC = ByteBufCodecs.VAR_INT.map(Disk::new, NonNegativeId::id); + + public Disk(int id) { + super(id); + } + + @Override + public void addToTooltip(Item.TooltipContext tooltipContext, Consumer out, TooltipFlag flags, DataComponentGetter stack) { + if (flags.isAdvanced()) addToTooltip("gui.computercraft.tooltip.disk_id", out); + } + } } diff --git a/projects/common/src/main/resources/computercraft-common.accesswidener b/projects/common/src/main/resources/computercraft-common.accesswidener index 90a926fa5..f87364ba0 100644 --- a/projects/common/src/main/resources/computercraft-common.accesswidener +++ b/projects/common/src/main/resources/computercraft-common.accesswidener @@ -22,9 +22,12 @@ accessible class net/minecraft/data/tags/TagsProvider$TagAppender accessible field net/minecraft/client/data/models/BlockModelGenerators blockStateOutput Ljava/util/function/Consumer; accessible field net/minecraft/client/data/models/BlockModelGenerators itemModelOutput Lnet/minecraft/client/data/models/ItemModelOutput; accessible field net/minecraft/client/data/models/BlockModelGenerators modelOutput Ljava/util/function/BiConsumer; +accessible method net/minecraft/client/data/models/BlockModelGenerators condition ()Lnet/minecraft/client/data/models/blockstates/ConditionBuilder; accessible method net/minecraft/client/data/models/BlockModelGenerators createHorizontallyRotatedBlock (Lnet/minecraft/world/level/block/Block;Lnet/minecraft/client/data/models/model/TexturedModel$Provider;)V accessible method net/minecraft/client/data/models/BlockModelGenerators createParticleOnlyBlock (Lnet/minecraft/world/level/block/Block;)V -accessible method net/minecraft/client/data/models/BlockModelGenerators createSimpleBlock (Lnet/minecraft/world/level/block/Block;Lnet/minecraft/resources/ResourceLocation;)Lnet/minecraft/client/data/models/blockstates/MultiVariantGenerator; +accessible method net/minecraft/client/data/models/BlockModelGenerators createSimpleBlock (Lnet/minecraft/world/level/block/Block;Lnet/minecraft/client/data/models/MultiVariant;)Lnet/minecraft/client/data/models/blockstates/MultiVariantGenerator; +accessible method net/minecraft/client/data/models/BlockModelGenerators or ([Lnet/minecraft/client/data/models/blockstates/ConditionBuilder;)Lnet/minecraft/client/renderer/block/model/multipart/Condition; +accessible method net/minecraft/client/data/models/BlockModelGenerators plainVariant (Lnet/minecraft/resources/ResourceLocation;)Lnet/minecraft/client/data/models/MultiVariant; accessible method net/minecraft/client/data/models/BlockModelGenerators registerSimpleItemModel (Lnet/minecraft/world/item/Item;Lnet/minecraft/resources/ResourceLocation;)V accessible method net/minecraft/client/data/models/BlockModelGenerators registerSimpleItemModel (Lnet/minecraft/world/level/block/Block;Lnet/minecraft/resources/ResourceLocation;)V accessible field net/minecraft/client/data/models/ItemModelGenerators itemModelOutput Lnet/minecraft/client/data/models/ItemModelOutput; diff --git a/projects/common/src/main/resources/computercraft.accesswidener b/projects/common/src/main/resources/computercraft.accesswidener index 704b863c9..b61fcc967 100644 --- a/projects/common/src/main/resources/computercraft.accesswidener +++ b/projects/common/src/main/resources/computercraft.accesswidener @@ -10,12 +10,8 @@ accessWidener v1 named accessible class net/minecraft/world/level/block/entity/BlockEntityType$BlockEntitySupplier accessible method net/minecraft/world/level/block/entity/BlockEntityType (Lnet/minecraft/world/level/block/entity/BlockEntityType$BlockEntitySupplier;Ljava/util/Set;)V -accessible method net/minecraft/client/renderer/RenderType create (Ljava/lang/String;Lcom/mojang/blaze3d/vertex/VertexFormat;Lcom/mojang/blaze3d/vertex/VertexFormat$Mode;IZZLnet/minecraft/client/renderer/RenderType$CompositeState;)Lnet/minecraft/client/renderer/RenderType$CompositeRenderType; accessible method net/minecraft/world/level/storage/LevelResource (Ljava/lang/String;)V -# DirectVertexBuffer -accessible field com/mojang/blaze3d/vertex/VertexBuffer indexCount I - # ClientTableFormatter accessible field net/minecraft/client/gui/components/ChatComponent allMessages Ljava/util/List; @@ -30,3 +26,7 @@ accessible field net/minecraft/client/sounds/SoundEngine executor Lnet/minecraft # Turtle model accessible class net/minecraft/util/datafix/fixes/ItemStackComponentizationFix$ItemStackData + +# TODO(1.21.5): Add these to Fabric. +accessible field net/minecraft/client/data/models/BlockModelGenerators ROTATION_FACING Lnet/minecraft/client/data/models/blockstates/PropertyDispatch; +accessible field net/minecraft/client/data/models/BlockModelGenerators ROTATION_HORIZONTAL_FACING Lnet/minecraft/client/data/models/blockstates/PropertyDispatch; diff --git a/projects/common/src/test/java/dan200/computercraft/shared/util/NBTUtilTest.java b/projects/common/src/test/java/dan200/computercraft/shared/util/NBTUtilTest.java index cc6c2c5b7..db652cae7 100644 --- a/projects/common/src/test/java/dan200/computercraft/shared/util/NBTUtilTest.java +++ b/projects/common/src/test/java/dan200/computercraft/shared/util/NBTUtilTest.java @@ -9,12 +9,13 @@ import net.minecraft.Util; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; import net.minecraft.nbt.NbtIo; +import net.minecraft.nbt.Tag; import org.junit.jupiter.api.Test; import java.io.DataOutput; import java.io.DataOutputStream; -import java.io.File; import java.io.IOException; +import java.nio.file.Path; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.List; @@ -57,7 +58,7 @@ public class NBTUtilTest { var nbt1 = makeCompoundTag(false); var nbt2 = makeCompoundTag(true); assertNotEquals( - List.copyOf(nbt1.getAllKeys()), List.copyOf(nbt2.getAllKeys()), + List.copyOf(nbt1.keySet()), List.copyOf(nbt2.keySet()), "Expected makeCompoundTag to return keys with different orders." ); } @@ -100,7 +101,7 @@ public class NBTUtilTest { } /** - * Equivalent to {@link NBTUtil#getNBTHash(CompoundTag)}, but using the default {@link NbtIo#write(CompoundTag, File)} method. + * Equivalent to {@link NBTUtil#getNBTHash(Tag)}, but using the default {@link NbtIo#write(CompoundTag, Path)} method. * * @param tag The tag to hash. * @return The resulting hash. diff --git a/projects/common/src/testMod/java/dan200/computercraft/export/ImageRenderer.java b/projects/common/src/testMod/java/dan200/computercraft/export/ImageRenderer.java index f1a3d4447..38afead7d 100644 --- a/projects/common/src/testMod/java/dan200/computercraft/export/ImageRenderer.java +++ b/projects/common/src/testMod/java/dan200/computercraft/export/ImageRenderer.java @@ -8,9 +8,7 @@ import com.mojang.blaze3d.ProjectionType; import com.mojang.blaze3d.pipeline.TextureTarget; import com.mojang.blaze3d.platform.NativeImage; import com.mojang.blaze3d.systems.RenderSystem; -import net.minecraft.client.Minecraft; import org.joml.Matrix4f; -import org.lwjgl.opengl.GL11; import java.io.IOException; import java.nio.file.Files; @@ -23,19 +21,19 @@ public class ImageRenderer implements AutoCloseable { public static final int WIDTH = 64; public static final int HEIGHT = 64; - private final TextureTarget framebuffer = new TextureTarget(WIDTH, HEIGHT, true); + private final TextureTarget framebuffer = new TextureTarget("Export", WIDTH, HEIGHT, true); private final NativeImage image = new NativeImage(WIDTH, HEIGHT, true); public ImageRenderer() { - framebuffer.setClearColor(0, 0, 0, 0); - framebuffer.clear(); + // framebuffer.setFilterMode(0, 0, 0, 0); + // framebuffer.clear(); } public void captureRender(Path output, Runnable render) throws IOException { Files.createDirectories(output.getParent()); - RenderSystem.clear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT); - framebuffer.bindWrite(true); + // RenderSystem.clear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT); + // framebuffer.bindWrite(true); // Setup rendering state RenderSystem.backupProjectionMatrix(); @@ -53,14 +51,14 @@ public class ImageRenderer implements AutoCloseable { RenderSystem.restoreProjectionMatrix(); transform.popMatrix(); - framebuffer.unbindWrite(); - Minecraft.getInstance().getMainRenderTarget().bindWrite(true); + // framebuffer.unbindWrite(); + // Minecraft.getInstance().getMainRenderTarget().bindWrite(true); // And save the image - framebuffer.bindRead(); - image.downloadTexture(0, false); - image.flipY(); - framebuffer.unbindRead(); + // framebuffer.bindRead(); + // image.downloadTexture(0, false); + // image.flipY(); + // framebuffer.unbindRead(); image.writeToFile(output); } diff --git a/projects/common/src/testMod/java/dan200/computercraft/gametest/api/ComputerState.java b/projects/common/src/testMod/java/dan200/computercraft/gametest/api/ComputerState.java index bf1693231..193c072ab 100644 --- a/projects/common/src/testMod/java/dan200/computercraft/gametest/api/ComputerState.java +++ b/projects/common/src/testMod/java/dan200/computercraft/gametest/api/ComputerState.java @@ -5,7 +5,6 @@ package dan200.computercraft.gametest.api; import dan200.computercraft.gametest.core.TestAPI; -import net.minecraft.gametest.framework.GameTestAssertException; import net.minecraft.gametest.framework.GameTestSequence; import org.jspecify.annotations.Nullable; @@ -32,9 +31,9 @@ public class ComputerState { return markers.contains(marker); } - public void check(String marker) { + public @Nullable String check(String marker) { if (!markers.contains(marker)) throw new IllegalStateException("Not yet at " + marker); - if (error != null) throw new GameTestAssertException(error); + return error; } public static @Nullable ComputerState get(String label) { diff --git a/projects/common/src/testMod/java/dan200/computercraft/gametest/core/CCTestCommand.java b/projects/common/src/testMod/java/dan200/computercraft/gametest/core/CCTestCommand.java index 48411325a..480503e65 100644 --- a/projects/common/src/testMod/java/dan200/computercraft/gametest/core/CCTestCommand.java +++ b/projects/common/src/testMod/java/dan200/computercraft/gametest/core/CCTestCommand.java @@ -6,7 +6,6 @@ package dan200.computercraft.gametest.core; import com.mojang.brigadier.CommandDispatcher; import dan200.computercraft.api.ComputerCraftAPI; -import dan200.computercraft.mixin.gametest.TestCommandAccessor; import dan200.computercraft.shared.ModRegistry; import dan200.computercraft.shared.util.NonNegativeId; import net.minecraft.ChatFormatting; @@ -15,14 +14,14 @@ import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.arguments.item.ItemArgument; import net.minecraft.commands.arguments.item.ItemInput; import net.minecraft.core.component.DataComponents; -import net.minecraft.gametest.framework.GameTestRegistry; import net.minecraft.gametest.framework.StructureUtils; import net.minecraft.nbt.CompoundTag; import net.minecraft.network.chat.Component; import net.minecraft.server.MinecraftServer; import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.decoration.ArmorStand; -import net.minecraft.world.level.block.entity.StructureBlockEntity; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraft.world.level.block.entity.TestInstanceBlockEntity; import net.minecraft.world.level.storage.LevelResource; import java.io.IOException; @@ -41,38 +40,18 @@ class CCTestCommand { public static void register(CommandDispatcher dispatcher, CommandBuildContext buildContext) { dispatcher.register(choice("cctest") - .then(literal("import").executes(context -> { - importFiles(context.getSource().getServer()); - return 0; - })) - .then(literal("export").executes(context -> { - exportFiles(context.getSource().getServer()); - - for (var function : GameTestRegistry.getAllTestFunctions()) { - TestCommandAccessor.callExportTestStructure(context.getSource(), function.structureName()); - } - return 0; - })) - .then(literal("regen-structures").executes(context -> { - for (var function : GameTestRegistry.getAllTestFunctions()) { - dispatcher.execute("test import " + function.structureName(), context.getSource()); - TestCommandAccessor.callExportTestStructure(context.getSource(), function.structureName()); - } - return 0; - })) - .then(literal("marker").executes(context -> { var player = context.getSource().getPlayerOrException(); - var pos = StructureUtils.findNearestStructureBlock(player.blockPosition(), 15, player.serverLevel()).orElse(null); + var pos = StructureUtils.findNearestTest(player.blockPosition(), 15, player.serverLevel()).orElse(null); if (pos == null) return error(context.getSource(), "No nearby test"); - var structureBlock = (StructureBlockEntity) player.level().getBlockEntity(pos); - if (structureBlock == null) return error(context.getSource(), "No nearby structure block"); - var info = GameTestRegistry.getTestFunction(structureBlock.getMetaData()); + var test = player.level().getBlockEntity(pos, BlockEntityType.TEST_INSTANCE_BLOCK) + .flatMap(TestInstanceBlockEntity::test).orElse(null); + if (test == null) return error(context.getSource(), "No nearby structure block"); // Kill the existing armor stand var level = player.serverLevel(); - level.getEntities(EntityType.ARMOR_STAND, x -> x.isAlive() && x.getName().getString().equals(info.testName())) + level.getEntities(EntityType.ARMOR_STAND, x -> x.isAlive() && x.getName().getString().equals(test.location().getPath())) .forEach(e -> e.kill(level)); // And create a new one @@ -82,7 +61,7 @@ class CCTestCommand { var armorStand = new ArmorStand(EntityType.ARMOR_STAND, level); armorStand.readAdditionalSaveData(nbt); armorStand.copyPosition(player); - armorStand.setCustomName(Component.literal(info.testName())); + armorStand.setCustomName(Component.literal(test.location().getPath())); level.addFreshEntity(armorStand); return 0; })) @@ -91,16 +70,16 @@ class CCTestCommand { var item = context.getArgument("item", ItemInput.class); var player = context.getSource().getPlayerOrException(); - var pos = StructureUtils.findNearestStructureBlock(player.blockPosition(), 15, player.serverLevel()).orElse(null); + var pos = StructureUtils.findNearestTest(player.blockPosition(), 15, player.serverLevel()).orElse(null); if (pos == null) return error(context.getSource(), "No nearby test"); - var structureBlock = (StructureBlockEntity) player.level().getBlockEntity(pos); - if (structureBlock == null) return error(context.getSource(), "No nearby structure block"); - var info = GameTestRegistry.getTestFunction(structureBlock.getMetaData()); + var test = player.level().getBlockEntity(pos, BlockEntityType.TEST_INSTANCE_BLOCK) + .flatMap(TestInstanceBlockEntity::test).orElse(null); + if (test == null) return error(context.getSource(), "No nearby structure block"); var stack = item.createItemStack(1, false); - stack.set(ModRegistry.DataComponents.COMPUTER_ID.get(), new NonNegativeId(1)); - stack.set(DataComponents.CUSTOM_NAME, Component.literal(info.testName())); + stack.set(ModRegistry.DataComponents.COMPUTER_ID.get(), new NonNegativeId.Computer(1)); + stack.set(DataComponents.CUSTOM_NAME, Component.literal(test.location().getPath())); if (!player.getInventory().add(stack)) { var itemEntity = player.drop(stack, false); if (itemEntity != null) { diff --git a/projects/common/src/testMod/java/dan200/computercraft/mixin/gametest/GameTestInfoAccessor.java b/projects/common/src/testMod/java/dan200/computercraft/mixin/gametest/GameTestInfoAccessor.java index e0b555228..721bff2a8 100644 --- a/projects/common/src/testMod/java/dan200/computercraft/mixin/gametest/GameTestInfoAccessor.java +++ b/projects/common/src/testMod/java/dan200/computercraft/mixin/gametest/GameTestInfoAccessor.java @@ -11,5 +11,5 @@ import org.spongepowered.asm.mixin.gen.Invoker; @Mixin(GameTestInfo.class) public interface GameTestInfoAccessor { @Invoker("getTick") - long computercraft$getTick(); + int computercraft$getTick(); } diff --git a/projects/common/src/testMod/java/dan200/computercraft/mixin/gametest/GameTestSequenceAccessor.java b/projects/common/src/testMod/java/dan200/computercraft/mixin/gametest/GameTestSequenceAccessor.java index af4efb51d..13697639e 100644 --- a/projects/common/src/testMod/java/dan200/computercraft/mixin/gametest/GameTestSequenceAccessor.java +++ b/projects/common/src/testMod/java/dan200/computercraft/mixin/gametest/GameTestSequenceAccessor.java @@ -15,5 +15,5 @@ public interface GameTestSequenceAccessor { GameTestInfo getParent(); @Accessor - long getLastTick(); + int getLastTick(); } diff --git a/projects/common/src/testMod/java/dan200/computercraft/mixin/gametest/GameTestSequenceMixin.java b/projects/common/src/testMod/java/dan200/computercraft/mixin/gametest/GameTestSequenceMixin.java index 33a970a7c..676cec9cc 100644 --- a/projects/common/src/testMod/java/dan200/computercraft/mixin/gametest/GameTestSequenceMixin.java +++ b/projects/common/src/testMod/java/dan200/computercraft/mixin/gametest/GameTestSequenceMixin.java @@ -5,9 +5,7 @@ package dan200.computercraft.mixin.gametest; import dan200.computercraft.gametest.core.TestHooks; -import net.minecraft.gametest.framework.GameTestAssertException; -import net.minecraft.gametest.framework.GameTestInfo; -import net.minecraft.gametest.framework.GameTestSequence; +import net.minecraft.gametest.framework.*; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Overwrite; @@ -20,29 +18,29 @@ class GameTestSequenceMixin { GameTestInfo parent; /** - * Override {@link GameTestSequence#tickAndContinue(long)} to catch non-{@link GameTestAssertException} failures. + * Override {@link GameTestSequence#tickAndContinue(int)} to catch non-{@link GameTestAssertException} failures. * * @param ticks The current tick. * @author Jonathan Coates * @reason There's no sense doing this in a more compatible way for game tests. */ @Overwrite - public void tickAndContinue(long ticks) { + public void tickAndContinue(int ticks) { try { tick(ticks); - } catch (GameTestAssertException ignored) { + } catch (GameTestException ignored) { // Mimic the original behaviour. } catch (AssertionError e) { - parent.fail(e); + parent.fail(new UnknownGameTestException(e)); } catch (Exception | LinkageError | VirtualMachineError e) { // Fail the test, rather than crashing the server. - TestHooks.LOG.error("{} threw unexpected exception", parent.getTestName(), e); - parent.fail(e); + TestHooks.LOG.error("{} threw unexpected exception", parent.id(), e); + parent.fail(new UnknownGameTestException(e)); } } @Shadow @SuppressWarnings("unused") - private void tick(long tick) { + private void tick(int tick) { } } diff --git a/projects/common/src/testMod/java/dan200/computercraft/mixin/gametest/NbtUtilsMixin.java b/projects/common/src/testMod/java/dan200/computercraft/mixin/gametest/NbtUtilsMixin.java index b868c48b7..49d61f8da 100644 --- a/projects/common/src/testMod/java/dan200/computercraft/mixin/gametest/NbtUtilsMixin.java +++ b/projects/common/src/testMod/java/dan200/computercraft/mixin/gametest/NbtUtilsMixin.java @@ -33,7 +33,7 @@ class NbtUtilsMixin { var newTag = structure.save(new CompoundTag()); // Overwrite the existing tag. - tag.getAllKeys().clear(); - for (var key : newTag.getAllKeys()) tag.put(key, newTag.get(key)); + tag.keySet().clear(); + for (var key : newTag.keySet()) tag.put(key, newTag.get(key)); } } diff --git a/projects/common/src/testMod/java/dan200/computercraft/mixin/gametest/TestCommandAccessor.java b/projects/common/src/testMod/java/dan200/computercraft/mixin/gametest/TestCommandAccessor.java deleted file mode 100644 index 4837eb198..000000000 --- a/projects/common/src/testMod/java/dan200/computercraft/mixin/gametest/TestCommandAccessor.java +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers -// -// SPDX-License-Identifier: MPL-2.0 - -package dan200.computercraft.mixin.gametest; - -import net.minecraft.commands.CommandSourceStack; -import net.minecraft.gametest.framework.TestCommand; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.gen.Invoker; - -@Mixin(TestCommand.class) -public interface TestCommandAccessor { - @Invoker - static int callExportTestStructure(CommandSourceStack source, String structure) { - return 0; - } -} diff --git a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Component_Test.kt b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Component_Test.kt index 5ec3017d7..4e11ee187 100644 --- a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Component_Test.kt +++ b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Component_Test.kt @@ -4,6 +4,7 @@ package dan200.computercraft.gametest +import dan200.computercraft.gametest.api.GameTest import dan200.computercraft.gametest.api.assertNoPeripheral import dan200.computercraft.gametest.api.assertPeripheral import dan200.computercraft.gametest.api.immediate @@ -11,7 +12,6 @@ import dan200.computercraft.shared.ModRegistry import dan200.computercraft.shared.platform.ComponentAccess import net.minecraft.core.BlockPos import net.minecraft.core.Direction -import net.minecraft.gametest.framework.GameTest import net.minecraft.gametest.framework.GameTestHelper import java.util.* diff --git a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Computer_Test.kt b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Computer_Test.kt index a31b346ad..37adbf61f 100644 --- a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Computer_Test.kt +++ b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Computer_Test.kt @@ -15,7 +15,6 @@ import dan200.computercraft.test.core.assertArrayEquals import dan200.computercraft.test.core.computer.getApi import net.minecraft.core.BlockPos import net.minecraft.core.Direction -import net.minecraft.gametest.framework.GameTest import net.minecraft.gametest.framework.GameTestHelper import net.minecraft.world.item.ItemStack import net.minecraft.world.item.Items @@ -122,7 +121,7 @@ class Computer_Test { /** * Check the client can open the computer UI and interact with it. */ - @ClientGameTest + @GameTest(tag = TestTags.CLIENT) fun Open_on_client(context: GameTestHelper) = context.sequence { // Write "Hello, world!" and then print each event to the terminal. thenOnComputer { getApi().write(Coerced("Hello, world!")) } diff --git a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/CraftOs_Test.kt b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/CraftOs_Test.kt index 62b700698..f3cb40845 100644 --- a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/CraftOs_Test.kt +++ b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/CraftOs_Test.kt @@ -4,10 +4,10 @@ package dan200.computercraft.gametest +import dan200.computercraft.gametest.api.GameTest import dan200.computercraft.gametest.api.Timeouts import dan200.computercraft.gametest.api.sequence import dan200.computercraft.gametest.api.thenComputerOk -import net.minecraft.gametest.framework.GameTest import net.minecraft.gametest.framework.GameTestHelper class CraftOs_Test { diff --git a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Details_Test.kt b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Details_Test.kt index 1e7c2303f..5bf65dd8d 100644 --- a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Details_Test.kt +++ b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Details_Test.kt @@ -5,9 +5,9 @@ package dan200.computercraft.gametest import dan200.computercraft.api.detail.VanillaDetailRegistries +import dan200.computercraft.gametest.api.GameTest import dan200.computercraft.gametest.api.Structures import dan200.computercraft.gametest.api.sequence -import net.minecraft.gametest.framework.GameTest import net.minecraft.gametest.framework.GameTestHelper import net.minecraft.world.item.ItemStack import net.minecraft.world.item.Items diff --git a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Disk_Drive_Test.kt b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Disk_Drive_Test.kt index d665c590a..e081b8a86 100644 --- a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Disk_Drive_Test.kt +++ b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Disk_Drive_Test.kt @@ -6,9 +6,11 @@ package dan200.computercraft.gametest import dan200.computercraft.core.apis.FSAPI import dan200.computercraft.gametest.api.* +import dan200.computercraft.gametest.api.GameTest import dan200.computercraft.shared.ModRegistry import dan200.computercraft.shared.media.items.TreasureDisk import dan200.computercraft.shared.peripheral.diskdrive.DiskDriveBlock +import dan200.computercraft.shared.peripheral.diskdrive.DiskDriveBlockEntity import dan200.computercraft.shared.peripheral.diskdrive.DiskDrivePeripheral import dan200.computercraft.shared.peripheral.diskdrive.DiskDriveState import dan200.computercraft.shared.util.DataComponentUtil @@ -16,14 +18,11 @@ import dan200.computercraft.shared.util.NonNegativeId import dan200.computercraft.test.core.assertArrayEquals import dan200.computercraft.test.core.computer.getApi import net.minecraft.core.BlockPos -import net.minecraft.core.component.DataComponentPatch import net.minecraft.core.component.DataComponents -import net.minecraft.gametest.framework.GameTest import net.minecraft.gametest.framework.GameTestHelper import net.minecraft.network.chat.Component import net.minecraft.world.item.ItemStack import net.minecraft.world.item.Items -import net.minecraft.world.item.component.DyedItemColor import net.minecraft.world.level.block.RedStoneWireBlock import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.Matchers.array @@ -121,7 +120,7 @@ class Disk_Drive_Test { fun Creates_disk_id(helper: GameTestHelper) = helper.sequence { val drivePos = BlockPos(2, 1, 2) thenWaitUntil { - val drive = helper.getBlockEntity(drivePos, ModRegistry.BlockEntities.DISK_DRIVE.get()) + val drive = helper.getBlockEntity(drivePos, DiskDriveBlockEntity::class.java) if (!drive.getItem(0).has(ModRegistry.DataComponents.DISK_ID.get())) { helper.fail("Disk has no item", drivePos) } @@ -138,7 +137,7 @@ class Disk_Drive_Test { // Adding items should provide power thenExecute { - val drive = helper.getBlockEntity(drivePos, ModRegistry.BlockEntities.DISK_DRIVE.get()) + val drive = helper.getBlockEntity(drivePos, DiskDriveBlockEntity::class.java) drive.setItem(0, ItemStack(ModRegistry.Items.TREASURE_DISK.get())) drive.setChanged() } @@ -147,7 +146,7 @@ class Disk_Drive_Test { // And removing them should reset power. thenExecute { - val drive = helper.getBlockEntity(drivePos, ModRegistry.BlockEntities.DISK_DRIVE.get()) + val drive = helper.getBlockEntity(drivePos, DiskDriveBlockEntity::class.java) drive.setItem(0, ItemStack.EMPTY) drive.setChanged() } @@ -163,7 +162,7 @@ class Disk_Drive_Test { val pos = BlockPos(2, 1, 2) thenExecute { - val drive = helper.getBlockEntity(pos, ModRegistry.BlockEntities.DISK_DRIVE.get()) + val drive = helper.getBlockEntity(pos, DiskDriveBlockEntity::class.java) drive.setItem(0, ItemStack(Items.DIRT)) drive.setChanged() @@ -222,12 +221,8 @@ class Disk_Drive_Test { BlockPos(1, 1, 2), listOf( ItemStack(ModRegistry.Items.DISK.get()).also { - it.applyComponents( - DataComponentPatch.builder() - .set(ModRegistry.DataComponents.DISK_ID.get(), NonNegativeId(123)) - .set(DataComponents.DYED_COLOR, DyedItemColor(123456, false)) - .build(), - ) + it.set(ModRegistry.DataComponents.DISK_ID.get(), NonNegativeId.Disk(123)) + DataComponentUtil.setDyeColour(it, 123456) }, ), ) @@ -236,12 +231,8 @@ class Disk_Drive_Test { BlockPos(3, 1, 2), listOf( ItemStack(ModRegistry.Items.TREASURE_DISK.get()).also { - it.applyComponents( - DataComponentPatch.builder() - .set(ModRegistry.DataComponents.TREASURE_DISK.get(), TreasureDisk("Demo disk", "demo")) - .set(DataComponents.DYED_COLOR, DyedItemColor(123456, false)) - .build(), - ) + it.set(ModRegistry.DataComponents.TREASURE_DISK.get(), TreasureDisk("Demo disk", "demo")) + DataComponentUtil.setDyeColour(it, 123456) }, ), ) diff --git a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Inventory_Test.kt b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Inventory_Test.kt index 1401db615..52e148ba1 100644 --- a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Inventory_Test.kt +++ b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Inventory_Test.kt @@ -6,6 +6,7 @@ package dan200.computercraft.gametest import dan200.computercraft.api.lua.ObjectArguments import dan200.computercraft.core.apis.PeripheralAPI +import dan200.computercraft.gametest.api.GameTest import dan200.computercraft.gametest.api.assertContainerExactly import dan200.computercraft.gametest.api.sequence import dan200.computercraft.gametest.api.thenOnComputer @@ -13,7 +14,6 @@ import dan200.computercraft.test.core.assertArrayEquals import dan200.computercraft.test.core.computer.getApi import net.minecraft.core.BlockPos import net.minecraft.core.NonNullList -import net.minecraft.gametest.framework.GameTest import net.minecraft.gametest.framework.GameTestHelper import net.minecraft.world.item.ItemStack import net.minecraft.world.item.Items diff --git a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Loot_Test.kt b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Loot_Test.kt index 9c68f2466..a851307ce 100644 --- a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Loot_Test.kt +++ b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Loot_Test.kt @@ -4,11 +4,11 @@ package dan200.computercraft.gametest +import dan200.computercraft.gametest.api.GameTest import dan200.computercraft.gametest.api.Structures import dan200.computercraft.gametest.api.sequence import dan200.computercraft.shared.ModRegistry import net.minecraft.core.BlockPos -import net.minecraft.gametest.framework.GameTest import net.minecraft.gametest.framework.GameTestHelper import net.minecraft.world.level.block.Blocks import net.minecraft.world.level.block.entity.ChestBlockEntity @@ -24,7 +24,7 @@ class Loot_Test { val pos = BlockPos(2, 1, 2) context.setBlock(pos, Blocks.CHEST) - val chest = context.getBlockEntity(pos) as ChestBlockEntity + val chest = context.getBlockEntity(pos, ChestBlockEntity::class.java) chest.setLootTable(BuiltInLootTables.SIMPLE_DUNGEON, 123) chest.unpackLootTable(null) diff --git a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Modem_Test.kt b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Modem_Test.kt index be645d86e..1b9d8dcc3 100644 --- a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Modem_Test.kt +++ b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Modem_Test.kt @@ -8,16 +8,17 @@ import dan200.computercraft.api.lua.ObjectArguments import dan200.computercraft.core.apis.PeripheralAPI import dan200.computercraft.core.computer.ComputerSide import dan200.computercraft.gametest.api.* +import dan200.computercraft.gametest.api.GameTest import dan200.computercraft.impl.network.wired.WiredNodeImpl import dan200.computercraft.shared.ModRegistry import dan200.computercraft.shared.peripheral.modem.wired.CableBlock import dan200.computercraft.shared.peripheral.modem.wired.CableModemVariant +import dan200.computercraft.shared.peripheral.modem.wired.WiredModemFullBlockEntity import dan200.computercraft.test.core.assertArrayEquals import dan200.computercraft.test.core.computer.LuaTaskContext import dan200.computercraft.test.core.computer.getApi import net.minecraft.core.BlockPos import net.minecraft.core.Direction -import net.minecraft.gametest.framework.GameTest import net.minecraft.gametest.framework.GameTestHelper import net.minecraft.world.item.ItemStack import net.minecraft.world.item.Items @@ -84,8 +85,8 @@ class Modem_Test { @GameTest(setupTicks = 1) fun Full_modems_form_networks(helper: GameTestHelper) = helper.sequence { thenExecute { - val modem1 = helper.getBlockEntity(BlockPos(1, 1, 1), ModRegistry.BlockEntities.WIRED_MODEM_FULL.get()) - val modem2 = helper.getBlockEntity(BlockPos(3, 1, 1), ModRegistry.BlockEntities.WIRED_MODEM_FULL.get()) + val modem1 = helper.getBlockEntity(BlockPos(1, 1, 1), WiredModemFullBlockEntity::class.java) + val modem2 = helper.getBlockEntity(BlockPos(3, 1, 1), WiredModemFullBlockEntity::class.java) assertEquals((modem1.element.node as WiredNodeImpl).network, (modem2.element.node as WiredNodeImpl).network, "On the same network") } } diff --git a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Monitor_Test.kt b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Monitor_Test.kt index 0d00484b8..4ef908248 100644 --- a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Monitor_Test.kt +++ b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Monitor_Test.kt @@ -5,16 +5,17 @@ package dan200.computercraft.gametest import dan200.computercraft.gametest.api.* +import dan200.computercraft.gametest.core.TestInstance import dan200.computercraft.shared.ModRegistry import dan200.computercraft.shared.peripheral.monitor.MonitorBlock +import dan200.computercraft.shared.peripheral.monitor.MonitorBlockEntity import dan200.computercraft.shared.peripheral.monitor.MonitorEdgeState import net.minecraft.commands.arguments.blocks.BlockInput import net.minecraft.core.BlockPos -import net.minecraft.gametest.framework.GameTest -import net.minecraft.gametest.framework.GameTestGenerator -import net.minecraft.gametest.framework.GameTestHelper -import net.minecraft.gametest.framework.TestFunction +import net.minecraft.core.Holder +import net.minecraft.gametest.framework.* import net.minecraft.nbt.CompoundTag +import net.minecraft.resources.ResourceLocation import net.minecraft.world.item.ItemStack import net.minecraft.world.level.GameType import net.minecraft.world.level.block.Blocks @@ -41,7 +42,7 @@ class Monitor_Test { } thenIdle(2) thenExecute { - val tile = context.getBlockEntity(pos, ModRegistry.BlockEntities.MONITOR_ADVANCED.get()) + val tile = context.getBlockEntity(pos, MonitorBlockEntity::class.java) if (tile.width != 1 || tile.height != 1) { context.fail("Tile has width and height of ${tile.width}x${tile.height}, but should be 1x1", pos) @@ -67,7 +68,7 @@ class Monitor_Test { @GameTest fun Creates_terminal(helper: GameTestHelper) = helper.sequence { fun monitorAt(x: Int) = - helper.getBlockEntity(BlockPos(x, 1, 2), ModRegistry.BlockEntities.MONITOR_ADVANCED.get()) + helper.getBlockEntity(BlockPos(x, 1, 2), MonitorBlockEntity::class.java) thenExecute { for (i in 1..3) { @@ -104,24 +105,26 @@ class Monitor_Test { * Test monitors render correctly */ @GameTestGenerator - fun Render_monitor_tests(): List { - val tests = mutableListOf() + fun Render_monitor_tests(): List { + val tests = mutableListOf() - fun addTest(label: String, time: Long = Times.NOON, tag: String = TestTags.CLIENT) { + fun addTest(label: String, time: Int = Times.NOON, tag: String = TestTags.CLIENT) { if (!TestTags.isEnabled(tag)) return val className = this::class.java.simpleName.lowercase() val testName = "$className.render_monitor" tests.add( - TestFunction( + TestInstance( "$testName.$label", - "$testName.$label", - testName, - Timeouts.DEFAULT, - 0, - true, - ) { renderMonitor(it, time) }, + TestData( + Holder.direct(TestEnvironmentDefinition.TimeOfDay(time)), + ResourceLocation.parse(testName), + Timeouts.DEFAULT, + 0, + true, + ), + ) { renderMonitor(it) }, ) } @@ -137,13 +140,12 @@ class Monitor_Test { return tests } - private fun renderMonitor(helper: GameTestHelper, time: Long) = helper.sequence { + private fun renderMonitor(helper: GameTestHelper) = helper.sequence { thenExecute { - helper.level.dayTime = time helper.positionAtArmorStand() // Get the monitor and peripheral. This forces us to create a server monitor at this location. - val monitor = helper.getBlockEntity(BlockPos(2, 1, 3), ModRegistry.BlockEntities.MONITOR_ADVANCED.get()) + val monitor = helper.getBlockEntity(BlockPos(2, 1, 3), MonitorBlockEntity::class.java) monitor.peripheral() val terminal = monitor.cachedServerMonitor!!.terminal!! @@ -156,6 +158,6 @@ class Monitor_Test { thenScreenshot() - thenExecute { helper.level.dayTime = Times.NOON } + thenExecute { helper.level.dayTime = Times.NOON.toLong() } } } diff --git a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Pocket_Computer_Test.kt b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Pocket_Computer_Test.kt index e6297f948..2bf4198ee 100644 --- a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Pocket_Computer_Test.kt +++ b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Pocket_Computer_Test.kt @@ -11,6 +11,7 @@ import dan200.computercraft.api.upgrades.UpgradeData import dan200.computercraft.client.pocket.ClientPocketComputers import dan200.computercraft.core.apis.TermAPI import dan200.computercraft.gametest.api.* +import dan200.computercraft.gametest.api.GameTest import dan200.computercraft.mixin.gametest.GameTestHelperAccessor import dan200.computercraft.shared.ModRegistry import dan200.computercraft.shared.computer.core.ComputerState @@ -20,7 +21,6 @@ import dan200.computercraft.test.core.computer.getApi import net.minecraft.core.BlockPos import net.minecraft.core.component.DataComponentPatch import net.minecraft.core.component.DataComponents -import net.minecraft.gametest.framework.GameTest import net.minecraft.gametest.framework.GameTestHelper import net.minecraft.gametest.framework.GameTestSequence import net.minecraft.network.chat.Component @@ -33,7 +33,7 @@ class Pocket_Computer_Test { /** * Checks pocket computer state is synced to the holding player. */ - @ClientGameTest(template = Structures.DEFAULT) + @GameTest(template = Structures.DEFAULT, tag = TestTags.CLIENT) fun Sync_state(context: GameTestHelper) = context.sequence { // We use a unique label for each test run as computers from previous runs may not have been disposed yet. val unique = java.lang.Long.toHexString(Random.nextLong()) @@ -75,7 +75,7 @@ class Pocket_Computer_Test { /** * Checks pocket computers are rendered when being held like a map. */ - @ClientGameTest(template = Structures.DEFAULT) + @GameTest(template = Structures.DEFAULT, tag = TestTags.CLIENT) fun Renders_map_view(context: GameTestHelper) = context.sequence { // We use a unique label for each test run as computers from previous runs may not have been disposed yet. val unique = java.lang.Long.toHexString(Random.nextLong()) @@ -104,8 +104,7 @@ class Pocket_Computer_Test { val player = level.randomPlayer!! player.inventory.clearContent() - val testName = (this as GameTestHelperAccessor).testInfo.testName - val label = testName + (if (name == null) "" else ".$name") + val label = (this as GameTestHelperAccessor).testInfo.getComputerLabel(name) val item = ItemStack(ModRegistry.Items.POCKET_COMPUTER_ADVANCED.get()) item.set(DataComponents.CUSTOM_NAME, Component.literal(label)) @@ -130,7 +129,7 @@ class Pocket_Computer_Test { DataComponentUtil.setCustomName(it, "Test") it.applyComponents( DataComponentPatch.builder() - .set(ModRegistry.DataComponents.COMPUTER_ID.get(), NonNegativeId(123)) + .set(ModRegistry.DataComponents.COMPUTER_ID.get(), NonNegativeId.Computer(123)) .set(ModRegistry.DataComponents.POCKET_UPGRADE.get(), UpgradeData.ofDefault(upgrade)) .build(), ) diff --git a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Printer_Test.kt b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Printer_Test.kt index 2777ca787..ba33089ee 100644 --- a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Printer_Test.kt +++ b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Printer_Test.kt @@ -7,14 +7,15 @@ package dan200.computercraft.gametest import dan200.computercraft.api.lua.Coerced import dan200.computercraft.api.lua.LuaException import dan200.computercraft.gametest.api.* +import dan200.computercraft.gametest.api.GameTest import dan200.computercraft.shared.ModRegistry import dan200.computercraft.shared.media.items.PrintoutData import dan200.computercraft.shared.peripheral.printer.PrinterBlock +import dan200.computercraft.shared.peripheral.printer.PrinterBlockEntity import dan200.computercraft.shared.peripheral.printer.PrinterPeripheral import dan200.computercraft.shared.util.DataComponentUtil import net.minecraft.core.BlockPos import net.minecraft.core.component.DataComponents -import net.minecraft.gametest.framework.GameTest import net.minecraft.gametest.framework.GameTestHelper import net.minecraft.network.chat.Component import net.minecraft.world.item.ItemStack @@ -34,7 +35,7 @@ class Printer_Test { // Adding items should provide power thenExecute { - val printer = helper.getBlockEntity(printerPos, ModRegistry.BlockEntities.PRINTER.get()) + val printer = helper.getBlockEntity(printerPos, PrinterBlockEntity::class.java) printer.setItem(0, ItemStack(Items.BLACK_DYE)) printer.setItem(1, ItemStack(Items.PAPER)) printer.setChanged() @@ -44,7 +45,7 @@ class Printer_Test { // And removing them should reset power. thenExecute { - val printer = helper.getBlockEntity(printerPos, ModRegistry.BlockEntities.PRINTER.get()) + val printer = helper.getBlockEntity(printerPos, PrinterBlockEntity::class.java) printer.clearContent() printer.setChanged() } @@ -60,7 +61,7 @@ class Printer_Test { val pos = BlockPos(2, 1, 2) thenExecute { - val printer = helper.getBlockEntity(pos, ModRegistry.BlockEntities.PRINTER.get()) + val printer = helper.getBlockEntity(pos, PrinterBlockEntity::class.java) printer.setItem(1, ItemStack(Items.PAPER)) printer.setChanged() @@ -92,7 +93,7 @@ class Printer_Test { val pos = BlockPos(2, 1, 2) thenExecute { - val printer = helper.getBlockEntity(pos, ModRegistry.BlockEntities.PRINTER.get()) + val printer = helper.getBlockEntity(pos, PrinterBlockEntity::class.java) val peripheral = printer.peripheral() as PrinterPeripheral // Try to print with no pages @@ -169,7 +170,7 @@ class Printer_Test { val pos = BlockPos(2, 1, 2) thenExecute { - val printer = helper.getBlockEntity(pos, ModRegistry.BlockEntities.PRINTER.get()) + val printer = helper.getBlockEntity(pos, PrinterBlockEntity::class.java) val peripheral = printer.peripheral() as PrinterPeripheral assertTrue(peripheral.newPage()) assertFalse(peripheral.endPage(), "Cannot print when full") @@ -217,7 +218,7 @@ class Printer_Test { @GameTest fun Data_fixers(helper: GameTestHelper) = helper.sequence { thenExecute { - val container = helper.getBlockEntity(BlockPos(2, 1, 2), ModRegistry.BlockEntities.PRINTER.get()) + val container = helper.getBlockEntity(BlockPos(2, 1, 2), PrinterBlockEntity::class.java) val contents = container.getItem(1) assertEquals(ModRegistry.Items.PRINTED_PAGE.get(), contents.item) diff --git a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Printout_Test.kt b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Printout_Test.kt index 8244cceb9..514f2d65e 100644 --- a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Printout_Test.kt +++ b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Printout_Test.kt @@ -5,14 +5,15 @@ package dan200.computercraft.gametest import dan200.computercraft.gametest.api.* +import dan200.computercraft.gametest.core.TestInstance import dan200.computercraft.shared.ModRegistry import dan200.computercraft.shared.media.items.PrintoutData import dan200.computercraft.shared.util.DataComponentUtil import dan200.computercraft.test.shared.ItemStackMatcher.isStack -import net.minecraft.gametest.framework.GameTest -import net.minecraft.gametest.framework.GameTestGenerator +import net.minecraft.core.Holder import net.minecraft.gametest.framework.GameTestHelper -import net.minecraft.gametest.framework.TestFunction +import net.minecraft.gametest.framework.TestData +import net.minecraft.resources.ResourceLocation import net.minecraft.world.item.Item import net.minecraft.world.item.ItemStack import net.minecraft.world.item.Items @@ -25,24 +26,26 @@ class Printout_Test { * Test printouts render correctly */ @GameTestGenerator - fun Render_in_frame(): List { - val tests = mutableListOf() + fun Render_in_frame(): List { + val tests = mutableListOf() - fun addTest(label: String, time: Long = Times.NOON, tag: String = TestTags.CLIENT) { + fun addTest(label: String, time: Int = Times.NOON, tag: String = TestTags.CLIENT) { if (!TestTags.isEnabled(tag)) return val className = this::class.java.simpleName.lowercase() val testName = "$className.render_in_frame" tests.add( - TestFunction( + TestInstance( "$testName.$label", - "$testName.$label", - testName, - Timeouts.DEFAULT, - 0, - true, - ) { renderPrintout(it, time) }, + TestData( + Holder.direct(ClientTestEnvironment(time)), + ResourceLocation.parse(testName), + Timeouts.DEFAULT, + 0, + true, + ), + ) { renderPrintout(it) }, ) } @@ -57,15 +60,9 @@ class Printout_Test { return tests } - private fun renderPrintout(helper: GameTestHelper, time: Long) = helper.sequence { - thenExecute { - helper.level.dayTime = time - helper.positionAtArmorStand() - } - + private fun renderPrintout(helper: GameTestHelper) = helper.sequence { + thenExecute { helper.positionAtArmorStand() } thenScreenshot() - - thenExecute { helper.level.dayTime = Times.NOON } } @GameTest(template = "default") diff --git a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Recipe_Test.kt b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Recipe_Test.kt index 952b3a0d2..90799cdec 100644 --- a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Recipe_Test.kt +++ b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Recipe_Test.kt @@ -5,13 +5,13 @@ package dan200.computercraft.gametest import com.mojang.authlib.GameProfile +import dan200.computercraft.gametest.api.GameTest import dan200.computercraft.gametest.api.Structures import dan200.computercraft.gametest.api.craftItem import dan200.computercraft.gametest.api.sequence import dan200.computercraft.shared.ModRegistry import net.minecraft.core.component.DataComponentPatch import net.minecraft.core.component.DataComponents -import net.minecraft.gametest.framework.GameTest import net.minecraft.gametest.framework.GameTestHelper import net.minecraft.world.item.ItemStack import net.minecraft.world.item.Items diff --git a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Relay_Test.kt b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Relay_Test.kt index 191029410..d18260930 100644 --- a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Relay_Test.kt +++ b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Relay_Test.kt @@ -5,14 +5,13 @@ package dan200.computercraft.gametest import dan200.computercraft.core.computer.ComputerSide +import dan200.computercraft.gametest.api.GameTest import dan200.computercraft.gametest.api.assertBlockHas -import dan200.computercraft.gametest.api.getBlockEntity import dan200.computercraft.gametest.api.modifyBlock import dan200.computercraft.gametest.api.sequence -import dan200.computercraft.shared.ModRegistry +import dan200.computercraft.shared.peripheral.redstone.RedstoneRelayBlockEntity import dan200.computercraft.shared.peripheral.redstone.RedstoneRelayPeripheral import net.minecraft.core.BlockPos -import net.minecraft.gametest.framework.GameTest import net.minecraft.gametest.framework.GameTestHelper import net.minecraft.world.level.block.Blocks import net.minecraft.world.level.block.LeverBlock @@ -64,7 +63,7 @@ class Relay_Test { val lamp = BlockPos(2, 1, 3) thenExecute { - val peripheral = context.getBlockEntity(BlockPos(2, 1, 2), ModRegistry.BlockEntities.REDSTONE_RELAY.get()) + val peripheral = context.getBlockEntity(BlockPos(2, 1, 2), RedstoneRelayBlockEntity::class.java) .peripheral() as RedstoneRelayPeripheral peripheral.setOutput(ComputerSide.BACK, true) @@ -83,7 +82,7 @@ class Relay_Test { */ @GameTest fun Self_output_update(context: GameTestHelper) = context.sequence { - fun relay() = context.getBlockEntity(BlockPos(2, 1, 2), ModRegistry.BlockEntities.REDSTONE_RELAY.get()) + fun relay() = context.getBlockEntity(BlockPos(2, 1, 2), RedstoneRelayBlockEntity::class.java) .peripheral() as RedstoneRelayPeripheral thenExecute { relay().setOutput(ComputerSide.BACK, true) } diff --git a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Speaker_Test.kt b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Speaker_Test.kt index 304e746fe..2f5369cbf 100644 --- a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Speaker_Test.kt +++ b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Speaker_Test.kt @@ -4,12 +4,12 @@ package dan200.computercraft.gametest +import dan200.computercraft.gametest.api.GameTest import dan200.computercraft.gametest.api.sequence import dan200.computercraft.gametest.api.thenOnComputer import dan200.computercraft.gametest.api.tryMultipleTimes import dan200.computercraft.shared.peripheral.speaker.SpeakerPeripheral import dan200.computercraft.test.core.assertArrayEquals -import net.minecraft.gametest.framework.GameTest import net.minecraft.gametest.framework.GameTestHelper import net.minecraft.sounds.SoundEvents diff --git a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Turtle_Test.kt b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Turtle_Test.kt index 47ecddd48..d5e7eb880 100644 --- a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Turtle_Test.kt +++ b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Turtle_Test.kt @@ -14,6 +14,7 @@ import dan200.computercraft.api.turtle.TurtleSide import dan200.computercraft.api.upgrades.UpgradeData import dan200.computercraft.core.apis.PeripheralAPI import dan200.computercraft.gametest.api.* +import dan200.computercraft.gametest.api.GameTest import dan200.computercraft.gametest.core.TestHooks import dan200.computercraft.mixin.gametest.GameTestHelperAccessor import dan200.computercraft.mixin.gametest.GameTestInfoAccessor @@ -25,6 +26,7 @@ import dan200.computercraft.shared.peripheral.monitor.MonitorBlock import dan200.computercraft.shared.peripheral.monitor.MonitorEdgeState import dan200.computercraft.shared.turtle.TurtleOverlay import dan200.computercraft.shared.turtle.apis.TurtleAPI +import dan200.computercraft.shared.turtle.blocks.TurtleBlockEntity import dan200.computercraft.shared.turtle.core.TurtleCraftCommand import dan200.computercraft.shared.turtle.items.TurtleItem import dan200.computercraft.shared.util.WaterloggableHelpers @@ -33,7 +35,6 @@ import dan200.computercraft.test.core.computer.LuaTaskContext import dan200.computercraft.test.core.computer.getApi import net.minecraft.core.BlockPos import net.minecraft.core.registries.Registries -import net.minecraft.gametest.framework.GameTest import net.minecraft.gametest.framework.GameTestHelper import net.minecraft.resources.ResourceLocation import net.minecraft.world.entity.EntityType @@ -46,7 +47,7 @@ import net.minecraft.world.level.block.BeehiveBlock import net.minecraft.world.level.block.Blocks import net.minecraft.world.level.block.ComposterBlock import net.minecraft.world.level.block.FenceBlock -import net.minecraft.world.level.block.entity.BlockEntityType +import net.minecraft.world.level.block.entity.SignBlockEntity import net.minecraft.world.level.block.state.properties.BlockStateProperties import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.Matchers.array @@ -107,7 +108,7 @@ class Turtle_Test { .assertArrayEquals(true, message = "Placed sign") } thenExecute { - val sign = helper.getBlockEntity(BlockPos(2, 1, 1), BlockEntityType.SIGN) + val sign = helper.getBlockEntity(BlockPos(2, 1, 1), SignBlockEntity::class.java) val lines = listOf("", "Test", "message", "") for ((i, line) in lines.withIndex()) { assertEquals(line, sign.frontText.getMessage(i, false).string, "Line $i") @@ -321,7 +322,7 @@ class Turtle_Test { helper.assertBlockPresent(Blocks.AIR, BlockPos(2, 1, 3)) helper.assertContainerExactly(BlockPos(2, 1, 2), listOf(ItemStack(Items.COBBLESTONE))) - val turtle = helper.getBlockEntity(BlockPos(2, 1, 2), ModRegistry.BlockEntities.TURTLE_NORMAL.get()).access + val turtle = helper.getBlockEntity(BlockPos(2, 1, 2), TurtleBlockEntity::class.java).access val upgrade = turtle.getUpgrade(TurtleSide.LEFT) assertEquals( helper.level.registryAccess().lookupOrThrow(ITurtleUpgrade.REGISTRY) @@ -346,7 +347,7 @@ class Turtle_Test { helper.assertBlockPresent(Blocks.AIR, BlockPos(2, 1, 3)) helper.assertContainerExactly(BlockPos(2, 1, 2), listOf(ItemStack(Items.COBBLESTONE))) - val turtle = helper.getBlockEntity(BlockPos(2, 1, 2), ModRegistry.BlockEntities.TURTLE_NORMAL.get()).access + val turtle = helper.getBlockEntity(BlockPos(2, 1, 2), TurtleBlockEntity::class.java).access val upgrade = turtle.getUpgrade(TurtleSide.LEFT) assertEquals(null, upgrade, "Upgrade broke") @@ -371,7 +372,7 @@ class Turtle_Test { helper.assertBlockPresent(Blocks.AIR, BlockPos(2, 1, 3)) helper.assertContainerExactly(BlockPos(2, 1, 2), listOf(ItemStack(Items.STONE))) - val turtle = helper.getBlockEntity(BlockPos(2, 1, 2), ModRegistry.BlockEntities.TURTLE_NORMAL.get()).access + val turtle = helper.getBlockEntity(BlockPos(2, 1, 2), TurtleBlockEntity::class.java).access val upgrade = turtle.getUpgrade(TurtleSide.LEFT) assertEquals( helper.level.registryAccess().lookupOrThrow(ITurtleUpgrade.REGISTRY) @@ -612,7 +613,7 @@ class Turtle_Test { thenExecute { helper.assertContainerExactly(BlockPos(2, 1, 3), listOf(ItemStack(Items.DIRT, 32))) - val turtle = helper.getBlockEntity(BlockPos(2, 1, 3), ModRegistry.BlockEntities.TURTLE_NORMAL.get()) + val turtle = helper.getBlockEntity(BlockPos(2, 1, 3), TurtleBlockEntity::class.java) assertEquals(1, turtle.computerID) assertEquals("turtle_test.move_preserves_state", turtle.label) assertEquals(79, turtle.access.fuelLevel) @@ -692,7 +693,7 @@ class Turtle_Test { } thenExecute { helper.assertEntityNotPresent(EntityType.SHEEP) - val count = helper.getBlockEntity(turtlePos, ModRegistry.BlockEntities.TURTLE_NORMAL.get()) + val count = helper.getBlockEntity(turtlePos, TurtleBlockEntity::class.java) .countItem(Items.WHITE_WOOL) if (count == 0) helper.fail("Expected turtle to have white wool", turtlePos) } @@ -776,7 +777,7 @@ class Turtle_Test { val upgrade = helper.level.registryAccess().lookupOrThrow(ITurtleUpgrade.REGISTRY) .getValue(ResourceLocation.withDefaultNamespace("diamond_pickaxe"))!! - val turtleBe = helper.getBlockEntity(BlockPos(1, 1, 1), ModRegistry.BlockEntities.TURTLE_NORMAL.get()) + val turtleBe = helper.getBlockEntity(BlockPos(1, 1, 1), TurtleBlockEntity::class.java) assertEquals(overlay, turtleBe.overlay) assertEquals(upgrade, turtleBe.getUpgrade(TurtleSide.LEFT)) @@ -845,7 +846,7 @@ class Turtle_Test { @GameTest fun Craft_shapeless(helper: GameTestHelper) = helper.sequence { thenExecute { - val turtle = helper.getBlockEntity(BlockPos(2, 1, 2), ModRegistry.BlockEntities.TURTLE_NORMAL.get()) + val turtle = helper.getBlockEntity(BlockPos(2, 1, 2), TurtleBlockEntity::class.java) assertTrue(TurtleCraftCommand(64).execute(turtle.access).isSuccess, "Crafting succeeded") helper.assertContainerExactly( @@ -868,7 +869,7 @@ class Turtle_Test { @GameTest fun Craft_remainder(helper: GameTestHelper) = helper.sequence { thenExecute { - val turtle = helper.getBlockEntity(BlockPos(2, 1, 2), ModRegistry.BlockEntities.TURTLE_NORMAL.get()) + val turtle = helper.getBlockEntity(BlockPos(2, 1, 2), TurtleBlockEntity::class.java) assertTrue(TurtleCraftCommand(1).execute(turtle.access).isSuccess, "Crafting succeeded") val turtleStack = ItemStack(ModRegistry.Items.TURTLE_NORMAL.get()) @@ -895,7 +896,7 @@ class Turtle_Test { for (offset in listOf(0, 1, 4, 5)) { thenExecute { val turtlePos = BlockPos(2, 1, 2) - val turtle = helper.getBlockEntity(turtlePos, ModRegistry.BlockEntities.TURTLE_NORMAL.get()) + val turtle = helper.getBlockEntity(turtlePos, TurtleBlockEntity::class.java) // Set up turtle inventory turtle.clearContent() @@ -931,7 +932,7 @@ class Turtle_Test { turtle.equipLeft().await().assertArrayEquals(true) } thenExecute { - val turtle = helper.getBlockEntity(BlockPos(2, 1, 2), ModRegistry.BlockEntities.TURTLE_NORMAL.get()) + val turtle = helper.getBlockEntity(BlockPos(2, 1, 2), TurtleBlockEntity::class.java) assertEquals( helper.level.registryAccess().lookupOrThrow(ITurtleUpgrade.REGISTRY) .getValue(ResourceLocation.withDefaultNamespace("diamond_pickaxe")), @@ -970,13 +971,13 @@ class Turtle_Test { */ @GameTest fun Can_extract_items(helper: GameTestHelper) = helper.sequence { - thenWaitUntil { helper.assertContainerEmpty(BlockPos(2, 2, 2)) } + thenWaitUntil { helper.assertContainerLikeEmpty(BlockPos(2, 2, 2)) } } /** * Render turtles as an item. */ - @ClientGameTest + @GameTest(tag = TestTags.CLIENT) fun Render_turtle_items(helper: GameTestHelper) = helper.sequence { thenExecute { helper.positionAtArmorStand() } thenScreenshot() @@ -985,7 +986,7 @@ class Turtle_Test { /** * Render turtles as a block entity. */ - @ClientGameTest + @GameTest(tag = TestTags.CLIENT) fun Render_turtle_blocks(helper: GameTestHelper) = helper.sequence { thenExecute { helper.positionAtArmorStand() } thenScreenshot() diff --git a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/api/ClientGameTest.kt b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/api/ClientGameTest.kt deleted file mode 100644 index c7b81c29e..000000000 --- a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/api/ClientGameTest.kt +++ /dev/null @@ -1,32 +0,0 @@ -// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers -// -// SPDX-License-Identifier: MPL-2.0 - -package dan200.computercraft.gametest.api - -import net.minecraft.gametest.framework.GameTest - -/** - * Similar to [GameTest], this annotation defines a method which runs under Minecraft's gametest sequence. - * - * Unlike standard game tests, client game tests are only registered when running under the Minecraft client, and run - * sequentially rather than in parallel. - */ -@Target(AnnotationTarget.FUNCTION) -@Retention(AnnotationRetention.RUNTIME) -annotation class ClientGameTest( - /** - * The template to use for this test, identical to [GameTest.template] - */ - val template: String = "", - - /** - * The timeout for this test, identical to [GameTest.timeoutTicks]. - */ - val timeoutTicks: Int = Timeouts.DEFAULT, - - /** - * The tag associated with this test, denoting when it should run. - */ - val tag: String = TestTags.CLIENT, -) diff --git a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/api/ClientTestExtensions.kt b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/api/ClientTestExtensions.kt index b605ee4c9..fd024cde9 100644 --- a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/api/ClientTestExtensions.kt +++ b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/api/ClientTestExtensions.kt @@ -15,6 +15,7 @@ import net.minecraft.core.registries.BuiltInRegistries import net.minecraft.gametest.framework.GameTestAssertException import net.minecraft.gametest.framework.GameTestHelper import net.minecraft.gametest.framework.GameTestSequence +import net.minecraft.network.chat.Component import net.minecraft.server.level.ServerPlayer import net.minecraft.world.entity.EntityType import net.minecraft.world.inventory.AbstractContainerMenu @@ -34,7 +35,7 @@ fun Minecraft.isRenderingStable(): Boolean = (this as MinecraftExtensions).`comp fun GameTestSequence.thenOnClient(task: ClientTestHelper.() -> Unit): GameTestSequence { var future: CompletableFuture? = null thenExecute { future = Minecraft.getInstance().submit { task(ClientTestHelper()) } } - thenWaitUntil { if (!future!!.isDone) throw GameTestAssertException("Not done task yet") } + thenWaitUntil { if (!future!!.isDone) fail("Not done task yet") } thenExecute { try { future!!.get() @@ -50,18 +51,18 @@ fun GameTestSequence.thenOnClient(task: ClientTestHelper.() -> Unit): GameTestSe */ fun GameTestSequence.thenScreenshot(name: String? = null, showGui: Boolean = false): GameTestSequence { val suffix = if (name == null) "" else "-$name" - val test = (this as GameTestSequenceAccessor).parent - val fullName = "${test.testName}$suffix" + val testName = (this as GameTestSequenceAccessor).parent.id().path + val fullName = testName + suffix // Wait until all chunks have been rendered and we're idle for an extended period. var counter = 0 thenWaitUntil { if (Minecraft.getInstance().isRenderingStable()) { val idleFor = ++counter - if (idleFor <= 20) throw GameTestAssertException("Only idle for $idleFor ticks") + if (idleFor <= 20) fail("Only idle for $idleFor ticks") } else { counter = 0 - throw GameTestAssertException("Waiting for client to finish rendering") + fail("Waiting for client to finish rendering") } } @@ -73,7 +74,7 @@ fun GameTestSequence.thenScreenshot(name: String? = null, showGui: Boolean = fal // Take a screenshot and wait for it to have finished. val hasScreenshot = AtomicBoolean() thenOnClient { screenshot("$fullName.png") { hasScreenshot.set(true) } } - thenWaitUntil { if (!hasScreenshot.get()) throw GameTestAssertException("Screenshot does not exist") } + thenWaitUntil { if (!hasScreenshot.get()) fail("Screenshot does not exist") } thenOnClient { minecraft.options.hideGui = false } return this @@ -91,7 +92,7 @@ fun ServerPlayer.setupForTest() { */ fun GameTestHelper.positionAtArmorStand() { val stand = getEntity(EntityType.ARMOR_STAND) - val player = level.randomPlayer ?: throw GameTestAssertException("Player does not exist") + val player = level.randomPlayer ?: fail("Player does not exist") player.setupForTest() player.connection.teleport(stand.x, stand.y, stand.z, stand.yRot, stand.xRot) @@ -102,7 +103,7 @@ fun GameTestHelper.positionAtArmorStand() { */ fun GameTestHelper.positionAt(pos: BlockPos, yRot: Float = 0.0f, xRot: Float = 0.0f) { val absolutePos = absolutePos(pos) - val player = level.randomPlayer ?: throw GameTestAssertException("Player does not exist") + val player = level.randomPlayer ?: fail("Player does not exist") player.setupForTest() player.connection.teleport(absolutePos.x + 0.5, absolutePos.y + 0.5, absolutePos.z + 0.5, yRot, xRot) @@ -127,9 +128,9 @@ class ClientTestHelper { val screen = minecraft.screen @Suppress("UNCHECKED_CAST") when { - screen == null -> throw GameTestAssertException("Expected a ${getName(type)} menu, but no screen is open") - screen !is MenuAccess<*> -> throw GameTestAssertException("Expected a ${getName(type)} menu, but a $screen is open") - screen.menu.type != type -> throw GameTestAssertException("Expected a ${getName(type)} menu, but a ${getName(screen.menu.type)} is open") + screen == null -> throw GameTestAssertException(Component.literal("Expected a ${getName(type)} menu, but no screen is open"), 0) + screen !is MenuAccess<*> -> throw GameTestAssertException(Component.literal("Expected a ${getName(type)} menu, but a $screen is open"), 0) + screen.menu.type != type -> throw GameTestAssertException(Component.literal("Expected a ${getName(type)} menu, but a ${getName(screen.menu.type)} is open"), 0) else -> return screen.menu as T } } diff --git a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/api/GameTest.kt b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/api/GameTest.kt new file mode 100644 index 000000000..54a4dbd44 --- /dev/null +++ b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/api/GameTest.kt @@ -0,0 +1,72 @@ +// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers +// +// SPDX-License-Identifier: MPL-2.0 + +package dan200.computercraft.gametest.api + +import com.mojang.serialization.MapCodec +import net.minecraft.gametest.framework.TestEnvironmentDefinition +import net.minecraft.server.level.ServerLevel + +/** + * This annotation defines a method which runs under Minecraft's gametest sequence. + * + * Unlike standard game tests, client game tests are only registered when running under the Minecraft client, and run + * sequentially rather than in parallel. + */ +@Target(AnnotationTarget.FUNCTION) +@Retention(AnnotationRetention.RUNTIME) +annotation class GameTest( + /** + * The template to use for this test. + */ + val template: String = "", + + /** + * The timeout for this test. + */ + val timeoutTicks: Int = Timeouts.DEFAULT, + + /** + * The number of ticks to wait before the test starts. + */ + val setupTicks: Int = 0, + + /** + * Whether this test is required. + */ + val required: Boolean = true, + + /** + * The tag associated with this test, denoting when it should run. + */ + val tag: String = TestTags.COMMON, +) + +/** + * A function that generates + */ +@Target(AnnotationTarget.FUNCTION) +@Retention(AnnotationRetention.RUNTIME) +annotation class GameTestGenerator + +/** + * A test environment for client game tests ([TestTags.Client]), which ensures + * the test runs in a unique batch. + */ +class ClientTestEnvironment(val time: Int = Times.NOON) : TestEnvironmentDefinition { + override fun setup(level: ServerLevel) { + level.dayTime = time.toLong() + } + + override fun teardown(level: ServerLevel) { + level.dayTime = Times.NOON.toLong() + } + + override fun codec(): MapCodec = CODEC + + companion object { + @JvmField + val CODEC: MapCodec = MapCodec.unit { ClientTestEnvironment() } + } +} diff --git a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/api/TestExtensions.kt b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/api/TestExtensions.kt index 3b079edb6..55ab50bcc 100644 --- a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/api/TestExtensions.kt +++ b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/api/TestExtensions.kt @@ -17,7 +17,11 @@ import net.minecraft.core.BlockPos import net.minecraft.core.Direction import net.minecraft.core.NonNullList import net.minecraft.core.registries.BuiltInRegistries -import net.minecraft.gametest.framework.* +import net.minecraft.gametest.framework.GameTestAssertException +import net.minecraft.gametest.framework.GameTestHelper +import net.minecraft.gametest.framework.GameTestInfo +import net.minecraft.gametest.framework.GameTestSequence +import net.minecraft.network.chat.Component import net.minecraft.resources.ResourceLocation import net.minecraft.world.Container import net.minecraft.world.InteractionHand @@ -39,6 +43,7 @@ import net.minecraft.world.phys.BlockHitResult import net.minecraft.world.phys.Vec3 import org.hamcrest.Matchers import org.hamcrest.StringDescription +import kotlin.jvm.optionals.getOrNull /** * Globally usable structures. @@ -52,9 +57,9 @@ object Structures { /** Pre-set in-game times */ object Times { - const val NOON: Long = 6000 + const val NOON: Int = 6000 - const val MIDNIGHT: Long = 18000 + const val MIDNIGHT: Int = 18000 } /** @@ -79,17 +84,25 @@ fun GameTestSequence.thenExecuteFailFast(task: Runnable): GameTestSequence = if (failure != null) throw failure } +fun GameTestInfo.getComputerLabel(name: String? = null): String = id().path + (if (name == null) "" else ".$name") + +fun GameTestSequence.getComputerLabel(name: String? = null): String = + (this as GameTestSequenceAccessor).parent.getComputerLabel(name) + /** * Wait until a computer has finished running and check it is OK. */ fun GameTestSequence.thenComputerOk(name: String? = null, marker: String = ComputerState.DONE): GameTestSequence { - val label = (this as GameTestSequenceAccessor).parent.testName + (if (name == null) "" else ".$name") + val label = getComputerLabel(name) thenWaitUntil { val computer = ComputerState.get(label) - if (computer == null || !computer.isDone(marker)) throw GameTestAssertException("Computer '$label' has not reached $marker yet.") + if (computer == null || !computer.isDone(marker)) fail("Computer '$label' has not reached $marker yet.") + } + thenExecuteFailFast { + val error = ComputerState.get(label)!!.check(marker) + if (error != null) fail(error) } - thenExecuteFailFast { ComputerState.get(label)!!.check(marker) } return this } @@ -98,7 +111,7 @@ fun GameTestSequence.thenComputerOk(name: String? = null, marker: String = Compu */ fun GameTestSequence.thenStartComputer(name: String? = null, action: suspend LuaTaskContext.() -> Unit): GameTestSequence { val test = (this as GameTestSequenceAccessor).parent - val label = test.testName + (if (name == null) "" else ".$name") + val label = getComputerLabel(name) return thenExecuteFailFast { ManagedComputers.enqueue(test, label, action) } } @@ -109,13 +122,13 @@ fun GameTestSequence.thenOnComputer(name: String? = null, action: suspend LuaTas val self = (this as GameTestSequenceAccessor) val test = self.parent - val label = test.testName + (if (name == null) "" else ".$name") + val label = getComputerLabel(name) var monitor: ManagedComputers.Monitor? = null thenExecuteFailFast { monitor = ManagedComputers.enqueue(test, label, action) } thenWaitUntil { if (!monitor!!.isFinished) { val runningFor = (test as GameTestInfoAccessor).`computercraft$getTick`() - self.lastTick - throw GameTestAssertException("Computer '$label' has not finished yet (running for $runningFor ticks).") + fail("Computer '$label' has not finished yet (running for $runningFor ticks).") } } thenExecuteFailFast { monitor!!.check() } @@ -139,24 +152,28 @@ fun GameTestHelper.immediate(run: () -> Unit) { succeed() } -/** - * A custom instance of [GameTestAssertPosException] which allows for longer error messages. - */ -private class VerboseGameTestAssertPosException(message: String, absolutePos: BlockPos, relativePos: BlockPos, tick: Long) : - GameTestAssertPosException(message, absolutePos, relativePos, tick) { - override fun getMessageToShowAtBlock(): String = message!!.lineSequence().first() -} +// Helper functions for failing tests -/** - * Fail this test. Unlike [GameTestHelper.fail], this trims the in-game error message to the first line. - */ -private fun GameTestHelper.failVerbose(message: String, pos: BlockPos): Nothing { - throw VerboseGameTestAssertPosException(message, absolutePos(pos), pos, tick) -} +/** Raise a [GameTestAssertException]. */ +fun GameTestHelper.fail(message: String): Nothing = throw assertionException(Component.literal(message)) + +/** Raise a [GameTestAssertException] at a position. */ +fun GameTestHelper.fail(message: String, pos: BlockPos): Nothing = + throw assertionException(pos, Component.literal(message)) + +/** Assert a condition is true, or raise a [GameTestAssertException] if not. */ +fun GameTestHelper.assertTrue(condition: Boolean, message: String) = assertTrue(condition, Component.literal(message)) + +/** Raise a [GameTestAssertException]. */ +fun GameTestSequence.fail(message: String): Nothing = + throw GameTestAssertException( + Component.literal(message), + ((this as GameTestSequenceAccessor).parent as GameTestInfoAccessor).`computercraft$getTick`(), + ) /** Fail with an optional context message. */ private fun GameTestHelper.fail(message: String?, detail: String, pos: BlockPos): Nothing { - failVerbose(if (message.isNullOrEmpty()) detail else "$message: $detail", pos) + fail(if (message.isNullOrEmpty()) detail else "$message: $detail", pos) } /** @@ -189,11 +206,22 @@ fun > GameTestHelper.assertBlockHas(pos: BlockPos, property: P * Get a [Container] at a given position. */ fun GameTestHelper.getContainerAt(pos: BlockPos): Container = - when (val container: BlockEntity = getBlockEntity(pos)) { + when (val container: BlockEntity? = level.getBlockEntity(absolutePos(pos))) { is Container -> container - else -> failVerbose("Expected a container at $pos, found ${getName(container.type)}", pos) + null -> fail("Expected a container at $pos, found nothing", pos) + else -> fail("Expected a container at $pos, found ${getName(container.type)}", pos) } +/** + * Assert a container is empty. Identical to [GameTestHelper.assertContainerEmpty], but works on any container BE, not + * just [BaseContainerBlockEntity]. + * + * @param pos The position of the container. + */ +fun GameTestHelper.assertContainerLikeEmpty(pos: BlockPos) { + if (!getContainerAt(pos).isEmpty) throw assertionException(pos, "test.error.expected_empty_container") +} + /** * Assert a container contains exactly these items and no more. * @@ -226,7 +254,7 @@ private fun GameTestHelper.assertContainerExactlyImpl(pos: BlockPos, container: if (slot >= 0) { val invItems = (0 until container.containerSize).map { container.getItem(it) }.dropLastWhile { it.isEmpty } - failVerbose( + fail( """ Items do not match (first mismatch at slot $slot). Expected: ${formatItems(items)} @@ -277,34 +305,22 @@ fun GameTestHelper.assertExactlyItems(vararg expected: ItemStack, message: Strin fun GameTestHelper.assertItemEntityCountIs(expected: Item, count: Int) { val actualCount = getEntities(EntityType.ITEM).sumOf { if (it.item.`is`(expected)) it.item.count else 0 } if (actualCount != count) { - throw GameTestAssertException("Expected $count ${ItemStack(expected).itemName.string} items to exist (found $actualCount)") + fail("Expected $count ${ItemStack(expected).itemName.string} items to exist (found $actualCount)") } } private fun getName(type: BlockEntityType<*>): ResourceLocation = RegistryHelper.getKeyOrThrow(BuiltInRegistries.BLOCK_ENTITY_TYPE, type) -/** - * Get a [BlockEntity] of a specific type. - */ -fun GameTestHelper.getBlockEntity(pos: BlockPos, type: BlockEntityType): T { - val tile: BlockEntity = getBlockEntity(pos) - @Suppress("UNCHECKED_CAST") - return when { - tile.type != type -> failVerbose("Expected ${getName(type)} but got ${getName(tile.type)}", pos) - else -> tile as T - } -} - /** * Get an [Entity] inside the game structure, requiring there to be a single one. */ fun GameTestHelper.getEntity(type: EntityType): T { val entities = getEntities(type) when (entities.size) { - 0 -> throw GameTestAssertException("No $type entities") + 0 -> fail("No $type entities") 1 -> return entities[0] - else -> throw GameTestAssertException("Multiple $type entities (${entities.size} in bounding box)") + else -> fail("Multiple $type entities (${entities.size} in bounding box)") } } @@ -364,10 +380,8 @@ fun GameTestHelper.craftItem(vararg items: ItemStack): ItemStack { for ((i, item) in items.withIndex()) container[i] = item val input = CraftingInput.of(3, 3, container) - val recipe = level.server.recipeManager - .getRecipeFor(RecipeType.CRAFTING, input, level) - .orElseThrow { GameTestAssertException("No recipe matches $items") } - + val recipe = level.server.recipeManager.getRecipeFor(RecipeType.CRAFTING, input, level).getOrNull() + ?: fail("No recipe matches $items") return recipe.value.assemble(input, level.registryAccess()) } diff --git a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/core/ClientTestHooks.kt b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/core/ClientTestHooks.kt index 32fc729db..7d9478e98 100644 --- a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/core/ClientTestHooks.kt +++ b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/core/ClientTestHooks.kt @@ -137,7 +137,11 @@ object ClientTestHooks { LOG.info("Server ready, starting.") val tests = GameTestRunner.Builder.fromBatches( - GameTestBatchFactory.fromTestFunction(GameTestRegistry.getAllTestFunctions(), server.overworld()), + GameTestBatchFactory.divideIntoBatches( + server.registryAccess().lookupOrThrow(Registries.TEST_INSTANCE).listElements().toList(), + GameTestBatchFactory.DIRECT, + server.overworld(), + ), server.overworld(), ) .newStructureSpawner(StructureGridSpawner(TestHooks.getTestOrigin(server), 8, false)) @@ -178,13 +182,13 @@ object ClientTestHooks { LOG.info("========= {} GAME TESTS COMPLETE ======================", testTracker.totalCount) if (testTracker.hasFailedRequired()) { LOG.info("{} required tests failed :(", testTracker.failedRequiredCount) - for (test in testTracker.failedRequired) LOG.info(" - {}", test.testName) + for (test in testTracker.failedRequired) LOG.info(" - {}", test.id()) } else { LOG.info("All {} required tests passed :)", testTracker.totalCount) } if (testTracker.hasFailedOptional()) { LOG.info("{} optional tests failed", testTracker.failedOptionalCount) - for (test in testTracker.failedOptional) LOG.info(" - {}", test.testName) + for (test in testTracker.failedOptional) LOG.info(" - {}", test.id()) } LOG.info("====================================================") diff --git a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/core/ManagedComputers.kt b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/core/ManagedComputers.kt index 6f81790a2..af6ea57cc 100644 --- a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/core/ManagedComputers.kt +++ b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/core/ManagedComputers.kt @@ -17,6 +17,7 @@ import net.minecraft.gametest.framework.GameTestAssertException import net.minecraft.gametest.framework.GameTestAssertPosException import net.minecraft.gametest.framework.GameTestInfo import net.minecraft.gametest.framework.GameTestSequence +import net.minecraft.network.chat.Component import org.slf4j.LoggerFactory import java.io.InputStream import java.util.* @@ -96,12 +97,14 @@ object ManagedComputers : ILuaMachine.Factory { private fun fail(message: String): Nothing { val computer = ServerContext.get(test.level.server).registry().computers.firstOrNull { it.label == label } + + val tick = (test as GameTestInfoAccessor).`computercraft$getTick`() if (computer == null) { - throw GameTestAssertException(message) + throw GameTestAssertException(Component.literal(message), tick) } else { val pos = computer.position - val relativePos = pos.subtract(test.structureBlockPos!!) - throw GameTestAssertPosException(message, pos, relativePos, (test as GameTestInfoAccessor).`computercraft$getTick`()) + val relativePos = pos.subtract(test.testOrigin) + throw GameTestAssertPosException(Component.literal(message), pos, relativePos, tick) } } } diff --git a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/core/TestHooks.kt b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/core/TestHooks.kt index 9f8cec778..048e942c0 100644 --- a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/core/TestHooks.kt +++ b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/core/TestHooks.kt @@ -8,19 +8,21 @@ import dan200.computercraft.api.ComputerCraftAPI import dan200.computercraft.core.ComputerContext import dan200.computercraft.core.computer.computerthread.ComputerThread import dan200.computercraft.gametest.* -import dan200.computercraft.gametest.api.ClientGameTest -import dan200.computercraft.gametest.api.TestTags -import dan200.computercraft.gametest.api.Times +import dan200.computercraft.gametest.api.* import dan200.computercraft.shared.computer.core.ServerContext import net.minecraft.core.BlockPos +import net.minecraft.core.Holder +import net.minecraft.core.registries.Registries import net.minecraft.gametest.framework.* +import net.minecraft.resources.ResourceKey +import net.minecraft.resources.ResourceLocation import net.minecraft.server.MinecraftServer import net.minecraft.server.level.ServerLevel import net.minecraft.world.level.GameRules import net.minecraft.world.level.Level import net.minecraft.world.level.LevelAccessor import net.minecraft.world.level.block.Blocks -import net.minecraft.world.level.block.entity.StructureBlockEntity +import net.minecraft.world.level.block.entity.BlockEntityType import net.minecraft.world.level.block.state.BlockState import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager import net.minecraft.world.phys.Vec3 @@ -36,11 +38,14 @@ import java.nio.file.Path import java.nio.file.Paths import java.util.function.Consumer import javax.xml.parsers.ParserConfigurationException +import kotlin.jvm.optionals.getOrNull object TestHooks { @JvmField val LOG: Logger = LoggerFactory.getLogger(TestHooks::class.java) + const val MOD_ID: String = "cctest" + @JvmStatic val sourceDir: Path = Paths.get(System.getProperty("cctest.sources")).normalize().toAbsolutePath() @@ -51,7 +56,7 @@ object TestHooks { fun init() { ServerContext.luaMachine = ManagedComputers ComputerCraftAPI.registerAPIFactory(::TestAPI) - StructureUtils.testStructuresDir = sourceDir.resolve("structures").toString() + StructureUtils.testStructuresDir = sourceDir.resolve("structures") // Set up our test reporter if configured. val outputPath = System.getProperty("cctest.gametest-report") @@ -78,14 +83,14 @@ object TestHooks { fun onServerStarted(server: MinecraftServer) { val rules = server.gameRules rules.getRule(GameRules.RULE_DAYLIGHT).set(false, server) - server.overworld().dayTime = Times.NOON + server.overworld().dayTime = Times.NOON.toLong() LOG.info("Cleaning up after last run") val level = server.overworld() - StructureUtils.findStructureBlocks(getTestOrigin(server), 200, level).forEach { pos -> - val structure = level.getBlockEntity(pos) as StructureBlockEntity? ?: return@forEach - StructureUtils.clearSpaceForStructure(StructureUtils.getStructureBoundingBox(structure), level) + StructureUtils.findTestBlocks(getTestOrigin(server), 200, level).forEach { pos -> + val test = level.getBlockEntity(pos, BlockEntityType.TEST_INSTANCE_BLOCK).getOrNull() ?: return@forEach + StructureUtils.clearSpaceForStructure(test.structureBoundingBox, level) } structureManager = server.structureManager @@ -125,66 +130,57 @@ object TestHooks { Turtle_Test::class.java, ) + private val defaultEnvironment: Holder = Holder.direct(TestEnvironmentDefinition.AllOf()) + /** - * Register all of our gametests. - * - * This is super nasty, as it bypasses any loader-specific hooks for registering tests. However, it makes it much - * easier to ensure consistent behaviour between loaders (namely making [GameTest.template] point to a - * structure rather than a per-test-class one), as well as supporting our custom client tests. - * - * @param fallbackRegister A fallback function which registers non-test methods (such as [BeforeBatch]). This - * should be [GameTestRegistry.register] or equivalent. + * Gather a list of all game tests. */ @JvmStatic - fun loadTests(fallbackRegister: Consumer) { + fun loadTests(): List { + val tests = mutableListOf() for (testClass in testClasses) { for (method in testClass.declaredMethods) { - registerTest(testClass, method, fallbackRegister) + registerTest(testClass, method, tests) } } + return tests } - private fun registerTest(testClass: Class<*>, method: Method, fallbackRegister: Consumer) { + private fun registerTest(testClass: Class<*>, method: Method, out: MutableList) { val className = testClass.simpleName.lowercase() val testName = className + "." + method.name.lowercase() method.getAnnotation(GameTest::class.java)?.let { testInfo -> - if (!TestTags.isEnabled(TestTags.COMMON)) return - - GameTestRegistry.getAllTestFunctions().add( - TestFunction( - testInfo.batch, testName, testInfo.template.ifEmpty { testName }, - StructureUtils.getRotationForRotationSteps(testInfo.rotationSteps), - testInfo.timeoutTicks, - testInfo.setupTicks, - testInfo.required, testInfo.manualOnly, - testInfo.attempts, - testInfo.requiredSuccesses, - testInfo.skyAccess, - ) { value -> safeInvoke(method, value) }, - ) - GameTestRegistry.getAllTestClassNames().add(testClass.simpleName) - return - } - - method.getAnnotation(ClientGameTest::class.java)?.let { testInfo -> if (!TestTags.isEnabled(testInfo.tag)) return - GameTestRegistry.getAllTestFunctions().add( - TestFunction( + val environment: Holder = when (testInfo.tag) { + TestTags.COMMON -> defaultEnvironment + else -> Holder.direct(ClientTestEnvironment()) + } + + out.add( + TestInstance( testName, - testName, - testInfo.template.ifEmpty { testName }, - testInfo.timeoutTicks, - 0, - true, + TestData( + environment, + ResourceLocation.parse(testInfo.template.ifEmpty { testName }), + testInfo.timeoutTicks, + testInfo.setupTicks, + testInfo.required, + ), ) { value -> safeInvoke(method, value) }, ) - GameTestRegistry.getAllTestClassNames().add(testClass.simpleName) return } - fallbackRegister.accept(method) + if (method.getAnnotation(GameTestGenerator::class.java) != null) { + val instance = + if (Modifier.isStatic(method.modifiers)) null else method.declaringClass.getConstructor().newInstance() + + @Suppress("UNCHECKED_CAST") + val tests = method.invoke(instance) as Collection + out.addAll(tests) + } } private fun safeInvoke(method: Method, value: Any) { @@ -251,3 +247,15 @@ private object ComputerThreadReflection { return isFullyIdle.invokeExact(computerThread) as Boolean } } + +class TestInstance( + val name: String, + val data: TestData>, + val function: Consumer, +) { + val id: ResourceLocation = ResourceLocation.fromNamespaceAndPath(TestHooks.MOD_ID, name) + + val instance: GameTestInstance + get() = + FunctionGameTestInstance(ResourceKey.create>(Registries.TEST_FUNCTION, id), data) +} diff --git a/projects/common/src/testMod/resources/computercraft-gametest.mixins.json b/projects/common/src/testMod/resources/computercraft-gametest.mixins.json index a82eb7496..dc6f7cb5d 100644 --- a/projects/common/src/testMod/resources/computercraft-gametest.mixins.json +++ b/projects/common/src/testMod/resources/computercraft-gametest.mixins.json @@ -14,8 +14,7 @@ "GameTestServerMixin", "NbtUtilsMixin", "StructureTemplateAccessor", - "StructureTemplateManagerMixin", - "TestCommandAccessor" + "StructureTemplateManagerMixin" ], "client": [ "client.MinecraftMixin", diff --git a/projects/common/src/testMod/resources/data/cctest/structures/disk_drive_test.can_extract_items.snbt b/projects/common/src/testMod/resources/data/cctest/structures/disk_drive_test.can_extract_items.snbt index 879edfc4e..8bbe86558 100644 --- a/projects/common/src/testMod/resources/data/cctest/structures/disk_drive_test.can_extract_items.snbt +++ b/projects/common/src/testMod/resources/data/cctest/structures/disk_drive_test.can_extract_items.snbt @@ -27,13 +27,13 @@ {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: [2, 1, 2], state: "minecraft:hopper{enabled:false,facing:down}", nbt: {Items: [], TransferCooldown: 0, id: "minecraft:hopper"}}, + {pos: [2, 1, 2], state: "minecraft:hopper{enabled:true,facing:down}", nbt: {Items: [], TransferCooldown: 0, id: "minecraft:hopper"}}, {pos: [2, 2, 2], state: "computercraft:disk_drive{facing:north,state:full}", nbt: {Item: {Count: 1b, id: "computercraft:computer_normal"}, id: "computercraft:disk_drive"}} ], entities: [], palette: [ "minecraft:polished_andesite", - "minecraft:hopper{enabled:false,facing:down}", + "minecraft:hopper{enabled:true,facing:down}", "computercraft:disk_drive{facing:north,state:full}" ] } diff --git a/projects/common/src/testMod/resources/data/cctest/structures/disk_drive_test.can_insert_items.snbt b/projects/common/src/testMod/resources/data/cctest/structures/disk_drive_test.can_insert_items.snbt index ea138e3cd..89da949d7 100644 --- a/projects/common/src/testMod/resources/data/cctest/structures/disk_drive_test.can_insert_items.snbt +++ b/projects/common/src/testMod/resources/data/cctest/structures/disk_drive_test.can_insert_items.snbt @@ -28,12 +28,12 @@ {pos: [4, 0, 3], state: "minecraft:polished_andesite"}, {pos: [4, 0, 4], state: "minecraft:polished_andesite"}, {pos: [2, 1, 2], state: "computercraft:disk_drive{facing:north,state:empty}", nbt: {id: "computercraft:disk_drive"}}, - {pos: [2, 2, 2], state: "minecraft:hopper{enabled:false,facing:down}", nbt: {Items: [{Count: 1b, Slot: 0b, id: "computercraft:computer_normal"}], TransferCooldown: 0, id: "minecraft:hopper"}} + {pos: [2, 2, 2], state: "minecraft:hopper{enabled:true,facing:down}", nbt: {Items: [{Count: 1b, Slot: 0b, id: "computercraft:computer_normal"}], TransferCooldown: 0, id: "minecraft:hopper"}} ], entities: [], palette: [ "minecraft:polished_andesite", "computercraft:disk_drive{facing:north,state:empty}", - "minecraft:hopper{enabled:false,facing:down}" + "minecraft:hopper{enabled:true,facing:down}" ] } diff --git a/projects/common/src/testMod/resources/data/cctest/structures/printer_test.can_extract_items.snbt b/projects/common/src/testMod/resources/data/cctest/structures/printer_test.can_extract_items.snbt index 463ac6054..145db13f4 100644 --- a/projects/common/src/testMod/resources/data/cctest/structures/printer_test.can_extract_items.snbt +++ b/projects/common/src/testMod/resources/data/cctest/structures/printer_test.can_extract_items.snbt @@ -27,13 +27,13 @@ {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: [2, 1, 2], state: "minecraft:hopper{enabled:false,facing:down}", nbt: {Items: [], TransferCooldown: 0, id: "minecraft:hopper"}}, + {pos: [2, 1, 2], state: "minecraft:hopper{enabled:true,facing:down}", nbt: {Items: [], TransferCooldown: 0, id: "minecraft:hopper"}}, {pos: [2, 2, 2], state: "computercraft:printer{bottom:true,facing:north,top:false}", nbt: {Items: [{Count: 1b, Slot: 7b, id: "computercraft:printed_page", tag: {Color0: "fffffffffffffffffffffffff", Color1: "fffffffffffffffffffffffff", Color10: "fffffffffffffffffffffffff", Color11: "fffffffffffffffffffffffff", Color12: "fffffffffffffffffffffffff", Color13: "fffffffffffffffffffffffff", Color14: "fffffffffffffffffffffffff", Color15: "fffffffffffffffffffffffff", Color16: "fffffffffffffffffffffffff", Color17: "fffffffffffffffffffffffff", Color18: "fffffffffffffffffffffffff", Color19: "fffffffffffffffffffffffff", Color2: "fffffffffffffffffffffffff", Color20: "fffffffffffffffffffffffff", Color3: "fffffffffffffffffffffffff", Color4: "fffffffffffffffffffffffff", Color5: "fffffffffffffffffffffffff", Color6: "fffffffffffffffffffffffff", Color7: "fffffffffffffffffffffffff", Color8: "fffffffffffffffffffffffff", Color9: "fffffffffffffffffffffffff", Pages: 1, Text0: " ", Text1: " ", Text10: " ", Text11: " ", Text12: " ", Text13: " ", Text14: " ", Text15: " ", Text16: " ", Text17: " ", Text18: " ", Text19: " ", Text2: " ", Text20: " ", Text3: " ", Text4: " ", Text5: " ", Text6: " ", Text7: " ", Text8: " ", Text9: " ", Title: ""}}], PageTitle: "", Printing: 0b, id: "computercraft:printer", term_bgColour: 15, term_cursorBlink: 0b, term_cursorX: 0, term_cursorY: 0, term_palette: [I; 1118481, 13388876, 5744206, 8349260, 3368652, 11691749, 5020082, 10066329, 5000268, 15905484, 8375321, 14605932, 10072818, 15040472, 15905331, 15790320], term_textBgColour_0: "fffffffffffffffffffffffff", term_textBgColour_1: "fffffffffffffffffffffffff", term_textBgColour_10: "fffffffffffffffffffffffff", term_textBgColour_11: "fffffffffffffffffffffffff", term_textBgColour_12: "fffffffffffffffffffffffff", term_textBgColour_13: "fffffffffffffffffffffffff", term_textBgColour_14: "fffffffffffffffffffffffff", term_textBgColour_15: "fffffffffffffffffffffffff", term_textBgColour_16: "fffffffffffffffffffffffff", term_textBgColour_17: "fffffffffffffffffffffffff", term_textBgColour_18: "fffffffffffffffffffffffff", term_textBgColour_19: "fffffffffffffffffffffffff", term_textBgColour_2: "fffffffffffffffffffffffff", term_textBgColour_20: "fffffffffffffffffffffffff", term_textBgColour_3: "fffffffffffffffffffffffff", term_textBgColour_4: "fffffffffffffffffffffffff", term_textBgColour_5: "fffffffffffffffffffffffff", term_textBgColour_6: "fffffffffffffffffffffffff", term_textBgColour_7: "fffffffffffffffffffffffff", term_textBgColour_8: "fffffffffffffffffffffffff", term_textBgColour_9: "fffffffffffffffffffffffff", term_textColour: 15, term_textColour_0: "fffffffffffffffffffffffff", term_textColour_1: "fffffffffffffffffffffffff", term_textColour_10: "fffffffffffffffffffffffff", term_textColour_11: "fffffffffffffffffffffffff", term_textColour_12: "fffffffffffffffffffffffff", term_textColour_13: "fffffffffffffffffffffffff", term_textColour_14: "fffffffffffffffffffffffff", term_textColour_15: "fffffffffffffffffffffffff", term_textColour_16: "fffffffffffffffffffffffff", term_textColour_17: "fffffffffffffffffffffffff", term_textColour_18: "fffffffffffffffffffffffff", term_textColour_19: "fffffffffffffffffffffffff", term_textColour_2: "fffffffffffffffffffffffff", term_textColour_20: "fffffffffffffffffffffffff", term_textColour_3: "fffffffffffffffffffffffff", term_textColour_4: "fffffffffffffffffffffffff", term_textColour_5: "fffffffffffffffffffffffff", term_textColour_6: "fffffffffffffffffffffffff", term_textColour_7: "fffffffffffffffffffffffff", term_textColour_8: "fffffffffffffffffffffffff", term_textColour_9: "fffffffffffffffffffffffff", term_text_0: " ", term_text_1: " ", term_text_10: " ", term_text_11: " ", term_text_12: " ", term_text_13: " ", term_text_14: " ", term_text_15: " ", term_text_16: " ", term_text_17: " ", term_text_18: " ", term_text_19: " ", term_text_2: " ", term_text_20: " ", term_text_3: " ", term_text_4: " ", term_text_5: " ", term_text_6: " ", term_text_7: " ", term_text_8: " ", term_text_9: " "}} ], entities: [], palette: [ "minecraft:polished_andesite", - "minecraft:hopper{enabled:false,facing:down}", + "minecraft:hopper{enabled:true,facing:down}", "computercraft:printer{bottom:true,facing:north,top:false}" ] } diff --git a/projects/common/src/testMod/resources/data/cctest/structures/printer_test.can_insert_items.snbt b/projects/common/src/testMod/resources/data/cctest/structures/printer_test.can_insert_items.snbt index ee28d339e..961733d2b 100644 --- a/projects/common/src/testMod/resources/data/cctest/structures/printer_test.can_insert_items.snbt +++ b/projects/common/src/testMod/resources/data/cctest/structures/printer_test.can_insert_items.snbt @@ -29,14 +29,14 @@ {pos: [4, 0, 4], state: "minecraft:polished_andesite"}, {pos: [1, 1, 2], state: "computercraft:printer{bottom:false,facing:north,top:false}", nbt: {Items: [], PageTitle: "", Printing: 0b, id: "computercraft:printer", term_bgColour: 15, term_cursorBlink: 0b, term_cursorX: 0, term_cursorY: 0, term_palette: [I; 1118481, 13388876, 5744206, 8349260, 3368652, 11691749, 5020082, 10066329, 5000268, 15905484, 8375321, 14605932, 10072818, 15040472, 15905331, 15790320], term_textBgColour_0: "fffffffffffffffffffffffff", term_textBgColour_1: "fffffffffffffffffffffffff", term_textBgColour_10: "fffffffffffffffffffffffff", term_textBgColour_11: "fffffffffffffffffffffffff", term_textBgColour_12: "fffffffffffffffffffffffff", term_textBgColour_13: "fffffffffffffffffffffffff", term_textBgColour_14: "fffffffffffffffffffffffff", term_textBgColour_15: "fffffffffffffffffffffffff", term_textBgColour_16: "fffffffffffffffffffffffff", term_textBgColour_17: "fffffffffffffffffffffffff", term_textBgColour_18: "fffffffffffffffffffffffff", term_textBgColour_19: "fffffffffffffffffffffffff", term_textBgColour_2: "fffffffffffffffffffffffff", term_textBgColour_20: "fffffffffffffffffffffffff", term_textBgColour_3: "fffffffffffffffffffffffff", term_textBgColour_4: "fffffffffffffffffffffffff", term_textBgColour_5: "fffffffffffffffffffffffff", term_textBgColour_6: "fffffffffffffffffffffffff", term_textBgColour_7: "fffffffffffffffffffffffff", term_textBgColour_8: "fffffffffffffffffffffffff", term_textBgColour_9: "fffffffffffffffffffffffff", term_textColour: 0, term_textColour_0: "0000000000000000000000000", term_textColour_1: "0000000000000000000000000", term_textColour_10: "0000000000000000000000000", term_textColour_11: "0000000000000000000000000", term_textColour_12: "0000000000000000000000000", term_textColour_13: "0000000000000000000000000", term_textColour_14: "0000000000000000000000000", term_textColour_15: "0000000000000000000000000", term_textColour_16: "0000000000000000000000000", term_textColour_17: "0000000000000000000000000", term_textColour_18: "0000000000000000000000000", term_textColour_19: "0000000000000000000000000", term_textColour_2: "0000000000000000000000000", term_textColour_20: "0000000000000000000000000", term_textColour_3: "0000000000000000000000000", term_textColour_4: "0000000000000000000000000", term_textColour_5: "0000000000000000000000000", term_textColour_6: "0000000000000000000000000", term_textColour_7: "0000000000000000000000000", term_textColour_8: "0000000000000000000000000", term_textColour_9: "0000000000000000000000000", term_text_0: " ", term_text_1: " ", term_text_10: " ", term_text_11: " ", term_text_12: " ", term_text_13: " ", term_text_14: " ", term_text_15: " ", term_text_16: " ", term_text_17: " ", term_text_18: " ", term_text_19: " ", term_text_2: " ", term_text_20: " ", term_text_3: " ", term_text_4: " ", term_text_5: " ", term_text_6: " ", term_text_7: " ", term_text_8: " ", term_text_9: " "}}, {pos: [3, 1, 2], state: "computercraft:printer{bottom:false,facing:north,top:false}", nbt: {Items: [], PageTitle: "", Printing: 0b, id: "computercraft:printer", term_bgColour: 15, term_cursorBlink: 0b, term_cursorX: 0, term_cursorY: 0, term_palette: [I; 1118481, 13388876, 5744206, 8349260, 3368652, 11691749, 5020082, 10066329, 5000268, 15905484, 8375321, 14605932, 10072818, 15040472, 15905331, 15790320], term_textBgColour_0: "fffffffffffffffffffffffff", term_textBgColour_1: "fffffffffffffffffffffffff", term_textBgColour_10: "fffffffffffffffffffffffff", term_textBgColour_11: "fffffffffffffffffffffffff", term_textBgColour_12: "fffffffffffffffffffffffff", term_textBgColour_13: "fffffffffffffffffffffffff", term_textBgColour_14: "fffffffffffffffffffffffff", term_textBgColour_15: "fffffffffffffffffffffffff", term_textBgColour_16: "fffffffffffffffffffffffff", term_textBgColour_17: "fffffffffffffffffffffffff", term_textBgColour_18: "fffffffffffffffffffffffff", term_textBgColour_19: "fffffffffffffffffffffffff", term_textBgColour_2: "fffffffffffffffffffffffff", term_textBgColour_20: "fffffffffffffffffffffffff", term_textBgColour_3: "fffffffffffffffffffffffff", term_textBgColour_4: "fffffffffffffffffffffffff", term_textBgColour_5: "fffffffffffffffffffffffff", term_textBgColour_6: "fffffffffffffffffffffffff", term_textBgColour_7: "fffffffffffffffffffffffff", term_textBgColour_8: "fffffffffffffffffffffffff", term_textBgColour_9: "fffffffffffffffffffffffff", term_textColour: 0, term_textColour_0: "0000000000000000000000000", term_textColour_1: "0000000000000000000000000", term_textColour_10: "0000000000000000000000000", term_textColour_11: "0000000000000000000000000", term_textColour_12: "0000000000000000000000000", term_textColour_13: "0000000000000000000000000", term_textColour_14: "0000000000000000000000000", term_textColour_15: "0000000000000000000000000", term_textColour_16: "0000000000000000000000000", term_textColour_17: "0000000000000000000000000", term_textColour_18: "0000000000000000000000000", term_textColour_19: "0000000000000000000000000", term_textColour_2: "0000000000000000000000000", term_textColour_20: "0000000000000000000000000", term_textColour_3: "0000000000000000000000000", term_textColour_4: "0000000000000000000000000", term_textColour_5: "0000000000000000000000000", term_textColour_6: "0000000000000000000000000", term_textColour_7: "0000000000000000000000000", term_textColour_8: "0000000000000000000000000", term_textColour_9: "0000000000000000000000000", term_text_0: " ", term_text_1: " ", term_text_10: " ", term_text_11: " ", term_text_12: " ", term_text_13: " ", term_text_14: " ", term_text_15: " ", term_text_16: " ", term_text_17: " ", term_text_18: " ", term_text_19: " ", term_text_2: " ", term_text_20: " ", term_text_3: " ", term_text_4: " ", term_text_5: " ", term_text_6: " ", term_text_7: " ", term_text_8: " ", term_text_9: " "}}, - {pos: [3, 1, 3], state: "minecraft:hopper{enabled:false,facing:north}", nbt: {Items: [{Count: 1b, Slot: 0b, id: "minecraft:black_dye"}, {Count: 1b, Slot: 1b, id: "minecraft:paper"}], TransferCooldown: 0, id: "minecraft:hopper"}}, - {pos: [1, 2, 2], state: "minecraft:hopper{enabled:false,facing:down}", nbt: {Items: [{Count: 1b, Slot: 0b, id: "minecraft:black_dye"}, {Count: 1b, Slot: 1b, id: "minecraft:paper"}], TransferCooldown: 0, id: "minecraft:hopper"}} + {pos: [3, 1, 3], state: "minecraft:hopper{enabled:true,facing:north}", nbt: {Items: [{Count: 1b, Slot: 0b, id: "minecraft:black_dye"}, {Count: 1b, Slot: 1b, id: "minecraft:paper"}], TransferCooldown: 0, id: "minecraft:hopper"}}, + {pos: [1, 2, 2], state: "minecraft:hopper{enabled:true,facing:down}", nbt: {Items: [{Count: 1b, Slot: 0b, id: "minecraft:black_dye"}, {Count: 1b, Slot: 1b, id: "minecraft:paper"}], TransferCooldown: 0, id: "minecraft:hopper"}} ], entities: [], palette: [ "minecraft:polished_andesite", "computercraft:printer{bottom:false,facing:north,top:false}", - "minecraft:hopper{enabled:false,facing:north}", - "minecraft:hopper{enabled:false,facing:down}" + "minecraft:hopper{enabled:true,facing:north}", + "minecraft:hopper{enabled:true,facing:down}" ] } diff --git a/projects/common/src/testMod/resources/data/cctest/structures/turtle_test.can_extract_items.snbt b/projects/common/src/testMod/resources/data/cctest/structures/turtle_test.can_extract_items.snbt index 9892011e1..83362654f 100644 --- a/projects/common/src/testMod/resources/data/cctest/structures/turtle_test.can_extract_items.snbt +++ b/projects/common/src/testMod/resources/data/cctest/structures/turtle_test.can_extract_items.snbt @@ -27,13 +27,13 @@ {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: [2, 1, 2], state: "minecraft:hopper{enabled:false,facing:down}", nbt: {Items: [], TransferCooldown: 0, id: "minecraft:hopper"}}, + {pos: [2, 1, 2], state: "minecraft:hopper{enabled:true,facing:down}", nbt: {Items: [], TransferCooldown: 0, id: "minecraft:hopper"}}, {pos: [2, 2, 2], state: "computercraft:turtle_normal{facing:south,waterlogged:false}", nbt: {Items: [{Count: 1b, Slot: 1b, id: "computercraft:computer_normal"}], id: "computercraft:turtle_normal"}} ], entities: [], palette: [ "minecraft:polished_andesite", - "minecraft:hopper{enabled:false,facing:down}", + "minecraft:hopper{enabled:true,facing:down}", "computercraft:turtle_normal{facing:south,waterlogged:false}" ] } diff --git a/projects/common/src/testMod/resources/data/cctest/structures/turtle_test.can_insert_items.snbt b/projects/common/src/testMod/resources/data/cctest/structures/turtle_test.can_insert_items.snbt index 20603c9fe..90a6be603 100644 --- a/projects/common/src/testMod/resources/data/cctest/structures/turtle_test.can_insert_items.snbt +++ b/projects/common/src/testMod/resources/data/cctest/structures/turtle_test.can_insert_items.snbt @@ -28,12 +28,12 @@ {pos: [4, 0, 3], state: "minecraft:polished_andesite"}, {pos: [4, 0, 4], state: "minecraft:polished_andesite"}, {pos: [2, 1, 2], state: "computercraft:turtle_normal{facing:south,waterlogged:false}", nbt: {id: "computercraft:turtle_normal"}}, - {pos: [2, 2, 2], state: "minecraft:hopper{enabled:false,facing:down}", nbt: {Items: [{Count: 1b, Slot: 0b, id: "computercraft:computer_normal"}], TransferCooldown: 0, id: "minecraft:hopper"}} + {pos: [2, 2, 2], state: "minecraft:hopper{enabled:true,facing:down}", nbt: {Items: [{Count: 1b, Slot: 0b, id: "computercraft:computer_normal"}], TransferCooldown: 0, id: "minecraft:hopper"}} ], entities: [], palette: [ "minecraft:polished_andesite", "computercraft:turtle_normal{facing:south,waterlogged:false}", - "minecraft:hopper{enabled:false,facing:down}" + "minecraft:hopper{enabled:true,facing:down}" ] } diff --git a/projects/fabric-api/src/client/java/dan200/computercraft/api/client/FabricComputerCraftAPIClient.java b/projects/fabric-api/src/client/java/dan200/computercraft/api/client/FabricComputerCraftAPIClient.java index 538e5803c..ac5e1b4d5 100644 --- a/projects/fabric-api/src/client/java/dan200/computercraft/api/client/FabricComputerCraftAPIClient.java +++ b/projects/fabric-api/src/client/java/dan200/computercraft/api/client/FabricComputerCraftAPIClient.java @@ -4,8 +4,8 @@ package dan200.computercraft.api.client; -import dan200.computercraft.api.client.turtle.RegisterTurtleUpgradeModeller; -import dan200.computercraft.api.client.turtle.TurtleUpgradeModeller; +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; @@ -20,20 +20,19 @@ public final class FabricComputerCraftAPIClient { } /** - * Register a {@link TurtleUpgradeModeller} for a class of turtle upgrades. + * Register a {@link TurtleUpgradeModel} for a class of turtle upgrades. *

* This may be called at any point after registry creation, though it is recommended to call it within your client * setup step. *

- * This method may be used as a {@link RegisterTurtleUpgradeModeller}, for - * convenient use in multi-loader code. + * This method may be used as a {@link RegisterTurtleUpgradeModel}, for convenient use in multi-loader code. * - * @param type The turtle upgrade type. - * @param modeller The upgrade modeller. - * @param The type of the turtle upgrade. + * @param type The turtle upgrade type. + * @param model The upgrade model. + * @param The type of the turtle upgrade. */ - public static void registerTurtleUpgradeModeller(UpgradeType type, TurtleUpgradeModeller modeller) { - getInstance().registerTurtleUpgradeModeller(type, modeller); + public static void registerTurtleUpgradeModeller(UpgradeType type, TurtleUpgradeModel.Unbaked model) { + getInstance().registerTurtleUpgradeModeller(type, model); } private static FabricComputerCraftAPIClientService getInstance() { diff --git a/projects/fabric-api/src/client/java/dan200/computercraft/impl/client/FabricComputerCraftAPIClientService.java b/projects/fabric-api/src/client/java/dan200/computercraft/impl/client/FabricComputerCraftAPIClientService.java index 49b0d236f..b1e421672 100644 --- a/projects/fabric-api/src/client/java/dan200/computercraft/impl/client/FabricComputerCraftAPIClientService.java +++ b/projects/fabric-api/src/client/java/dan200/computercraft/impl/client/FabricComputerCraftAPIClientService.java @@ -4,7 +4,7 @@ package dan200.computercraft.impl.client; -import dan200.computercraft.api.client.turtle.TurtleUpgradeModeller; +import dan200.computercraft.api.client.turtle.TurtleUpgradeModel; import dan200.computercraft.api.turtle.ITurtleUpgrade; import dan200.computercraft.api.upgrades.UpgradeType; import dan200.computercraft.impl.Services; @@ -23,7 +23,7 @@ public interface FabricComputerCraftAPIClientService { return instance == null ? Services.raise(FabricComputerCraftAPIClientService.class, Instance.ERROR) : instance; } - void registerTurtleUpgradeModeller(UpgradeType type, TurtleUpgradeModeller modeller); + void registerTurtleUpgradeModeller(UpgradeType type, TurtleUpgradeModel.Unbaked model); final class Instance { static final @Nullable FabricComputerCraftAPIClientService INSTANCE; diff --git a/projects/fabric/src/client/java/dan200/computercraft/client/ComputerCraftClient.java b/projects/fabric/src/client/java/dan200/computercraft/client/ComputerCraftClient.java index 0066a58a1..7b5eeda1f 100644 --- a/projects/fabric/src/client/java/dan200/computercraft/client/ComputerCraftClient.java +++ b/projects/fabric/src/client/java/dan200/computercraft/client/ComputerCraftClient.java @@ -9,7 +9,11 @@ import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.builder.RequiredArgumentBuilder; import dan200.computercraft.api.ComputerCraftAPI; import dan200.computercraft.api.client.FabricComputerCraftAPIClient; +import dan200.computercraft.api.client.StandaloneModel; +import dan200.computercraft.api.client.turtle.TurtleUpgradeModel; +import dan200.computercraft.api.turtle.ITurtleUpgrade; import dan200.computercraft.client.model.ExtraModels; +import dan200.computercraft.client.platform.FabricModelKey; import dan200.computercraft.core.util.Nullability; import dan200.computercraft.impl.Services; import dan200.computercraft.shared.CommonHooks; @@ -25,6 +29,8 @@ import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; import net.fabricmc.fabric.api.client.item.v1.ItemTooltipCallback; import net.fabricmc.fabric.api.client.model.loading.v1.PreparableModelLoadingPlugin; +import net.fabricmc.fabric.api.client.model.loading.v1.SimpleUnbakedExtraModel; +import net.fabricmc.fabric.api.client.model.loading.v1.UnbakedExtraModel; import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents; import net.fabricmc.loader.api.FabricLoader; @@ -36,6 +42,7 @@ import net.minecraft.client.renderer.RenderType; import net.minecraft.client.renderer.item.ItemModels; import net.minecraft.client.renderer.item.properties.conditional.ConditionalItemModelProperties; import net.minecraft.client.renderer.item.properties.select.SelectItemModelProperties; +import net.minecraft.client.resources.model.ModelBaker; import net.minecraft.world.phys.BlockHitResult; import java.nio.file.Files; @@ -54,7 +61,7 @@ public class ComputerCraftClient { } ClientRegistry.register(); - ClientRegistry.registerTurtleModellers(FabricComputerCraftAPIClient::registerTurtleUpgradeModeller); + ClientRegistry.registerTurtleModels(FabricComputerCraftAPIClient::registerTurtleUpgradeModeller); ClientRegistry.registerMenuScreens(MenuScreens::register); ClientRegistry.registerItemModels(ItemModels.ID_MAPPER::put); ClientRegistry.registerItemColours(ItemTintSources.ID_MAPPER::put); @@ -63,7 +70,11 @@ public class ComputerCraftClient { PreparableModelLoadingPlugin.register( (resources, executor) -> CompletableFuture.supplyAsync(() -> ExtraModels.loadAll(resources), executor), - (state, context) -> ClientRegistry.registerExtraModels(context::addModels, state) + (state, context) -> ClientRegistry.registerExtraModels( + (key, model) -> context.addModel(FabricModelKey.key(key), new SimpleUnbakedExtraModel<>(model, StandaloneModel::of)), + (key, model) -> context.addModel(FabricModelKey.erased(key), new TurtleModelWrapper<>(model)), + state + ) ); BlockRenderLayerMap.INSTANCE.putBlock(ModRegistry.Blocks.COMPUTER_NORMAL.get(), RenderType.cutout()); @@ -100,4 +111,18 @@ public class ComputerCraftClient { ((FabricConfigFile) ConfigSpec.clientSpec).load(FabricLoader.getInstance().getConfigDir().resolve(ComputerCraftAPI.MOD_ID + "-client.toml")); } + + private record TurtleModelWrapper( + TurtleUpgradeModel.Unbaked model + ) implements UnbakedExtraModel> { + @Override + public TurtleUpgradeModel bake(ModelBaker baker) { + return model().bake(baker); + } + + @Override + public void resolveDependencies(Resolver resolver) { + model().resolveDependencies(resolver); + } + } } diff --git a/projects/fabric/src/client/java/dan200/computercraft/client/platform/ClientPlatformHelperImpl.java b/projects/fabric/src/client/java/dan200/computercraft/client/platform/ClientPlatformHelperImpl.java index 684a134ce..1ed5cd6eb 100644 --- a/projects/fabric/src/client/java/dan200/computercraft/client/platform/ClientPlatformHelperImpl.java +++ b/projects/fabric/src/client/java/dan200/computercraft/client/platform/ClientPlatformHelperImpl.java @@ -5,36 +5,14 @@ package dan200.computercraft.client.platform; import com.google.auto.service.AutoService; -import com.mojang.blaze3d.vertex.PoseStack; -import dan200.computercraft.client.render.ModelRenderer; -import net.fabricmc.fabric.api.renderer.v1.model.ModelHelper; -import net.minecraft.client.renderer.MultiBufferSource; -import net.minecraft.client.renderer.Sheets; -import net.minecraft.client.resources.model.BakedModel; -import net.minecraft.client.resources.model.ModelManager; +import net.fabricmc.fabric.api.client.model.loading.v1.ExtraModelKey; +import net.minecraft.client.resources.model.ModelDebugName; import net.minecraft.resources.ResourceLocation; -import net.minecraft.util.RandomSource; -import org.jspecify.annotations.Nullable; -@AutoService(dan200.computercraft.impl.client.ClientPlatformHelper.class) +@AutoService(ClientPlatformHelper.class) public class ClientPlatformHelperImpl implements ClientPlatformHelper { - private static final RandomSource random = RandomSource.create(0); - @Override - public BakedModel getModel(ModelManager manager, ResourceLocation resourceLocation) { - var model = manager.getModel(resourceLocation); - return model == null ? manager.getMissingModel() : model; - } - - @Override - public void renderBakedModel(PoseStack transform, MultiBufferSource buffers, BakedModel model, int lightmapCoord, int overlayLight, int @Nullable [] 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 = buffers.getBuffer(Sheets.translucentItemSheet()); - 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); - } + public ModelKey createModelKey(ResourceLocation id, ModelDebugName name) { + return new FabricModelKey<>(ExtraModelKey.create(name::debugName)); } } diff --git a/projects/fabric/src/client/java/dan200/computercraft/client/platform/FabricModelKey.java b/projects/fabric/src/client/java/dan200/computercraft/client/platform/FabricModelKey.java new file mode 100644 index 000000000..efcc380f0 --- /dev/null +++ b/projects/fabric/src/client/java/dan200/computercraft/client/platform/FabricModelKey.java @@ -0,0 +1,36 @@ +// SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers +// +// SPDX-License-Identifier: MPL-2.0 + +package dan200.computercraft.client.platform; + +import net.fabricmc.fabric.api.client.model.loading.v1.ExtraModelKey; +import net.minecraft.client.resources.model.ModelManager; +import org.jspecify.annotations.Nullable; + +/** + * Wraps a Fabric {@link ExtraModelKey} into our multi-loader {@link ModelKey}. + * + * @param The type of the baked model. + */ +public final class FabricModelKey implements ModelKey { + private final ExtraModelKey key; + + FabricModelKey(ExtraModelKey key) { + this.key = key; + } + + public static ExtraModelKey key(ModelKey key) { + return ((FabricModelKey) key).key; + } + + @SuppressWarnings("unchecked") + public static ExtraModelKey erased(ModelKey key) { + return ((FabricModelKey) key).key; + } + + @Override + public @Nullable T get(ModelManager manager) { + return manager.getModel(key); + } +} diff --git a/projects/fabric/src/client/java/dan200/computercraft/impl/client/FabricComputerCraftAPIClientImpl.java b/projects/fabric/src/client/java/dan200/computercraft/impl/client/FabricComputerCraftAPIClientImpl.java index 500b88772..6b90fd004 100644 --- a/projects/fabric/src/client/java/dan200/computercraft/impl/client/FabricComputerCraftAPIClientImpl.java +++ b/projects/fabric/src/client/java/dan200/computercraft/impl/client/FabricComputerCraftAPIClientImpl.java @@ -5,15 +5,15 @@ package dan200.computercraft.impl.client; import com.google.auto.service.AutoService; -import dan200.computercraft.api.client.turtle.TurtleUpgradeModeller; +import dan200.computercraft.api.client.turtle.TurtleUpgradeModel; import dan200.computercraft.api.turtle.ITurtleUpgrade; import dan200.computercraft.api.upgrades.UpgradeType; -import dan200.computercraft.client.turtle.TurtleUpgradeModellers; +import dan200.computercraft.client.turtle.TurtleUpgradeModels; @AutoService(FabricComputerCraftAPIClientService.class) public final class FabricComputerCraftAPIClientImpl implements FabricComputerCraftAPIClientService { @Override - public void registerTurtleUpgradeModeller(UpgradeType type, TurtleUpgradeModeller modeller) { - TurtleUpgradeModellers.register(type, modeller); + public void registerTurtleUpgradeModeller(UpgradeType type, TurtleUpgradeModel.Unbaked modeller) { + TurtleUpgradeModels.register(type, modeller); } } diff --git a/projects/fabric/src/client/java/dan200/computercraft/mixin/client/BlockRenderDispatcherMixin.java b/projects/fabric/src/client/java/dan200/computercraft/mixin/client/BlockRenderDispatcherMixin.java deleted file mode 100644 index 5fb2f0aea..000000000 --- a/projects/fabric/src/client/java/dan200/computercraft/mixin/client/BlockRenderDispatcherMixin.java +++ /dev/null @@ -1,66 +0,0 @@ -// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers -// -// SPDX-License-Identifier: MPL-2.0 - -package dan200.computercraft.mixin.client; - -import com.mojang.blaze3d.vertex.PoseStack; -import com.mojang.blaze3d.vertex.VertexConsumer; -import dan200.computercraft.client.ClientHooks; -import net.minecraft.client.renderer.block.BlockModelShaper; -import net.minecraft.client.renderer.block.BlockRenderDispatcher; -import net.minecraft.client.renderer.block.ModelBlockRenderer; -import net.minecraft.client.renderer.texture.OverlayTexture; -import net.minecraft.core.BlockPos; -import net.minecraft.util.RandomSource; -import net.minecraft.world.level.BlockAndTintGetter; -import net.minecraft.world.level.block.state.BlockState; -import org.spongepowered.asm.mixin.Final; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -/** - * Provides custom block breaking progress for modems, so it only applies to the current part. - * - * @see BlockRenderDispatcher#renderBreakingTexture(BlockState, BlockPos, BlockAndTintGetter, PoseStack, VertexConsumer) - */ -@Mixin(BlockRenderDispatcher.class) -class BlockRenderDispatcherMixin { - @Shadow - @Final - private RandomSource random; - - @Shadow - @Final - private BlockModelShaper blockModelShaper; - - @Shadow - @Final - private ModelBlockRenderer modelRenderer; - - @Inject( - method = "renderBreakingTexture", - at = @At("HEAD"), - cancellable = true, - require = 0 // This isn't critical functionality, so don't worry if we can't apply it. - ) - @SuppressWarnings("UnusedMethod") - private void renderBlockDamage( - BlockState state, BlockPos pos, BlockAndTintGetter world, PoseStack pose, VertexConsumer buffers, - CallbackInfo info - ) { - var newState = ClientHooks.getBlockBreakingState(state, pos); - if (newState != null) { - info.cancel(); - - var model = blockModelShaper.getBlockModel(newState); - modelRenderer.tesselateBlock( - world, model, newState, pos, pose, buffers, true, random, newState.getSeed(pos), - OverlayTexture.NO_OVERLAY - ); - } - } -} diff --git a/projects/fabric/src/client/resources/computercraft-client.fabric.mixins.json b/projects/fabric/src/client/resources/computercraft-client.fabric.mixins.json index 7a2320f61..c27b9630c 100644 --- a/projects/fabric/src/client/resources/computercraft-client.fabric.mixins.json +++ b/projects/fabric/src/client/resources/computercraft-client.fabric.mixins.json @@ -7,7 +7,6 @@ "defaultRequire": 1 }, "client": [ - "BlockRenderDispatcherMixin", "DebugScreenOverlayMixin", "ItemFrameRendererMixin", "ItemFrameRenderStateMixin", diff --git a/projects/fabric/src/examples/java/com/example/examplemod/FabricExampleModClient.java b/projects/fabric/src/examples/java/com/example/examplemod/FabricExampleModClient.java index 7b48afe66..40ecba67e 100644 --- a/projects/fabric/src/examples/java/com/example/examplemod/FabricExampleModClient.java +++ b/projects/fabric/src/examples/java/com/example/examplemod/FabricExampleModClient.java @@ -1,14 +1,14 @@ package com.example.examplemod; import dan200.computercraft.api.client.FabricComputerCraftAPIClient; -import dan200.computercraft.api.client.turtle.TurtleUpgradeModeller; +import dan200.computercraft.api.client.turtle.TurtleUpgradeModel; import net.fabricmc.api.ClientModInitializer; public class FabricExampleModClient implements ClientModInitializer { @Override public void onInitializeClient() { - // @start region=turtle_modellers - FabricComputerCraftAPIClient.registerTurtleUpgradeModeller(ExampleMod.EXAMPLE_TURTLE_UPGRADE, TurtleUpgradeModeller.flatItem()); - // @end region=turtle_modellers + // @start region=turtle_model + FabricComputerCraftAPIClient.registerTurtleUpgradeModeller(ExampleMod.EXAMPLE_TURTLE_UPGRADE, TurtleUpgradeModel.flatItem()); + // @end region=turtle_model } } diff --git a/projects/fabric/src/main/java/dan200/computercraft/shared/platform/PlatformHelperImpl.java b/projects/fabric/src/main/java/dan200/computercraft/shared/platform/PlatformHelperImpl.java index dc2685cca..546155e98 100644 --- a/projects/fabric/src/main/java/dan200/computercraft/shared/platform/PlatformHelperImpl.java +++ b/projects/fabric/src/main/java/dan200/computercraft/shared/platform/PlatformHelperImpl.java @@ -241,8 +241,7 @@ public class PlatformHelperImpl implements PlatformHelper { @Override public ClickEvent createOpenFolderAction(Path path) { - return new ClickEvent( - ClickEvent.Action.RUN_COMMAND, + return new ClickEvent.RunCommand( "/" + ComputerCraft.CLIENT_OPEN_FOLDER + " " + StringArgumentType.escapeIfRequired(path.toAbsolutePath().toString()) ); } diff --git a/projects/fabric/src/main/resources/fabric.mod.json b/projects/fabric/src/main/resources/fabric.mod.json index a6bcc4977..5900e0306 100644 --- a/projects/fabric/src/main/resources/fabric.mod.json +++ b/projects/fabric/src/main/resources/fabric.mod.json @@ -39,6 +39,10 @@ "mixins": [ "computercraft.mixins.json", "computercraft.fabric.mixins.json", + { + "config": "computercraft-client.mixins.json", + "environment": "client" + }, { "config": "computercraft-client.fabric.mixins.json", "environment": "client" @@ -46,8 +50,8 @@ ], "depends": { "fabricloader": ">=0.16.10", - "fabric-api": ">=0.118.0", - "minecraft": "=1.21.4" + "fabric-api": ">=0.119.5", + "minecraft": "=1.21.5" }, "accessWidener": "computercraft.accesswidener" } diff --git a/projects/fabric/src/testMod/java/dan200/computercraft/gametest/core/TestMod.java b/projects/fabric/src/testMod/java/dan200/computercraft/gametest/core/TestMod.java index bca5f21f6..8f28b1748 100644 --- a/projects/fabric/src/testMod/java/dan200/computercraft/gametest/core/TestMod.java +++ b/projects/fabric/src/testMod/java/dan200/computercraft/gametest/core/TestMod.java @@ -6,6 +6,8 @@ package dan200.computercraft.gametest.core; import dan200.computercraft.api.ComputerCraftAPI; import dan200.computercraft.export.Exporter; +import dan200.computercraft.gametest.api.ClientTestEnvironment; +import dan200.computercraft.mixin.gametest.RegistryDataLoaderLoaderAccessor; import net.fabricmc.api.ClientModInitializer; import net.fabricmc.api.ModInitializer; import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; @@ -15,10 +17,21 @@ import net.fabricmc.fabric.api.event.Event; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents; import net.fabricmc.fabric.api.event.player.PlayerBlockBreakEvents; -import net.minecraft.gametest.framework.GameTestRegistry; +import net.minecraft.core.Registry; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.core.registries.Registries; +import net.minecraft.gametest.framework.GameTestInstance; +import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; +import org.jspecify.annotations.Nullable; + +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Objects; public class TestMod implements ModInitializer, ClientModInitializer { + private static @Nullable List tests = null; + @Override public void onInitialize() { TestHooks.init(); @@ -29,7 +42,10 @@ public class TestMod implements ModInitializer, ClientModInitializer { CommandRegistrationCallback.EVENT.register((dispatcher, buildContext, environment) -> CCTestCommand.register(dispatcher, buildContext)); PlayerBlockBreakEvents.BEFORE.register((level, player, pos, state, blockEntity) -> !TestHooks.onBeforeDestroyBlock(level, pos, state)); - TestHooks.loadTests(GameTestRegistry::register); + Registry.register(BuiltInRegistries.TEST_ENVIRONMENT_DEFINITION_TYPE, ResourceLocation.fromNamespaceAndPath(TestHooks.MOD_ID, "client"), ClientTestEnvironment.CODEC); + + var tests = TestMod.tests = TestHooks.loadTests(); + for (var test : tests) Registry.register(BuiltInRegistries.TEST_FUNCTION, test.getId(), test.getFunction()); } @Override @@ -38,4 +54,17 @@ public class TestMod implements ModInitializer, ClientModInitializer { ScreenEvents.AFTER_INIT.register((client, screen, scaledWidth, scaledHeight) -> ClientTestHooks.onOpenScreen(screen)); ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> Exporter.register(dispatcher)); } + + public static void registerDynamicEntries(List> registriesList) { + var registries = new IdentityHashMap>, Registry>(registriesList.size()); + for (var entry : registriesList) registries.put(entry.getRegistry().key(), entry.getRegistry()); + + @SuppressWarnings("unchecked") var testInstances = (Registry) registries.get(Registries.TEST_INSTANCE); + if (testInstances == null) return; + for (var test : Objects.requireNonNull(tests)) { + if (!testInstances.containsKey(test.getId())) { + Registry.register(testInstances, test.getId(), test.getInstance()); + } + } + } } diff --git a/projects/fabric/src/testMod/java/dan200/computercraft/mixin/gametest/RegistryDataLoaderLoaderAccessor.java b/projects/fabric/src/testMod/java/dan200/computercraft/mixin/gametest/RegistryDataLoaderLoaderAccessor.java new file mode 100644 index 000000000..6bf80a3a3 --- /dev/null +++ b/projects/fabric/src/testMod/java/dan200/computercraft/mixin/gametest/RegistryDataLoaderLoaderAccessor.java @@ -0,0 +1,19 @@ +// SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers +// +// SPDX-License-Identifier: MPL-2.0 + +package dan200.computercraft.mixin.gametest; + +import net.minecraft.core.WritableRegistry; +import net.minecraft.resources.RegistryDataLoader; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(targets = "net/minecraft/resources/RegistryDataLoader$Loader") +public interface RegistryDataLoaderLoaderAccessor { + @Accessor("data") + RegistryDataLoader.RegistryData getData(); + + @Accessor("registry") + WritableRegistry getRegistry(); +} diff --git a/projects/fabric/src/testMod/java/dan200/computercraft/mixin/gametest/RegistryDataLoaderMixin.java b/projects/fabric/src/testMod/java/dan200/computercraft/mixin/gametest/RegistryDataLoaderMixin.java new file mode 100644 index 000000000..b52e0903f --- /dev/null +++ b/projects/fabric/src/testMod/java/dan200/computercraft/mixin/gametest/RegistryDataLoaderMixin.java @@ -0,0 +1,40 @@ +// SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers +// +// SPDX-License-Identifier: MPL-2.0 + +package dan200.computercraft.mixin.gametest; + +import com.llamalad7.mixinextras.sugar.Local; +import dan200.computercraft.gametest.core.TestMod; +import net.minecraft.core.HolderLookup; +import net.minecraft.core.RegistryAccess; +import net.minecraft.resources.RegistryDataLoader; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Coerce; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.util.List; + +/** + * Register game tests into the dynamic registry. This just mirrors Fabric's own hook, but loading our annotations + * instead. + */ +@Mixin(RegistryDataLoader.class) +class RegistryDataLoaderMixin { + @Inject( + method = "load(Lnet/minecraft/resources/RegistryDataLoader$LoadingFunction;Ljava/util/List;Ljava/util/List;)Lnet/minecraft/core/RegistryAccess$Frozen;", + at = @At(value = "INVOKE", target = "Ljava/util/List;forEach(Ljava/util/function/Consumer;)V", ordinal = 1) + ) + @SuppressWarnings("unused") + private static void beforeFreeze( + @Coerce Object loadable, + List> wrappers, + List> entries, + CallbackInfoReturnable cir, + @Local(ordinal = 2) List> registriesList + ) { + TestMod.registerDynamicEntries(registriesList); + } +} diff --git a/projects/fabric/src/testMod/resources/computercraft-gametest.fabric.mixins.json b/projects/fabric/src/testMod/resources/computercraft-gametest.fabric.mixins.json new file mode 100644 index 000000000..adf870be0 --- /dev/null +++ b/projects/fabric/src/testMod/resources/computercraft-gametest.fabric.mixins.json @@ -0,0 +1,13 @@ +{ + "required": true, + "package": "dan200.computercraft.mixin.gametest", + "minVersion": "0.8", + "compatibilityLevel": "JAVA_21", + "injectors": { + "defaultRequire": 1 + }, + "mixins": [ + "RegistryDataLoaderMixin", + "RegistryDataLoaderLoaderAccessor" + ] +} diff --git a/projects/fabric/src/testMod/resources/fabric.mod.json b/projects/fabric/src/testMod/resources/fabric.mod.json index ffc442bec..f688bc382 100644 --- a/projects/fabric/src/testMod/resources/fabric.mod.json +++ b/projects/fabric/src/testMod/resources/fabric.mod.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "id": "cc-datagen", + "id": "cctest", "version": "1.0.0", "entrypoints": { "main": [ @@ -11,7 +11,8 @@ ] }, "mixins": [ - "computercraft-gametest.mixins.json" + "computercraft-gametest.mixins.json", + "computercraft-gametest.fabric.mixins.json" ], "depends": { "computercraft": "*" diff --git a/projects/forge-api/src/client/java/dan200/computercraft/api/client/turtle/RegisterTurtleModellersEvent.java b/projects/forge-api/src/client/java/dan200/computercraft/api/client/turtle/RegisterTurtleModelEvent.java similarity index 61% rename from projects/forge-api/src/client/java/dan200/computercraft/api/client/turtle/RegisterTurtleModellersEvent.java rename to projects/forge-api/src/client/java/dan200/computercraft/api/client/turtle/RegisterTurtleModelEvent.java index 48ea95534..24b2da98a 100644 --- a/projects/forge-api/src/client/java/dan200/computercraft/api/client/turtle/RegisterTurtleModellersEvent.java +++ b/projects/forge-api/src/client/java/dan200/computercraft/api/client/turtle/RegisterTurtleModelEvent.java @@ -14,18 +14,18 @@ import net.neoforged.fml.event.lifecycle.FMLCommonSetupEvent; import org.jetbrains.annotations.ApiStatus; /** - * This event is fired to register {@link TurtleUpgradeModeller}s for a mod's {@linkplain TurtleUpgradeType turtle + * This event is fired to register {@link TurtleUpgradeModel}s for a mod's {@linkplain TurtleUpgradeType turtle * upgrades}. *

- * This event is fired during the initial resource load. Registries will be frozen, but mods may not be fully + * 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 - * dispatched). Subscribers should be careful not to + * dispatched). */ -public class RegisterTurtleModellersEvent extends Event implements IModBusEvent, RegisterTurtleUpgradeModeller { - private final RegisterTurtleUpgradeModeller dispatch; +public class RegisterTurtleModelEvent extends Event implements IModBusEvent, RegisterTurtleUpgradeModel { + private final RegisterTurtleUpgradeModel dispatch; @ApiStatus.Internal - public RegisterTurtleModellersEvent(RegisterTurtleUpgradeModeller dispatch) { + public RegisterTurtleModelEvent(RegisterTurtleUpgradeModel dispatch) { this.dispatch = dispatch; } @@ -33,7 +33,7 @@ public class RegisterTurtleModellersEvent extends Event implements IModBusEvent, * {@inheritDoc} */ @Override - public void register(UpgradeType type, TurtleUpgradeModeller modeller) { + public void register(UpgradeType type, TurtleUpgradeModel.Unbaked modeller) { dispatch.register(type, modeller); } } diff --git a/projects/forge/src/client/java/dan200/computercraft/client/ForgeClientRegistry.java b/projects/forge/src/client/java/dan200/computercraft/client/ForgeClientRegistry.java index 523d879ff..82f5d9ffd 100644 --- a/projects/forge/src/client/java/dan200/computercraft/client/ForgeClientRegistry.java +++ b/projects/forge/src/client/java/dan200/computercraft/client/ForgeClientRegistry.java @@ -6,12 +6,18 @@ package dan200.computercraft.client; import com.google.common.reflect.TypeToken; import dan200.computercraft.api.ComputerCraftAPI; -import dan200.computercraft.api.client.turtle.RegisterTurtleModellersEvent; +import dan200.computercraft.api.client.StandaloneModel; +import dan200.computercraft.api.client.turtle.RegisterTurtleModelEvent; +import dan200.computercraft.api.client.turtle.TurtleUpgradeModel; +import dan200.computercraft.api.turtle.ITurtleUpgrade; import dan200.computercraft.client.model.ExtraModels; +import dan200.computercraft.client.platform.ForgeModelKey; import dan200.computercraft.client.render.ExtendedItemFrameRenderState; -import dan200.computercraft.client.turtle.TurtleUpgradeModellers; +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; +import net.minecraft.client.resources.model.ResolvableModel; import net.minecraft.resources.ResourceLocation; import net.minecraft.util.context.ContextKey; import net.neoforged.api.distmarker.Dist; @@ -20,6 +26,7 @@ import net.neoforged.fml.ModLoader; import net.neoforged.fml.common.EventBusSubscriber; import net.neoforged.fml.event.lifecycle.FMLClientSetupEvent; import net.neoforged.neoforge.client.event.*; +import net.neoforged.neoforge.client.model.standalone.UnbakedStandaloneModel; import net.neoforged.neoforge.client.renderstate.RegisterRenderStateModifiersEvent; @@ -30,39 +37,24 @@ import net.neoforged.neoforge.client.renderstate.RegisterRenderStateModifiersEve public final class ForgeClientRegistry { static final ContextKey ITEM_FRAME_STATE = new ContextKey<>(ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "item_frame")); - private static final Object lock = new Object(); - private static boolean gatheredModellers = false; - private ForgeClientRegistry() { } - /** - * Turtle upgrade modellers must be loaded before we gather additional models. - *

- * Unfortunately, due to the nature of parallel mod loading (resource loading and mod setup events are fired in - * parallel), there's no way to guarantee this using existing events. Instead, we piggyback off - * {@link ModelEvent.RegisterAdditional}, registering models the first time the event is fired. - */ - private static void gatherModellers() { - if (gatheredModellers) return; - synchronized (lock) { - if (gatheredModellers) return; - - gatheredModellers = true; - ModLoader.postEvent(new RegisterTurtleModellersEvent(TurtleUpgradeModellers::register)); - } - } - @SubscribeEvent - public static void registerModels(ModelEvent.RegisterAdditional event) { - gatherModellers(); + public static void registerModels(ModelEvent.RegisterStandalone event) { + TurtleUpgradeModels.fetch(() -> ModLoader.postEvent(new RegisterTurtleModelEvent(TurtleUpgradeModels::register))); + var extraModels = ExtraModels.loadAll(Minecraft.getInstance().getResourceManager()); - ClientRegistry.registerExtraModels(event::register, extraModels); + ClientRegistry.registerExtraModels( + (key, model) -> event.register(ForgeModelKey.key(key), StandaloneModel::of), + (key, model) -> event.register(ForgeModelKey.erased(key), new TurtleModelWrapper<>(model)), + extraModels + ); } @SubscribeEvent - public static void onTurtleModellers(RegisterTurtleModellersEvent event) { - ClientRegistry.registerTurtleModellers(event); + public static void registerTurtleModels(RegisterTurtleModelEvent event) { + ClientRegistry.registerTurtleModels(event); } @SubscribeEvent @@ -109,4 +101,18 @@ public final class ForgeClientRegistry { public static void setupClient(FMLClientSetupEvent event) { ClientRegistry.register(); } + + private record TurtleModelWrapper( + TurtleUpgradeModel.Unbaked model + ) implements UnbakedStandaloneModel> { + @Override + public TurtleUpgradeModel bake(ModelBaker baker) { + return model().bake(baker); + } + + @Override + public void resolveDependencies(ResolvableModel.Resolver resolver) { + model().resolveDependencies(resolver); + } + } } diff --git a/projects/forge/src/client/java/dan200/computercraft/client/platform/ClientPlatformHelperImpl.java b/projects/forge/src/client/java/dan200/computercraft/client/platform/ClientPlatformHelperImpl.java index 24727a028..0e53b5307 100644 --- a/projects/forge/src/client/java/dan200/computercraft/client/platform/ClientPlatformHelperImpl.java +++ b/projects/forge/src/client/java/dan200/computercraft/client/platform/ClientPlatformHelperImpl.java @@ -5,38 +5,14 @@ package dan200.computercraft.client.platform; import com.google.auto.service.AutoService; -import com.mojang.blaze3d.vertex.PoseStack; -import dan200.computercraft.client.render.ModelRenderer; -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.client.resources.model.ModelDebugName; import net.minecraft.resources.ResourceLocation; -import net.minecraft.util.RandomSource; -import net.minecraft.world.item.ItemStack; -import net.neoforged.neoforge.client.model.data.ModelData; -import org.jspecify.annotations.Nullable; +import net.neoforged.neoforge.client.model.standalone.StandaloneModelKey; -import java.util.Arrays; - -@AutoService(dan200.computercraft.impl.client.ClientPlatformHelper.class) +@AutoService(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 resourceLocation) { - return manager.getStandaloneModel(resourceLocation); - } - - @Override - public void renderBakedModel(PoseStack transform, MultiBufferSource buffers, BakedModel model, int lightmapCoord, int overlayLight, int @Nullable [] tints) { - var renderType = model.getRenderType(ItemStack.EMPTY); - 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); - } + public ModelKey createModelKey(ResourceLocation id, ModelDebugName name) { + return new ForgeModelKey<>(new StandaloneModelKey(id)); } } diff --git a/projects/forge/src/client/java/dan200/computercraft/client/platform/ForgeModelKey.java b/projects/forge/src/client/java/dan200/computercraft/client/platform/ForgeModelKey.java new file mode 100644 index 000000000..09c2aa6a3 --- /dev/null +++ b/projects/forge/src/client/java/dan200/computercraft/client/platform/ForgeModelKey.java @@ -0,0 +1,36 @@ +// SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers +// +// SPDX-License-Identifier: MPL-2.0 + +package dan200.computercraft.client.platform; + +import net.minecraft.client.resources.model.ModelManager; +import net.neoforged.neoforge.client.model.standalone.StandaloneModelKey; +import org.jspecify.annotations.Nullable; + +/** + * Wraps a Forge {@link StandaloneModelKey} into our multi-loader {@link ModelKey}. + * + * @param The type of the baked model. + */ +public final class ForgeModelKey implements ModelKey { + private final StandaloneModelKey key; + + ForgeModelKey(StandaloneModelKey key) { + this.key = key; + } + + public static StandaloneModelKey key(ModelKey key) { + return ((ForgeModelKey) key).key; + } + + @SuppressWarnings("unchecked") + public static StandaloneModelKey erased(ModelKey key) { + return ((ForgeModelKey) key).key; + } + + @Override + public @Nullable T get(ModelManager manager) { + return manager.getStandaloneModel(key); + } +} diff --git a/projects/forge/src/client/java/dan200/computercraft/mixin/client/BlockRenderDispatcherMixin.java b/projects/forge/src/client/java/dan200/computercraft/mixin/client/BlockRenderDispatcherMixin.java deleted file mode 100644 index 8884ebcb7..000000000 --- a/projects/forge/src/client/java/dan200/computercraft/mixin/client/BlockRenderDispatcherMixin.java +++ /dev/null @@ -1,66 +0,0 @@ -// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers -// -// SPDX-License-Identifier: MPL-2.0 - -package dan200.computercraft.mixin.client; - -import com.mojang.blaze3d.vertex.PoseStack; -import com.mojang.blaze3d.vertex.VertexConsumer; -import dan200.computercraft.client.ClientHooks; -import net.minecraft.client.renderer.block.BlockModelShaper; -import net.minecraft.client.renderer.block.BlockRenderDispatcher; -import net.minecraft.client.renderer.block.ModelBlockRenderer; -import net.minecraft.client.renderer.texture.OverlayTexture; -import net.minecraft.core.BlockPos; -import net.minecraft.util.RandomSource; -import net.minecraft.world.level.BlockAndTintGetter; -import net.minecraft.world.level.block.state.BlockState; -import net.neoforged.neoforge.client.model.data.ModelData; -import org.spongepowered.asm.mixin.Final; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -/** - * Provides custom block breaking progress for modems, so it only applies to the current part. - * - * @see BlockRenderDispatcher#renderBreakingTexture(BlockState, BlockPos, BlockAndTintGetter, PoseStack, VertexConsumer, ModelData) - */ -@Mixin(BlockRenderDispatcher.class) -public class BlockRenderDispatcherMixin { - @Shadow - @Final - private RandomSource random; - - @Shadow - @Final - private BlockModelShaper blockModelShaper; - - @Shadow - @Final - private ModelBlockRenderer modelRenderer; - - @Inject( - method = "name=/^renderBreakingTexture/ desc=/ModelData;\\)V$/", - at = @At("HEAD"), - cancellable = true, - require = 0 // This isn't critical functionality, so don't worry if we can't apply it. - ) - public void renderBlockDamage( - BlockState state, BlockPos pos, BlockAndTintGetter world, PoseStack pose, VertexConsumer buffers, ModelData modelData, - CallbackInfo info - ) { - var newState = ClientHooks.getBlockBreakingState(state, pos); - if (newState != null) { - info.cancel(); - - var model = blockModelShaper.getBlockModel(newState); - modelRenderer.tesselateBlock( - world, model, newState, pos, pose, buffers, true, random, newState.getSeed(pos), - OverlayTexture.NO_OVERLAY, modelData, null - ); - } - } -} diff --git a/projects/forge/src/examples/java/com/example/examplemod/ForgeExampleModClient.java b/projects/forge/src/examples/java/com/example/examplemod/ForgeExampleModClient.java index 23f29d847..79656f2dd 100644 --- a/projects/forge/src/examples/java/com/example/examplemod/ForgeExampleModClient.java +++ b/projects/forge/src/examples/java/com/example/examplemod/ForgeExampleModClient.java @@ -1,7 +1,7 @@ package com.example.examplemod; -import dan200.computercraft.api.client.turtle.RegisterTurtleModellersEvent; -import dan200.computercraft.api.client.turtle.TurtleUpgradeModeller; +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; @@ -11,10 +11,10 @@ import net.neoforged.fml.common.EventBusSubscriber; */ @EventBusSubscriber(modid = ExampleMod.MOD_ID, value = Dist.CLIENT, bus = EventBusSubscriber.Bus.MOD) public class ForgeExampleModClient { - // @start region=turtle_modellers + // @start region=turtle_model @SubscribeEvent - public static void onRegisterTurtleModellers(RegisterTurtleModellersEvent event) { - event.register(ExampleMod.EXAMPLE_TURTLE_UPGRADE, TurtleUpgradeModeller.flatItem()); + public static void onRegisterTurtleModellers(RegisterTurtleModelEvent event) { + event.register(ExampleMod.EXAMPLE_TURTLE_UPGRADE, TurtleUpgradeModel.flatItem()); } - // @end region=turtle_modellers + // @end region=turtle_model } diff --git a/projects/forge/src/main/java/dan200/computercraft/shared/platform/PlatformHelperImpl.java b/projects/forge/src/main/java/dan200/computercraft/shared/platform/PlatformHelperImpl.java index fe74da372..aeb71ce40 100644 --- a/projects/forge/src/main/java/dan200/computercraft/shared/platform/PlatformHelperImpl.java +++ b/projects/forge/src/main/java/dan200/computercraft/shared/platform/PlatformHelperImpl.java @@ -248,7 +248,7 @@ public class PlatformHelperImpl implements PlatformHelper { @Override public ClickEvent createOpenFolderAction(Path path) { - return new ClickEvent(ClickEvent.Action.OPEN_FILE, path.toAbsolutePath().toString()); + return new ClickEvent.OpenFile(path.toAbsolutePath().toString()); } private record RegistrationHelperImpl(DeferredRegister registry) implements RegistrationHelper { diff --git a/projects/forge/src/main/resources/META-INF/accesstransformer.cfg b/projects/forge/src/main/resources/META-INF/accesstransformer.cfg index 12ac6f714..240a3ab71 100644 --- a/projects/forge/src/main/resources/META-INF/accesstransformer.cfg +++ b/projects/forge/src/main/resources/META-INF/accesstransformer.cfg @@ -2,9 +2,6 @@ # # SPDX-License-Identifier: MPL-2.0 -# DirectVertexBuffer -public com.mojang.blaze3d.vertex.VertexBuffer indexCount - # ClientTableFormatter public net.minecraft.client.gui.components.ChatComponent allMessages diff --git a/projects/forge/src/main/resources/META-INF/neoforge.mods.toml b/projects/forge/src/main/resources/META-INF/neoforge.mods.toml index 26fd5a625..2bb0827d4 100644 --- a/projects/forge/src/main/resources/META-INF/neoforge.mods.toml +++ b/projects/forge/src/main/resources/META-INF/neoforge.mods.toml @@ -26,7 +26,7 @@ CC: Tweaked is a fork of ComputerCraft, adding programmable computers, turtles a [[dependencies.computercraft]] modId="neoforge" type="required" - versionRange="[${neoVersion},21.5)" + versionRange="[${neoVersion},21.6)" ordering="NONE" side="BOTH" @@ -34,4 +34,4 @@ CC: Tweaked is a fork of ComputerCraft, adding programmable computers, turtles a config = "computercraft.mixins.json" [[mixins]] -config = "computercraft-client.forge.mixins.json" +config = "computercraft-client.mixins.json" diff --git a/projects/forge/src/testMod/java/dan200/computercraft/gametest/core/TestMod.java b/projects/forge/src/testMod/java/dan200/computercraft/gametest/core/TestMod.java index bb541a94e..dcae5e588 100644 --- a/projects/forge/src/testMod/java/dan200/computercraft/gametest/core/TestMod.java +++ b/projects/forge/src/testMod/java/dan200/computercraft/gametest/core/TestMod.java @@ -5,6 +5,7 @@ package dan200.computercraft.gametest.core; import dan200.computercraft.export.Exporter; +import net.minecraft.core.registries.Registries; import net.neoforged.api.distmarker.Dist; import net.neoforged.bus.api.EventPriority; import net.neoforged.bus.api.IEventBus; @@ -18,6 +19,7 @@ import net.neoforged.neoforge.event.RegisterGameTestsEvent; import net.neoforged.neoforge.event.level.BlockEvent; import net.neoforged.neoforge.event.server.ServerStartedEvent; import net.neoforged.neoforge.event.tick.ServerTickEvent; +import net.neoforged.neoforge.registries.RegisterEvent; @Mod("cctest") public class TestMod { @@ -33,7 +35,13 @@ public class TestMod { if (FMLEnvironment.dist == Dist.CLIENT) TestMod.onInitializeClient(); - modBus.addListener((RegisterGameTestsEvent event) -> TestHooks.loadTests(event::register)); + var tests = TestHooks.loadTests(); + modBus.addListener((RegisterEvent event) -> { + for (var test : tests) event.register(Registries.TEST_FUNCTION, test.getId(), test::getFunction); + }); + modBus.addListener((RegisterGameTestsEvent event) -> { + for (var test : tests) event.registerTest(test.getId(), test.getInstance()); + }); } private static void onInitializeClient() { diff --git a/settings.gradle.kts b/settings.gradle.kts index 838e8f308..af8209b8b 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -5,6 +5,7 @@ pluginManagement { // Duplicated in buildSrc/build.gradle.kts repositories { + mavenLocal() mavenCentral() gradlePluginPortal() @@ -23,12 +24,12 @@ pluginManagement { } } - maven("https://maven.squiddev.cc") { + /*maven("https://maven.squiddev.cc") { name = "SquidDev" content { includeGroup("cc.tweaked.vanilla-extract") } - } + }*/ } }