Update to Minecraft 26.2

- Move block and item ids into separate API-accessible classes, to
   match vanilla.
 - Switch to Fabric's built-in permission API. We no longer need to
   optionally support fabric-permission-api, which makes things a bit
   simpler.
 - Lots of rendering changes. Crucially, switch monitor rendering back
   to using VBOs, using the new FeatureRenderer system. This currently
   requires mixins on Fabric, but there's a PR open to add an API for
   it in the future.
This commit is contained in:
Jonathan Coates
2026-06-16 17:33:30 +01:00
parent 06abd03e9f
commit 51c7c1ce3f
82 changed files with 908 additions and 557 deletions
@@ -0,0 +1,45 @@
// SPDX-FileCopyrightText: 2026 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.api;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.Identifier;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.level.block.Block;
/**
* The identifiers of blocks provided by ComputerCraft.
*
* @see net.minecraft.references.BlockIds
* @see ComputerCraftBlockItemIds
*/
public final class ComputerCraftBlockIds {
public static final ResourceKey<Block> COMPUTER_NORMAL = ComputerCraftBlockItemIds.COMPUTER_NORMAL.block();
public static final ResourceKey<Block> COMPUTER_ADVANCED = ComputerCraftBlockItemIds.COMPUTER_ADVANCED.block();
public static final ResourceKey<Block> COMPUTER_COMMAND = ComputerCraftBlockItemIds.COMPUTER_COMMAND.block();
public static final ResourceKey<Block> TURTLE_NORMAL = ComputerCraftBlockItemIds.TURTLE_NORMAL.block();
public static final ResourceKey<Block> TURTLE_ADVANCED = ComputerCraftBlockItemIds.TURTLE_ADVANCED.block();
public static final ResourceKey<Block> CABLE = create("cable");
public static final ResourceKey<Block> DISK_DRIVE = ComputerCraftBlockItemIds.DISK_DRIVE.block();
public static final ResourceKey<Block> MONITOR_ADVANCED = ComputerCraftBlockItemIds.MONITOR_ADVANCED.block();
public static final ResourceKey<Block> MONITOR_NORMAL = ComputerCraftBlockItemIds.MONITOR_NORMAL.block();
public static final ResourceKey<Block> PRINTER = ComputerCraftBlockItemIds.PRINTER.block();
public static final ResourceKey<Block> REDSTONE_RELAY = ComputerCraftBlockItemIds.REDSTONE_RELAY.block();
public static final ResourceKey<Block> SPEAKER = ComputerCraftBlockItemIds.SPEAKER.block();
public static final ResourceKey<Block> WIRED_MODEM_FULL = ComputerCraftBlockItemIds.WIRED_MODEM_FULL.block();
public static final ResourceKey<Block> WIRELESS_MODEM_ADVANCED = ComputerCraftBlockItemIds.WIRELESS_MODEM_ADVANCED.block();
public static final ResourceKey<Block> WIRELESS_MODEM_NORMAL = ComputerCraftBlockItemIds.WIRELESS_MODEM_NORMAL.block();
public static final ResourceKey<Block> LECTERN = create("lectern");
private static ResourceKey<Block> create(String id) {
return ResourceKey.create(Registries.BLOCK, Identifier.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, id));
}
private ComputerCraftBlockIds() {
}
}
@@ -0,0 +1,42 @@
// SPDX-FileCopyrightText: 2026 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.api;
import net.minecraft.references.BlockItemId;
import net.minecraft.resources.Identifier;
/**
* The identifiers of blocks and items provided by ComputerCraft.
*
* @see net.minecraft.references.BlockItemIds
* @see ComputerCraftBlockIds
* @see ComputerCraftItemIds
*/
public final class ComputerCraftBlockItemIds {
public static final BlockItemId COMPUTER_NORMAL = create("computer_normal");
public static final BlockItemId COMPUTER_ADVANCED = create("computer_advanced");
public static final BlockItemId COMPUTER_COMMAND = create("computer_command");
public static final BlockItemId TURTLE_NORMAL = create("turtle_normal");
public static final BlockItemId TURTLE_ADVANCED = create("turtle_advanced");
public static final BlockItemId DISK_DRIVE = create("disk_drive");
public static final BlockItemId MONITOR_ADVANCED = create("monitor_advanced");
public static final BlockItemId MONITOR_NORMAL = create("monitor_normal");
public static final BlockItemId PRINTER = create("printer");
public static final BlockItemId REDSTONE_RELAY = create("redstone_relay");
public static final BlockItemId SPEAKER = create("speaker");
public static final BlockItemId WIRED_MODEM_FULL = create("wired_modem_full");
public static final BlockItemId WIRELESS_MODEM_ADVANCED = create("wireless_modem_advanced");
public static final BlockItemId WIRELESS_MODEM_NORMAL = create("wireless_modem_normal");
private static BlockItemId create(String name) {
var id = Identifier.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, name);
return BlockItemId.create(id, id);
}
private ComputerCraftBlockItemIds() {
}
}
@@ -0,0 +1,54 @@
// SPDX-FileCopyrightText: 2026 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.api;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.Identifier;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.item.Item;
/**
* The identifiers of items provided by ComputerCraft.
*
* @see net.minecraft.references.ItemIds
* @see ComputerCraftBlockItemIds
*/
public final class ComputerCraftItemIds {
public static final ResourceKey<Item> COMPUTER_NORMAL = ComputerCraftBlockItemIds.COMPUTER_NORMAL.item();
public static final ResourceKey<Item> COMPUTER_ADVANCED = ComputerCraftBlockItemIds.COMPUTER_ADVANCED.item();
public static final ResourceKey<Item> COMPUTER_COMMAND = ComputerCraftBlockItemIds.COMPUTER_COMMAND.item();
public static final ResourceKey<Item> TURTLE_NORMAL = ComputerCraftBlockItemIds.TURTLE_NORMAL.item();
public static final ResourceKey<Item> TURTLE_ADVANCED = ComputerCraftBlockItemIds.TURTLE_ADVANCED.item();
public static final ResourceKey<Item> POCKET_COMPUTER_NORMAL = create("pocket_computer_normal");
public static final ResourceKey<Item> POCKET_COMPUTER_ADVANCED = create("pocket_computer_advanced");
public static final ResourceKey<Item> DISK = create("disk");
public static final ResourceKey<Item> TREASURE_DISK = create("treasure_disk");
public static final ResourceKey<Item> PRINTED_PAGE = create("printed_page");
public static final ResourceKey<Item> PRINTED_PAGES = create("printed_pages");
public static final ResourceKey<Item> PRINTED_BOOK = create("printed_book");
public static final ResourceKey<Item> CABLE = create("cable");
public static final ResourceKey<Item> DISK_DRIVE = ComputerCraftBlockItemIds.DISK_DRIVE.item();
public static final ResourceKey<Item> MONITOR_ADVANCED = ComputerCraftBlockItemIds.MONITOR_ADVANCED.item();
public static final ResourceKey<Item> MONITOR_NORMAL = ComputerCraftBlockItemIds.MONITOR_NORMAL.item();
public static final ResourceKey<Item> PRINTER = ComputerCraftBlockItemIds.PRINTER.item();
public static final ResourceKey<Item> REDSTONE_RELAY = ComputerCraftBlockItemIds.REDSTONE_RELAY.item();
public static final ResourceKey<Item> SPEAKER = ComputerCraftBlockItemIds.SPEAKER.item();
public static final ResourceKey<Item> WIRED_MODEM = create("wired_modem");
public static final ResourceKey<Item> WIRED_MODEM_FULL = ComputerCraftBlockItemIds.WIRED_MODEM_FULL.item();
public static final ResourceKey<Item> WIRELESS_MODEM_ADVANCED = ComputerCraftBlockItemIds.WIRELESS_MODEM_ADVANCED.item();
public static final ResourceKey<Item> WIRELESS_MODEM_NORMAL = ComputerCraftBlockItemIds.WIRELESS_MODEM_NORMAL.item();
private static ResourceKey<Item> create(String id) {
return ResourceKey.create(Registries.ITEM, Identifier.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, id));
}
private ComputerCraftItemIds() {
}
}
@@ -6,6 +6,7 @@ package dan200.computercraft.api;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.Identifier;
import net.minecraft.tags.BlockItemTagId;
import net.minecraft.tags.TagKey;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.entity.player.Player;
@@ -21,11 +22,11 @@ import net.minecraft.world.phys.BlockHitResult;
* Tags provided by ComputerCraft.
*/
public class ComputerCraftTags {
public static class Items {
public static final TagKey<Item> COMPUTER = make("computer");
public static final TagKey<Item> TURTLE = make("turtle");
public static final TagKey<Item> WIRED_MODEM = make("wired_modem");
public static final TagKey<Item> MONITOR = make("monitor");
public static final class Items {
public static final TagKey<Item> COMPUTER = BlockItems.COMPUTER.item();
public static final TagKey<Item> TURTLE = BlockItems.TURTLE.item();
public static final TagKey<Item> WIRED_MODEM = BlockItems.WIRED_MODEM.item();
public static final TagKey<Item> MONITOR = BlockItems.MONITOR.item();
/**
* Floppy disks. Both the read/write version, and treasure disks.
@@ -60,13 +61,16 @@ public class ComputerCraftTags {
private static TagKey<Item> make(String name) {
return TagKey.create(Registries.ITEM, Identifier.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, name));
}
private Items() {
}
}
public static class Blocks {
public static final TagKey<Block> COMPUTER = make("computer");
public static final TagKey<Block> TURTLE = make("turtle");
public static final TagKey<Block> WIRED_MODEM = make("wired_modem");
public static final TagKey<Block> MONITOR = make("monitor");
public static final class Blocks {
public static final TagKey<Block> COMPUTER = BlockItems.COMPUTER.block();
public static final TagKey<Block> TURTLE = BlockItems.TURTLE.block();
public static final TagKey<Block> WIRED_MODEM = BlockItems.WIRED_MODEM.block();
public static final TagKey<Block> MONITOR = BlockItems.MONITOR.block();
/**
* Blocks which should be ignored by a {@code peripheral_hub} peripheral.
@@ -102,8 +106,26 @@ public class ComputerCraftTags {
*/
public static final TagKey<Block> TURTLE_CAN_USE = make("turtle_can_use");
private Blocks() {
}
private static TagKey<Block> make(String name) {
return TagKey.create(Registries.BLOCK, Identifier.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, name));
}
}
public static final class BlockItems {
public static final BlockItemTagId COMPUTER = make("computer");
public static final BlockItemTagId TURTLE = make("turtle");
public static final BlockItemTagId WIRED_MODEM = make("wired_modem");
public static final BlockItemTagId MONITOR = make("monitor");
private BlockItems() {
}
private static BlockItemTagId make(String name) {
var id = Identifier.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, name);
return BlockItemTagId.create(id, id);
}
}
}
@@ -33,9 +33,11 @@ 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;
import net.minecraft.client.renderer.feature.FeatureRenderer;
import net.minecraft.client.renderer.feature.FeatureRendererType;
import net.minecraft.client.renderer.feature.submit.SubmitNode;
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;
@@ -56,7 +58,6 @@ import java.util.concurrent.ConcurrentHashMap;
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;
/**
@@ -208,7 +209,7 @@ public final class ClientRegistry {
}
public interface RegisterPictureInPictureRenderer {
<T extends PictureInPictureRenderState> void register(Class<T> state, Function<MultiBufferSource.BufferSource, PictureInPictureRenderer<T>> factory);
<T extends PictureInPictureRenderState> void register(Class<T> state, Supplier<PictureInPictureRenderer<T>> factory);
}
public static void registerPictureInPictureRenderers(RegisterPictureInPictureRenderer register) {
@@ -218,4 +219,12 @@ public final class ClientRegistry {
public static void registerDebugScreenEntries(BiConsumer<Identifier, DebugScreenEntry> register) {
register.accept(LookingAtBlockEntityDebugEntry.ID, LookingAtBlockEntityDebugEntry.create());
}
public interface RegisterFeatureRenderer {
<T extends SubmitNode> void register(FeatureRendererType<T> type, Supplier<FeatureRenderer<T>> renderer);
}
public static void registerFeatureRenderers(RegisterFeatureRenderer register) {
register.register(MonitorBlockEntityRenderer.MonitorFeatureRenderer.TYPE, MonitorBlockEntityRenderer.MonitorFeatureRenderer::new);
}
}
@@ -61,7 +61,7 @@ public class ClientTableFormatter implements TableFormatter {
@Override
public void writeLine(String label, Component component) {
var mc = Minecraft.getInstance();
var chat = mc.gui.getChat();
var chat = mc.gui.hud.getChat();
// TODO: Trim the text if it goes over the allowed length
// int maxWidth = MathHelper.floor( chat.getChatWidth() / chat.getScale() );
@@ -72,7 +72,7 @@ public class ClientTableFormatter implements TableFormatter {
@Override
public void display(TableBuilder table) {
var chat = Minecraft.getInstance().gui.getChat();
var chat = Minecraft.getInstance().gui.hud.getChat();
var tag = createTag(table.getId());
if (chat.allMessages.removeIf(guiMessage -> guiMessage.tag() != null && Objects.equals(guiMessage.tag().logTag(), tag.logTag()))) {
@@ -101,7 +101,7 @@ public abstract class AbstractComputerScreen<T extends AbstractComputerMenu> ext
if (uploadNagDeadline != Long.MAX_VALUE && Util.getNanos() >= uploadNagDeadline) {
new ItemToast(minecraft, displayStack, NO_RESPONSE_TITLE, NO_RESPONSE_MSG, ItemToast.TRANSFER_NO_RESPONSE_TOKEN)
.showOrReplace(minecraft.getToastManager());
.showOrReplace(minecraft.gui.toastManager());
uploadNagDeadline = Long.MAX_VALUE;
}
}
@@ -230,7 +230,7 @@ public abstract class AbstractComputerScreen<T extends AbstractComputerMenu> ext
private void alert(Component title, Component message) {
OptionScreen.show(minecraft, this, title, message,
List.of(OptionScreen.newButton(OK, b -> minecraft.setScreen(this)))
List.of(OptionScreen.newButton(OK, b -> minecraft.gui.setScreen(this)))
);
}
}
@@ -52,7 +52,7 @@ public class NoTermComputerScreen<T extends AbstractComputerMenu> extends Screen
// First ensure we're still grabbing the mouse, so the user can look around. Then reset bits of state that
// grabbing unsets.
minecraft.mouseHandler.grabMouse();
minecraft.screen = this;
minecraft.gui.screen = this;
KeyMapping.releaseAll();
super.init();
@@ -52,7 +52,7 @@ public final class OptionScreen extends Screen {
}
public static void show(Minecraft minecraft, Screen originalScreen, Component title, Component message, List<AbstractWidget> buttons) {
minecraft.setScreen(new OptionScreen(title, message, buttons, unwrap(originalScreen)));
minecraft.gui.setScreen(new OptionScreen(title, message, buttons, unwrap(originalScreen)));
}
@Contract("!null -> !null")
@@ -106,7 +106,7 @@ public final class OptionScreen extends Screen {
@Override
public void onClose() {
minecraft.setScreen(originalScreen);
minecraft.gui.setScreen(originalScreen);
}
public static AbstractWidget newButton(Component component, Button.OnPress clicked) {
@@ -6,6 +6,7 @@ package dan200.computercraft.client.gui;
import com.mojang.blaze3d.vertex.PoseStack;
import dan200.computercraft.client.render.PrintoutRenderer;
import dan200.computercraft.client.render.text.FixedWidthFontRenderer;
import dan200.computercraft.core.terminal.TextBuffer;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.media.PrintoutMenu;
@@ -15,7 +16,7 @@ import net.minecraft.client.gui.navigation.ScreenRectangle;
import net.minecraft.client.gui.render.pip.PictureInPictureRenderer;
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
import net.minecraft.client.input.KeyEvent;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.SubmitNodeCollector;
import net.minecraft.client.renderer.state.gui.GuiElementRenderState;
import net.minecraft.client.renderer.state.gui.pip.PictureInPictureRenderState;
import net.minecraft.network.chat.Component;
@@ -176,23 +177,20 @@ public final class PrintoutScreen extends AbstractContainerScreen<PrintoutMenu>
* multiple z-levels.
*/
public static final class PrintoutPictureRenderer extends PictureInPictureRenderer<PrintoutRenderState> {
public PrintoutPictureRenderer(MultiBufferSource.BufferSource bufferSource) {
super(bufferSource);
}
@Override
protected void renderToTexture(PrintoutRenderState state, PoseStack pose) {
protected void renderToTexture(PrintoutRenderState state, PoseStack pose, SubmitNodeCollector collector) {
pose.pushPose();
pose.translate(-0.5f * X_SIZE, -(Y_SIZE + COVER_SIZE), 0);
pose.scale(1.0f, 1.0f, -1.0f);
var buffer = bufferSource.getBuffer(PrintoutRenderer.BACKGROUND);
drawBorder(pose.last().pose(), buffer, 0, 0, 0, state.page(), state.printout().pages(), state.printout().book(), LightCoordsUtil.FULL_BRIGHT);
drawText(
pose, bufferSource, X_TEXT_MARGIN, Y_TEXT_MARGIN, PrintoutData.LINES_PER_PAGE * state.page(), LightCoordsUtil.FULL_BRIGHT,
state.printout().text(), state.printout().colour()
collector.submitCustomGeometry(pose, PrintoutRenderer.BACKGROUND, (p, buffer) -> drawBorder(
p.pose(), buffer, 0, 0, 0, state.page(), state.printout().pages(), state.printout().book(), LightCoordsUtil.FULL_BRIGHT)
);
collector.submitCustomGeometry(pose, FixedWidthFontRenderer.TERMINAL_TEXT, (p, buffer) -> drawText(
p.pose(), buffer, X_TEXT_MARGIN, Y_TEXT_MARGIN, PrintoutData.LINES_PER_PAGE * state.page(), LightCoordsUtil.FULL_BRIGHT,
state.printout().text(), state.printout().colour()
));
pose.popPose();
}
@@ -107,7 +107,7 @@ public final class ClientNetworkContextImpl implements ClientNetworkContext {
public void handleUploadResult(int containerId, UploadResult result, @Nullable Component errorMessage) {
var minecraft = Minecraft.getInstance();
var screen = OptionScreen.unwrap(minecraft.screen);
var screen = OptionScreen.unwrap(minecraft.gui.screen());
if (screen instanceof AbstractComputerScreen<?> && ((AbstractComputerScreen<?>) screen).getMenu().containerId == containerId) {
((AbstractComputerScreen<?>) screen).uploadResult(result, errorMessage);
}
@@ -4,7 +4,11 @@
package dan200.computercraft.client.platform;
import com.mojang.blaze3d.vertex.PoseStack;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.impl.Services;
import dan200.computercraft.shared.peripheral.monitor.ClientMonitor;
import net.minecraft.client.renderer.OrderedSubmitNodeCollector;
import net.minecraft.client.resources.model.ModelDebugName;
import org.jetbrains.annotations.Contract;
import org.jspecify.annotations.Nullable;
@@ -25,6 +29,20 @@ public interface ClientPlatformHelper {
@Contract("_ -> new")
<T> ModelKey<T> createModelKey(ModelDebugName name);
/**
* Submit a monitor to be rendered.
*
* @param collector The submit node collector.
* @param poseStack The current translation.
* @param monitor The monitor to draw.
* @param terminal The terminal contents of the monitor.
* @param xMargin The X margin.
* @param yMargin The Y margin.
*/
void submitMonitor(
OrderedSubmitNodeCollector collector, PoseStack poseStack, ClientMonitor monitor, Terminal terminal, float xMargin, float yMargin
);
final class Instance {
static final @Nullable ClientPlatformHelper INSTANCE;
static final @Nullable Throwable ERROR;
@@ -10,7 +10,7 @@ import dan200.computercraft.client.ClientHooks;
import net.minecraft.client.Camera;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.LevelRenderer;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.SubmitNodeCollector;
import net.minecraft.client.renderer.rendertype.RenderTypes;
import net.minecraft.client.renderer.state.level.LevelRenderState;
import net.minecraft.util.ARGB;
@@ -32,20 +32,32 @@ public final class BlockOutlineRenderer {
* @param transform The current transformations.
* @param bufferSource The buffer source.
* @param renderer The function to render a highlight.
* @see LevelRenderer#renderBlockOutline(MultiBufferSource.BufferSource, PoseStack, boolean, LevelRenderState)
* @param camera The current camera.
* @param hit The hit result of the block we're rendering.
* @see LevelRenderer#submitBlockOutline(PoseStack, SubmitNodeCollector, LevelRenderState)
*/
public static void render(PoseStack transform, MultiBufferSource bufferSource, Renderer renderer) {
public static void render(PoseStack transform, SubmitNodeCollector bufferSource, Renderer renderer, Camera camera, BlockHitResult hit) {
var cameraPos = camera.position();
var xOffset = hit.getBlockPos().getX() - cameraPos.x();
var yOffset = hit.getBlockPos().getY() - cameraPos.y();
var zOffset = hit.getBlockPos().getZ() - cameraPos.z();
transform.pushPose();
transform.translate(xOffset, yOffset, zOffset);
var highContrast = Minecraft.getInstance().options.highContrastBlockOutline().get();
if (highContrast) {
renderer.render(transform, bufferSource.getBuffer(RenderTypes.secondaryBlockOutline()), 0xff000000, 7f);
bufferSource.submitCustomGeometry(transform, RenderTypes.secondaryBlockOutline(), (p, b) -> renderer.render(p, b, 0xff000000, 7f));
}
var colour = highContrast ? CommonColors.HIGH_CONTRAST_DIAMOND : ARGB.color(0x66, CommonColors.BLACK);
renderer.render(transform, bufferSource.getBuffer(RenderTypes.lines()), colour, Minecraft.getInstance().getWindow().getAppropriateLineWidth());
bufferSource.submitCustomGeometry(transform, RenderTypes.lines(), (p, b) -> renderer.render(p, b, colour, Minecraft.getInstance().getWindow().getAppropriateLineWidth()));
transform.popPose();
}
@FunctionalInterface
public interface Renderer {
void render(PoseStack transform, VertexConsumer buffer, int colour, float width);
void render(PoseStack.Pose transform, VertexConsumer buffer, int colour, float width);
}
}
@@ -9,8 +9,8 @@ import dan200.computercraft.shared.peripheral.modem.wired.CableBlock;
import dan200.computercraft.shared.peripheral.modem.wired.CableShapes;
import dan200.computercraft.shared.util.WorldUtil;
import net.minecraft.client.Camera;
import net.minecraft.client.renderer.ShapeRenderer;
import net.minecraft.world.phys.BlockHitResult;
import org.joml.Vector3f;
import org.jspecify.annotations.Nullable;
public final class CableHighlightRenderer {
@@ -25,11 +25,10 @@ public final class CableHighlightRenderer {
* @return The custom renderer.
*/
public static BlockOutlineRenderer.@Nullable Renderer drawHighlight(Camera camera, BlockHitResult hit) {
var pos = hit.getBlockPos();
var player = camera.entity();
if (player == null) return null;
var pos = hit.getBlockPos();
var state = player.level().getBlockState(pos);
// We only care about instances with both cable and modem.
@@ -41,11 +40,13 @@ public final class CableHighlightRenderer {
? CableShapes.getModemShape(state)
: CableShapes.getCableShape(state);
var cameraPos = camera.position();
var xOffset = pos.getX() - cameraPos.x();
var yOffset = pos.getY() - cameraPos.y();
var zOffset = pos.getZ() - cameraPos.z();
return (transform, buffer, colour, width) -> ShapeRenderer.renderShape(transform, buffer, shape, xOffset, yOffset, zOffset, colour, width);
return (transform, buffer, colour, width) -> {
var normal = new Vector3f();
shape.forAllEdges((x1, y1, z1, x2, y2, z2) -> {
normal.set((float) (x2 - x1), (float) (y2 - y1), (float) (z2 - z1)).normalize();
buffer.addVertex(transform, (float) x1, (float) y1, (float) z1).setColor(colour).setNormal(transform, normal).setLineWidth(width);
buffer.addVertex(transform, (float) x2, (float) y2, (float) z2).setColor(colour).setNormal(transform, normal).setLineWidth(width);
});
};
}
}
@@ -4,13 +4,11 @@
package dan200.computercraft.client.render;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import dan200.computercraft.client.render.text.FixedWidthFontRenderer;
import dan200.computercraft.core.terminal.Palette;
import dan200.computercraft.core.terminal.TextBuffer;
import dan200.computercraft.shared.media.items.PrintoutData;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.rendertype.RenderType;
import net.minecraft.client.renderer.rendertype.RenderTypes;
import net.minecraft.resources.Identifier;
@@ -73,11 +71,10 @@ public final class PrintoutRenderer {
private PrintoutRenderer() {
}
public static void drawText(PoseStack transform, MultiBufferSource bufferSource, int x, int y, int start, int light, TextBuffer[] text, TextBuffer[] colours) {
var buffer = bufferSource.getBuffer(FixedWidthFontRenderer.TERMINAL_TEXT);
public static void drawText(Matrix4f matrix, VertexConsumer buffer, int x, int y, int start, int light, TextBuffer[] text, TextBuffer[] colours) {
for (var line = 0; line < LINES_PER_PAGE && line < text.length; line++) {
FixedWidthFontRenderer.drawString(
transform.last().pose(), buffer,
matrix, buffer,
x, y + line * FONT_HEIGHT, text[start + line], colours[start + line],
Palette.DEFAULT, light
);
@@ -122,7 +122,6 @@ public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBloc
if (state.label != null) {
collector.submitNameTag(
transform, new Vec3(0.5, 1.2, 0.5), 0, Component.literal(state.label), false, state.lightCoords,
camera.pos.distanceToSqr(Vec3.atCenterOf(state.blockPos)), // TODO: Should we read camera from the render state instead?
camera
);
}
@@ -5,13 +5,13 @@
package dan200.computercraft.client.render.monitor;
import com.mojang.blaze3d.buffers.GpuBuffer;
import com.mojang.blaze3d.pipeline.RenderPipeline;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.math.Axis;
import dan200.computercraft.annotations.ForgeOverride;
import dan200.computercraft.client.FrameInfo;
import dan200.computercraft.client.integration.ShaderMod;
import dan200.computercraft.client.platform.ClientPlatformHelper;
import dan200.computercraft.client.render.text.DirectFixedWidthFontRenderer;
import dan200.computercraft.client.render.text.FixedWidthFontRenderer;
import dan200.computercraft.core.terminal.Terminal;
@@ -21,27 +21,26 @@ import dan200.computercraft.shared.peripheral.monitor.ClientMonitor;
import dan200.computercraft.shared.peripheral.monitor.MonitorBlockEntity;
import dan200.computercraft.shared.util.DirectionUtil;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.RenderPipelines;
import net.minecraft.client.renderer.SubmitNodeCollector;
import net.minecraft.client.renderer.blockentity.BlockEntityRenderer;
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider;
import net.minecraft.client.renderer.blockentity.state.BlockEntityRenderState;
import net.minecraft.client.renderer.feature.FeatureFrameContext;
import net.minecraft.client.renderer.feature.FeatureRenderer;
import net.minecraft.client.renderer.feature.FeatureRendererType;
import net.minecraft.client.renderer.feature.ModelFeatureRenderer;
import net.minecraft.client.renderer.feature.submit.SubmitNode;
import net.minecraft.client.renderer.fog.FogRenderer;
import net.minecraft.client.renderer.rendertype.RenderType;
import net.minecraft.client.renderer.state.level.CameraRenderState;
import net.minecraft.core.Direction;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import org.joml.Matrix4f;
import org.joml.Vector3f;
import org.joml.Vector4f;
import org.jspecify.annotations.Nullable;
import org.lwjgl.system.MemoryUtil;
import java.nio.ByteBuffer;
import java.util.OptionalDouble;
import java.util.OptionalInt;
import java.util.List;
import static dan200.computercraft.client.render.text.FixedWidthFontRenderer.FONT_HEIGHT;
import static dan200.computercraft.client.render.text.FixedWidthFontRenderer.FONT_WIDTH;
@@ -112,13 +111,7 @@ public class MonitorBlockEntityRenderer implements BlockEntityRenderer<MonitorBl
var xMargin = (float) (MARGIN / xScale);
var yMargin = (float) (MARGIN / yScale);
collector.submitCustomGeometry(transform, FixedWidthFontRenderer.TERMINAL_TEXT, (pose, buffer) -> {
FixedWidthFontRenderer.drawTerminalBackground(pose.pose(), buffer, 0, 0, terminal, yMargin, yMargin, xMargin, xMargin);
});
collector.submitCustomGeometry(transform, FixedWidthFontRenderer.TERMINAL_TEXT_OFFSET, (pose, buffer) -> {
FixedWidthFontRenderer.drawTerminalForeground(pose.pose(), buffer, 0, 0, terminal);
FixedWidthFontRenderer.drawCursor(pose.pose(), buffer, 0, 0, terminal);
});
ClientPlatformHelper.get().submitMonitor(collector, transform, state.terminal, terminal, xMargin, yMargin);
transform.popPose();
} else {
@@ -128,8 +121,8 @@ public class MonitorBlockEntityRenderer implements BlockEntityRenderer<MonitorBl
transform.popPose();
}
private static void renderTerminal(
Matrix4f matrix, ClientMonitor monitor, MonitorRenderState renderState, Terminal terminal, float xMargin, float yMargin
private static void prepareTerminal(
ClientMonitor monitor, MonitorRenderState renderState, Terminal terminal, float xMargin, float yMargin
) {
var redraw = monitor.pollTerminalChanged();
if (renderState.vertexBuffer == null) redraw = true;
@@ -186,79 +179,6 @@ public class MonitorBlockEntityRenderer implements BlockEntityRenderer<MonitorBl
renderState.vertexCountAfterForeground = vertexCountAfterForeground;
renderState.vertexCountAfterCursor = vertexCountAfterCursor;
}
if (renderState.vertexCountAfterCursor == 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.
// There's not really a good way around this, at least without using a custom render type (which the VBO
// renderer is trying to avoid!). Instead, we just disable fog entirely by setting the fog start to an
// absurdly high value.
var oldFog = Nullability.assertNonNull(RenderSystem.getShaderFog());
RenderSystem.setShaderFog(Minecraft.getInstance().gameRenderer.fogRenderer.getBuffer(FogRenderer.FogMode.NONE));
// Compose the existing model view matrix with our transformation matrix.
RenderSystem.getModelViewStack().pushMatrix();
RenderSystem.getModelViewStack().mul(matrix);
// Render background geometry
drawWithShader(renderState, FixedWidthFontRenderer.TERMINAL_TEXT, RenderPipelines.TEXT, 0, renderState.vertexCountAfterBackground);
drawWithShader(
renderState, FixedWidthFontRenderer.TERMINAL_TEXT_OFFSET, RenderPipelines.TEXT_POLYGON_OFFSET, renderState.vertexCountAfterBackground,
(
FixedWidthFontRenderer.isCursorVisible(terminal) && FrameInfo.getGlobalCursorBlink()
? renderState.vertexCountAfterCursor : renderState.vertexCountAfterForeground
) - renderState.vertexCountAfterBackground
);
// Clear state
RenderSystem.getModelViewStack().popMatrix();
RenderSystem.setShaderFog(oldFog);
}
private static void drawWithShader(MonitorRenderState renderState, RenderType renderType, RenderPipeline pipeline, int vertexOffset, int vertexCount) {
if (renderState.vertexBuffer == null) {
throw new IllegalStateException("MonitorRenderState has not been initialised");
}
if (vertexCount == 0) return;
var transforms = RenderSystem.getDynamicUniforms().writeTransform(
RenderSystem.getModelViewMatrix(),
new Vector4f(1.0F, 1.0F, 1.0F, 1.0F),
new Vector3f(),
new Matrix4f()
);
var autoStorageBuffer = RenderSystem.getSequentialBuffer(renderType.mode());
var indexCount = FixedWidthFontRenderer.TERMINAL_TEXT.mode().indexCount(vertexCount);
var indexBuffer = autoStorageBuffer.getBuffer(indexCount);
var target = Minecraft.getInstance().getMainRenderTarget();
var colourTarget = RenderSystem.outputColorTextureOverride != null ? RenderSystem.outputColorTextureOverride : target.getColorTextureView();
var depthTarget = target.useDepth
? (RenderSystem.outputDepthTextureOverride != null ? RenderSystem.outputDepthTextureOverride : target.getDepthTextureView())
: null;
try (var renderPass = RenderSystem.getDevice().createCommandEncoder().createRenderPass(
() -> "Monitor", Nullability.assertNonNull(colourTarget), OptionalInt.empty(), depthTarget, OptionalDouble.empty()
)) {
renderPass.setPipeline(pipeline);
RenderSystem.bindDefaultUniforms(renderPass);
renderPass.setUniform("DynamicTransforms", transforms);
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.bindTexture("Sampler" + j, gpuTexture);
}
*/
renderPass.drawIndexed(vertexOffset, 0, indexCount, 1);
}
}
@Override
@@ -296,4 +216,79 @@ public class MonitorBlockEntityRenderer implements BlockEntityRenderer<MonitorBl
private State() {
}
}
public record MonitorSubmit(
PoseStack.Pose pose, ClientMonitor monitor, Terminal terminal, MonitorRenderState state,
float xMargin, float yMargin
) implements SubmitNode {
@Override
public FeatureRendererType<? extends SubmitNode> featureType() {
return MonitorFeatureRenderer.TYPE;
}
}
public static final class MonitorFeatureRenderer implements FeatureRenderer<MonitorSubmit> {
public static final FeatureRendererType<MonitorSubmit> TYPE = FeatureRendererType.create("Monitor");
@Override
public void prepareGroup(FeatureFrameContext context, List<MonitorSubmit> submits, boolean strictlyOrdered) {
for (var submit : submits) {
prepareTerminal(submit.monitor(), submit.state(), submit.terminal(), submit.xMargin(), submit.yMargin());
}
}
@Override
public void executeGroup(FeatureFrameContext context, int groupIndex, List<MonitorSubmit> submits, boolean strictlyOrdered) {
if (submits.isEmpty()) 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.
// There's not really a good way around this, at least without using a custom render type (which the VBO
// renderer is trying to avoid!). Instead, we just disable fog entirely by setting the fog start to an
// absurdly high value.
var oldFog = Nullability.assertNonNull(RenderSystem.getShaderFog());
RenderSystem.setShaderFog(Minecraft.getInstance().gameRenderer.fogRenderer.getBuffer(FogRenderer.FogMode.NONE));
var autoStorageBuffer = RenderSystem.getSequentialBuffer(FixedWidthFontRenderer.TERMINAL_TEXT.primitiveTopology());
for (var submit : submits) {
// Compose the existing model view matrix with our transformation matrix.
RenderSystem.getModelViewStack().pushMatrix();
RenderSystem.getModelViewStack().mul(submit.pose().pose());
// Render geometry
var renderState = submit.state();
if (renderState.vertexBuffer == null) {
throw new IllegalStateException("MonitorRenderState has not been initialised");
}
drawWithShader(
renderState.vertexBuffer, autoStorageBuffer, FixedWidthFontRenderer.TERMINAL_TEXT,
0, renderState.vertexCountAfterBackground
);
drawWithShader(
renderState.vertexBuffer, autoStorageBuffer, FixedWidthFontRenderer.TERMINAL_TEXT_OFFSET,
renderState.vertexCountAfterBackground,
(
FixedWidthFontRenderer.isCursorVisible(submit.terminal()) && FrameInfo.getGlobalCursorBlink()
? renderState.vertexCountAfterCursor : renderState.vertexCountAfterForeground
) - renderState.vertexCountAfterBackground
);
RenderSystem.getModelViewStack().popMatrix();
}
// Clear state
RenderSystem.setShaderFog(oldFog);
}
private static void drawWithShader(GpuBuffer buffer, RenderSystem.AutoStorageIndexBuffer autoStorageBuffer, RenderType renderType, int vertexOffset, int vertexCount) {
if (vertexCount == 0) return;
var indexCount = FixedWidthFontRenderer.TERMINAL_TEXT.primitiveTopology().indexCount(vertexCount);
renderType.prepare().drawFromBuffer(
buffer, autoStorageBuffer.getBuffer(indexCount), autoStorageBuffer.type(), vertexOffset, 0, indexCount
);
}
}
}
@@ -31,7 +31,6 @@ public final class MonitorHighlightRenderer {
if (player == null || player.isCrouching()) return null;
var pos = hit.getBlockPos();
if (!(player.level().getBlockEntity(pos) instanceof MonitorBlockEntity monitor)) return null;
// Determine which sides are part of the external faces of the monitor, and so which need to be rendered.
@@ -43,17 +42,7 @@ public final class MonitorHighlightRenderer {
if (monitor.getYIndex() != 0) faces.remove(monitor.getDown().getOpposite());
if (monitor.getYIndex() != monitor.getHeight() - 1) faces.remove(monitor.getDown());
var cameraPos = camera.position();
var xOffset = pos.getX() - cameraPos.x();
var yOffset = pos.getY() - cameraPos.y();
var zOffset = pos.getZ() - cameraPos.z();
return (transform, buffer, colour, width) -> {
transform.pushPose();
transform.translate(xOffset, yOffset, zOffset);
draw(buffer, transform.last(), faces, colour, width);
transform.popPose();
};
return (transform, buffer, colour, width) -> draw(buffer, transform, faces, colour, width);
}
private static void draw(VertexConsumer buffer, PoseStack.Pose transform, EnumSet<Direction> faces, int colour, float width) {
@@ -202,7 +202,7 @@ public final class DirectFixedWidthFontRenderer {
// Emit a single quad to our buffer. This uses Unsafe (well, LWJGL's MemoryUtil) to directly blit bytes to the
// underlying buffer. This allows us to have a single bounds check up-front, rather than one for every write.
// This provides significant performance gains, at the cost of well, using Unsafe.
// Each vertex is 28 bytes, giving 112 bytes in total. Vertices are of the form (xyz:FFF)(abgr:BBBB)(uv1:FF)(uv2:SS),
// Each vertex is 28 bytes, giving 112 bytes in total. Vertices are of the form (xyz:FFF)(uv1:FF)(uv2:SS)(abgr:BBBB),
// which matches the POSITION_COLOR_TEX_LIGHTMAP vertex format.
var position = buffer.position();
var addr = MemoryUtil.memAddress(buffer);
@@ -221,38 +221,38 @@ public final class DirectFixedWidthFontRenderer {
memPutFloat(addr + 0, x1);
memPutFloat(addr + 4, y1);
memPutFloat(addr + 8, z);
memPutInt(addr + 12, nativeColour);
memPutFloat(addr + 16, u1);
memPutFloat(addr + 20, v1);
memPutShort(addr + 24, (short) 0xF0);
memPutShort(addr + 26, (short) 0xF0);
memPutFloat(addr + 12, u1);
memPutFloat(addr + 16, v1);
memPutShort(addr + 20, (short) 0xF0);
memPutShort(addr + 22, (short) 0xF0);
memPutInt(addr + 24, nativeColour);
memPutFloat(addr + 28, x1);
memPutFloat(addr + 32, y2);
memPutFloat(addr + 36, z);
memPutInt(addr + 40, nativeColour);
memPutFloat(addr + 44, u1);
memPutFloat(addr + 48, v2);
memPutShort(addr + 52, (short) 0xF0);
memPutShort(addr + 54, (short) 0xF0);
memPutFloat(addr + 40, u1);
memPutFloat(addr + 44, v2);
memPutShort(addr + 48, (short) 0xF0);
memPutShort(addr + 50, (short) 0xF0);
memPutInt(addr + 52, nativeColour);
memPutFloat(addr + 56, x2);
memPutFloat(addr + 60, y2);
memPutFloat(addr + 64, z);
memPutInt(addr + 68, nativeColour);
memPutFloat(addr + 72, u2);
memPutFloat(addr + 76, v2);
memPutShort(addr + 80, (short) 0xF0);
memPutShort(addr + 82, (short) 0xF0);
memPutFloat(addr + 68, u2);
memPutFloat(addr + 72, v2);
memPutShort(addr + 76, (short) 0xF0);
memPutShort(addr + 78, (short) 0xF0);
memPutInt(addr + 80, nativeColour);
memPutFloat(addr + 84, x2);
memPutFloat(addr + 88, y1);
memPutFloat(addr + 92, z);
memPutInt(addr + 96, nativeColour);
memPutFloat(addr + 100, u2);
memPutFloat(addr + 104, v1);
memPutShort(addr + 108, (short) 0xF0);
memPutShort(addr + 110, (short) 0xF0);
memPutFloat(addr + 96, u2);
memPutFloat(addr + 100, v1);
memPutShort(addr + 104, (short) 0xF0);
memPutShort(addr + 106, (short) 0xF0);
memPutInt(addr + 108, nativeColour);
// Finally increment the position.
buffer.position(position + 112);
@@ -25,6 +25,7 @@ public class LevelRendererMixin {
@ModifyExpressionValue(
method = "submitBlockDestroyAnimation", at = @At(value = "INVOKE", target = "Ljava/util/Iterator;next()Ljava/lang/Object;")
)
@SuppressWarnings("unused")
private Object submitBlockDestroyAnimation(Object breaking) {
return getBlockDamageState((BlockBreakingRenderState) breaking);
}
@@ -2,7 +2,7 @@
"required": true,
"package": "dan200.computercraft.mixin.client",
"minVersion": "0.8",
"compatibilityLevel": "JAVA_21",
"compatibilityLevel": "JAVA_25",
"injectors": {
"defaultRequire": 1
},
@@ -11,7 +11,7 @@ import dan200.computercraft.shared.data.HasComputerIdLootCondition;
import dan200.computercraft.shared.data.PlayerCreativeLootCondition;
import dan200.computercraft.shared.peripheral.modem.wired.CableBlock;
import dan200.computercraft.shared.peripheral.modem.wired.CableModemVariant;
import net.minecraft.advancements.criterion.StatePropertiesPredicate;
import net.minecraft.advancements.predicates.StatePropertiesPredicate;
import net.minecraft.core.component.DataComponents;
import net.minecraft.data.loot.LootTableProvider.SubProviderEntry;
import net.minecraft.resources.ResourceKey;
@@ -28,9 +28,9 @@ import dan200.computercraft.shared.turtle.items.TurtleItem;
import dan200.computercraft.shared.turtle.recipes.TurtleUpgradeRecipe;
import dan200.computercraft.shared.util.ColourUtils;
import dan200.computercraft.shared.util.RegistryHelper;
import net.minecraft.advancements.Criterion;
import net.minecraft.advancements.criterion.InventoryChangeTrigger;
import net.minecraft.advancements.criterion.ItemPredicate;
import net.minecraft.advancements.predicates.ItemPredicate;
import net.minecraft.advancements.triggers.Criterion;
import net.minecraft.advancements.triggers.InventoryChangeTrigger;
import net.minecraft.core.HolderGetter;
import net.minecraft.core.HolderLookup;
import net.minecraft.core.component.DataComponentPatch;
@@ -4,18 +4,26 @@
package dan200.computercraft.data;
import dan200.computercraft.api.ComputerCraftBlockIds;
import dan200.computercraft.api.ComputerCraftBlockItemIds;
import dan200.computercraft.api.ComputerCraftItemIds;
import dan200.computercraft.api.ComputerCraftTags;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.integration.ExternalModTags;
import net.minecraft.data.tags.TagAppender;
import dan200.computercraft.shared.platform.RegistryEntry;
import net.minecraft.core.registries.Registries;
import net.minecraft.data.tags.BlockItemTagAppender;
import net.minecraft.data.tags.TagsProvider;
import net.minecraft.references.BlockIds;
import net.minecraft.references.BlockItemIds;
import net.minecraft.references.ItemIds;
import net.minecraft.resources.ResourceKey;
import net.minecraft.tags.BlockItemTagId;
import net.minecraft.tags.BlockTags;
import net.minecraft.tags.ItemTags;
import net.minecraft.tags.TagKey;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.Items;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
/**
* Generators for block and item tags.
@@ -25,102 +33,105 @@ import net.minecraft.world.level.block.Blocks;
*/
class TagProvider {
public static void blockTags(TagConsumer<Block> tags) {
itemAndBlockTags((b, i) -> tags.tag(b));
tags.tag(ComputerCraftTags.Blocks.WIRED_MODEM).add(ModRegistry.Blocks.CABLE.get(), ModRegistry.Blocks.WIRED_MODEM_FULL.get());
itemAndBlockTags(i -> tags.tag(i.block()));
tags.tag(ComputerCraftTags.Blocks.WIRED_MODEM).add(ComputerCraftBlockIds.CABLE, ComputerCraftBlockIds.WIRED_MODEM_FULL);
tags.tag(ComputerCraftTags.Blocks.PERIPHERAL_HUB_IGNORE).addTag(ComputerCraftTags.Blocks.WIRED_MODEM);
tags.tag(ComputerCraftTags.Blocks.TURTLE_ALWAYS_BREAKABLE).addTag(BlockTags.LEAVES).add(
Blocks.BAMBOO, Blocks.BAMBOO_SAPLING // Bamboo isn't instabreak for some odd reason.
BlockItemIds.BAMBOO.block(), BlockIds.BAMBOO_SAPLING // Bamboo isn't instabreak for some odd reason.
);
tags.tag(ComputerCraftTags.Blocks.TURTLE_SHOVEL_BREAKABLE).addTag(BlockTags.MINEABLE_WITH_SHOVEL).add(
Blocks.MELON,
Blocks.PUMPKIN,
Blocks.CARVED_PUMPKIN,
Blocks.JACK_O_LANTERN
BlockItemIds.MELON,
BlockItemIds.PUMPKIN,
BlockItemIds.CARVED_PUMPKIN,
BlockItemIds.JACK_O_LANTERN
);
tags.tag(ComputerCraftTags.Blocks.TURTLE_HOE_BREAKABLE).addTag(BlockTags.CROPS).addTag(BlockTags.MINEABLE_WITH_HOE).add(
Blocks.CACTUS,
Blocks.MELON,
Blocks.PUMPKIN,
Blocks.CARVED_PUMPKIN,
Blocks.JACK_O_LANTERN
BlockItemIds.CACTUS,
BlockItemIds.MELON,
BlockItemIds.PUMPKIN,
BlockItemIds.CARVED_PUMPKIN,
BlockItemIds.JACK_O_LANTERN
);
tags.tag(ComputerCraftTags.Blocks.TURTLE_SWORD_BREAKABLE).addTag(BlockTags.WOOL).add(Blocks.COBWEB);
tags.tag(ComputerCraftTags.Blocks.TURTLE_SWORD_BREAKABLE)
.addTag(BlockTags.WOOL)
.addTag(BlockTags.SWORD_INSTANTLY_MINES)
.add(BlockItemIds.COBWEB);
tags.tag(ComputerCraftTags.Blocks.TURTLE_CAN_USE);
// Make all blocks aside from command computer mineable.
tags.tag(BlockTags.MINEABLE_WITH_PICKAXE).add(
ModRegistry.Blocks.COMPUTER_NORMAL.get(),
ModRegistry.Blocks.COMPUTER_ADVANCED.get(),
ModRegistry.Blocks.TURTLE_NORMAL.get(),
ModRegistry.Blocks.TURTLE_ADVANCED.get(),
ModRegistry.Blocks.SPEAKER.get(),
ModRegistry.Blocks.DISK_DRIVE.get(),
ModRegistry.Blocks.PRINTER.get(),
ModRegistry.Blocks.MONITOR_NORMAL.get(),
ModRegistry.Blocks.MONITOR_ADVANCED.get(),
ModRegistry.Blocks.WIRELESS_MODEM_NORMAL.get(),
ModRegistry.Blocks.WIRELESS_MODEM_ADVANCED.get(),
ModRegistry.Blocks.WIRED_MODEM_FULL.get(),
ModRegistry.Blocks.CABLE.get(),
ModRegistry.Blocks.REDSTONE_RELAY.get()
ComputerCraftBlockIds.COMPUTER_NORMAL,
ComputerCraftBlockIds.COMPUTER_ADVANCED,
ComputerCraftBlockIds.TURTLE_NORMAL,
ComputerCraftBlockIds.TURTLE_ADVANCED,
ComputerCraftBlockIds.SPEAKER,
ComputerCraftBlockIds.DISK_DRIVE,
ComputerCraftBlockIds.PRINTER,
ComputerCraftBlockIds.MONITOR_NORMAL,
ComputerCraftBlockIds.MONITOR_ADVANCED,
ComputerCraftBlockIds.WIRELESS_MODEM_NORMAL,
ComputerCraftBlockIds.WIRELESS_MODEM_ADVANCED,
ComputerCraftBlockIds.WIRED_MODEM_FULL,
ComputerCraftBlockIds.CABLE,
ComputerCraftBlockIds.REDSTONE_RELAY
);
tags.tag(BlockTags.MINEABLE_WITH_AXE).add(ModRegistry.Blocks.LECTERN.get());
tags.tag(BlockTags.MINEABLE_WITH_AXE).add(ComputerCraftBlockIds.LECTERN);
tags.tag(BlockTags.WITHER_IMMUNE).add(ModRegistry.Blocks.COMPUTER_COMMAND.get());
tags.tag(BlockTags.WITHER_IMMUNE).add(ComputerCraftBlockIds.COMPUTER_COMMAND);
tags.tag(ExternalModTags.Blocks.CREATE_BRITTLE).add(
ModRegistry.Blocks.CABLE.get(),
ModRegistry.Blocks.WIRELESS_MODEM_NORMAL.get(),
ModRegistry.Blocks.WIRELESS_MODEM_ADVANCED.get()
ComputerCraftBlockIds.CABLE,
ComputerCraftBlockIds.WIRELESS_MODEM_NORMAL,
ComputerCraftBlockIds.WIRELESS_MODEM_ADVANCED
);
}
public static void itemTags(TagConsumer<Item> tags) {
itemAndBlockTags((b, i) -> tags.tag(i).map(Block::asItem));
tags.tag(ComputerCraftTags.Items.WIRED_MODEM).add(ModRegistry.Items.WIRED_MODEM.get(), ModRegistry.Items.WIRED_MODEM_FULL.get());
tags.tag(ComputerCraftTags.Items.DISKS).add(ModRegistry.Items.DISK.get(), ModRegistry.Items.TREASURE_DISK.get());
tags.tag(ComputerCraftTags.Items.POCKET_COMPUTERS).add(ModRegistry.Items.POCKET_COMPUTER_NORMAL.get(), ModRegistry.Items.POCKET_COMPUTER_ADVANCED.get());
itemAndBlockTags(i -> tags.tag(i.item()));
tags.tag(ComputerCraftTags.Items.WIRED_MODEM).add(ComputerCraftItemIds.WIRED_MODEM, item(ModRegistry.Items.WIRED_MODEM_FULL));
tags.tag(ComputerCraftTags.Items.DISKS).add(ComputerCraftItemIds.DISK, item(ModRegistry.Items.TREASURE_DISK));
tags.tag(ComputerCraftTags.Items.POCKET_COMPUTERS).add(ComputerCraftItemIds.POCKET_COMPUTER_NORMAL, item(ModRegistry.Items.POCKET_COMPUTER_ADVANCED));
tags.tag(ComputerCraftTags.Items.DYEABLE)
.addTag(ComputerCraftTags.Items.TURTLE)
.add(ModRegistry.Items.DISK.get(), ModRegistry.Items.POCKET_COMPUTER_NORMAL.get(), ModRegistry.Items.POCKET_COMPUTER_ADVANCED.get());
.add(ComputerCraftItemIds.DISK, ComputerCraftItemIds.POCKET_COMPUTER_NORMAL, item(ModRegistry.Items.POCKET_COMPUTER_ADVANCED));
tags.tag(ItemTags.PIGLIN_LOVED).add(
ModRegistry.Items.COMPUTER_ADVANCED.get(), ModRegistry.Items.TURTLE_ADVANCED.get(),
ModRegistry.Items.WIRELESS_MODEM_ADVANCED.get(), ModRegistry.Items.POCKET_COMPUTER_ADVANCED.get(),
ModRegistry.Items.MONITOR_ADVANCED.get()
ComputerCraftItemIds.COMPUTER_ADVANCED, ComputerCraftItemIds.TURTLE_ADVANCED,
ComputerCraftItemIds.WIRELESS_MODEM_ADVANCED, ComputerCraftItemIds.POCKET_COMPUTER_ADVANCED,
ComputerCraftItemIds.MONITOR_ADVANCED
);
tags.tag(ItemTags.CAULDRON_CAN_REMOVE_DYE).addTag(ComputerCraftTags.Items.TURTLE);
// Allow printed books to be placed in bookshelves.
tags.tag(ItemTags.BOOKSHELF_BOOKS).add(ModRegistry.Items.PRINTED_BOOK.get());
tags.tag(ItemTags.BOOKSHELF_BOOKS).add(item(ModRegistry.Items.PRINTED_BOOK));
tags.tag(ComputerCraftTags.Items.TURTLE_CAN_PLACE)
.add(Items.GLASS_BOTTLE)
.add(ItemIds.GLASS_BOTTLE)
.addTag(ItemTags.BOATS);
}
private static void itemAndBlockTags(BlockItemTagConsumer tags) {
tags.tag(ComputerCraftTags.Blocks.COMPUTER, ComputerCraftTags.Items.COMPUTER).add(
ModRegistry.Blocks.COMPUTER_NORMAL.get(),
ModRegistry.Blocks.COMPUTER_ADVANCED.get(),
ModRegistry.Blocks.COMPUTER_COMMAND.get()
tags.tag(ComputerCraftTags.BlockItems.COMPUTER).add(
ComputerCraftBlockItemIds.COMPUTER_NORMAL,
ComputerCraftBlockItemIds.COMPUTER_ADVANCED,
ComputerCraftBlockItemIds.COMPUTER_COMMAND
);
tags.tag(ComputerCraftTags.Blocks.TURTLE, ComputerCraftTags.Items.TURTLE).add(
ModRegistry.Blocks.TURTLE_NORMAL.get(),
ModRegistry.Blocks.TURTLE_ADVANCED.get()
tags.tag(ComputerCraftTags.BlockItems.TURTLE).add(
ComputerCraftBlockItemIds.TURTLE_NORMAL,
ComputerCraftBlockItemIds.TURTLE_ADVANCED
);
tags.tag(ComputerCraftTags.Blocks.MONITOR, ComputerCraftTags.Items.MONITOR).add(
ModRegistry.Blocks.MONITOR_NORMAL.get(),
ModRegistry.Blocks.MONITOR_ADVANCED.get()
tags.tag(ComputerCraftTags.BlockItems.MONITOR).add(
ComputerCraftBlockItemIds.MONITOR_NORMAL,
ComputerCraftBlockItemIds.MONITOR_ADVANCED
);
}
@@ -130,10 +141,14 @@ class TagProvider {
* @param <T> The type of object we're providing tags for.
*/
public interface TagConsumer<T> {
TagAppender<T, T> tag(TagKey<T> tag);
BlockItemTagAppender<T> tag(TagKey<T> tag);
}
private interface BlockItemTagConsumer {
TagAppender<Block, ?> tag(TagKey<Block> blockTag, TagKey<Item> itemTag);
BlockItemTagAppender<?> tag(BlockItemTagId tag);
}
private static ResourceKey<Item> item(RegistryEntry<? extends Item> entry) {
return ResourceKey.create(Registries.ITEM, entry.id());
}
}
@@ -8,8 +8,8 @@ import com.mojang.serialization.DataResult;
import dan200.computercraft.shared.recipe.RecipeProperties;
import net.minecraft.advancements.AdvancementRequirements;
import net.minecraft.advancements.AdvancementRewards;
import net.minecraft.advancements.Criterion;
import net.minecraft.advancements.criterion.RecipeUnlockedTrigger;
import net.minecraft.advancements.triggers.Criterion;
import net.minecraft.advancements.triggers.RecipeUnlockedTrigger;
import net.minecraft.core.HolderGetter;
import net.minecraft.core.registries.Registries;
import net.minecraft.data.recipes.RecipeBuilder;
@@ -8,6 +8,8 @@ import com.mojang.brigadier.arguments.ArgumentType;
import com.mojang.serialization.Codec;
import com.mojang.serialization.MapCodec;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.ComputerCraftBlockIds;
import dan200.computercraft.api.ComputerCraftItemIds;
import dan200.computercraft.api.component.ComputerComponents;
import dan200.computercraft.api.detail.DetailProvider;
import dan200.computercraft.api.detail.VanillaDetailRegistries;
@@ -44,7 +46,6 @@ import dan200.computercraft.shared.data.PlayerCreativeLootCondition;
import dan200.computercraft.shared.details.BlockDetails;
import dan200.computercraft.shared.details.EntityDetails;
import dan200.computercraft.shared.details.ItemDetails;
import dan200.computercraft.shared.integration.PermissionRegistry;
import dan200.computercraft.shared.lectern.CustomLecternBlock;
import dan200.computercraft.shared.lectern.CustomLecternBlockEntity;
import dan200.computercraft.shared.lectern.PocketComputerLecternMenu;
@@ -71,6 +72,7 @@ import dan200.computercraft.shared.peripheral.redstone.RedstoneRelayBlock;
import dan200.computercraft.shared.peripheral.redstone.RedstoneRelayBlockEntity;
import dan200.computercraft.shared.peripheral.speaker.SpeakerBlock;
import dan200.computercraft.shared.peripheral.speaker.SpeakerBlockEntity;
import dan200.computercraft.shared.platform.PermissionRegistry;
import dan200.computercraft.shared.platform.PlatformHelper;
import dan200.computercraft.shared.platform.RegistrationHelper;
import dan200.computercraft.shared.platform.RegistryEntry;
@@ -126,6 +128,7 @@ import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.GameMasterBlock;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.entity.BlockEntityTypes;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.material.MapColor;
import net.minecraft.world.level.storage.loot.predicates.LootItemCondition;
@@ -148,11 +151,8 @@ public final class ModRegistry {
public static final class Blocks {
static final RegistrationHelper<Block> REGISTRY = PlatformHelper.get().createRegistrationHelper(Registries.BLOCK);
private static <T extends Block> RegistryEntry<T> register(String name, Function<BlockBehaviour.Properties, T> build, BlockBehaviour.Properties properties) {
return REGISTRY.register(name, () -> {
properties.setId(ResourceKey.create(Registries.BLOCK, Identifier.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, name)));
return build.apply(properties);
});
private static <T extends Block> RegistryEntry<T> register(ResourceKey<Block> name, Function<BlockBehaviour.Properties, T> build, BlockBehaviour.Properties properties) {
return REGISTRY.register(name, () -> build.apply(properties.setId(name)));
}
private static BlockBehaviour.Properties properties() {
@@ -173,42 +173,42 @@ public final class ModRegistry {
return BlockBehaviour.Properties.of().strength(1.5f);
}
public static final RegistryEntry<ComputerBlock<ComputerBlockEntity>> COMPUTER_NORMAL = register("computer_normal",
public static final RegistryEntry<ComputerBlock<ComputerBlockEntity>> COMPUTER_NORMAL = register(ComputerCraftBlockIds.COMPUTER_NORMAL,
p -> new ComputerBlock<>(p, BlockEntities.COMPUTER_NORMAL), redstoneConductor().mapColor(MapColor.STONE));
public static final RegistryEntry<ComputerBlock<ComputerBlockEntity>> COMPUTER_ADVANCED = register("computer_advanced",
public static final RegistryEntry<ComputerBlock<ComputerBlockEntity>> COMPUTER_ADVANCED = register(ComputerCraftBlockIds.COMPUTER_ADVANCED,
p -> new ComputerBlock<>(p, BlockEntities.COMPUTER_ADVANCED), redstoneConductor().mapColor(MapColor.GOLD));
public static final RegistryEntry<ComputerBlock<ComputerBlockEntity>> COMPUTER_COMMAND = register("computer_command",
public static final RegistryEntry<ComputerBlock<ComputerBlockEntity>> COMPUTER_COMMAND = register(ComputerCraftBlockIds.COMPUTER_COMMAND,
p -> new CommandComputerBlock<>(p, BlockEntities.COMPUTER_COMMAND), redstoneConductor().strength(-1, 6000000.0F));
public static final RegistryEntry<TurtleBlock> TURTLE_NORMAL = register("turtle_normal",
public static final RegistryEntry<TurtleBlock> TURTLE_NORMAL = register(ComputerCraftBlockIds.TURTLE_NORMAL,
p -> new TurtleBlock(p, BlockEntities.TURTLE_NORMAL), turtleProperties().mapColor(MapColor.STONE));
public static final RegistryEntry<TurtleBlock> TURTLE_ADVANCED = register("turtle_advanced",
public static final RegistryEntry<TurtleBlock> TURTLE_ADVANCED = register(ComputerCraftBlockIds.TURTLE_ADVANCED,
p -> new TurtleBlock(p, BlockEntities.TURTLE_ADVANCED), turtleProperties().mapColor(MapColor.GOLD).explosionResistance(TurtleBlock.IMMUNE_EXPLOSION_RESISTANCE));
public static final RegistryEntry<SpeakerBlock> SPEAKER = register("speaker", SpeakerBlock::new, properties().mapColor(MapColor.STONE));
public static final RegistryEntry<DiskDriveBlock> DISK_DRIVE = register("disk_drive", DiskDriveBlock::new, properties().mapColor(MapColor.STONE));
public static final RegistryEntry<PrinterBlock> PRINTER = register("printer", PrinterBlock::new, properties().mapColor(MapColor.STONE));
public static final RegistryEntry<SpeakerBlock> SPEAKER = register(ComputerCraftBlockIds.SPEAKER, SpeakerBlock::new, properties().mapColor(MapColor.STONE));
public static final RegistryEntry<DiskDriveBlock> DISK_DRIVE = register(ComputerCraftBlockIds.DISK_DRIVE, DiskDriveBlock::new, properties().mapColor(MapColor.STONE));
public static final RegistryEntry<PrinterBlock> PRINTER = register(ComputerCraftBlockIds.PRINTER, PrinterBlock::new, properties().mapColor(MapColor.STONE));
public static final RegistryEntry<MonitorBlock> MONITOR_NORMAL = register("monitor_normal",
public static final RegistryEntry<MonitorBlock> MONITOR_NORMAL = register(ComputerCraftBlockIds.MONITOR_NORMAL,
p -> new MonitorBlock(p, BlockEntities.MONITOR_NORMAL), properties().mapColor(MapColor.STONE));
public static final RegistryEntry<MonitorBlock> MONITOR_ADVANCED = register("monitor_advanced",
public static final RegistryEntry<MonitorBlock> MONITOR_ADVANCED = register(ComputerCraftBlockIds.MONITOR_ADVANCED,
p -> new MonitorBlock(p, BlockEntities.MONITOR_ADVANCED), properties().mapColor(MapColor.GOLD));
public static final RegistryEntry<WirelessModemBlock> WIRELESS_MODEM_NORMAL = register("wireless_modem_normal",
public static final RegistryEntry<WirelessModemBlock> WIRELESS_MODEM_NORMAL = register(ComputerCraftBlockIds.WIRELESS_MODEM_NORMAL,
p -> new WirelessModemBlock(p, BlockEntities.WIRELESS_MODEM_NORMAL), properties().mapColor(MapColor.STONE));
public static final RegistryEntry<WirelessModemBlock> WIRELESS_MODEM_ADVANCED = register("wireless_modem_advanced",
public static final RegistryEntry<WirelessModemBlock> WIRELESS_MODEM_ADVANCED = register(ComputerCraftBlockIds.WIRELESS_MODEM_ADVANCED,
p -> new WirelessModemBlock(p, BlockEntities.WIRELESS_MODEM_ADVANCED), properties().mapColor(MapColor.GOLD));
public static final RegistryEntry<WiredModemFullBlock> WIRED_MODEM_FULL = register("wired_modem_full",
public static final RegistryEntry<WiredModemFullBlock> WIRED_MODEM_FULL = register(ComputerCraftBlockIds.WIRED_MODEM_FULL,
WiredModemFullBlock::new, modemProperties().mapColor(MapColor.STONE));
public static final RegistryEntry<CableBlock> CABLE = register("cable", CableBlock::new, modemProperties().mapColor(MapColor.STONE));
public static final RegistryEntry<CableBlock> CABLE = register(ComputerCraftBlockIds.CABLE, CableBlock::new, modemProperties().mapColor(MapColor.STONE));
public static final RegistryEntry<CustomLecternBlock> LECTERN = register("lectern", CustomLecternBlock::new,
public static final RegistryEntry<CustomLecternBlock> LECTERN = register(ComputerCraftBlockIds.LECTERN, CustomLecternBlock::new,
BlockBehaviour.Properties.ofFullCopy(net.minecraft.world.level.block.Blocks.LECTERN)
.overrideDescription(net.minecraft.world.level.block.Blocks.LECTERN.getDescriptionId())
);
public static final RegistryEntry<RedstoneRelayBlock> REDSTONE_RELAY = register("redstone_relay", RedstoneRelayBlock::new,
public static final RegistryEntry<RedstoneRelayBlock> REDSTONE_RELAY = register(ComputerCraftBlockIds.REDSTONE_RELAY, RedstoneRelayBlock::new,
redstoneConductor().mapColor(MapColor.STONE));
}
@@ -285,44 +285,44 @@ public final class ModRegistry {
);
}
private static <T extends Item> RegistryEntry<T> register(String name, Function<Item.Properties, T> build, Supplier<Item.Properties> properties) {
return REGISTRY.register(name, () -> build.apply(properties.get().setId(ResourceKey.create(Registries.ITEM, Identifier.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, name)))));
private static <T extends Item> RegistryEntry<T> register(ResourceKey<Item> name, Function<Item.Properties, T> build, Supplier<Item.Properties> properties) {
return REGISTRY.register(name, () -> build.apply(properties.get().setId(name)));
}
private static <T extends Item> RegistryEntry<T> register(String name, Function<Item.Properties, T> build, Item.Properties properties) {
private static <T extends Item> RegistryEntry<T> register(ResourceKey<Item> name, Function<Item.Properties, T> build, Item.Properties properties) {
return register(name, build, () -> properties);
}
private static <B extends Block, I extends Item> RegistryEntry<I> ofBlock(RegistryEntry<B> parent, BiFunction<B, Item.Properties, I> supplier, Item.Properties properties) {
return register(parent.id().getPath(), p -> supplier.apply(parent.get(), p), properties.useBlockDescriptionPrefix());
return register(ResourceKey.create(Registries.ITEM, parent.id()), p -> supplier.apply(parent.get(), p), properties.useBlockDescriptionPrefix());
}
public static final RegistryEntry<BlockItem> COMPUTER_NORMAL = ofBlock(Blocks.COMPUTER_NORMAL, BlockItem::new, properties());
public static final RegistryEntry<BlockItem> COMPUTER_ADVANCED = ofBlock(Blocks.COMPUTER_ADVANCED, BlockItem::new, properties());
public static final RegistryEntry<GameMasterBlockItem> COMPUTER_COMMAND = ofBlock(Blocks.COMPUTER_COMMAND, GameMasterBlockItem::new, properties());
public static final RegistryEntry<PocketComputerItem> POCKET_COMPUTER_NORMAL = register("pocket_computer_normal",
public static final RegistryEntry<PocketComputerItem> POCKET_COMPUTER_NORMAL = register(ComputerCraftItemIds.POCKET_COMPUTER_NORMAL,
p -> new PocketComputerItem(p, ComputerFamily.NORMAL), dyeableProperties().stacksTo(1));
public static final RegistryEntry<PocketComputerItem> POCKET_COMPUTER_ADVANCED = register("pocket_computer_advanced",
public static final RegistryEntry<PocketComputerItem> POCKET_COMPUTER_ADVANCED = register(ComputerCraftItemIds.POCKET_COMPUTER_ADVANCED,
p -> new PocketComputerItem(p, ComputerFamily.ADVANCED), dyeableProperties().stacksTo(1));
public static final RegistryEntry<TurtleItem> TURTLE_NORMAL = ofBlock(Blocks.TURTLE_NORMAL, TurtleItem::new, dyeableProperties());
public static final RegistryEntry<TurtleItem> TURTLE_ADVANCED = ofBlock(Blocks.TURTLE_ADVANCED, TurtleItem::new, dyeableProperties());
public static final RegistryEntry<DiskItem> DISK =
register("disk", DiskItem::new, dyeableProperties().stacksTo(1));
register(ComputerCraftItemIds.DISK, DiskItem::new, dyeableProperties().stacksTo(1));
public static final RegistryEntry<DiskItem> TREASURE_DISK =
register("treasure_disk", DiskItem::new, dyeableProperties().stacksTo(1));
register(ComputerCraftItemIds.TREASURE_DISK, DiskItem::new, dyeableProperties().stacksTo(1));
private static Item.Properties printoutProperties() {
return properties().stacksTo(1).component(DataComponents.PRINTOUT.get(), PrintoutData.EMPTY);
}
public static final RegistryEntry<PrintoutItem> PRINTED_PAGE = register("printed_page",
public static final RegistryEntry<PrintoutItem> PRINTED_PAGE = register(ComputerCraftItemIds.PRINTED_PAGE,
PrintoutItem::new, Items::printoutProperties);
public static final RegistryEntry<PrintoutItem> PRINTED_PAGES = register("printed_pages",
public static final RegistryEntry<PrintoutItem> PRINTED_PAGES = register(ComputerCraftItemIds.PRINTED_PAGES,
PrintoutItem::new, Items::printoutProperties);
public static final RegistryEntry<PrintoutItem> PRINTED_BOOK = register("printed_book",
public static final RegistryEntry<PrintoutItem> PRINTED_BOOK = register(ComputerCraftItemIds.PRINTED_BOOK,
PrintoutItem::new, Items::printoutProperties);
public static final RegistryEntry<BlockItem> SPEAKER = ofBlock(Blocks.SPEAKER, BlockItem::new, properties());
@@ -335,9 +335,9 @@ public final class ModRegistry {
public static final RegistryEntry<BlockItem> WIRED_MODEM_FULL = ofBlock(Blocks.WIRED_MODEM_FULL, BlockItem::new, properties());
public static final RegistryEntry<BlockItem> REDSTONE_RELAY = ofBlock(Blocks.REDSTONE_RELAY, BlockItem::new, properties());
public static final RegistryEntry<CableBlockItem.Cable> CABLE = register("cable",
public static final RegistryEntry<CableBlockItem.Cable> CABLE = register(ComputerCraftItemIds.CABLE,
p -> new CableBlockItem.Cable(Blocks.CABLE.get(), p), properties().useBlockDescriptionPrefix());
public static final RegistryEntry<CableBlockItem.WiredModem> WIRED_MODEM = register("wired_modem",
public static final RegistryEntry<CableBlockItem.WiredModem> WIRED_MODEM = register(ComputerCraftItemIds.WIRED_MODEM,
p -> new CableBlockItem.WiredModem(Blocks.CABLE.get(), p), properties().useBlockDescriptionPrefix());
}
@@ -576,7 +576,7 @@ public final class ModRegistry {
}
public static class Permissions {
static final PermissionRegistry REGISTRY = PermissionRegistry.create();
static final PermissionRegistry REGISTRY = PlatformHelper.get().createPermissionRegistry();
public static final Predicate<CommandSourceStack> PERMISSION_DUMP = REGISTRY.registerCommand("dump", UserLevel.OWNER_OP);
public static final Predicate<CommandSourceStack> PERMISSION_SHUTDOWN = REGISTRY.registerCommand("shutdown", UserLevel.OWNER_OP);
@@ -692,7 +692,7 @@ public final class ModRegistry {
peripherals.registerForBlockEntity(ModRegistry.BlockEntities.CABLE.get(), CableBlockEntity::getPeripheral);
peripherals.registerForBlockEntity(ModRegistry.BlockEntities.REDSTONE_RELAY.get(), (b, d) -> b.peripheral());
peripherals.registerForBlockEntity(BlockEntityType.COMMAND_BLOCK, (b, d) -> Config.enableCommandBlock ? new CommandBlockPeripheral(b) : null);
peripherals.registerForBlockEntity(BlockEntityTypes.COMMAND_BLOCK, (b, d) -> Config.enableCommandBlock ? new CommandBlockPeripheral(b) : null);
}
public static void registerWiredElements(BlockComponent<WiredElement, Direction> wiredElements) {
@@ -13,7 +13,7 @@ import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.computer.core.ServerComputer;
import dan200.computercraft.shared.computer.core.ServerContext;
import net.minecraft.advancements.criterion.MinMaxBounds;
import net.minecraft.advancements.predicates.MinMaxBounds;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.SharedSuggestionProvider;
import net.minecraft.commands.arguments.UuidArgument;
@@ -2,28 +2,25 @@
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.shared.integration;
package dan200.computercraft.shared.platform;
import com.google.errorprone.annotations.OverridingMethodsMustInvokeSuper;
import com.mojang.brigadier.builder.ArgumentBuilder;
import dan200.computercraft.shared.command.CommandComputerCraft;
import dan200.computercraft.shared.command.UserLevel;
import dan200.computercraft.shared.platform.RegistrationHelper;
import net.minecraft.commands.CommandSourceStack;
import java.util.Optional;
import java.util.ServiceLoader;
import java.util.function.Predicate;
/**
* A registry of nodes in a permission system.
* <p>
* This acts as an abstraction layer over permission systems such Forge's built-in permissions API, or Fabric's
* unofficial <a href="https://github.com/lucko/fabric-permissions-api">fabric-permissions-api-v0</a>.
* This acts as an abstraction layer over permission systems such Forge or Fabric's built-in permissions API.
* <p>
* This behaves similarly to {@link RegistrationHelper} (aka Forge's deferred registry), in that you {@linkplain #create()
* create a registry}, {@linkplain #registerCommand(String, UserLevel) add nodes to it} and then finally {@linkplain
* #register()} all created nodes.
* This behaves similarly to {@link RegistrationHelper} (aka Forge's deferred registry), in that you
* {@linkplain PlatformHelper#createPermissionRegistry()} create a registry},
* {@linkplain #registerCommand(String, UserLevel) add nodes to it} and then finally {@linkplain #register()} all
* created nodes.
*
* @see dan200.computercraft.shared.ModRegistry.Permissions
*/
@@ -56,24 +53,4 @@ public abstract class PermissionRegistry {
public void register() {
frozen = true;
}
public interface Provider {
Optional<PermissionRegistry> get();
}
public static PermissionRegistry create() {
return ServiceLoader.load(Provider.class)
.stream()
.flatMap(x -> x.get().get().stream())
.findFirst()
.orElseGet(DefaultPermissionRegistry::new);
}
private static final class DefaultPermissionRegistry extends PermissionRegistry {
@Override
public Predicate<CommandSourceStack> registerCommand(String command, UserLevel fallback) {
checkNotFrozen();
return fallback;
}
}
}
@@ -89,6 +89,13 @@ public interface PlatformHelper {
*/
<T> RegistrationHelper<T> createRegistrationHelper(ResourceKey<Registry<T>> registry);
/**
* Create a new {@linkplain PermissionRegistry registry for permissions}.
*
* @return The newly created permission registry.
*/
PermissionRegistry createPermissionRegistry();
/**
* Register a new argument type.
*
@@ -32,6 +32,17 @@ public interface RegistrationHelper<T> {
*/
<U extends T> RegistryEntry<U> register(String name, Supplier<U> create);
/**
* Register an entry in this helper. This does <em>NOT</em> immediately register the object in the underlying
* {@link Registry}.
*
* @param id The id of this entry.
* @param create A factory method to create the entry.
* @param <U> The type of this item in the registry.
* @return The {@link RegistryEntry} for the registered entry.
*/
<U extends T> RegistryEntry<U> register(ResourceKey<T> id, Supplier<U> create);
/**
* Register this helper.
*/
@@ -240,7 +240,7 @@ public class TurtleTool extends AbstractTurtleUpgrade {
var knockBack = EnchantmentHelper.modifyKnockback(player.level(), tool, entity, source, (float) player.getAttributeValue(Attributes.ATTACK_KNOCKBACK));
if (knockBack > 0) {
if (entity instanceof LivingEntity target) {
target.knockback(knockBack * 0.5, -direction.getStepX(), -direction.getStepZ());
target.knockback(knockBack * 0.5, -direction.getStepX(), -direction.getStepZ(), source, damage);
} else {
entity.push(direction.getStepX() * knockBack * 0.5, 0.1, direction.getStepZ() * knockBack * 0.5);
}
@@ -10,7 +10,7 @@ import net.minecraft.core.Direction;
import net.minecraft.core.Position;
import net.minecraft.core.dispenser.DefaultDispenseItemBehavior;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.EntityTypes;
import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.entity.projectile.ProjectileUtil;
@@ -174,7 +174,7 @@ public final class WorldUtil {
private final Block block;
ContextlessClipContext(Level level, Vec3 from, Vec3 to, Block block, Fluid fluid) {
super(from, to, block, fluid, new ItemEntity(EntityType.ITEM, level));
super(from, to, block, fluid, new ItemEntity(EntityTypes.ITEM, level));
this.block = block;
}
@@ -34,7 +34,7 @@ accessible field net/minecraft/client/data/models/ItemModelGenerators itemModelO
accessible field net/minecraft/client/data/models/ItemModelGenerators modelOutput Ljava/util/function/BiConsumer;
accessible method net/minecraft/client/data/models/ItemModelGenerators generateFlatItem (Lnet/minecraft/world/item/Item;Lnet/minecraft/client/data/models/model/ModelTemplate;)V
accessible method net/minecraft/client/data/models/model/TextureSlot create (Ljava/lang/String;)Lnet/minecraft/client/data/models/model/TextureSlot;
accessible method net/minecraft/data/recipes/RecipeProvider inventoryTrigger ([Lnet/minecraft/advancements/criterion/ItemPredicate;)Lnet/minecraft/advancements/Criterion;
accessible method net/minecraft/data/recipes/RecipeProvider inventoryTrigger ([Lnet/minecraft/advancements/predicates/ItemPredicate;)Lnet/minecraft/advancements/triggers/Criterion;
# GUI elements
accessible class net/minecraft/client/gui/GuiGraphicsExtractor$ScissorStack
@@ -7,13 +7,13 @@ accessWidener v1 official
# Shared vanilla and Fabric access wideners. This should not include things already exposed by Fabric's transitive
# wideners.
accessible class net/minecraft/world/level/block/entity/BlockEntityType$BlockEntitySupplier
accessible method net/minecraft/world/level/block/entity/BlockEntityType <init> (Lnet/minecraft/world/level/block/entity/BlockEntityType$BlockEntitySupplier;Ljava/util/Set;)V
# ClientTableFormatter
accessible field net/minecraft/client/gui/components/ChatComponent allMessages Ljava/util/List;
accessible method net/minecraft/client/gui/components/ChatComponent addMessage (Lnet/minecraft/network/chat/Component;Lnet/minecraft/network/chat/MessageSignature;Lnet/minecraft/client/multiplayer/chat/GuiMessageSource;Lnet/minecraft/client/multiplayer/chat/GuiMessageTag;)V
# NoTermComputerScreen
accessible field net/minecraft/client/gui/Gui screen Lnet/minecraft/client/gui/screens/Screen;
# ItemPocketRenderer/ItemPrintoutRenderer
accessible method net/minecraft/client/renderer/ItemInHandRenderer calculateMapTilt (F)F
accessible method net/minecraft/client/renderer/ItemInHandRenderer renderMapHand (Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/SubmitNodeCollector;ILnet/minecraft/world/entity/HumanoidArm;)V
@@ -2,7 +2,7 @@
"required": true,
"package": "dan200.computercraft.mixin",
"minVersion": "0.8",
"compatibilityLevel": "JAVA_21",
"compatibilityLevel": "JAVA_25",
"injectors": {
"defaultRequire": 1
},
@@ -64,7 +64,12 @@ public class TestPlatformHelper extends AbstractComputerCraftAPI implements Plat
@Override
public <T> RegistrationHelper<T> createRegistrationHelper(ResourceKey<Registry<T>> registry) {
throw new UnsupportedOperationException("Cannot query registry inside tests");
throw new UnsupportedOperationException("Cannot create RegistrationHelper inside tests");
}
@Override
public PermissionRegistry createPermissionRegistry() {
throw new UnsupportedOperationException("Cannot create PermissionRegistry inside tests");
}
@Override
@@ -52,7 +52,7 @@ public class Exporter {
private static void run(String path) {
var output = new File(path).getAbsoluteFile().toPath();
if (!Files.isDirectory(output)) {
Minecraft.getInstance().gui.getChat().addClientSystemMessage(Component.literal("Output path does not exist"));
Minecraft.getInstance().gui.hud.getChat().addClientSystemMessage(Component.literal("Output path does not exist"));
return;
}
@@ -63,7 +63,7 @@ public class Exporter {
throw new UncheckedIOException(e);
}
Minecraft.getInstance().gui.getChat().addClientSystemMessage(Component.literal("Export finished!"));
Minecraft.getInstance().gui.hud.getChat().addClientSystemMessage(Component.literal("Export finished!"));
}
private static void export(Path root) throws IOException {
@@ -19,9 +19,9 @@ 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.EntityTypes;
import net.minecraft.world.entity.decoration.ArmorStand;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.entity.BlockEntityTypes;
import net.minecraft.world.level.block.entity.TestInstanceBlockEntity;
import net.minecraft.world.level.storage.LevelResource;
@@ -46,20 +46,20 @@ class CCTestCommand {
var pos = StructureUtils.findNearestTest(player.blockPosition(), 15, player.level()).orElse(null);
if (pos == null) return error(context.getSource(), "No nearby test");
var test = player.level().getBlockEntity(pos, BlockEntityType.TEST_INSTANCE_BLOCK)
var test = player.level().getBlockEntity(pos, BlockEntityTypes.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.level();
level.getEntities(EntityType.ARMOR_STAND, x -> x.isAlive() && x.getName().getString().equals(test.identifier().getPath()))
level.getEntities(EntityTypes.ARMOR_STAND, x -> x.isAlive() && x.getName().getString().equals(test.identifier().getPath()))
.forEach(e -> e.kill(level));
// And create a new one
var nbt = new CompoundTag();
nbt.putBoolean("Marker", true);
nbt.putBoolean("Invisible", true);
var armorStand = new ArmorStand(EntityType.ARMOR_STAND, level);
var armorStand = new ArmorStand(EntityTypes.ARMOR_STAND, level);
armorStand.setInvisible(true);
((ArmorStandAccessor) armorStand).computercraft$setMarker(true);
armorStand.copyPosition(player);
@@ -75,7 +75,7 @@ class CCTestCommand {
var pos = StructureUtils.findNearestTest(player.blockPosition(), 15, player.level()).orElse(null);
if (pos == null) return error(context.getSource(), "No nearby test");
var test = player.level().getBlockEntity(pos, BlockEntityType.TEST_INSTANCE_BLOCK)
var test = player.level().getBlockEntity(pos, BlockEntityTypes.TEST_INSTANCE_BLOCK)
.flatMap(TestInstanceBlockEntity::test).orElse(null);
if (test == null) return error(context.getSource(), "No nearby structure block");
@@ -11,6 +11,7 @@ import net.minecraft.server.MinecraftServer;
import net.minecraft.server.Services;
import net.minecraft.server.WorldStem;
import net.minecraft.server.level.progress.LevelLoadListener;
import net.minecraft.server.notifications.NotificationManager;
import net.minecraft.server.packs.repository.PackRepository;
import net.minecraft.world.level.gamerules.GameRules;
import net.minecraft.world.level.storage.LevelStorageSource;
@@ -27,9 +28,9 @@ abstract class GameTestServerMixin extends MinecraftServer {
GameTestServerMixin(
Thread serverThread, LevelStorageSource.LevelStorageAccess storageSource, PackRepository packRepository,
WorldStem worldStem, Optional<GameRules> gameRules, Proxy proxy, DataFixer fixerUpper, Services services,
LevelLoadListener progressListenerFactory, boolean propagatesCrashes
LevelLoadListener progressListenerFactory, boolean propagatesCrashes, NotificationManager notificationManager
) {
super(serverThread, storageSource, packRepository, worldStem, gameRules, proxy, fixerUpper, services, progressListenerFactory, propagatesCrashes);
super(serverThread, storageSource, packRepository, worldStem, gameRules, proxy, fixerUpper, services, progressListenerFactory, propagatesCrashes, notificationManager);
}
/**
@@ -42,7 +42,7 @@ class MinecraftMixin implements MinecraftExtensions {
private void updateStable(boolean render, CallbackInfo ci) {
isStable.set(
level != null && player != null &&
levelRenderer.isSectionCompiledAndVisible(player.blockPosition()) && levelRenderer.countRenderedSections() > 10 &&
levelRenderer.isSectionCompiledAndVisible(player.blockPosition()) && levelRenderer.visibleSections().size() > 10 &&
levelRenderer.hasRenderedAllSections()
);
}
@@ -155,7 +155,7 @@ class Computer_Test {
}
// Press a key on the client
thenOnClient {
val screen = minecraft.screen as AbstractComputerScreen<*>
val screen = minecraft.gui.screen() as AbstractComputerScreen<*>
screen.keyPressed(KeyEvent(GLFW.GLFW_KEY_A, 0, 0))
screen.keyReleased(KeyEvent(GLFW.GLFW_KEY_A, 0, 0))
}
@@ -30,7 +30,7 @@ class Disk_Test {
assertThat(
"Disk with dye",
helper.craftItem(ItemStack(Items.REDSTONE), ItemStack(Items.PAPER), ItemStack(Items.GREEN_DYE)),
helper.craftItem(ItemStack(Items.REDSTONE), ItemStack(Items.PAPER), ItemStack(Items.DYE.green)),
isStack(DataComponentUtil.createDyedStack(ModRegistry.Items.DISK.get(), Colour.GREEN.hex)),
)
}
@@ -7,7 +7,6 @@ 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
@@ -36,7 +35,7 @@ class Printer_Test {
// Adding items should provide power
thenExecute {
val printer = helper.getBlockEntity(printerPos, PrinterBlockEntity::class.java)
printer.setItem(0, ItemStack(Items.BLACK_DYE))
printer.setItem(0, ItemStack(Items.DYE.black))
printer.setItem(1, ItemStack(Items.PAPER))
printer.setChanged()
}
@@ -100,7 +99,7 @@ class Printer_Test {
assertFalse(peripheral.newPage(), "newPage fails with no items")
// Try to print with just ink
printer.setItem(0, ItemStack(Items.BLUE_DYE))
printer.setItem(0, ItemStack(Items.DYE.blue))
printer.setChanged()
assertFalse(peripheral.newPage(), "newPage fails with no paper")
@@ -114,7 +113,7 @@ class Printer_Test {
printer.clearContent()
// Try to print with both items
printer.setItem(0, ItemStack(Items.BLUE_DYE))
printer.setItem(0, ItemStack(Items.DYE.blue))
printer.setItem(1, ItemStack(Items.PAPER))
printer.setChanged()
assertTrue(peripheral.newPage(), "newPage succeeds")
@@ -187,7 +186,7 @@ class Printer_Test {
helper.assertExactlyItems(
DataComponentUtil.createStack(ModRegistry.Items.PRINTER.get(), DataComponents.CUSTOM_NAME, Component.literal("My Printer")),
ItemStack(Items.PAPER),
ItemStack(Items.BLACK_DYE),
ItemStack(Items.DYE.black),
message = "Breaking a printer should drop the contents",
)
}
@@ -200,7 +199,7 @@ class Printer_Test {
fun Can_insert_items(helper: GameTestHelper) = helper.sequence {
thenWaitUntil {
helper.assertContainerExactly(BlockPos(1, 1, 2), listOf(ItemStack.EMPTY, ItemStack(Items.PAPER)))
helper.assertContainerExactly(BlockPos(3, 1, 2), listOf(ItemStack(Items.BLACK_DYE)))
helper.assertContainerExactly(BlockPos(3, 1, 2), listOf(ItemStack(Items.DYE.black)))
}
}
@@ -41,6 +41,7 @@ import net.minecraft.core.registries.Registries
import net.minecraft.gametest.framework.GameTestHelper
import net.minecraft.resources.Identifier
import net.minecraft.world.entity.EntityType
import net.minecraft.world.entity.EntityTypes
import net.minecraft.world.entity.item.PrimedTnt
import net.minecraft.world.item.BlockItem
import net.minecraft.world.item.ItemStack
@@ -129,7 +130,7 @@ class Turtle_Test {
thenOnComputer {
turtle.placeDown(ObjectArguments()).await().assertArrayEquals(true, message = "Placed boat")
}
thenExecute { helper.assertEntityPresent(EntityType.OAK_BOAT) }
thenExecute { helper.assertEntityPresent(EntityTypes.OAK_BOAT) }
}
/**
@@ -485,7 +486,7 @@ class Turtle_Test {
tnt.fuse = 1
helper.level.addFreshEntity(tnt)
}
thenWaitUntil { helper.assertEntityNotPresent(EntityType.TNT) }
thenWaitUntil { helper.assertEntityNotPresent(EntityTypes.TNT) }
thenExecute {
helper.assertBlockPresent(ModRegistry.Blocks.TURTLE_ADVANCED.get(), BlockPos(2, 1, 2))
helper.assertBlockPresent(Blocks.AIR, BlockPos(2, 1, 1))
@@ -497,8 +498,8 @@ class Turtle_Test {
*/
@GameTest
fun Resists_entity_explosions(helper: GameTestHelper) = helper.sequence {
thenExecute { helper.getEntity(EntityType.CREEPER).ignite() }
thenWaitUntil { helper.assertEntityNotPresent(EntityType.CREEPER) }
thenExecute { helper.getEntity(EntityTypes.CREEPER).ignite() }
thenWaitUntil { helper.assertEntityNotPresent(EntityTypes.CREEPER) }
thenExecute {
helper.assertBlockPresent(ModRegistry.Blocks.TURTLE_ADVANCED.get(), BlockPos(2, 1, 2))
helper.assertBlockPresent(ModRegistry.Blocks.TURTLE_NORMAL.get(), BlockPos(2, 1, 1))
@@ -529,7 +530,7 @@ class Turtle_Test {
@GameTest
fun Drop_into_entity(helper: GameTestHelper) = helper.sequence {
// When running /test runthis, the previous items pop from the chest. Remove them first!
thenExecute { for (it in helper.getEntities(EntityType.ITEM)) it.discard() }
thenExecute { for (it in helper.getEntities(EntityTypes.ITEM)) it.discard() }
thenOnComputer {
turtle.drop(Optional.of(32)).await()
@@ -537,8 +538,8 @@ class Turtle_Test {
}
thenExecute {
helper.assertContainerExactly(BlockPos(2, 1, 2), listOf(ItemStack(Blocks.DIRT, 32)))
helper.assertContainerExactly(helper.getEntity(EntityType.CHEST_MINECART), listOf(ItemStack(Blocks.DIRT, 48)))
helper.assertEntityNotPresent(EntityType.ITEM)
helper.assertContainerExactly(helper.getEntity(EntityTypes.CHEST_MINECART), listOf(ItemStack(Blocks.DIRT, 48)))
helper.assertEntityNotPresent(EntityTypes.ITEM)
}
}
@@ -621,7 +622,7 @@ class Turtle_Test {
assertEquals("turtle_test.move_preserves_state", turtle.label)
assertEquals(79, turtle.access.fuelLevel)
helper.assertEntityNotPresent(EntityType.ITEM)
helper.assertEntityNotPresent(EntityTypes.ITEM)
}
}
@@ -677,9 +678,9 @@ class Turtle_Test {
// As has the villager
val pos = BlockPos(2, 3, 2)
helper.assertEntityPresent(EntityType.VILLAGER, pos)
helper.assertEntityPresent(EntityTypes.VILLAGER, pos)
val villager = helper.getEntity(EntityType.VILLAGER)
val villager = helper.getEntity(EntityTypes.VILLAGER)
val expectedY = helper.absolutePos(pos).y - 0.125
if (villager.y < expectedY) helper.abort("Expected villager at y>=$expectedY, but at ${villager.y}", pos)
}
@@ -695,9 +696,9 @@ class Turtle_Test {
turtle.attack(Optional.empty()).await().assertArrayEquals(true, message = "Attacked entity")
}
thenExecute {
helper.assertEntityNotPresent(EntityType.SHEEP)
helper.assertEntityNotPresent(EntityTypes.SHEEP)
val count = helper.getBlockEntity(turtlePos, TurtleBlockEntity::class.java)
.countItem(Items.WHITE_WOOL)
.countItem(Items.WOOL.white)
if (count == 0) helper.abort("Expected turtle to have white wool", turtlePos)
}
}
@@ -17,7 +17,7 @@ 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.entity.EntityTypes
import net.minecraft.world.inventory.AbstractContainerMenu
import net.minecraft.world.inventory.MenuType
import java.util.concurrent.CompletableFuture
@@ -68,14 +68,14 @@ fun GameTestSequence.thenScreenshot(name: String? = null, showGui: Boolean = fal
// Now disable the GUI, take a screenshot and reenable it. Sleep a little afterwards to ensure the render thread
// has caught up.
thenOnClient { minecraft.options.hideGui = !showGui }
thenOnClient { if (minecraft.gui.hud.isHidden == showGui) minecraft.gui.hud.toggle() }
thenIdle(2)
// Take a screenshot and wait for it to have finished.
val hasScreenshot = AtomicBoolean()
thenOnClient { screenshot("$fullName.png") { hasScreenshot.set(true) } }
thenWaitUntil { if (!hasScreenshot.get()) abort("Screenshot does not exist") }
thenOnClient { minecraft.options.hideGui = false }
thenOnClient { if (minecraft.gui.hud.isHidden) minecraft.gui.hud.toggle() }
return this
}
@@ -91,7 +91,7 @@ fun ServerPlayer.setupForTest() {
* Position the player at an armor stand.
*/
fun GameTestHelper.positionAtArmorStand() {
val stand = getEntity(EntityType.ARMOR_STAND)
val stand = getEntity(EntityTypes.ARMOR_STAND)
val player = level.randomPlayer ?: abort("Player does not exist")
player.setupForTest()
@@ -116,7 +116,7 @@ class ClientTestHelper {
val minecraft: Minecraft = Minecraft.getInstance()
fun screenshot(name: String, callback: () -> Unit = {}) {
Screenshot.grab(minecraft.gameDirectory, name, minecraft.mainRenderTarget, 1) { callback() }
Screenshot.grab(minecraft.gameDirectory, name, minecraft.gameRenderer.mainRenderTarget(), 1) { callback() }
}
/**
@@ -125,7 +125,7 @@ class ClientTestHelper {
fun <T : AbstractContainerMenu> getOpenMenu(type: MenuType<T>): T {
fun getName(type: MenuType<*>) = RegistryHelper.getKeyOrThrow(BuiltInRegistries.MENU, type)
val screen = minecraft.screen
val screen = minecraft.gui.screen()
@Suppress("UNCHECKED_CAST")
when {
screen == null -> throw GameTestAssertException(Component.literal("Expected a ${getName(type)} menu, but no screen is open"), 0)
@@ -30,6 +30,7 @@ import net.minecraft.world.InteractionHand
import net.minecraft.world.clock.WorldClock
import net.minecraft.world.entity.Entity
import net.minecraft.world.entity.EntityType
import net.minecraft.world.entity.EntityTypes
import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.ItemStackTemplate
@@ -294,7 +295,7 @@ fun GameTestHelper.assertNoPeripheral(pos: BlockPos, direction: Direction = Dire
}
fun GameTestHelper.assertExactlyItems(vararg expected: ItemStack, message: String? = null) {
val actual = getEntities(EntityType.ITEM).map { it.item }
val actual = getEntities(EntityTypes.ITEM).map { it.item }
val matcher = Matchers.containsInAnyOrder(expected.map { isStack(it) })
if (!matcher.matches(actual)) {
val description = StringDescription()
@@ -307,7 +308,7 @@ fun GameTestHelper.assertExactlyItems(vararg expected: ItemStack, message: Strin
* Similar to [GameTestHelper.assertItemEntityCountIs], but searching anywhere in the structure bounds.
*/
fun GameTestHelper.assertItemEntityCountIs(expected: Item, count: Int) {
val actualCount = getEntities(EntityType.ITEM).sumOf { if (it.item.`is`(expected)) it.item.count else 0 }
val actualCount = getEntities(EntityTypes.ITEM).sumOf { if (it.item.`is`(expected)) it.item.count else 0 }
if (actualCount != count) {
abort("Expected $count ${ItemStack(expected).itemName.string} items to exist (found $actualCount)")
}
@@ -83,7 +83,7 @@ object ClientTestHooks {
if (minecraft.levelSource.levelExists(LEVEL_NAME)) {
LOG.info("World already exists, opening.")
minecraft.createWorldOpenFlows().openWorld(LEVEL_NAME) { minecraft.setScreen(screen) }
minecraft.createWorldOpenFlows().openWorld(LEVEL_NAME) { minecraft.gui.setScreen(screen) }
} else {
LOG.info("World does not exist, creating it.")
val rules = GameRules(FeatureFlags.DEFAULT_FLAGS)
@@ -22,6 +22,7 @@ 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.BlockEntityType
import net.minecraft.world.level.block.entity.BlockEntityTypes
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.gamerules.GameRules
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager
@@ -91,7 +92,7 @@ object TestHooks {
val level = server.overworld()
StructureUtils.findTestBlocks(getTestOrigin(server), 200, level).toList().forEach { pos ->
val test = level.getBlockEntity(pos, BlockEntityType.TEST_INSTANCE_BLOCK).getOrNull() ?: return@forEach
val test = level.getBlockEntity(pos, BlockEntityTypes.TEST_INSTANCE_BLOCK).getOrNull() ?: return@forEach
StructureUtils.clearSpaceForStructure(test.structureBoundingBox, level)
}
@@ -2,7 +2,7 @@
"required": true,
"package": "dan200.computercraft.mixin.gametest",
"minVersion": "0.8",
"compatibilityLevel": "JAVA_21",
"compatibilityLevel": "JAVA_25",
"injectors": {
"defaultRequire": 1
},
@@ -11,7 +11,7 @@ import org.jspecify.annotations.Nullable;
import java.io.IOException;
import java.nio.channels.SeekableByteChannel;
import java.util.concurrent.TimeUnit;
import java.time.Duration;
/**
* An abstract mount based on some archive of files, such as a Zip or Minecraft's resources.
@@ -33,7 +33,7 @@ public abstract class ArchiveMount<T extends ArchiveMount.FileEntry<T>> extends
*/
private static final Cache<FileEntry<?>, byte[]> CONTENTS_CACHE = CacheBuilder.newBuilder()
.concurrencyLevel(4)
.expireAfterAccess(60, TimeUnit.SECONDS)
.expireAfterAccess(Duration.ofSeconds(60))
.maximumWeight(MAX_CACHE_SIZE)
.weakKeys()
.<FileEntry<?>, byte[]>weigher((k, v) -> v.length)
@@ -13,6 +13,7 @@ import dan200.computercraft.client.platform.ClientNetworkContextImpl;
import dan200.computercraft.client.platform.FabricModelKey;
import dan200.computercraft.client.platform.ModelKey;
import dan200.computercraft.client.render.BlockOutlineRenderer;
import dan200.computercraft.client.render.ExtendedItemFrameRenderState;
import dan200.computercraft.shared.ComputerCraft;
import dan200.computercraft.shared.config.ConfigSpec;
import dan200.computercraft.shared.network.NetworkMessages;
@@ -25,6 +26,7 @@ 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.ModelLayerRegistry;
import net.fabricmc.fabric.api.client.rendering.v1.PictureInPictureRendererRegistry;
import net.fabricmc.fabric.api.client.rendering.v1.RenderStateDataKey;
import net.fabricmc.fabric.api.client.rendering.v1.level.LevelRenderEvents;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.client.Minecraft;
@@ -32,7 +34,6 @@ 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.screens.MenuScreens;
import net.minecraft.client.renderer.MultiBufferSource;
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;
@@ -46,9 +47,11 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
public class ComputerCraftClient {
public static final RenderStateDataKey<ExtendedItemFrameRenderState> ITEM_FRAME_STATE = RenderStateDataKey.create(() -> "Extended item frame");
public static void init() {
var clientNetwork = new ClientNetworkContextImpl();
for (var type : NetworkMessages.getClientbound()) {
@@ -78,8 +81,8 @@ public class ComputerCraftClient {
ClientRegistry.registerPictureInPictureRenderers(new ClientRegistry.RegisterPictureInPictureRenderer() {
@Override
public <T extends PictureInPictureRenderState> void register(Class<T> ty, Function<MultiBufferSource.BufferSource, PictureInPictureRenderer<T>> f) {
PictureInPictureRendererRegistry.register(c -> f.apply(c.bufferSource()));
public <T extends PictureInPictureRenderState> void register(Class<T> ty, Supplier<PictureInPictureRenderer<T>> f) {
PictureInPictureRendererRegistry.register(c -> f.get());
}
});
@@ -92,11 +95,11 @@ public class ComputerCraftClient {
return true;
}
var camera = context.gameRenderer().getMainCamera();
var camera = context.gameRenderer().mainCamera();
var renderer = ClientHooks.drawHighlight(camera, blockHit);
if (renderer == null) return true;
BlockOutlineRenderer.render(context.poseStack(), context.bufferSource(), renderer);
BlockOutlineRenderer.render(context.poseStack(), context.submitNodeCollector(), renderer, camera, blockHit);
return false;
});
@@ -1,24 +0,0 @@
// SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.client;
import com.mojang.blaze3d.vertex.PoseStack;
import dan200.computercraft.client.render.ExtendedItemFrameRenderState;
import net.minecraft.client.renderer.SubmitNodeCollector;
import net.minecraft.client.renderer.entity.state.ItemFrameRenderState;
/**
* An interface implemented on {@link ItemFrameRenderState} to provide a {@link ExtendedItemFrameRenderState}.
*
* @see ClientHooks#onRenderItemFrame(PoseStack, SubmitNodeCollector, ItemFrameRenderState, ExtendedItemFrameRenderState)
*/
public interface ExtendedItemFrameRenderStateHolder {
/**
* Get or create the CC-specific render state.
*
* @return The CC-specific render state.
*/
ExtendedItemFrameRenderState computercraft$state();
}
@@ -5,7 +5,12 @@
package dan200.computercraft.client.platform;
import com.google.auto.service.AutoService;
import com.mojang.blaze3d.vertex.PoseStack;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.impl.client.ExtendedOrderedSubmitNodeCollector;
import dan200.computercraft.shared.peripheral.monitor.ClientMonitor;
import net.fabricmc.fabric.api.client.model.loading.v1.ExtraModelKey;
import net.minecraft.client.renderer.OrderedSubmitNodeCollector;
import net.minecraft.client.resources.model.ModelDebugName;
@AutoService(ClientPlatformHelper.class)
@@ -14,4 +19,9 @@ public class ClientPlatformHelperImpl implements ClientPlatformHelper {
public <T> ModelKey<T> createModelKey(ModelDebugName name) {
return new FabricModelKey<>(ExtraModelKey.create(name::debugName));
}
@Override
public void submitMonitor(OrderedSubmitNodeCollector collector, PoseStack poseStack, ClientMonitor monitor, Terminal terminal, float xMargin, float yMargin) {
((ExtendedOrderedSubmitNodeCollector) collector).computercraft$submitMonitor(poseStack, monitor, terminal, xMargin, yMargin);
}
}
@@ -0,0 +1,32 @@
// SPDX-FileCopyrightText: 2026 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.impl.client;
import com.mojang.blaze3d.vertex.PoseStack;
import dan200.computercraft.client.render.monitor.MonitorBlockEntityRenderer;
import dan200.computercraft.client.render.text.FixedWidthFontRenderer;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.shared.peripheral.monitor.ClientMonitor;
import net.minecraft.client.renderer.OrderedSubmitNodeCollector;
/**
* Extension interface to {@link OrderedSubmitNodeCollector} that allows submitting monitor contents.
*
* @see MonitorBlockEntityRenderer
*/
public interface ExtendedOrderedSubmitNodeCollector {
default void computercraft$submitMonitor(
PoseStack poseStack, ClientMonitor monitor, Terminal terminal, float xMargin, float yMargin
) {
var collector = (OrderedSubmitNodeCollector) this;
collector.submitCustomGeometry(poseStack, FixedWidthFontRenderer.TERMINAL_TEXT, (pose, buffer) -> {
FixedWidthFontRenderer.drawTerminalBackground(pose.pose(), buffer, 0, 0, terminal, yMargin, yMargin, xMargin, xMargin);
});
collector.submitCustomGeometry(poseStack, FixedWidthFontRenderer.TERMINAL_TEXT_OFFSET, (pose, buffer) -> {
FixedWidthFontRenderer.drawTerminalForeground(pose.pose(), buffer, 0, 0, terminal);
FixedWidthFontRenderer.drawCursor(pose.pose(), buffer, 0, 0, terminal);
});
}
}
@@ -0,0 +1,42 @@
// SPDX-FileCopyrightText: 2026 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.mixin.client;
import dan200.computercraft.client.ClientRegistry;
import net.minecraft.client.renderer.SubmitNodeCollection;
import net.minecraft.client.renderer.feature.FeatureRenderDispatcher;
import net.minecraft.client.renderer.feature.FeatureRenderer;
import net.minecraft.client.renderer.feature.FeatureRendererMap;
import net.minecraft.client.renderer.feature.FeatureRendererType;
import net.minecraft.client.renderer.feature.submit.SubmitNode;
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;
import java.util.function.Supplier;
/**
* Monitor support for {@link SubmitNodeCollection}.
*/
@Mixin(FeatureRenderDispatcher.class)
abstract class FeatureRenderDispatcherMixin {
@Shadow
@Final
private FeatureRendererMap featureRenderers;
@Inject(method = "<init>", at = @At("RETURN"))
@SuppressWarnings("unused")
private void registerExtendedFeatureRenderers(CallbackInfo ci) {
ClientRegistry.registerFeatureRenderers(new ClientRegistry.RegisterFeatureRenderer() {
@Override
public <T extends SubmitNode> void register(FeatureRendererType<T> type, Supplier<FeatureRenderer<T>> renderer) {
featureRenderers.put(type, renderer.get());
}
});
}
}
@@ -1,22 +0,0 @@
// SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.mixin.client;
import dan200.computercraft.client.ExtendedItemFrameRenderStateHolder;
import dan200.computercraft.client.render.ExtendedItemFrameRenderState;
import net.minecraft.client.renderer.entity.state.ItemFrameRenderState;
import org.jspecify.annotations.Nullable;
import org.spongepowered.asm.mixin.Mixin;
@Mixin(ItemFrameRenderState.class)
class ItemFrameRenderStateMixin implements ExtendedItemFrameRenderStateHolder {
private @Nullable ExtendedItemFrameRenderState computercraft$state;
@Override
public ExtendedItemFrameRenderState computercraft$state() {
if (computercraft$state == null) computercraft$state = new ExtendedItemFrameRenderState();
return computercraft$state;
}
}
@@ -6,7 +6,8 @@ package dan200.computercraft.mixin.client;
import com.mojang.blaze3d.vertex.PoseStack;
import dan200.computercraft.client.ClientHooks;
import dan200.computercraft.client.ExtendedItemFrameRenderStateHolder;
import dan200.computercraft.client.ComputerCraftClient;
import dan200.computercraft.client.render.ExtendedItemFrameRenderState;
import net.minecraft.client.renderer.SubmitNodeCollector;
import net.minecraft.client.renderer.entity.ItemFrameRenderer;
import net.minecraft.client.renderer.entity.state.ItemFrameRenderState;
@@ -14,6 +15,7 @@ import net.minecraft.client.renderer.state.level.CameraRenderState;
import net.minecraft.world.entity.decoration.ItemFrame;
import org.objectweb.asm.Opcodes;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@@ -27,9 +29,8 @@ class ItemFrameRendererMixin {
cancellable = true
)
@SuppressWarnings("unused")
private void submit(ItemFrameRenderState frame, PoseStack pose, SubmitNodeCollector buffers, CameraRenderState camera, CallbackInfo ci) {
var state = ((ExtendedItemFrameRenderStateHolder) frame).computercraft$state();
if (ClientHooks.onRenderItemFrame(pose, buffers, frame, state)) {
private void submit(ItemFrameRenderState state, PoseStack pose, SubmitNodeCollector buffers, CameraRenderState camera, CallbackInfo ci) {
if (ClientHooks.onRenderItemFrame(pose, buffers, state, getState(state))) {
ci.cancel();
pose.popPose();
}
@@ -41,6 +42,15 @@ class ItemFrameRendererMixin {
)
@SuppressWarnings("unused")
private void extractRenderState(ItemFrame entity, ItemFrameRenderState state, float f, CallbackInfo ci) {
((ExtendedItemFrameRenderStateHolder) state).computercraft$state().setup(entity.getItem());
getState(state).setup(entity.getItem());
}
@Unique
private static ExtendedItemFrameRenderState getState(ItemFrameRenderState state) {
var extendedState = state.getData(ComputerCraftClient.ITEM_FRAME_STATE);
if (extendedState == null) {
state.setData(ComputerCraftClient.ITEM_FRAME_STATE, extendedState = new ExtendedItemFrameRenderState());
}
return extendedState;
}
}
@@ -18,9 +18,9 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(ItemInHandRenderer.class)
class ItemInHandRendererMixin {
@Inject(method = "renderArmWithItem", at = @At("HEAD"), cancellable = true)
@Inject(method = "submitArmWithItem", at = @At("HEAD"), cancellable = true)
@SuppressWarnings("unused")
private void onRenderItem(
private void onSubmitArmWithItem(
AbstractClientPlayer player, float partialTicks, float pitch, InteractionHand hand, float swingProgress, ItemStack stack,
float equippedProgress, PoseStack transform, SubmitNodeCollector collector, int combinedLight, CallbackInfo ci
) {
@@ -0,0 +1,16 @@
// SPDX-FileCopyrightText: 2026 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.mixin.client;
import dan200.computercraft.impl.client.ExtendedOrderedSubmitNodeCollector;
import net.minecraft.client.renderer.OrderedSubmitNodeCollector;
import org.spongepowered.asm.mixin.Mixin;
/**
* Monitor support for {@link OrderedSubmitNodeCollector}.
*/
@Mixin(OrderedSubmitNodeCollector.class)
interface OrderedSubmitNodeCollectorMixin extends ExtendedOrderedSubmitNodeCollector {
}
@@ -0,0 +1,33 @@
// SPDX-FileCopyrightText: 2026 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.mixin.client;
import com.mojang.blaze3d.vertex.PoseStack;
import dan200.computercraft.client.render.monitor.MonitorBlockEntityRenderer;
import dan200.computercraft.client.render.monitor.MonitorRenderState;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.impl.client.ExtendedOrderedSubmitNodeCollector;
import dan200.computercraft.shared.peripheral.monitor.ClientMonitor;
import net.minecraft.client.renderer.SubmitNodeCollection;
import net.minecraft.client.renderer.feature.phase.SimpleFeatureRenderPhase;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
/**
* Monitor support for {@link SubmitNodeCollection}.
*/
@Mixin(SubmitNodeCollection.class)
abstract class SubmitNodeCollectionMixin implements ExtendedOrderedSubmitNodeCollector {
@Shadow
@Final
public SimpleFeatureRenderPhase solid;
@Override
public void computercraft$submitMonitor(PoseStack poseStack, ClientMonitor monitor, Terminal terminal, float xMargin, float yMargin) {
var renderState = monitor.getRenderState(MonitorRenderState::new);
solid.submit(new MonitorBlockEntityRenderer.MonitorSubmit(poseStack.last().copy(), monitor, terminal, renderState, xMargin, yMargin));
}
}
@@ -0,0 +1,23 @@
// SPDX-FileCopyrightText: 2026 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.mixin.client;
import com.mojang.blaze3d.vertex.PoseStack;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.impl.client.ExtendedOrderedSubmitNodeCollector;
import dan200.computercraft.shared.peripheral.monitor.ClientMonitor;
import net.minecraft.client.renderer.SubmitNodeCollector;
import org.spongepowered.asm.mixin.Mixin;
/**
* Monitor support for {@link SubmitNodeCollector}.
*/
@Mixin(SubmitNodeCollector.class)
interface SubmitNodeCollectorMixin extends ExtendedOrderedSubmitNodeCollector {
@Override
default void computercraft$submitMonitor(PoseStack poseStack, ClientMonitor monitor, Terminal terminal, float xMargin, float yMargin) {
((ExtendedOrderedSubmitNodeCollector) ((SubmitNodeCollector) this).order(0)).computercraft$submitMonitor(poseStack, monitor, terminal, xMargin, yMargin);
}
}
@@ -2,16 +2,19 @@
"required": true,
"package": "dan200.computercraft.mixin.client",
"minVersion": "0.8",
"compatibilityLevel": "JAVA_21",
"compatibilityLevel": "JAVA_25",
"injectors": {
"defaultRequire": 1
},
"client": [
"FeatureRenderDispatcherMixin",
"ItemFrameRendererMixin",
"ItemFrameRenderStateMixin",
"ItemInHandRendererMixin",
"MinecraftMixin",
"MultiPlayerGameModeMixin",
"SoundEngineMixin"
"OrderedSubmitNodeCollectorMixin",
"SoundEngineMixin",
"SubmitNodeCollectionMixin",
"SubmitNodeCollectorMixin"
]
}
@@ -72,7 +72,7 @@ public class FabricDataProviders implements DataGeneratorEntrypoint {
return addWithRegistries((out, registries) -> new FabricTagsProvider.BlockTagsProvider(out, registries) {
@Override
protected void addTags(HolderLookup.Provider registries) {
tags.accept(this::valueLookupBuilder);
tags.accept(this::builder);
}
});
}
@@ -82,7 +82,7 @@ public class FabricDataProviders implements DataGeneratorEntrypoint {
return addWithRegistries((out, registries) -> new FabricTagsProvider.ItemTagsProvider(out, registries) {
@Override
protected void addTags(HolderLookup.Provider registries) {
tags.accept(this::valueLookupBuilder);
tags.accept(this::builder);
}
});
}
@@ -97,7 +97,7 @@ public class FabricDataProviders implements DataGeneratorEntrypoint {
@Override
protected void configure(HolderLookup.Provider registries, Entries entries) {
for (var reg : DynamicRegistries.getDynamicRegistries()) {
for (var reg : DynamicRegistries.getBootstrappingRegistries()) {
registries.lookupOrThrow(reg.key()).listElements().forEach(x -> register(entries, x));
}
}
@@ -8,7 +8,7 @@ import net.fabricmc.api.ModInitializer;
import net.minecraft.core.Registry;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.Identifier;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.entity.BlockEntityTypes;
/**
* The main entry point for our example mod.
@@ -25,7 +25,7 @@ public class FabricExampleMod implements ModInitializer {
ExampleMod.registerComputerCraft();
// @start region=peripherals
PeripheralLookup.get().registerForBlockEntity((f, s) -> new BrewingStandPeripheral(f), BlockEntityType.BREWING_STAND);
PeripheralLookup.get().registerForBlockEntity((f, s) -> new BrewingStandPeripheral(f), BlockEntityTypes.BREWING_STAND);
// @end region=peripherals
}
}
@@ -53,7 +53,7 @@ public class FabricExampleModDataGenerator implements DataGeneratorEntrypoint {
@Override
protected void configure(HolderLookup.Provider registries, Entries entries) {
for (var r : DynamicRegistries.getDynamicRegistries()) entries.addAll(registries.lookupOrThrow(r.key()));
for (var r : DynamicRegistries.getWorldRegistries()) entries.addAll(registries.lookupOrThrow(r.key()));
}
@Override
@@ -1,40 +0,0 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.shared.integration;
import com.google.auto.service.AutoService;
import dan200.computercraft.shared.command.UserLevel;
import me.lucko.fabric.api.permissions.v0.Permissions;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.commands.CommandSourceStack;
import java.util.Optional;
import java.util.function.Predicate;
/**
* An implementation of {@link PermissionRegistry} using Fabric's unofficial {@linkplain Permissions permissions api}.
*/
public final class FabricPermissionRegistry extends PermissionRegistry {
private FabricPermissionRegistry() {
}
@Override
public Predicate<CommandSourceStack> registerCommand(String command, UserLevel fallback) {
checkNotFrozen();
// var name = ComputerCraftAPI.MOD_ID + ".command." + command;
// Permissions.getPermissionValue(source, name).orElseGet(() -> ...)
return fallback;
}
@AutoService(PermissionRegistry.Provider.class)
public static final class Provider implements PermissionRegistry.Provider {
@Override
public Optional<PermissionRegistry> get() {
return FabricLoader.getInstance().isModLoaded("fabric-permissions-api-v0")
? Optional.of(new FabricPermissionRegistry())
: Optional.empty();
}
}
}
@@ -18,6 +18,7 @@ import dan200.computercraft.api.peripheral.PeripheralLookup;
import dan200.computercraft.impl.Peripherals;
import dan200.computercraft.mixin.ArgumentTypeInfosAccessor;
import dan200.computercraft.shared.ComputerCraft;
import dan200.computercraft.shared.command.UserLevel;
import dan200.computercraft.shared.config.ConfigFile;
import dan200.computercraft.shared.network.container.ContainerData;
import dan200.computercraft.shared.util.InventoryUtil;
@@ -29,10 +30,12 @@ import net.fabricmc.fabric.api.lookup.v1.block.BlockApiCache;
import net.fabricmc.fabric.api.lookup.v1.block.BlockApiLookup;
import net.fabricmc.fabric.api.menu.v1.ExtendedMenuProvider;
import net.fabricmc.fabric.api.menu.v1.ExtendedMenuType;
import net.fabricmc.fabric.api.permission.v1.PermissionNode;
import net.fabricmc.fabric.api.tag.convention.v2.ConventionalItemTags;
import net.fabricmc.fabric.api.transfer.v1.item.ContainerStorage;
import net.fabricmc.fabric.api.transfer.v1.item.ItemStorage;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.synchronization.ArgumentTypeInfo;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
@@ -71,6 +74,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.function.Supplier;
@AutoService(PlatformHelper.class)
@@ -97,6 +101,11 @@ public class PlatformHelperImpl implements PlatformHelper {
return new RegistrationHelperImpl<>(getRegistry(registry));
}
@Override
public PermissionRegistry createPermissionRegistry() {
return new PermissionRegistryImpl();
}
@Override
public <A extends ArgumentType<?>, T extends ArgumentTypeInfo.Template<A>, I extends ArgumentTypeInfo<A, T>> I registerArgumentTypeInfo(Class<A> klass, I info) {
ArgumentTypeInfosAccessor.classMap().put(klass, info);
@@ -255,7 +264,16 @@ public class PlatformHelperImpl implements PlatformHelper {
@Override
public <U extends T> RegistryEntry<U> register(String name, Supplier<U> create) {
var entry = new RegistryEntryImpl<>(Identifier.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, name), create);
return register(ResourceKey.create(registry.key(), Identifier.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, name)), create);
}
@Override
public <U extends T> RegistryEntry<U> register(ResourceKey<T> id, Supplier<U> create) {
if (!id.identifier().getNamespace().equals(ComputerCraftAPI.MOD_ID)) {
throw new IllegalArgumentException("Can only register items for ComputerCraft");
}
var entry = new RegistryEntryImpl<>(id.identifier(), create);
entries.add(entry);
return entry;
}
@@ -292,6 +310,19 @@ public class PlatformHelperImpl implements PlatformHelper {
}
}
private static final class PermissionRegistryImpl extends PermissionRegistry {
@Override
public Predicate<CommandSourceStack> registerCommand(String command, UserLevel fallback) {
checkNotFrozen();
var node = PermissionNode.of(Identifier.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "command." + command));
return source -> {
var result = source.checkPermission(node);
return result == null ? fallback.test(source) : result;
};
}
}
private record WrappedMenuProvider<T extends ContainerData>(
Component title, MenuConstructor menu, T data
) implements ExtendedMenuProvider<T> {
@@ -2,7 +2,7 @@
"required": true,
"package": "dan200.computercraft.mixin",
"minVersion": "0.8",
"compatibilityLevel": "JAVA_21",
"compatibilityLevel": "JAVA_25",
"injectors": {
"defaultRequire": 1
},
@@ -47,8 +47,8 @@
],
"depends": {
"fabricloader": ">=0.19.3",
"fabric-api": ">=0.146.1",
"minecraft": "~26.1.2"
"fabric-api": ">=0.152.0",
"minecraft": ">=26.2 <26.3"
},
"accessWidener": "computercraft.accesswidener"
}
@@ -2,7 +2,7 @@
"required": true,
"package": "dan200.computercraft.mixin.gametest",
"minVersion": "0.8",
"compatibilityLevel": "JAVA_21",
"compatibilityLevel": "JAVA_25",
"injectors": {
"defaultRequire": 1
},
@@ -49,8 +49,8 @@ public final class ForgeClientHooks {
var renderer = ClientHooks.drawHighlight(event.getCamera(), event.getHitResult());
if (renderer == null) return;
event.addCustomRenderer((state, buffers, transform, translucentPass, renderState) -> {
BlockOutlineRenderer.render(transform, buffers, renderer);
event.addCustomRenderer((state, buffers, transform, renderState) -> {
BlockOutlineRenderer.render(transform, buffers, renderer, event.getCamera(), event.getHitResult());
return true;
});
}
@@ -14,6 +14,9 @@ import dan200.computercraft.client.render.ExtendedItemFrameRenderState;
import dan200.computercraft.shared.network.NetworkMessages;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.entity.ItemFrameRenderer;
import net.minecraft.client.renderer.feature.FeatureRenderer;
import net.minecraft.client.renderer.feature.FeatureRendererType;
import net.minecraft.client.renderer.feature.submit.SubmitNode;
import net.minecraft.client.resources.model.ModelBaker;
import net.minecraft.client.resources.model.ModelDebugName;
import net.minecraft.client.resources.model.ResolvableModel;
@@ -32,6 +35,7 @@ import java.util.ArrayDeque;
import java.util.Queue;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Supplier;
/**
@@ -123,6 +127,16 @@ public final class ForgeClientRegistry {
ClientRegistry.registerDebugScreenEntries(event::register);
}
@SubscribeEvent
public static void registerFeatureRenderers(RegisterFeatureRenderersEvent event) {
ClientRegistry.registerFeatureRenderers(new ClientRegistry.RegisterFeatureRenderer() {
@Override
public <T extends SubmitNode> void register(FeatureRendererType<T> type, Supplier<FeatureRenderer<T>> renderer) {
event.register(type, renderer.get());
}
});
}
@SubscribeEvent
public static void setupClient(FMLClientSetupEvent event) {
ClientRegistry.register();
@@ -5,8 +5,15 @@
package dan200.computercraft.client.platform;
import com.google.auto.service.AutoService;
import com.mojang.blaze3d.vertex.PoseStack;
import dan200.computercraft.client.render.monitor.MonitorBlockEntityRenderer;
import dan200.computercraft.client.render.monitor.MonitorRenderState;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.shared.peripheral.monitor.ClientMonitor;
import net.minecraft.client.renderer.OrderedSubmitNodeCollector;
import net.minecraft.client.resources.model.ModelDebugName;
import net.neoforged.neoforge.client.model.standalone.StandaloneModelKey;
import net.neoforged.neoforge.client.submit.RenderPhaseKeys;
@AutoService(ClientPlatformHelper.class)
public class ClientPlatformHelperImpl implements ClientPlatformHelper {
@@ -14,4 +21,12 @@ public class ClientPlatformHelperImpl implements ClientPlatformHelper {
public <T> ModelKey<T> createModelKey(ModelDebugName name) {
return new ForgeModelKey<>(new StandaloneModelKey<T>(name));
}
@Override
public void submitMonitor(OrderedSubmitNodeCollector collector, PoseStack poseStack, ClientMonitor monitor, Terminal terminal, float xMargin, float yMargin) {
var renderState = monitor.getRenderState(MonitorRenderState::new);
collector.submitSpecial(
RenderPhaseKeys.SOLID, new MonitorBlockEntityRenderer.MonitorSubmit(poseStack.last().copy(), monitor, terminal, renderState, xMargin, yMargin)
);
}
}
@@ -14,8 +14,11 @@ import net.minecraft.core.RegistrySetBuilder;
import net.minecraft.data.DataGenerator;
import net.minecraft.data.DataProvider;
import net.minecraft.data.PackOutput;
import net.minecraft.data.tags.BlockItemTagAppender;
import net.minecraft.data.tags.TagsProvider;
import net.minecraft.references.BlockItemId;
import net.minecraft.resources.Identifier;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.item.Item;
import net.minecraft.world.level.block.Block;
import net.neoforged.bus.api.SubscribeEvent;
@@ -63,7 +66,12 @@ public class ForgeDataProviders {
return add(out -> new BlockTagsProvider(out, registries, ComputerCraftAPI.MOD_ID) {
@Override
protected void addTags(HolderLookup.Provider registries) {
tags.accept(this::tag);
tags.accept(tag -> new BlockItemTagAppender<>(tag(tag)) {
@Override
protected ResourceKey<Block> convertElement(BlockItemId blockItemId) {
return blockItemId.block();
}
});
}
});
}
@@ -73,7 +81,12 @@ public class ForgeDataProviders {
return add(out -> new ItemTagsProvider(out, registries, ComputerCraftAPI.MOD_ID) {
@Override
protected void addTags(HolderLookup.Provider registries) {
tags.accept(this::tag);
tags.accept(tag -> new BlockItemTagAppender<Item>(tag(tag)) {
@Override
protected ResourceKey<Item> convertElement(BlockItemId blockItemId) {
return blockItemId.item();
}
});
}
});
}
@@ -4,7 +4,7 @@ import com.example.examplemod.peripheral.BrewingStandPeripheral;
import dan200.computercraft.api.peripheral.PeripheralCapability;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import net.minecraft.resources.Identifier;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.entity.BlockEntityTypes;
import net.neoforged.bus.api.IEventBus;
import net.neoforged.fml.common.Mod;
import net.neoforged.fml.event.lifecycle.FMLCommonSetupEvent;
@@ -34,7 +34,7 @@ public class ForgeExampleMod {
// @start region=peripherals
modBus.addListener((RegisterCapabilitiesEvent event) -> {
event.registerBlockEntity(PeripheralCapability.get(), BlockEntityType.BREWING_STAND, (b, d) -> new BrewingStandPeripheral(b));
event.registerBlockEntity(PeripheralCapability.get(), BlockEntityTypes.BREWING_STAND, (b, d) -> new BrewingStandPeripheral(b));
});
// @end region=peripherals
}
@@ -1,65 +0,0 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.shared.integration;
import com.google.auto.service.AutoService;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.shared.command.UserLevel;
import net.minecraft.commands.CommandSourceStack;
import net.neoforged.neoforge.common.NeoForge;
import net.neoforged.neoforge.server.permission.PermissionAPI;
import net.neoforged.neoforge.server.permission.events.PermissionGatherEvent;
import net.neoforged.neoforge.server.permission.nodes.PermissionNode;
import net.neoforged.neoforge.server.permission.nodes.PermissionType;
import net.neoforged.neoforge.server.permission.nodes.PermissionTypes;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.function.Predicate;
/**
* An implementation of {@link PermissionRegistry} using Forge's {@link PermissionAPI}.
*/
public final class ForgePermissionRegistry extends PermissionRegistry {
private final List<PermissionNode<?>> nodes = new ArrayList<>();
private ForgePermissionRegistry() {
}
private <T> PermissionNode<T> registerNode(String nodeName, PermissionType<T> type, PermissionNode.PermissionResolver<T> defaultResolver) {
checkNotFrozen();
var node = new PermissionNode<>(ComputerCraftAPI.MOD_ID, nodeName, type, defaultResolver);
nodes.add(node);
return node;
}
@Override
public Predicate<CommandSourceStack> registerCommand(String command, UserLevel fallback) {
var node = registerNode(
"command." + command, PermissionTypes.BOOLEAN,
(player, uuid, context) -> player != null && fallback.test(player)
);
return source -> {
var player = source.getPlayer();
return player == null ? fallback.test(source) : PermissionAPI.getPermission(player, node);
};
}
@Override
public void register() {
super.register();
NeoForge.EVENT_BUS.addListener((PermissionGatherEvent.Nodes event) -> event.addNodes(nodes));
}
@AutoService(PermissionRegistry.Provider.class)
public static final class Provider implements PermissionRegistry.Provider {
@Override
public Optional<PermissionRegistry> get() {
return Optional.of(new ForgePermissionRegistry());
}
}
}
@@ -16,9 +16,11 @@ import dan200.computercraft.api.network.wired.WiredElementCapability;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.peripheral.PeripheralCapability;
import dan200.computercraft.impl.Peripherals;
import dan200.computercraft.shared.command.UserLevel;
import dan200.computercraft.shared.config.ConfigFile;
import dan200.computercraft.shared.network.container.ContainerData;
import dan200.computercraft.shared.util.InventoryUtil;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.synchronization.ArgumentTypeInfo;
import net.minecraft.commands.synchronization.ArgumentTypeInfos;
import net.minecraft.core.BlockPos;
@@ -59,19 +61,27 @@ import net.neoforged.neoforge.capabilities.BlockCapabilityCache;
import net.neoforged.neoforge.capabilities.Capabilities;
import net.neoforged.neoforge.common.CommonHooks;
import net.neoforged.neoforge.common.ItemAbilities;
import net.neoforged.neoforge.common.NeoForge;
import net.neoforged.neoforge.common.Tags;
import net.neoforged.neoforge.common.extensions.IMenuTypeExtension;
import net.neoforged.neoforge.event.EventHooks;
import net.neoforged.neoforge.registries.DeferredHolder;
import net.neoforged.neoforge.registries.DeferredRegister;
import net.neoforged.neoforge.server.permission.PermissionAPI;
import net.neoforged.neoforge.server.permission.events.PermissionGatherEvent;
import net.neoforged.neoforge.server.permission.nodes.PermissionNode;
import net.neoforged.neoforge.server.permission.nodes.PermissionType;
import net.neoforged.neoforge.server.permission.nodes.PermissionTypes;
import net.neoforged.neoforge.transfer.item.VanillaContainerWrapper;
import org.jspecify.annotations.Nullable;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.function.Supplier;
@AutoService(PlatformHelper.class)
@@ -91,6 +101,11 @@ public class PlatformHelperImpl implements PlatformHelper {
return new RegistrationHelperImpl<>(DeferredRegister.create(registry, ComputerCraftAPI.MOD_ID));
}
@Override
public PermissionRegistry createPermissionRegistry() {
return new PermissionRegistryImpl();
}
@Override
public <A extends ArgumentType<?>, T extends ArgumentTypeInfo.Template<A>, I extends ArgumentTypeInfo<A, T>> I registerArgumentTypeInfo(Class<A> klass, I info) {
return ArgumentTypeInfos.registerByClass(klass, info);
@@ -255,6 +270,15 @@ public class PlatformHelperImpl implements PlatformHelper {
return new RegistryEntryImpl<>(registry().register(name, create));
}
@Override
public <U extends R> RegistryEntry<U> register(ResourceKey<R> id, Supplier<U> create) {
if (!id.identifier().getNamespace().equals(ComputerCraftAPI.MOD_ID)) {
throw new IllegalArgumentException("Can only register items for ComputerCraft");
}
return register(id.identifier().getPath(), create);
}
@Override
public void register() {
registry().register(ComputerCraft.getEventBus());
@@ -321,4 +345,37 @@ public class PlatformHelperImpl implements PlatformHelper {
return Peripherals.getGenericPeripheral(cache.level(), cache.pos(), cache.context(), cache.level().getBlockEntity(cache.pos()));
}
}
/**
* An implementation of {@link PermissionRegistry} using Forge's {@link PermissionAPI}.
*/
private static final class PermissionRegistryImpl extends PermissionRegistry {
private final List<PermissionNode<?>> nodes = new ArrayList<>();
private <T> PermissionNode<T> registerNode(String nodeName, PermissionType<T> type, PermissionNode.PermissionResolver<T> defaultResolver) {
checkNotFrozen();
var node = new PermissionNode<>(ComputerCraftAPI.MOD_ID, nodeName, type, defaultResolver);
nodes.add(node);
return node;
}
@Override
public Predicate<CommandSourceStack> registerCommand(String command, UserLevel fallback) {
var node = registerNode(
"command." + command, PermissionTypes.BOOLEAN,
(player, uuid, context) -> player != null && fallback.test(player)
);
return source -> {
var player = source.getPlayer();
return player == null ? fallback.test(source) : PermissionAPI.getPermission(player, node);
};
}
@Override
public void register() {
super.register();
NeoForge.EVENT_BUS.addListener((PermissionGatherEvent.Nodes event) -> event.addNodes(nodes));
}
}
}
@@ -6,6 +6,9 @@
public net.minecraft.client.gui.components.ChatComponent allMessages
public net.minecraft.client.gui.components.ChatComponent addMessage(Lnet/minecraft/network/chat/Component;Lnet/minecraft/network/chat/MessageSignature;Lnet/minecraft/client/multiplayer/chat/GuiMessageSource;Lnet/minecraft/client/multiplayer/chat/GuiMessageTag;)V
# NoTermComputerScreen
public net.minecraft.client.gui.Gui screen
# ItemPocketRenderer/ItemPrintoutRenderer
public net.minecraft.client.renderer.ItemInHandRenderer calculateMapTilt(F)F
public net.minecraft.client.renderer.ItemInHandRenderer renderMapHand(Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/SubmitNodeCollector;ILnet/minecraft/world/entity/HumanoidArm;)V
@@ -26,7 +26,7 @@ CC: Tweaked is a fork of ComputerCraft, adding programmable computers, turtles a
[[dependencies.computercraft]]
modId="neoforge"
type="required"
versionRange="[${neoVersion},26.2)"
versionRange="[${neoVersion},26.3)"
ordering="NONE"
side="BOTH"