1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-10-15 14:07:38 +00:00

Finish lectern rendering

- Register our models via Minecraft's layer system. I confess, I don't
   fully understand what purpose this really solves (it's not
   data-driven or anything!), but makes it a bit more consistent with
   the rest of vanilla's code.

 - Update the lectern code to submit models correctly.
This commit is contained in:
Jonathan Coates
2025-10-14 20:04:31 +01:00
parent 64b1eb5fae
commit 91b3b60ca2
24 changed files with 390 additions and 201 deletions

View File

@@ -25,7 +25,7 @@ repositories {
name = "Fabric"
content {
includeGroup("net.fabricmc")
includeGroup("net.fabricmc.unpick")
includeGroup("net.fabricmc.unpick")
}
}

View File

@@ -40,8 +40,8 @@ emi = "1.1.7+1.21"
fabricPermissions = "0.3.3"
iris-fabric = "1.9.1+1.21.7-fabric"
iris-forge = "1.9.1+1.21.7-neoforge"
jei = "23.1.0.4"
modmenu = "13.0.2"
jei = "26.0.0.1"
modmenu = "16.0.0-rc.1"
moreRed = "6.0.0.3"
rei = "18.0.800"
sodium-fabric = "mc1.21.6-0.6.13-fabric"
@@ -113,9 +113,9 @@ fabric-loader = { module = "net.fabricmc:fabric-loader", version.ref = "fabric-l
fabricPermissions = { module = "me.lucko:fabric-permissions-api", version.ref = "fabricPermissions" }
iris-fabric = { module = "maven.modrinth:iris", version.ref = "iris-fabric" }
iris-forge = { module = "maven.modrinth:iris", version.ref = "iris-forge" }
jei-api = { module = "mezz.jei:jei-1.21.7-common-api", version.ref = "jei" }
jei-fabric = { module = "mezz.jei:jei-1.21.7-fabric", version.ref = "jei" }
jei-forge = { module = "mezz.jei:jei-1.21.7-neoforge", version.ref = "jei" }
jei-api = { module = "mezz.jei:jei-1.21.10-common-api", version.ref = "jei" }
jei-fabric = { module = "mezz.jei:jei-1.21.10-fabric", version.ref = "jei" }
jei-forge = { module = "mezz.jei:jei-1.21.10-neoforge", version.ref = "jei" }
mixin = { module = "org.spongepowered:mixin", version.ref = "mixin" }
mixinExtra = { module = "io.github.llamalad7:mixinextras-common", version.ref = "mixinExtra" }
modmenu = { module = "com.terraformersmc:modmenu", version.ref = "modmenu" }

View File

@@ -142,19 +142,21 @@ public final class StandaloneModel {
/**
* Render the model directly.
*
* @param transform The current pose stack transformations.
* @param collector The node collector to render to.
* @param light The current light texture coordinate.
* @param overlay The current overlay texture coordinate.
* @param tints The tints for this model.
* @param transform The current pose stack transformations.
* @param collector The node collector to render to.
* @param light The current light texture coordinate.
* @param overlay The current overlay texture coordinate.
* @param tints The tints for this model.
* @param crumblingOverlay The current breaking progress.
*/
public void submit(PoseStack transform, SubmitNodeCollector collector, int light, int overlay, int @Nullable [] tints, ModelFeatureRenderer.@Nullable CrumblingOverlay crumblingOverlay) {
collector.submitCustomGeometry(transform, renderType, (pose, buffer) -> render(pose, buffer, tints, light, overlay));
if (crumblingOverlay != null && renderType.affectsCrumbling()) {
// FIXME: We need a custom hook here, which renders to crumblingBufferSource. Currently the DESTROY_TYPES
// buffer gets flushed before the main model gets rendered.
collector.submitCustomGeometry(transform, ModelBakery.DESTROY_TYPES.get(crumblingOverlay.progress()), (pose, buffer) ->
// FIXME: This does not work. Should we have a custom hook for this instead?
render(pose, new SheetedDecalTextureGenerator(buffer, crumblingOverlay.cameraPose(), 1.0f), tints, light, overlay)
render(pose, new SheetedDecalTextureGenerator(buffer, crumblingOverlay.cameraPose(), 1.0f), null, light, overlay)
);
}
}

View File

@@ -7,7 +7,6 @@ package dan200.computercraft.client;
import com.mojang.blaze3d.audio.Channel;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.math.Axis;
import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.client.pocket.ClientPocketComputers;
import dan200.computercraft.client.render.CableHighlightRenderer;
import dan200.computercraft.client.render.ExtendedItemFrameRenderState;
@@ -22,9 +21,7 @@ import dan200.computercraft.shared.media.items.PrintoutItem;
import dan200.computercraft.shared.peripheral.modem.wired.CableBlock;
import dan200.computercraft.shared.peripheral.modem.wired.CableModemVariant;
import dan200.computercraft.shared.peripheral.modem.wired.CableShapes;
import dan200.computercraft.shared.peripheral.monitor.MonitorBlockEntity;
import dan200.computercraft.shared.pocket.items.PocketComputerItem;
import dan200.computercraft.shared.turtle.blocks.TurtleBlockEntity;
import dan200.computercraft.shared.util.PauseAwareTimer;
import dan200.computercraft.shared.util.WorldUtil;
import net.minecraft.client.Camera;
@@ -41,8 +38,6 @@ import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.HitResult;
import java.util.function.Consumer;
/**
* Event listeners for client-only code.
* <p>
@@ -72,6 +67,7 @@ public final class ClientHooks {
}
public static boolean drawHighlight(PoseStack transform, MultiBufferSource bufferSource, Camera camera, BlockHitResult hit) {
// TODO: Reconsider this API once https://github.com/FabricMC/fabric/pull/4906/ is merged.
return CableHighlightRenderer.drawHighlight(transform, bufferSource, camera, hit)
|| MonitorHighlightRenderer.drawHighlight(transform, bufferSource, camera, hit);
}
@@ -106,38 +102,6 @@ public final class ClientHooks {
SpeakerManager.onPlayStreaming(engine, channel, stream);
}
/**
* Add additional information about the currently targeted block to the debug screen.
*
* @param addText A callback which adds a single line of text.
*/
public static void addBlockDebugInfo(Consumer<String> addText) {
// TODO(1.21.9): Replacement for this
var minecraft = Minecraft.getInstance();
if (!minecraft.getDebugOverlay().showDebugScreen() || minecraft.level == null) return;
if (minecraft.hitResult == null || minecraft.hitResult.getType() != HitResult.Type.BLOCK) return;
var tile = minecraft.level.getBlockEntity(((BlockHitResult) minecraft.hitResult).getBlockPos());
if (tile instanceof MonitorBlockEntity monitor) {
addText.accept("");
addText.accept(
String.format("Targeted monitor: (%d, %d), %d x %d", monitor.getXIndex(), monitor.getYIndex(), monitor.getWidth(), monitor.getHeight())
);
} else if (tile instanceof TurtleBlockEntity turtle) {
addText.accept("");
addText.accept("Targeted turtle:");
addText.accept(String.format("Id: %d", turtle.getComputerID()));
addTurtleUpgrade(addText, turtle, TurtleSide.LEFT);
addTurtleUpgrade(addText, turtle, TurtleSide.RIGHT);
}
}
private static void addTurtleUpgrade(Consumer<String> out, TurtleBlockEntity turtle, TurtleSide side) {
var upgrade = turtle.getAccess().getUpgradeWithData(side);
if (upgrade != null) out.accept(String.format("Upgrade[%s]: %s", side, upgrade.holder().key().location()));
}
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()

View File

@@ -12,6 +12,9 @@ import dan200.computercraft.client.item.colour.PocketComputerLight;
import dan200.computercraft.client.item.model.TurtleOverlayModel;
import dan200.computercraft.client.item.properties.PocketComputerStateProperty;
import dan200.computercraft.client.item.properties.TurtleShowElfOverlay;
import dan200.computercraft.client.model.LecternBookModel;
import dan200.computercraft.client.model.LecternPocketModel;
import dan200.computercraft.client.model.LecternPrintoutModel;
import dan200.computercraft.client.platform.ClientPlatformHelper;
import dan200.computercraft.client.platform.ModelKey;
import dan200.computercraft.client.render.CustomLecternRenderer;
@@ -23,11 +26,14 @@ import dan200.computercraft.client.turtle.TurtleUpgradeModelManager;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.computer.inventory.AbstractComputerMenu;
import net.minecraft.client.color.item.ItemTintSource;
import net.minecraft.client.gui.components.debug.DebugScreenEntry;
import net.minecraft.client.gui.render.pip.PictureInPictureRenderer;
import net.minecraft.client.gui.render.state.pip.PictureInPictureRenderState;
import net.minecraft.client.gui.screens.MenuScreens;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.client.gui.screens.inventory.MenuAccess;
import net.minecraft.client.model.geom.ModelLayerLocation;
import net.minecraft.client.model.geom.builders.LayerDefinition;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider;
import net.minecraft.client.renderer.blockentity.BlockEntityRenderers;
@@ -51,6 +57,7 @@ import java.util.concurrent.Executor;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
/**
* Registers client-side objects, such as {@link BlockEntityRendererProvider}s and
@@ -193,6 +200,12 @@ public final class ClientRegistry {
register.accept(TurtleShowElfOverlay.ID, TurtleShowElfOverlay.CODEC);
}
public static void registerLayerDefinitions(BiConsumer<ModelLayerLocation, Supplier<LayerDefinition>> register) {
register.accept(LecternBookModel.LAYER, LecternBookModel::createLayer);
register.accept(LecternPrintoutModel.LAYER, LecternPrintoutModel::createLayer);
register.accept(LecternPocketModel.LAYER, LecternPocketModel::createLayer);
}
public interface RegisterPictureInPictureRenderer {
<T extends PictureInPictureRenderState> void register(Class<T> state, Function<MultiBufferSource.BufferSource, PictureInPictureRenderer<T>> factory);
}
@@ -200,4 +213,8 @@ public final class ClientRegistry {
public static void registerPictureInPictureRenderers(RegisterPictureInPictureRenderer register) {
register.register(PrintoutScreen.PrintoutRenderState.class, PrintoutScreen.PrintoutPictureRenderer::new);
}
public static void registerDebugScreenEntries(BiConsumer<ResourceLocation, DebugScreenEntry> register) {
register.accept(LookingAtBlockEntityDebugEntry.ID, LookingAtBlockEntityDebugEntry.create());
}
}

View File

@@ -0,0 +1,96 @@
// SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.client.gui;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.peripheral.monitor.MonitorBlockEntity;
import dan200.computercraft.shared.turtle.blocks.TurtleBlockEntity;
import net.minecraft.SharedConstants;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.components.debug.DebugEntryLookingAtBlock;
import net.minecraft.client.gui.components.debug.DebugScreenDisplayer;
import net.minecraft.client.gui.components.debug.DebugScreenEntry;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.HitResult;
import org.jspecify.annotations.Nullable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;
/**
* A {@link DebugScreenEntry} that provides information about the currently looked at block entity.
*
* @see DebugEntryLookingAtBlock
*/
public final class LookingAtBlockEntityDebugEntry implements DebugScreenEntry {
public static final ResourceLocation ID = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "looking_at_block_entity");
private final Map<BlockEntityType<?>, BiConsumer<List<String>, BlockEntity>> blockEntityEmitters = new HashMap<>();
private LookingAtBlockEntityDebugEntry() {
}
@Override
public void display(DebugScreenDisplayer displayer, @Nullable Level level, @Nullable LevelChunk clientChunk, @Nullable LevelChunk serverChunk) {
var entity = Minecraft.getInstance().getCameraEntity();
var trueLevel = SharedConstants.DEBUG_SHOW_SERVER_DEBUG_VALUES ? level : Minecraft.getInstance().level;
if (entity == null || trueLevel == null) return;
var hitResult = entity.pick(20.0, 0.0F, false);
if (hitResult.getType() != HitResult.Type.BLOCK) return;
var blockEntity = trueLevel.getBlockEntity(((BlockHitResult) hitResult).getBlockPos());
if (blockEntity == null) return;
var emitter = blockEntityEmitters.get(blockEntity.getType());
if (emitter == null) return;
List<String> lines = new ArrayList<>();
emitter.accept(lines, blockEntity);
displayer.addToGroup(ID, lines);
}
@SuppressWarnings("unchecked")
private <T extends BlockEntity> LookingAtBlockEntityDebugEntry register(BlockEntityType<T> type, BiConsumer<List<String>, T> emit) {
blockEntityEmitters.put(type, (BiConsumer<List<String>, BlockEntity>) emit);
return this;
}
public static DebugScreenEntry create() {
return new LookingAtBlockEntityDebugEntry()
.register(ModRegistry.BlockEntities.MONITOR_NORMAL.get(), LookingAtBlockEntityDebugEntry::debugMonitor)
.register(ModRegistry.BlockEntities.MONITOR_ADVANCED.get(), LookingAtBlockEntityDebugEntry::debugMonitor)
.register(ModRegistry.BlockEntities.TURTLE_NORMAL.get(), LookingAtBlockEntityDebugEntry::debugTurtle)
.register(ModRegistry.BlockEntities.TURTLE_ADVANCED.get(), LookingAtBlockEntityDebugEntry::debugTurtle);
}
private static void debugMonitor(List<String> lines, MonitorBlockEntity monitor) {
lines.add(
String.format("Targeted monitor: (%d, %d), %d x %d", monitor.getXIndex(), monitor.getYIndex(), monitor.getWidth(), monitor.getHeight())
);
}
private static void debugTurtle(List<String> lines, TurtleBlockEntity turtle) {
lines.add("Targeted turtle:");
lines.add(String.format("Id: %d", turtle.getComputerID()));
addTurtleUpgrade(lines, turtle, TurtleSide.LEFT);
addTurtleUpgrade(lines, turtle, TurtleSide.RIGHT);
}
private static void addTurtleUpgrade(List<String> out, TurtleBlockEntity turtle, TurtleSide side) {
var upgrade = turtle.getAccess().getUpgradeWithData(side);
if (upgrade != null) out.add(String.format("Upgrade[%s]: %s", side, upgrade.holder().key().location()));
}
}

View File

@@ -0,0 +1,65 @@
// SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.client.model;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.client.render.CustomLecternRenderer;
import dan200.computercraft.shared.media.items.PrintoutItem;
import net.minecraft.client.model.Model;
import net.minecraft.client.model.geom.ModelLayerLocation;
import net.minecraft.client.model.geom.ModelPart;
import net.minecraft.client.model.geom.PartPose;
import net.minecraft.client.model.geom.builders.CubeListBuilder;
import net.minecraft.client.model.geom.builders.LayerDefinition;
import net.minecraft.client.model.geom.builders.MeshDefinition;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.Unit;
import static dan200.computercraft.client.model.LecternPrintoutModelDefinitions.TEXTURE_HEIGHT;
import static dan200.computercraft.client.model.LecternPrintoutModelDefinitions.TEXTURE_WIDTH;
/**
* A model for {@linkplain PrintoutItem printed books} placed on a lectern.
*
* @see CustomLecternRenderer
*/
public final class LecternBookModel extends Model<Unit> {
public static final ModelLayerLocation LAYER = new ModelLayerLocation(ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "lectern_book"), "main");
public LecternBookModel(ModelPart root) {
super(root, RenderType::entitySolid);
}
public static LayerDefinition createLayer() {
var mesh = new MeshDefinition();
var parts = mesh.getRoot();
parts.addOrReplaceChild(
"spine",
CubeListBuilder.create().texOffs(12, 15).addBox(-0.005f, -5.0f, -0.5f, 0, 10, 1.0f),
PartPose.ZERO
);
var angle = (float) Math.toRadians(5);
parts.addOrReplaceChild(
"left",
CubeListBuilder.create()
.texOffs(0, 10).addBox(0, -5.0f, -6.0f, 0, 10, 6.0f)
.texOffs(0, 0).addBox(0.005f, -4.0f, -5.0f, 1.0f, 8.0f, 5.0f),
PartPose.offsetAndRotation(-0.005f, 0, -0.5f, 0, -angle, 0)
);
parts.addOrReplaceChild(
"right",
CubeListBuilder.create()
.texOffs(14, 10).addBox(0, -5.0f, 0, 0, 10, 6.0f)
.texOffs(0, 0).addBox(0.005f, -4.0f, 0, 1.0f, 8.0f, 5.0f),
PartPose.offsetAndRotation(-0.005f, 0, 0.5f, 0, angle, 0)
);
return LayerDefinition.create(mesh, TEXTURE_WIDTH, TEXTURE_HEIGHT);
}
}

View File

@@ -4,34 +4,43 @@
package dan200.computercraft.client.model;
import com.mojang.blaze3d.vertex.PoseStack;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.client.pocket.PocketComputerData;
import dan200.computercraft.client.render.CustomLecternRenderer;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.pocket.items.PocketComputerItem;
import net.minecraft.client.model.Model;
import net.minecraft.client.model.geom.ModelLayerLocation;
import net.minecraft.client.model.geom.ModelPart;
import net.minecraft.client.model.geom.PartPose;
import net.minecraft.client.model.geom.builders.CubeListBuilder;
import net.minecraft.client.model.geom.builders.LayerDefinition;
import net.minecraft.client.model.geom.builders.MeshDefinition;
import net.minecraft.client.renderer.texture.TextureAtlas;
import net.minecraft.client.renderer.LightTexture;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.Sheets;
import net.minecraft.client.renderer.SubmitNodeCollector;
import net.minecraft.client.renderer.texture.OverlayTexture;
import net.minecraft.client.resources.model.Material;
import net.minecraft.client.resources.model.MaterialSet;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.Unit;
import net.minecraft.world.item.component.DyedItemColor;
/**
* A model for {@linkplain PocketComputerItem pocket computers} placed on a lectern.
*
* @see CustomLecternRenderer
*/
public class LecternPocketModel {
public static final ResourceLocation TEXTURE_NORMAL = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "entity/pocket_computer_normal");
public static final ResourceLocation TEXTURE_ADVANCED = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "entity/pocket_computer_advanced");
public static final ResourceLocation TEXTURE_COLOUR = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "entity/pocket_computer_colour");
public static final ResourceLocation TEXTURE_FRAME = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "entity/pocket_computer_frame");
public static final ResourceLocation TEXTURE_LIGHT = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "entity/pocket_computer_light");
public class LecternPocketModel extends Model<Unit> {
public static final ModelLayerLocation LAYER = new ModelLayerLocation(ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "lectern_pocket"), "main");
private static final Material MATERIAL_NORMAL = new Material(TextureAtlas.LOCATION_BLOCKS, TEXTURE_NORMAL);
private static final Material MATERIAL_ADVANCED = new Material(TextureAtlas.LOCATION_BLOCKS, TEXTURE_ADVANCED);
private static final Material MATERIAL_COLOUR = new Material(TextureAtlas.LOCATION_BLOCKS, TEXTURE_COLOUR);
private static final Material MATERIAL_FRAME = new Material(TextureAtlas.LOCATION_BLOCKS, TEXTURE_FRAME);
private static final Material MATERIAL_LIGHT = new Material(TextureAtlas.LOCATION_BLOCKS, TEXTURE_LIGHT);
public static final Material MATERIAL_NORMAL = Sheets.BLOCK_ENTITIES_MAPPER.apply(ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "pocket_computer_normal"));
public static final Material MATERIAL_ADVANCED = Sheets.BLOCK_ENTITIES_MAPPER.apply(ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "pocket_computer_advanced"));
public static final Material MATERIAL_COLOUR = Sheets.BLOCK_ENTITIES_MAPPER.apply(ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "pocket_computer_colour"));
public static final Material MATERIAL_FRAME = Sheets.BLOCK_ENTITIES_MAPPER.apply(ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "pocket_computer_frame"));
public static final Material MATERIAL_LIGHT = Sheets.BLOCK_ENTITIES_MAPPER.apply(ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "pocket_computer_light"));
// The size of the terminal within the model.
public static final float TERM_WIDTH = 12.0f / 32.0f;
@@ -41,13 +50,11 @@ public class LecternPocketModel {
private static final int TEXTURE_WIDTH = 48 / 2;
private static final int TEXTURE_HEIGHT = 48 / 2;
private final ModelPart root;
public LecternPocketModel() {
root = buildPages();
public LecternPocketModel(ModelPart root) {
super(root, RenderType::entityCutout);
}
private static ModelPart buildPages() {
public static LayerDefinition createLayer() {
var mesh = new MeshDefinition();
var parts = mesh.getRoot();
parts.addOrReplaceChild(
@@ -55,29 +62,43 @@ public class LecternPocketModel {
CubeListBuilder.create().texOffs(0, 0).addBox(0f, -5.0f, -4.0f, 1f, 10.0f, 8.0f),
PartPose.ZERO
);
return mesh.getRoot().bake(TEXTURE_WIDTH, TEXTURE_HEIGHT);
return LayerDefinition.create(mesh, TEXTURE_WIDTH, TEXTURE_HEIGHT);
}
/**
* Render the pocket computer model.
*
* @param poseStack The current pose stack.
* @param bufferSource The buffer source to draw to.
* @param packedLight The current light level.
* @param packedOverlay The overlay texture (used for entity hurt animation).
* @param family The computer family.
* @param frameColour The pocket computer's {@linkplain DyedItemColor colour}.
* @param lightColour The pocket computer's {@linkplain PocketComputerData#getLightState() light colour}.
* @param poseStack The current pose stack.
* @param collector The collector to draw to.
* @param materials The current materials
* @param packedLight The current light level.
* @param family The computer family.
* @param frameColour The pocket computer's {@linkplain DyedItemColor colour}.
* @param lightColour The pocket computer's {@linkplain PocketComputerData#getLightState() light colour}.
*/
/* public void render(PoseStack poseStack, MultiBufferSource bufferSource, int packedLight, int packedOverlay, ComputerFamily family, int frameColour, int lightColour) {
public void submit(
PoseStack poseStack, SubmitNodeCollector collector, MaterialSet materials, int packedLight, ComputerFamily family, int frameColour, int lightColour
) {
if (frameColour != -1) {
root.render(poseStack, MATERIAL_FRAME.buffer(bufferSource, RenderType::entityCutout), packedLight, packedOverlay);
root.render(poseStack, MATERIAL_COLOUR.buffer(bufferSource, RenderType::entityCutout), packedLight, packedOverlay, frameColour);
collector.submitModel(
this, Unit.INSTANCE, poseStack, MATERIAL_FRAME.renderType(RenderType::entityCutout),
packedLight, OverlayTexture.NO_OVERLAY, -1, materials.get(MATERIAL_FRAME), 0, null
);
collector.submitModel(
this, Unit.INSTANCE, poseStack, MATERIAL_COLOUR.renderType(RenderType::entityCutout),
packedLight, OverlayTexture.NO_OVERLAY, frameColour, materials.get(MATERIAL_COLOUR), 0, null
);
} else {
var buffer = (family == ComputerFamily.ADVANCED ? MATERIAL_ADVANCED : MATERIAL_NORMAL).buffer(bufferSource, RenderType::entityCutout);
root.render(poseStack, buffer, packedLight, packedOverlay);
var material = family == ComputerFamily.ADVANCED ? MATERIAL_ADVANCED : MATERIAL_NORMAL;
collector.submitModel(
this, Unit.INSTANCE, poseStack, material.renderType(RenderType::entityCutout),
packedLight, OverlayTexture.NO_OVERLAY, -1, materials.get(material), 0, null
);
}
root.render(poseStack, MATERIAL_LIGHT.buffer(bufferSource, RenderType::entityCutout), LightTexture.FULL_BRIGHT, packedOverlay, lightColour);
}*/
collector.submitModel(
this, Unit.INSTANCE, poseStack, MATERIAL_LIGHT.renderType(RenderType::entityCutout),
LightTexture.FULL_BRIGHT, OverlayTexture.NO_OVERLAY, lightColour, materials.get(MATERIAL_LIGHT), 0, null
);
}
}

View File

@@ -4,54 +4,46 @@
package dan200.computercraft.client.model;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.client.render.CustomLecternRenderer;
import dan200.computercraft.shared.media.items.PrintoutItem;
import net.minecraft.client.model.Model;
import net.minecraft.client.model.geom.ModelLayerLocation;
import net.minecraft.client.model.geom.ModelPart;
import net.minecraft.client.model.geom.PartPose;
import net.minecraft.client.model.geom.builders.CubeListBuilder;
import net.minecraft.client.model.geom.builders.LayerDefinition;
import net.minecraft.client.model.geom.builders.MeshDefinition;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.SubmitNodeCollector;
import net.minecraft.client.renderer.texture.TextureAtlas;
import net.minecraft.client.resources.model.Material;
import net.minecraft.resources.ResourceLocation;
import java.util.List;
import static dan200.computercraft.client.model.LecternPrintoutModelDefinitions.TEXTURE_HEIGHT;
import static dan200.computercraft.client.model.LecternPrintoutModelDefinitions.TEXTURE_WIDTH;
/**
* A model for {@linkplain PrintoutItem printouts} placed on a lectern.
* <p>
* This provides two models, {@linkplain #renderPages(PoseStack, VertexConsumer, int, int, int) one for a variable
* number of pages}, and {@linkplain #renderBook(PoseStack, VertexConsumer, int, int) one for books}.
* A model for {@linkplain PrintoutItem printouts} placed on a lectern. This renders a variable number of pages (1-3),
* stored in {@link State#pages}.
*
* @see CustomLecternRenderer
*/
public class LecternPrintoutModel {
public static final ResourceLocation TEXTURE = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "entity/printout");
public static final Material MATERIAL = new Material(TextureAtlas.LOCATION_BLOCKS, TEXTURE);
private static final int TEXTURE_WIDTH = 32;
private static final int TEXTURE_HEIGHT = 32;
public final class LecternPrintoutModel extends Model<LecternPrintoutModel.State> {
public static final ModelLayerLocation LAYER = new ModelLayerLocation(ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "lectern_printout"), "main");
private static final String PAGE_1 = "page_1";
private static final String PAGE_2 = "page_2";
private static final String PAGE_3 = "page_3";
private static final List<String> PAGES = List.of(PAGE_1, PAGE_2, PAGE_3);
private final ModelPart pagesRoot;
private final ModelPart bookRoot;
private final ModelPart[] pages;
public LecternPrintoutModel() {
pagesRoot = buildPages();
bookRoot = buildBook();
pages = PAGES.stream().map(pagesRoot::getChild).toArray(ModelPart[]::new);
public LecternPrintoutModel(ModelPart root) {
super(root, RenderType::entitySolid);
pages = PAGES.stream().map(root::getChild).toArray(ModelPart[]::new);
}
private static ModelPart buildPages() {
public static LayerDefinition createLayer() {
var mesh = new MeshDefinition();
var parts = mesh.getRoot();
parts.addOrReplaceChild(
@@ -71,49 +63,20 @@ public class LecternPrintoutModel {
PartPose.offsetAndRotation(-0.25f, 0, -1.5f, (float) -Math.PI * (2f / 16), 0, 0)
);
return mesh.getRoot().bake(TEXTURE_WIDTH, TEXTURE_HEIGHT);
return LayerDefinition.create(mesh, TEXTURE_WIDTH, TEXTURE_HEIGHT);
}
private static ModelPart buildBook() {
var mesh = new MeshDefinition();
var parts = mesh.getRoot();
parts.addOrReplaceChild(
"spine",
CubeListBuilder.create().texOffs(12, 15).addBox(-0.005f, -5.0f, -0.5f, 0, 10, 1.0f),
PartPose.ZERO
);
var angle = (float) Math.toRadians(5);
parts.addOrReplaceChild(
"left",
CubeListBuilder.create()
.texOffs(0, 10).addBox(0, -5.0f, -6.0f, 0, 10, 6.0f)
.texOffs(0, 0).addBox(0.005f, -4.0f, -5.0f, 1.0f, 8.0f, 5.0f),
PartPose.offsetAndRotation(-0.005f, 0, -0.5f, 0, -angle, 0)
);
parts.addOrReplaceChild(
"right",
CubeListBuilder.create()
.texOffs(14, 10).addBox(0, -5.0f, 0, 0, 10, 6.0f)
.texOffs(0, 0).addBox(0.005f, -4.0f, 0, 1.0f, 8.0f, 5.0f),
PartPose.offsetAndRotation(-0.005f, 0, 0.5f, 0, angle, 0)
);
return mesh.getRoot().bake(TEXTURE_WIDTH, TEXTURE_HEIGHT);
}
public void submitBook(PoseStack poseStack, SubmitNodeCollector collector, int packedLight, int packedOverlay) {
collector.submitModelPart(bookRoot, poseStack, RenderType.entitySolid(LecternPrintoutModel.MATERIAL.texture()), packedLight, packedOverlay, null);
}
public void renderPages(PoseStack poseStack, VertexConsumer buffer, int packedLight, int packedOverlay, int pageCount) {
@Override
public void setupAnim(State renderState) {
var pageCount = renderState.pages;
if (pageCount > pages.length) pageCount = pages.length;
var i = 0;
for (; i < pageCount; i++) pages[i].visible = true;
for (; i < pages.length; i++) pages[i].visible = false;
}
pagesRoot.render(poseStack, buffer, packedLight, packedOverlay);
public static class State {
public int pages;
}
}

View File

@@ -0,0 +1,26 @@
// SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.client.model;
import dan200.computercraft.api.ComputerCraftAPI;
import net.minecraft.client.renderer.Sheets;
import net.minecraft.client.resources.model.Material;
import net.minecraft.resources.ResourceLocation;
/**
* Definitions for the lectern printout model.
*
* @see LecternBookModel
* @see LecternPrintoutModel
*/
public final class LecternPrintoutModelDefinitions {
public static final Material MATERIAL = Sheets.BLOCK_ENTITIES_MAPPER.apply(ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "printout"));
static final int TEXTURE_WIDTH = 32;
static final int TEXTURE_HEIGHT = 32;
private LecternPrintoutModelDefinitions() {
}
}

View File

@@ -6,17 +6,21 @@ package dan200.computercraft.client.render;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.math.Axis;
import dan200.computercraft.client.model.LecternBookModel;
import dan200.computercraft.client.model.LecternPocketModel;
import dan200.computercraft.client.model.LecternPrintoutModel;
import dan200.computercraft.client.model.LecternPrintoutModelDefinitions;
import dan200.computercraft.client.pocket.ClientPocketComputers;
import dan200.computercraft.client.render.text.FixedWidthFontRenderer;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.core.util.Colour;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.lectern.CustomLecternBlockEntity;
import dan200.computercraft.shared.media.items.PrintoutData;
import dan200.computercraft.shared.media.items.PrintoutItem;
import dan200.computercraft.shared.pocket.items.PocketComputerItem;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.SubmitNodeCollector;
import net.minecraft.client.renderer.blockentity.BlockEntityRenderer;
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider;
@@ -25,6 +29,9 @@ import net.minecraft.client.renderer.blockentity.state.BlockEntityRenderState;
import net.minecraft.client.renderer.feature.ModelFeatureRenderer;
import net.minecraft.client.renderer.state.CameraRenderState;
import net.minecraft.client.renderer.texture.OverlayTexture;
import net.minecraft.client.resources.model.MaterialSet;
import net.minecraft.util.ARGB;
import net.minecraft.util.Unit;
import net.minecraft.world.item.component.DyedItemColor;
import net.minecraft.world.level.block.LecternBlock;
import net.minecraft.world.phys.Vec3;
@@ -42,12 +49,16 @@ import static dan200.computercraft.client.render.text.FixedWidthFontRenderer.FON
public class CustomLecternRenderer implements BlockEntityRenderer<CustomLecternBlockEntity, CustomLecternRenderer.State> {
private static final int POCKET_TERMINAL_RENDER_DISTANCE = 32;
private final MaterialSet materials;
private final LecternPrintoutModel printoutModel;
private final LecternBookModel bookModel;
private final LecternPocketModel pocketModel;
public CustomLecternRenderer(BlockEntityRendererProvider.Context context) {
printoutModel = new LecternPrintoutModel();
pocketModel = new LecternPocketModel();
materials = context.materials();
bookModel = new LecternBookModel(context.bakeLayer(LecternBookModel.LAYER));
printoutModel = new LecternPrintoutModel(context.bakeLayer(LecternPrintoutModel.LAYER));
pocketModel = new LecternPocketModel(context.bakeLayer(LecternPocketModel.LAYER));
}
@Override
@@ -66,7 +77,8 @@ public class CustomLecternRenderer implements BlockEntityRenderer<CustomLecternB
var computer = ClientPocketComputers.get(item);
state.setPocket(
pocket.getFamily(), DyedItemColor.getOrDefault(item, -1),
computer == null ? -1 : computer.getLightState(),
ARGB.opaque(computer == null || computer.getLightState() == -1 ? Colour.BLACK.getHex() : computer.getLightState()),
// Only render a terminal if we're close to it.
computer == null || !Vec3.atCenterOf(lectern.getBlockPos()).closerThan(camera, POCKET_TERMINAL_RENDER_DISTANCE)
? null : computer.getTerminal()
);
@@ -85,35 +97,38 @@ public class CustomLecternRenderer implements BlockEntityRenderer<CustomLecternB
if (state.type == Type.PRINTOUT) {
if (state.isBook) {
printoutModel.submitBook(poseStack, collector, state.lightCoords, OverlayTexture.NO_OVERLAY);
collector.submitModel(
bookModel, Unit.INSTANCE, poseStack, LecternPrintoutModelDefinitions.MATERIAL.renderType(RenderType::entitySolid),
state.lightCoords, OverlayTexture.NO_OVERLAY, -1,
materials.get(LecternPrintoutModelDefinitions.MATERIAL), 0, null
);
} else {
// TODO: printoutModel.renderPages(poseStack, vertexConsumer, packedLight, packedOverlay, PrintoutData.getOrEmpty(item).pages());
collector.submitModel(
printoutModel, state.printoutState, poseStack, LecternPrintoutModelDefinitions.MATERIAL.renderType(RenderType::entitySolid),
state.lightCoords, OverlayTexture.NO_OVERLAY, -1,
materials.get(LecternPrintoutModelDefinitions.MATERIAL), 0, null
);
}
} else if (state.type == Type.POCKET_COMPUTER) {
// TODO: Pocket model rendering
/*pocketModel.render(
poseStack, buffer, packedLight, packedOverlay, pocket.getFamily(), DyedItemColor.getOrDefault(item, -1),
ARGB.opaque(computer == null || computer.getLightState() == -1 ? Colour.BLACK.getHex() : computer.getLightState())
);
pocketModel.submit(poseStack, collector, materials, state.lightCoords, state.pocketFamily, state.pocketColour, state.pocketLight);
// Jiggle the terminal about a bit, so (0, 0) is in the top left of the model's terminal hole.
poseStack.mulPose(Axis.YP.rotationDegrees(90f));
poseStack.translate(-0.5 * LecternPocketModel.TERM_WIDTH, 0.5 * LecternPocketModel.TERM_HEIGHT + 1f / 32.0f, 1 / 16.0f);
poseStack.mulPose(Axis.XP.rotationDegrees(180));
// Either render the terminal or a black screen, depending on how close we are.
var quadEmitter = FixedWidthFontRenderer.toVertexConsumer(poseStack, buffer.getBuffer(FixedWidthFontRenderer.TERMINAL_TEXT));
// Either render the terminal or a black screen.
if (state.pocketTerminal != null) {
renderPocketTerminal(poseStack, quadEmitter, state.pocketTerminal);
renderPocketTerminal(poseStack, collector, state.pocketTerminal);
} else {
FixedWidthFontRenderer.drawEmptyTerminal(quadEmitter, 0, 0, LecternPocketModel.TERM_WIDTH, LecternPocketModel.TERM_HEIGHT);
}*/
FixedWidthFontRenderer.drawEmptyTerminal(poseStack, collector, 0, 0, LecternPocketModel.TERM_WIDTH, LecternPocketModel.TERM_HEIGHT);
}
}
poseStack.popPose();
}
private static void renderPocketTerminal(PoseStack poseStack, FixedWidthFontRenderer.QuadEmitter quadEmitter, Terminal terminal) {
private static void renderPocketTerminal(PoseStack poseStack, SubmitNodeCollector collector, Terminal terminal) {
var width = terminal.getWidth() * FONT_WIDTH;
var height = terminal.getHeight() * FONT_HEIGHT;
@@ -127,7 +142,10 @@ public class CustomLecternRenderer implements BlockEntityRenderer<CustomLecternB
var marginX = ((LecternPocketModel.TERM_WIDTH / scale) - width) / 2;
var marginY = ((LecternPocketModel.TERM_HEIGHT / scale) - height) / 2;
FixedWidthFontRenderer.drawTerminal(quadEmitter, marginX, marginY, terminal, marginY, marginY, marginX, marginX);
collector.submitCustomGeometry(poseStack, FixedWidthFontRenderer.TERMINAL_TEXT, (pose, buffer) -> {
var quadEmitter = new FixedWidthFontRenderer.QuadEmitter(pose.pose(), buffer);
FixedWidthFontRenderer.drawTerminal(quadEmitter, marginX, marginY, terminal, marginY, marginY, marginX, marginX);
});
}
private enum Type {
@@ -139,7 +157,7 @@ public class CustomLecternRenderer implements BlockEntityRenderer<CustomLecternB
public static final class State extends BlockEntityRenderState {
private Type type = Type.PRINTOUT;
private boolean isBook;
private int pages;
private final LecternPrintoutModel.State printoutState = new LecternPrintoutModel.State();
private ComputerFamily pocketFamily = ComputerFamily.NORMAL;
private int pocketColour;
@@ -157,7 +175,7 @@ public class CustomLecternRenderer implements BlockEntityRenderer<CustomLecternB
private void setPrintout(boolean isBook, int pages) {
this.type = Type.PRINTOUT;
this.isBook = isBook;
this.pages = pages;
this.printoutState.pages = pages;
this.pocketTerminal = null;
}

View File

@@ -44,7 +44,7 @@ public final class PocketItemRenderer extends ItemMapLikeRenderer {
}
@Override
protected void renderItem(PoseStack transform, SubmitNodeCollector submit, ItemStack stack, int light) {
protected void renderItem(PoseStack transform, SubmitNodeCollector collector, ItemStack stack, int light) {
var computer = ClientPocketComputers.get(stack);
var terminal = computer == null ? null : computer.getTerminal();
@@ -76,21 +76,20 @@ public final class PocketItemRenderer extends ItemMapLikeRenderer {
var family = item.getFamily();
var frameColour = DyedItemColor.getOrDefault(stack, -1);
var matrix = transform.last().pose();
renderFrame(transform, submit, family, frameColour, light, width, height);
renderFrame(transform, collector, family, frameColour, light, width, height);
// Render the light
var lightColour = computer == null || computer.getLightState() == -1 ? Colour.BLACK.getHex() : computer.getLightState();
renderLight(transform, submit, lightColour, width, height);
renderLight(transform, collector, lightColour, width, height);
submit.submitCustomGeometry(transform, FixedWidthFontRenderer.TERMINAL_TEXT, (pose, buffer) -> {
var quadEmitter = new FixedWidthFontRenderer.QuadEmitter(pose.pose(), buffer);
if (terminal == null) {
FixedWidthFontRenderer.drawEmptyTerminal(quadEmitter, 0, 0, width, height);
} else {
if (terminal == null) {
FixedWidthFontRenderer.drawEmptyTerminal(transform, collector, 0, 0, width, height);
} else {
collector.submitCustomGeometry(transform, FixedWidthFontRenderer.TERMINAL_TEXT, (pose, buffer) -> {
var quadEmitter = new FixedWidthFontRenderer.QuadEmitter(pose.pose(), buffer);
FixedWidthFontRenderer.drawTerminal(quadEmitter, MARGIN, MARGIN, terminal, MARGIN, MARGIN, MARGIN, MARGIN);
}
});
});
}
transform.popPose();
}

View File

@@ -10,7 +10,6 @@ import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.SubmitNodeCollector;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.resources.ResourceLocation;
/**
@@ -21,8 +20,6 @@ import net.minecraft.resources.ResourceLocation;
* sheet.
*/
public class SpriteRenderer {
public static final ResourceLocation TEXTURE = ResourceLocation.withDefaultNamespace("textures/atlas/gui.png");
private final PoseStack transform;
private final SubmitNodeCollector submit;
private final int light;

View File

@@ -19,10 +19,8 @@ import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.turtle.blocks.TurtleBlockEntity;
import dan200.computercraft.shared.util.Holiday;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.Font;
import net.minecraft.client.renderer.SubmitNodeCollector;
import net.minecraft.client.renderer.block.model.ItemTransform;
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.state.BlockEntityRenderState;
@@ -44,14 +42,10 @@ public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBloc
public static final ResourceLocation ADVANCED_TURTLE_MODEL = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_advanced");
public static final ResourceLocation COLOUR_TURTLE_MODEL = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_colour");
private final BlockEntityRenderDispatcher renderer;
private final ItemModelResolver itemModelResolver;
private final Font font;
public TurtleBlockEntityRenderer(BlockEntityRendererProvider.Context context) {
renderer = context.blockEntityRenderDispatcher();
itemModelResolver = context.itemModelResolver();
font = context.font();
}
public static final class State extends BlockEntityRenderState {
@@ -64,10 +58,10 @@ public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBloc
private @Nullable StandaloneModel elfOverlay;
private float leftAngle;
private ItemStackRenderState leftUpgrade = new ItemStackRenderState();
private final ItemStackRenderState leftUpgrade = new ItemStackRenderState();
private float rightAngle;
private ItemStackRenderState rightUpgrade = new ItemStackRenderState();
private final ItemStackRenderState rightUpgrade = new ItemStackRenderState();
private State() {
}

View File

@@ -104,12 +104,7 @@ public class MonitorBlockEntityRenderer implements BlockEntityRenderer<MonitorBl
transform.popPose();
} else {
collector.submitCustomGeometry(transform, FixedWidthFontRenderer.TERMINAL_TEXT, (pose, consumer) -> {
FixedWidthFontRenderer.drawEmptyTerminal(
new FixedWidthFontRenderer.QuadEmitter(pose.pose(), consumer),
-MARGIN, MARGIN, (float) (xSize + 2 * MARGIN), (float) -(ySize + MARGIN * 2)
);
});
FixedWidthFontRenderer.drawEmptyTerminal(transform, collector, -MARGIN, MARGIN, (float) (xSize + 2 * MARGIN), (float) -(ySize + MARGIN * 2));
}
transform.popPose();

View File

@@ -13,6 +13,7 @@ import dan200.computercraft.core.terminal.TextBuffer;
import dan200.computercraft.core.util.Colour;
import net.minecraft.client.renderer.LightTexture;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.SubmitNodeCollector;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.ARGB;
import org.joml.Matrix4f;
@@ -213,8 +214,11 @@ public final class FixedWidthFontRenderer {
emitter.poseMatrix().set(transformBackup);
}
public static void drawEmptyTerminal(QuadEmitter emitter, float x, float y, float width, float height) {
drawQuad(emitter, x, y, 0, width, height, BLACK, LightTexture.FULL_BRIGHT);
public static void drawEmptyTerminal(PoseStack transform, SubmitNodeCollector collector, float x, float y, float width, float height) {
collector.submitCustomGeometry(transform, FixedWidthFontRenderer.TERMINAL_TEXT, (pose, buffer) -> {
var quadEmitter = new FixedWidthFontRenderer.QuadEmitter(pose.pose(), buffer);
drawQuad(quadEmitter, x, y, 0, width, height, BLACK, LightTexture.FULL_BRIGHT);
});
}
public record QuadEmitter(Matrix4f poseMatrix, VertexConsumer consumer) {

View File

@@ -10,7 +10,7 @@ import dan200.computercraft.api.pocket.IPocketUpgrade;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.client.gui.GuiSprites;
import dan200.computercraft.client.model.LecternPocketModel;
import dan200.computercraft.client.model.LecternPrintoutModel;
import dan200.computercraft.client.model.LecternPrintoutModelDefinitions;
import dan200.computercraft.client.turtle.TurtleOverlay;
import dan200.computercraft.data.client.BlockModelProvider;
import dan200.computercraft.data.client.ItemModelProvider;
@@ -71,9 +71,9 @@ public final class DataProviders {
generator.addFromCodec("Block atlases", PackOutput.Target.RESOURCE_PACK, "atlases", SpriteSources.FILE_CODEC, out -> {
out.accept(AtlasIds.BLOCKS, makeSprites(Stream.of(
LecternPrintoutModel.TEXTURE,
LecternPocketModel.TEXTURE_NORMAL, LecternPocketModel.TEXTURE_ADVANCED,
LecternPocketModel.TEXTURE_COLOUR, LecternPocketModel.TEXTURE_FRAME, LecternPocketModel.TEXTURE_LIGHT
LecternPrintoutModelDefinitions.MATERIAL.texture(),
LecternPocketModel.MATERIAL_NORMAL.texture(), LecternPocketModel.MATERIAL_ADVANCED.texture(),
LecternPocketModel.MATERIAL_COLOUR.texture(), LecternPocketModel.MATERIAL_FRAME.texture(), LecternPocketModel.MATERIAL_LIGHT.texture()
)));
out.accept(AtlasIds.GUI, makeSprites(

View File

@@ -24,11 +24,13 @@ import net.fabricmc.fabric.api.client.model.loading.v1.PreparableModelLoadingPlu
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.BlockRenderLayerMap;
import net.fabricmc.fabric.api.client.rendering.v1.EntityModelLayerRegistry;
import net.fabricmc.fabric.api.client.rendering.v1.SpecialGuiElementRegistry;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.Util;
import net.minecraft.client.Minecraft;
import net.minecraft.client.color.item.ItemTintSources;
import net.minecraft.client.gui.components.debug.DebugScreenEntries;
import net.minecraft.client.gui.render.pip.PictureInPictureRenderer;
import net.minecraft.client.gui.render.state.pip.PictureInPictureRenderState;
import net.minecraft.client.gui.screens.MenuScreens;
@@ -62,6 +64,7 @@ public class ComputerCraftClient {
ClientRegistry.registerItemColours(ItemTintSources.ID_MAPPER::put);
ClientRegistry.registerSelectItemProperties(SelectItemModelProperties.ID_MAPPER::put);
ClientRegistry.registerConditionalItemProperties(ConditionalItemModelProperties.ID_MAPPER::put);
ClientRegistry.registerLayerDefinitions((id, factory) -> EntityModelLayerRegistry.registerModelLayer(id, factory::get));
PreparableModelLoadingPlugin.register(
(state, executor) -> ClientRegistry.gatherExtraModels(state.resourceManager(), executor),
@@ -100,6 +103,8 @@ public class ComputerCraftClient {
});
*/
ClientRegistry.registerDebugScreenEntries(DebugScreenEntries::register);
// Register our open folder command
ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) ->
dispatcher.register(LiteralArgumentBuilder.<FabricClientCommandSource>literal(ComputerCraft.CLIENT_OPEN_FOLDER)

View File

@@ -53,6 +53,7 @@ neoForge {
register("client") {
client()
sourceSet = sourceSets.client
}
register("server") {
@@ -77,6 +78,7 @@ neoForge {
register("data") {
configureForData("computercraft", sourceSets.main.get())
loadedMods = listOf(computercraftDatagen.get())
sourceSet = sourceSets.datagen
}
fun RunModel.configureForGameTest() {
@@ -87,6 +89,7 @@ neoForge {
programArgument("--mixin.config=computercraft-gametest.mixins.json")
loadedMods.add(testMod)
sourceSet = sourceSets.testMod
jvmArgument("-ea")
}
@@ -119,6 +122,7 @@ neoForge {
register("exampleData") {
configureForData("examplemod", sourceSets.examples.get())
loadedMods.add(exampleMod.get())
sourceSet = sourceSets.examples
}
}
}

View File

@@ -51,11 +51,6 @@ public final class ForgeClientHooks {
}*/
}
@SubscribeEvent
public static void onRenderText(CustomizeGuiOverlayEvent.DebugText event) {
ClientHooks.addBlockDebugInfo(event.getRight()::add);
}
@SubscribeEvent
public static void onRenderInHand(RenderHandEvent event) {
if (ClientHooks.onRenderHeldItem(

View File

@@ -107,11 +107,18 @@ public final class ForgeClientRegistry {
});
}
@SubscribeEvent
public static void registerLayerDefinitions(EntityRenderersEvent.RegisterLayerDefinitions event) {
ClientRegistry.registerLayerDefinitions(event::registerLayerDefinition);
}
@SubscribeEvent
public static void registerPictureInPictureRenderers(RegisterPictureInPictureRenderersEvent event) {
ClientRegistry.registerPictureInPictureRenderers(event::register);
}
// TODO(1.21.10): ClientRegistry.registerDebugScreenEntries once https://github.com/neoforged/NeoForge/pull/2699 is merged
@SubscribeEvent
public static void setupClient(FMLClientSetupEvent event) {
ClientRegistry.register();

View File

@@ -12,6 +12,7 @@ import dan200.computercraft.api.network.wired.WiredElementCapability;
import dan200.computercraft.api.peripheral.PeripheralCapability;
import dan200.computercraft.api.pocket.IPocketUpgrade;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.impl.Peripherals;
import dan200.computercraft.impl.PocketUpgrades;
import dan200.computercraft.impl.TurtleUpgrades;
import dan200.computercraft.shared.CommonHooks;
@@ -112,8 +113,8 @@ public final class ComputerCraft {
ComputerCraftAPI.registerGenericSource(new FluidMethods());
ComputerCraftAPI.registerGenericSource(new EnergyMethods());
ForgeComputerCraftAPI.registerGenericCapability(Capabilities.Item.BLOCK);
ForgeComputerCraftAPI.registerGenericCapability(Capabilities.Fluid.BLOCK);
Peripherals.addGenericLookup(InventoryMethods::extractContainer);
Peripherals.addGenericLookup(FluidMethods::extractContainer);
ForgeComputerCraftAPI.registerGenericCapability(Capabilities.Energy.BLOCK);
ForgeDetailRegistries.FLUID_STACK.addProvider(FluidData::fill);

View File

@@ -10,9 +10,12 @@ import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.shared.util.CapabilityUtil;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.material.Fluid;
import net.neoforged.neoforge.capabilities.Capabilities;
import net.neoforged.neoforge.transfer.ResourceHandler;
@@ -94,6 +97,11 @@ public final class FluidMethods extends AbstractFluidMethods<FluidMethods.Storag
return moveFluid(from, to.storage(), fluid, actualLimit);
}
public static @Nullable StorageWrapper extractContainer(ServerLevel level, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, @Nullable Direction direction) {
var storage = CapabilityUtil.getCapability(level, Capabilities.Fluid.BLOCK, pos, state, blockEntity, direction);
return storage == null ? null : new StorageWrapper(storage);
}
@Nullable
private static ResourceHandler<FluidResource> extractHandler(IPeripheral peripheral) {
var object = peripheral.getTarget();

View File

@@ -11,10 +11,13 @@ import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.shared.platform.ForgeContainerTransfer;
import dan200.computercraft.shared.util.CapabilityUtil;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.Container;
import net.minecraft.world.item.Items;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.neoforged.neoforge.capabilities.Capabilities;
import net.neoforged.neoforge.transfer.ResourceHandler;
import net.neoforged.neoforge.transfer.item.ItemResource;
@@ -120,6 +123,11 @@ public final class InventoryMethods extends AbstractInventoryMethods<InventoryMe
return moveItem(from, fromSlot - 1, to.storage(), toSlot.orElse(0) - 1, actualLimit);
}
public static @Nullable StorageWrapper extractContainer(ServerLevel level, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, @Nullable Direction direction) {
var storage = CapabilityUtil.getCapability(level, Capabilities.Item.BLOCK, pos, state, blockEntity, direction);
return storage == null ? null : new StorageWrapper(storage);
}
@Nullable
private static ResourceHandler<ItemResource> extractHandler(IPeripheral peripheral) {
var object = peripheral.getTarget();