mirror of
				https://github.com/SquidDev-CC/CC-Tweaked
				synced 2025-10-31 13:42:59 +00:00 
			
		
		
		
	Update to 1.20.5 (#1793)
- Switch most network code to use StreamCodec - Turtle/pocket computer upgrades now use DataComponentPatch instead of raw NBT.
This commit is contained in:
		| @@ -21,13 +21,11 @@ import dan200.computercraft.client.turtle.TurtleUpgradeModellers; | ||||
| import dan200.computercraft.core.util.Colour; | ||||
| import dan200.computercraft.shared.ModRegistry; | ||||
| import dan200.computercraft.shared.command.CommandComputerCraft; | ||||
| import dan200.computercraft.shared.common.IColouredItem; | ||||
| import dan200.computercraft.shared.computer.core.ComputerState; | ||||
| import dan200.computercraft.shared.computer.core.ServerContext; | ||||
| import dan200.computercraft.shared.computer.inventory.AbstractComputerMenu; | ||||
| import dan200.computercraft.shared.computer.inventory.ViewComputerMenu; | ||||
| import dan200.computercraft.shared.media.items.DiskItem; | ||||
| import dan200.computercraft.shared.media.items.TreasureDiskItem; | ||||
| import net.minecraft.Util; | ||||
| import net.minecraft.client.Minecraft; | ||||
| import net.minecraft.client.color.item.ItemColor; | ||||
| @@ -44,11 +42,13 @@ import net.minecraft.network.chat.Component; | ||||
| import net.minecraft.resources.ResourceLocation; | ||||
| import net.minecraft.server.packs.resources.PreparableReloadListener; | ||||
| import net.minecraft.server.packs.resources.ResourceProvider; | ||||
| import net.minecraft.util.FastColor; | ||||
| import net.minecraft.world.entity.LivingEntity; | ||||
| import net.minecraft.world.inventory.AbstractContainerMenu; | ||||
| import net.minecraft.world.inventory.MenuType; | ||||
| import net.minecraft.world.item.Item; | ||||
| import net.minecraft.world.item.ItemStack; | ||||
| import net.minecraft.world.item.component.DyedItemColor; | ||||
| import net.minecraft.world.level.ItemLike; | ||||
| 
 | ||||
| import javax.annotation.Nullable; | ||||
| @@ -94,7 +94,7 @@ public final class ClientRegistry { | ||||
|             ModRegistry.Items.POCKET_COMPUTER_NORMAL, ModRegistry.Items.POCKET_COMPUTER_ADVANCED | ||||
|         ); | ||||
|         registerItemProperty(itemProperties, "coloured", | ||||
|             (stack, world, player, random) -> IColouredItem.getColourBasic(stack) != -1 ? 1 : 0, | ||||
|             (stack, world, player, random) -> DyedItemColor.getOrDefault(stack, -1) != -1 ? 1 : 0, | ||||
|             ModRegistry.Items.POCKET_COMPUTER_NORMAL, ModRegistry.Items.POCKET_COMPUTER_ADVANCED | ||||
|         ); | ||||
|     } | ||||
| @@ -162,12 +162,12 @@ public final class ClientRegistry { | ||||
| 
 | ||||
|     public static void registerItemColours(BiConsumer<ItemColor, ItemLike> register) { | ||||
|         register.accept( | ||||
|             (stack, layer) -> layer == 1 ? ((DiskItem) stack.getItem()).getColour(stack) : 0xFFFFFF, | ||||
|             (stack, layer) -> layer == 1 ? DiskItem.getColour(stack) : -1, | ||||
|             ModRegistry.Items.DISK.get() | ||||
|         ); | ||||
| 
 | ||||
|         register.accept( | ||||
|             (stack, layer) -> layer == 1 ? TreasureDiskItem.getColour(stack) : 0xFFFFFF, | ||||
|             (stack, layer) -> layer == 1 ? DyedItemColor.getOrDefault(stack, Colour.BLUE.getARGB()) : -1, | ||||
|             ModRegistry.Items.TREASURE_DISK.get() | ||||
|         ); | ||||
| 
 | ||||
| @@ -180,17 +180,17 @@ public final class ClientRegistry { | ||||
| 
 | ||||
|     private static int getPocketColour(ItemStack stack, int layer) { | ||||
|         return switch (layer) { | ||||
|             default -> 0xFFFFFF; | ||||
|             case 1 -> IColouredItem.getColourBasic(stack); // Frame colour | ||||
|             default -> -1; | ||||
|             case 1 -> DyedItemColor.getOrDefault(stack, -1); // Frame colour | ||||
|             case 2 -> { // Light colour | ||||
|                 var computer = ClientPocketComputers.get(stack); | ||||
|                 yield computer == null || computer.getLightState() == -1 ? Colour.BLACK.getHex() : computer.getLightState(); | ||||
|                 yield computer == null || computer.getLightState() == -1 ? Colour.BLACK.getARGB() : FastColor.ARGB32.opaque(computer.getLightState()); | ||||
|             } | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     private static int getTurtleColour(ItemStack stack, int layer) { | ||||
|         return layer == 0 ? ((IColouredItem) stack.getItem()).getColour(stack) : 0xFFFFFF; | ||||
|         return layer == 0 ? DyedItemColor.getOrDefault(stack, -1) : -1; | ||||
|     } | ||||
| 
 | ||||
|     public static void registerShaders(ResourceProvider resources, BiConsumer<ShaderInstance, Consumer<ShaderInstance>> load) throws IOException { | ||||
|   | ||||
| @@ -75,7 +75,7 @@ public class ClientTableFormatter implements TableFormatter { | ||||
| 
 | ||||
|         var tag = createTag(table.getId()); | ||||
|         if (chat.allMessages.removeIf(guiMessage -> guiMessage.tag() != null && Objects.equals(guiMessage.tag().logTag(), tag.logTag()))) { | ||||
|             chat.refreshTrimmedMessage(); | ||||
|             chat.rescaleChat(); | ||||
|         } | ||||
| 
 | ||||
|         TableFormatter.super.display(table); | ||||
|   | ||||
| @@ -6,7 +6,9 @@ package dan200.computercraft.client.gui; | ||||
| 
 | ||||
| import com.mojang.blaze3d.vertex.Tesselator; | ||||
| import dan200.computercraft.core.terminal.TextBuffer; | ||||
| import dan200.computercraft.shared.ModRegistry; | ||||
| import dan200.computercraft.shared.common.HeldItemMenu; | ||||
| import dan200.computercraft.shared.media.items.PrintoutData; | ||||
| import dan200.computercraft.shared.media.items.PrintoutItem; | ||||
| import net.minecraft.client.gui.GuiGraphics; | ||||
| import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; | ||||
| @@ -35,16 +37,17 @@ public class PrintoutScreen extends AbstractContainerScreen<HeldItemMenu> { | ||||
| 
 | ||||
|         imageHeight = Y_SIZE; | ||||
| 
 | ||||
|         var text = PrintoutItem.getText(container.getStack()); | ||||
|         this.text = new TextBuffer[text.length]; | ||||
|         for (var i = 0; i < this.text.length; i++) this.text[i] = new TextBuffer(text[i]); | ||||
| 
 | ||||
|         var colours = PrintoutItem.getColours(container.getStack()); | ||||
|         this.colours = new TextBuffer[colours.length]; | ||||
|         for (var i = 0; i < this.colours.length; i++) this.colours[i] = new TextBuffer(colours[i]); | ||||
|         var printout = container.getStack().getOrDefault(ModRegistry.DataComponents.PRINTOUT.get(), PrintoutData.EMPTY); | ||||
|         this.text = new TextBuffer[printout.lines().size()]; | ||||
|         this.colours = new TextBuffer[printout.lines().size()]; | ||||
|         for (var i = 0; i < this.text.length; i++) { | ||||
|             var line = printout.lines().get(i); | ||||
|             this.text[i] = new TextBuffer(line.text()); | ||||
|             this.colours[i] = new TextBuffer(line.foreground()); | ||||
|         } | ||||
| 
 | ||||
|         page = 0; | ||||
|         pages = Math.max(this.text.length / PrintoutItem.LINES_PER_PAGE, 1); | ||||
|         pages = Math.max(this.text.length / PrintoutData.LINES_PER_PAGE, 1); | ||||
|         book = ((PrintoutItem) container.getStack().getItem()).getType() == PrintoutItem.Type.BOOK; | ||||
|     } | ||||
| 
 | ||||
| @@ -89,7 +92,7 @@ public class PrintoutScreen extends AbstractContainerScreen<HeldItemMenu> { | ||||
| 
 | ||||
|         var renderer = MultiBufferSource.immediate(Tesselator.getInstance().getBuilder()); | ||||
|         drawBorder(graphics.pose(), renderer, leftPos, topPos, 0, page, pages, book, FULL_BRIGHT_LIGHTMAP); | ||||
|         drawText(graphics.pose(), renderer, leftPos + X_TEXT_MARGIN, topPos + Y_TEXT_MARGIN, PrintoutItem.LINES_PER_PAGE * page, FULL_BRIGHT_LIGHTMAP, text, colours); | ||||
|         drawText(graphics.pose(), renderer, leftPos + X_TEXT_MARGIN, topPos + Y_TEXT_MARGIN, PrintoutData.LINES_PER_PAGE * page, FULL_BRIGHT_LIGHTMAP, text, colours); | ||||
|         renderer.endBatch(); | ||||
| 
 | ||||
|         graphics.pose().popPose(); | ||||
|   | ||||
| @@ -27,13 +27,11 @@ public class EMIComputerCraft implements EmiPlugin { | ||||
|         registry.setDefaultComparison(ModRegistry.Items.POCKET_COMPUTER_ADVANCED.get(), pocketComparison); | ||||
|     } | ||||
| 
 | ||||
|     private static final Comparison turtleComparison = compareStacks((left, right) -> | ||||
|         left.getItem() instanceof TurtleItem turtle | ||||
|             && turtle.getUpgrade(left, TurtleSide.LEFT) == turtle.getUpgrade(right, TurtleSide.LEFT) | ||||
|             && turtle.getUpgrade(left, TurtleSide.RIGHT) == turtle.getUpgrade(right, TurtleSide.RIGHT)); | ||||
|     private static final Comparison turtleComparison = compareStacks((left, right) | ||||
|         -> TurtleItem.getUpgrade(left, TurtleSide.LEFT) == TurtleItem.getUpgrade(right, TurtleSide.LEFT) | ||||
|         && TurtleItem.getUpgrade(left, TurtleSide.RIGHT) == TurtleItem.getUpgrade(right, TurtleSide.RIGHT)); | ||||
| 
 | ||||
|     private static final Comparison pocketComparison = compareStacks((left, right) -> | ||||
|         left.getItem() instanceof PocketComputerItem && PocketComputerItem.getUpgrade(left) == PocketComputerItem.getUpgrade(right)); | ||||
|     private static final Comparison pocketComparison = compareStacks((left, right) -> PocketComputerItem.getUpgrade(left) == PocketComputerItem.getUpgrade(right)); | ||||
| 
 | ||||
|     private static Comparison compareStacks(BiPredicate<ItemStack, ItemStack> test) { | ||||
|         return Comparison.of((left, right) -> { | ||||
|   | ||||
| @@ -15,9 +15,11 @@ import dan200.computercraft.client.platform.ClientPlatformHelper; | ||||
| import dan200.computercraft.client.render.TurtleBlockEntityRenderer; | ||||
| import dan200.computercraft.client.turtle.TurtleUpgradeModellers; | ||||
| import dan200.computercraft.shared.turtle.items.TurtleItem; | ||||
| import dan200.computercraft.shared.util.DataComponentUtil; | ||||
| import dan200.computercraft.shared.util.Holiday; | ||||
| import net.minecraft.client.Minecraft; | ||||
| import net.minecraft.client.resources.model.BakedModel; | ||||
| import net.minecraft.core.component.DataComponents; | ||||
| import net.minecraft.resources.ResourceLocation; | ||||
| import net.minecraft.world.item.ItemStack; | ||||
| 
 | ||||
| @@ -54,13 +56,6 @@ public final class TurtleModelParts<T> { | ||||
|         boolean christmas, | ||||
|         boolean flip | ||||
|     ) { | ||||
|         Combination copy() { | ||||
|             if (leftUpgrade == null && rightUpgrade == null) return this; | ||||
|             return new Combination( | ||||
|                 colour, UpgradeData.copyOf(leftUpgrade), UpgradeData.copyOf(rightUpgrade), | ||||
|                 overlay, christmas, flip | ||||
|             ); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private final BakedModel familyModel; | ||||
| @@ -96,31 +91,18 @@ public final class TurtleModelParts<T> { | ||||
| 
 | ||||
|     public T getModel(ItemStack stack) { | ||||
|         var combination = getCombination(stack); | ||||
|         var existing = modelCache.get(combination); | ||||
|         if (existing != null) return existing; | ||||
| 
 | ||||
|         // Take a defensive copy of the upgrade data, and add it to the cache. | ||||
|         var newCombination = combination.copy(); | ||||
|         var newModel = buildModel.apply(newCombination); | ||||
|         modelCache.put(newCombination, newModel); | ||||
|         return newModel; | ||||
|         return modelCache.computeIfAbsent(combination, buildModel); | ||||
|     } | ||||
| 
 | ||||
|     private Combination getCombination(ItemStack stack) { | ||||
|         var christmas = Holiday.getCurrent() == Holiday.CHRISTMAS; | ||||
| 
 | ||||
|         if (!(stack.getItem() instanceof TurtleItem turtle)) { | ||||
|             return new Combination(false, null, null, null, christmas, false); | ||||
|         } | ||||
| 
 | ||||
|         var colour = turtle.getColour(stack); | ||||
|         var leftUpgrade = turtle.getUpgradeWithData(stack, TurtleSide.LEFT); | ||||
|         var rightUpgrade = turtle.getUpgradeWithData(stack, TurtleSide.RIGHT); | ||||
|         var overlay = turtle.getOverlay(stack); | ||||
|         var label = turtle.getLabel(stack); | ||||
|         var leftUpgrade = TurtleItem.getUpgradeWithData(stack, TurtleSide.LEFT); | ||||
|         var rightUpgrade = TurtleItem.getUpgradeWithData(stack, TurtleSide.RIGHT); | ||||
|         var overlay = TurtleItem.getOverlay(stack); | ||||
|         var label = DataComponentUtil.getCustomName(stack); | ||||
|         var flip = label != null && (label.equals("Dinnerbone") || label.equals("Grumm")); | ||||
| 
 | ||||
|         return new Combination(colour != -1, leftUpgrade, rightUpgrade, overlay, christmas, flip); | ||||
|         return new Combination(stack.has(DataComponents.DYED_COLOR), leftUpgrade, rightUpgrade, overlay, christmas, flip); | ||||
|     } | ||||
| 
 | ||||
|     private List<BakedModel> buildModel(Combination combo) { | ||||
|   | ||||
| @@ -4,10 +4,10 @@ | ||||
| 
 | ||||
| package dan200.computercraft.client.network; | ||||
| 
 | ||||
| import dan200.computercraft.client.platform.ClientPlatformHelper; | ||||
| import dan200.computercraft.shared.network.NetworkMessage; | ||||
| import dan200.computercraft.shared.network.server.ServerNetworkContext; | ||||
| import net.minecraft.client.Minecraft; | ||||
| import net.minecraft.network.protocol.common.ServerboundCustomPayloadPacket; | ||||
| 
 | ||||
| /** | ||||
|  * Methods for sending packets from clients to the server. | ||||
| @@ -23,6 +23,6 @@ public final class ClientNetworking { | ||||
|      */ | ||||
|     public static void sendToServer(NetworkMessage<ServerNetworkContext> message) { | ||||
|         var connection = Minecraft.getInstance().getConnection(); | ||||
|         if (connection != null) connection.send(ClientPlatformHelper.get().createPacket(message)); | ||||
|         if (connection != null) connection.send(new ServerboundCustomPayloadPacket(message)); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -5,13 +5,9 @@ | ||||
| package dan200.computercraft.client.platform; | ||||
| 
 | ||||
| import com.mojang.blaze3d.vertex.PoseStack; | ||||
| import dan200.computercraft.shared.network.NetworkMessage; | ||||
| import dan200.computercraft.shared.network.server.ServerNetworkContext; | ||||
| import net.minecraft.client.renderer.MultiBufferSource; | ||||
| import net.minecraft.client.resources.model.BakedModel; | ||||
| import net.minecraft.core.BlockPos; | ||||
| import net.minecraft.network.protocol.Packet; | ||||
| import net.minecraft.network.protocol.common.ServerCommonPacketListener; | ||||
| import net.minecraft.sounds.SoundEvent; | ||||
| 
 | ||||
| import javax.annotation.Nullable; | ||||
| @@ -21,14 +17,6 @@ public interface ClientPlatformHelper extends dan200.computercraft.impl.client.C | ||||
|         return (ClientPlatformHelper) dan200.computercraft.impl.client.ClientPlatformHelper.get(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Convert a serverbound {@link NetworkMessage} to a Minecraft {@link Packet}. | ||||
|      * | ||||
|      * @param message The messsge to convert. | ||||
|      * @return The converted message. | ||||
|      */ | ||||
|     Packet<ServerCommonPacketListener> createPacket(NetworkMessage<ServerNetworkContext> message); | ||||
| 
 | ||||
|     /** | ||||
|      * Render a {@link BakedModel}, using any loader-specific hooks. | ||||
|      * | ||||
|   | ||||
| @@ -4,11 +4,11 @@ | ||||
| 
 | ||||
| package dan200.computercraft.client.pocket; | ||||
| 
 | ||||
| import dan200.computercraft.shared.ModRegistry; | ||||
| import dan200.computercraft.shared.computer.core.ComputerState; | ||||
| import dan200.computercraft.shared.computer.core.ServerComputer; | ||||
| import dan200.computercraft.shared.computer.terminal.TerminalState; | ||||
| import dan200.computercraft.shared.network.client.PocketComputerDataMessage; | ||||
| import dan200.computercraft.shared.pocket.items.PocketComputerItem; | ||||
| import net.minecraft.world.item.ItemStack; | ||||
| 
 | ||||
| import javax.annotation.Nullable; | ||||
| @@ -53,7 +53,7 @@ public final class ClientPocketComputers { | ||||
|     } | ||||
| 
 | ||||
|     public static @Nullable PocketComputerData get(ItemStack stack) { | ||||
|         var id = PocketComputerItem.getInstanceID(stack); | ||||
|         return id == null ? null : instances.get(id); | ||||
|         var id = stack.get(ModRegistry.DataComponents.COMPUTER.get()); | ||||
|         return id == null ? null : instances.get(id.instance()); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -51,7 +51,6 @@ public final class CableHighlightRenderer { | ||||
| 
 | ||||
|         var buffer = bufferSource.getBuffer(RenderType.lines()); | ||||
|         var matrix4f = transform.last().pose(); | ||||
|         var normal = transform.last().normal(); | ||||
|         // TODO: Can we just accesstransformer out LevelRenderer.renderShape? | ||||
|         shape.forAllEdges((x1, y1, z1, x2, y2, z2) -> { | ||||
|             var xDelta = (float) (x2 - x1); | ||||
| @@ -65,12 +64,12 @@ public final class CableHighlightRenderer { | ||||
|             buffer | ||||
|                 .vertex(matrix4f, (float) (x1 + xOffset), (float) (y1 + yOffset), (float) (z1 + zOffset)) | ||||
|                 .color(0, 0, 0, 0.4f) | ||||
|                 .normal(normal, xDelta, yDelta, zDelta) | ||||
|                 .normal(transform.last(), xDelta, yDelta, zDelta) | ||||
|                 .endVertex(); | ||||
|             buffer | ||||
|                 .vertex(matrix4f, (float) (x2 + xOffset), (float) (y2 + yOffset), (float) (z2 + zOffset)) | ||||
|                 .color(0, 0, 0, 0.4f) | ||||
|                 .normal(normal, xDelta, yDelta, zDelta) | ||||
|                 .normal(transform.last(), xDelta, yDelta, zDelta) | ||||
|                 .endVertex(); | ||||
|         }); | ||||
| 
 | ||||
|   | ||||
| @@ -15,6 +15,7 @@ import dan200.computercraft.shared.config.Config; | ||||
| import dan200.computercraft.shared.pocket.items.PocketComputerItem; | ||||
| import net.minecraft.client.renderer.MultiBufferSource; | ||||
| import net.minecraft.world.item.ItemStack; | ||||
| import net.minecraft.world.item.component.DyedItemColor; | ||||
| import org.joml.Matrix4f; | ||||
| 
 | ||||
| import static dan200.computercraft.client.render.ComputerBorderRenderer.*; | ||||
| @@ -61,7 +62,7 @@ public final class PocketItemRenderer extends ItemMapLikeRenderer { | ||||
|         // Render the main frame | ||||
|         var item = (PocketComputerItem) stack.getItem(); | ||||
|         var family = item.getFamily(); | ||||
|         var frameColour = item.getColour(stack); | ||||
|         var frameColour = DyedItemColor.getOrDefault(stack, -1); | ||||
| 
 | ||||
|         var matrix = transform.last().pose(); | ||||
|         renderFrame(matrix, bufferSource, family, frameColour, light, width, height); | ||||
|   | ||||
| @@ -6,6 +6,8 @@ package dan200.computercraft.client.render; | ||||
| 
 | ||||
| import com.mojang.blaze3d.vertex.PoseStack; | ||||
| import com.mojang.math.Axis; | ||||
| import dan200.computercraft.shared.ModRegistry; | ||||
| import dan200.computercraft.shared.media.items.PrintoutData; | ||||
| import dan200.computercraft.shared.media.items.PrintoutItem; | ||||
| import net.minecraft.client.renderer.MultiBufferSource; | ||||
| import net.minecraft.world.entity.EntityType; | ||||
| @@ -15,8 +17,8 @@ import net.minecraft.world.item.ItemStack; | ||||
| import static dan200.computercraft.client.render.PrintoutRenderer.*; | ||||
| import static dan200.computercraft.client.render.text.FixedWidthFontRenderer.FONT_HEIGHT; | ||||
| import static dan200.computercraft.client.render.text.FixedWidthFontRenderer.FONT_WIDTH; | ||||
| import static dan200.computercraft.shared.media.items.PrintoutItem.LINES_PER_PAGE; | ||||
| import static dan200.computercraft.shared.media.items.PrintoutItem.LINE_MAX_LENGTH; | ||||
| import static dan200.computercraft.shared.media.items.PrintoutData.LINES_PER_PAGE; | ||||
| import static dan200.computercraft.shared.media.items.PrintoutData.LINE_LENGTH; | ||||
| 
 | ||||
| /** | ||||
|  * Emulates map and item-frame rendering for printouts. | ||||
| @@ -37,8 +39,6 @@ public final class PrintoutItemRenderer extends ItemMapLikeRenderer { | ||||
|     } | ||||
| 
 | ||||
|     public static void onRenderInFrame(PoseStack transform, MultiBufferSource render, ItemFrame frame, ItemStack stack, int packedLight) { | ||||
|         if (!(stack.getItem() instanceof PrintoutItem)) return; | ||||
| 
 | ||||
|         // Move a little bit forward to ensure we're not clipping with the frame | ||||
|         transform.translate(0.0f, 0.0f, -0.001f); | ||||
|         transform.mulPose(Axis.ZP.rotationDegrees(180f)); | ||||
| @@ -50,10 +50,12 @@ public final class PrintoutItemRenderer extends ItemMapLikeRenderer { | ||||
|     } | ||||
| 
 | ||||
|     private static void drawPrintout(PoseStack transform, MultiBufferSource render, ItemStack stack, int light) { | ||||
|         var pages = PrintoutItem.getPageCount(stack); | ||||
|         var pageData = stack.getOrDefault(ModRegistry.DataComponents.PRINTOUT.get(), PrintoutData.EMPTY); | ||||
| 
 | ||||
|         var pages = pageData.pages(); | ||||
|         var book = ((PrintoutItem) stack.getItem()).getType() == PrintoutItem.Type.BOOK; | ||||
| 
 | ||||
|         double width = LINE_MAX_LENGTH * FONT_WIDTH + X_TEXT_MARGIN * 2; | ||||
|         double width = LINE_LENGTH * FONT_WIDTH + X_TEXT_MARGIN * 2; | ||||
|         double height = LINES_PER_PAGE * FONT_HEIGHT + Y_TEXT_MARGIN * 2; | ||||
| 
 | ||||
|         // Non-books will be left aligned | ||||
| @@ -75,9 +77,6 @@ public final class PrintoutItemRenderer extends ItemMapLikeRenderer { | ||||
|         transform.translate((max - width) / 2.0, (max - height) / 2.0, 0.0); | ||||
| 
 | ||||
|         drawBorder(transform, render, 0, 0, -0.01f, 0, pages, book, light); | ||||
|         drawText( | ||||
|             transform, render, X_TEXT_MARGIN, Y_TEXT_MARGIN, 0, light, | ||||
|             PrintoutItem.getText(stack), PrintoutItem.getColours(stack) | ||||
|         ); | ||||
|         drawText(transform, render, X_TEXT_MARGIN, Y_TEXT_MARGIN, 0, light, pageData.lines()); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -9,11 +9,14 @@ 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 org.joml.Matrix4f; | ||||
| 
 | ||||
| import java.util.List; | ||||
| 
 | ||||
| import static dan200.computercraft.client.render.text.FixedWidthFontRenderer.FONT_HEIGHT; | ||||
| import static dan200.computercraft.shared.media.items.PrintoutItem.LINES_PER_PAGE; | ||||
| import static dan200.computercraft.shared.media.items.PrintoutData.LINES_PER_PAGE; | ||||
| 
 | ||||
| /** | ||||
|  * Renders printed pages or books, either for a GUI ({@link dan200.computercraft.client.gui.PrintoutScreen}) or | ||||
| @@ -69,13 +72,14 @@ public final class PrintoutRenderer { | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public static void drawText(PoseStack transform, MultiBufferSource bufferSource, int x, int y, int start, int light, String[] text, String[] colours) { | ||||
|     public static void drawText(PoseStack transform, MultiBufferSource bufferSource, int x, int y, int start, int light, List<PrintoutData.Line> lines) { | ||||
|         var buffer = bufferSource.getBuffer(RenderTypes.PRINTOUT_TEXT); | ||||
|         var emitter = FixedWidthFontRenderer.toVertexConsumer(transform, buffer); | ||||
|         for (var line = 0; line < LINES_PER_PAGE && line < text.length; line++) { | ||||
|         for (var line = 0; line < LINES_PER_PAGE && line < lines.size(); line++) { | ||||
|             var lineContents = lines.get(start + line); | ||||
|             FixedWidthFontRenderer.drawString(emitter, | ||||
|                 x, y + line * FONT_HEIGHT, | ||||
|                 new TextBuffer(text[start + line]), new TextBuffer(colours[start + line]), | ||||
|                 new TextBuffer(lineContents.text()), new TextBuffer(lineContents.foreground()), | ||||
|                 Palette.DEFAULT, light | ||||
|             ); | ||||
|         } | ||||
|   | ||||
| @@ -191,17 +191,23 @@ public class MonitorBlockEntityRenderer implements BlockEntityRenderer<MonitorBl | ||||
|                     }); | ||||
|                 } | ||||
| 
 | ||||
|                 // Our VBO doesn't transform its vertices with the provided pose stack, which means that the inverse view | ||||
|                 // rotation matrix gives entirely wrong numbers for fog distances. We just set it to the identity which | ||||
|                 // gives a good enough approximation. | ||||
|                 var oldInverseRotation = RenderSystem.getInverseViewRotationMatrix(); | ||||
|                 RenderSystem.setInverseViewRotationMatrix(IDENTITY_NORMAL); | ||||
|                 // 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 oldFogStart = RenderSystem.getShaderFogStart(); | ||||
|                 RenderSystem.setShaderFogStart(1e4f); | ||||
| 
 | ||||
|                 RenderTypes.TERMINAL.setupRenderState(); | ||||
| 
 | ||||
|                 // Compose the existing model view matrix with our transformation matrix. | ||||
|                 var modelView = new Matrix4f(RenderSystem.getModelViewMatrix()).mul(matrix); | ||||
| 
 | ||||
|                 // Render background geometry | ||||
|                 backgroundBuffer.bind(); | ||||
|                 backgroundBuffer.drawWithShader(matrix, RenderSystem.getProjectionMatrix(), RenderTypes.getTerminalShader()); | ||||
|                 backgroundBuffer.drawWithShader(modelView, RenderSystem.getProjectionMatrix(), RenderTypes.getTerminalShader()); | ||||
| 
 | ||||
|                 // Render foreground geometry with glPolygonOffset enabled. | ||||
|                 RenderSystem.polygonOffset(-1.0f, -10.0f); | ||||
| @@ -209,7 +215,7 @@ public class MonitorBlockEntityRenderer implements BlockEntityRenderer<MonitorBl | ||||
| 
 | ||||
|                 foregroundBuffer.bind(); | ||||
|                 foregroundBuffer.drawWithShader( | ||||
|                     matrix, RenderSystem.getProjectionMatrix(), RenderTypes.getTerminalShader(), | ||||
|                     modelView, RenderSystem.getProjectionMatrix(), RenderTypes.getTerminalShader(), | ||||
|                     // As mentioned in the above comment, render the extra cursor quad if it is visible this frame. Each | ||||
|                     // // quad has an index count of 6. | ||||
|                     FixedWidthFontRenderer.isCursorVisible(terminal) && FrameInfo.getGlobalCursorBlink() | ||||
| @@ -222,7 +228,7 @@ public class MonitorBlockEntityRenderer implements BlockEntityRenderer<MonitorBl | ||||
|                 RenderTypes.TERMINAL.clearRenderState(); | ||||
|                 VertexBuffer.unbind(); | ||||
| 
 | ||||
|                 RenderSystem.setInverseViewRotationMatrix(oldInverseRotation); | ||||
|                 RenderSystem.setShaderFogStart(oldFogStart); | ||||
|             } | ||||
|             case BEST -> throw new IllegalStateException("Impossible: Should never use BEST renderer"); | ||||
|         } | ||||
|   | ||||
| @@ -12,7 +12,6 @@ import net.minecraft.client.renderer.MultiBufferSource; | ||||
| import net.minecraft.client.renderer.RenderType; | ||||
| import net.minecraft.core.Direction; | ||||
| import net.minecraft.world.phys.BlockHitResult; | ||||
| import org.joml.Matrix3f; | ||||
| import org.joml.Matrix4f; | ||||
| 
 | ||||
| import java.util.EnumSet; | ||||
| @@ -53,7 +52,7 @@ public final class MonitorHighlightRenderer { | ||||
|         // I wish I could think of a better way to do this | ||||
|         var buffer = bufferSource.getBuffer(RenderType.lines()); | ||||
|         var transform = transformStack.last().pose(); | ||||
|         var normal = transformStack.last().normal(); | ||||
|         var normal = transformStack.last(); | ||||
|         if (faces.contains(NORTH) || faces.contains(WEST)) line(buffer, transform, normal, 0, 0, 0, UP); | ||||
|         if (faces.contains(SOUTH) || faces.contains(WEST)) line(buffer, transform, normal, 0, 0, 1, UP); | ||||
|         if (faces.contains(NORTH) || faces.contains(EAST)) line(buffer, transform, normal, 1, 0, 0, UP); | ||||
| @@ -71,7 +70,7 @@ public final class MonitorHighlightRenderer { | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     private static void line(VertexConsumer buffer, Matrix4f transform, Matrix3f normal, float x, float y, float z, Direction direction) { | ||||
|     private static void line(VertexConsumer buffer, Matrix4f transform, PoseStack.Pose normal, float x, float y, float z, Direction direction) { | ||||
|         buffer | ||||
|             .vertex(transform, x, y, z) | ||||
|             .color(0, 0, 0, 0.4f) | ||||
|   | ||||
| @@ -9,8 +9,9 @@ import dan200.computercraft.api.client.TransformedModel; | ||||
| import dan200.computercraft.api.client.turtle.TurtleUpgradeModeller; | ||||
| import dan200.computercraft.api.turtle.ITurtleAccess; | ||||
| import dan200.computercraft.api.turtle.TurtleSide; | ||||
| import dan200.computercraft.shared.ModRegistry; | ||||
| import dan200.computercraft.shared.turtle.upgrades.TurtleModem; | ||||
| import net.minecraft.nbt.CompoundTag; | ||||
| import net.minecraft.core.component.DataComponentPatch; | ||||
| import net.minecraft.resources.ResourceLocation; | ||||
| import org.jetbrains.annotations.Nullable; | ||||
| 
 | ||||
| @@ -41,12 +42,9 @@ public class TurtleModemModeller implements TurtleUpgradeModeller<TurtleModem> { | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public TransformedModel getModel(TurtleModem upgrade, @Nullable ITurtleAccess turtle, TurtleSide side, CompoundTag data) { | ||||
|         var active = false; | ||||
|         if (turtle != null) { | ||||
|             var turtleNBT = turtle.getUpgradeNBTData(side); | ||||
|             active = turtleNBT.contains("active") && turtleNBT.getBoolean("active"); | ||||
|         } | ||||
|     public TransformedModel getModel(TurtleModem upgrade, @Nullable ITurtleAccess turtle, TurtleSide side, DataComponentPatch data) { | ||||
|         var component = data.get(ModRegistry.DataComponents.ON.get()); | ||||
|         var active = component != null && component.isPresent() && component.get(); | ||||
| 
 | ||||
|         return side == TurtleSide.LEFT | ||||
|             ? TransformedModel.of(active ? leftOnModel : leftOffModel) | ||||
|   | ||||
| @@ -15,7 +15,7 @@ import dan200.computercraft.impl.RegistryHelper; | ||||
| import dan200.computercraft.impl.TurtleUpgrades; | ||||
| import dan200.computercraft.impl.UpgradeManager; | ||||
| import net.minecraft.client.Minecraft; | ||||
| import net.minecraft.nbt.CompoundTag; | ||||
| import net.minecraft.core.component.DataComponentPatch; | ||||
| import net.minecraft.resources.ResourceLocation; | ||||
| 
 | ||||
| import java.util.Map; | ||||
| @@ -60,10 +60,10 @@ public final class TurtleUpgradeModellers { | ||||
|     public static TransformedModel getModel(ITurtleUpgrade upgrade, ITurtleAccess access, TurtleSide side) { | ||||
|         @SuppressWarnings("unchecked") | ||||
|         var modeller = (TurtleUpgradeModeller<ITurtleUpgrade>) modelCache.computeIfAbsent(upgrade, TurtleUpgradeModellers::getModeller); | ||||
|         return modeller.getModel(upgrade, access, side, access.getUpgradeNBTData(side)); | ||||
|         return modeller.getModel(upgrade, access, side, access.getUpgradeData(side)); | ||||
|     } | ||||
| 
 | ||||
|     public static TransformedModel getModel(ITurtleUpgrade upgrade, CompoundTag data, TurtleSide side) { | ||||
|     public static TransformedModel getModel(ITurtleUpgrade upgrade, DataComponentPatch data, TurtleSide side) { | ||||
|         @SuppressWarnings("unchecked") | ||||
|         var modeller = (TurtleUpgradeModeller<ITurtleUpgrade>) modelCache.computeIfAbsent(upgrade, TurtleUpgradeModellers::getModeller); | ||||
|         return modeller.getModel(upgrade, null, side, data); | ||||
|   | ||||
| @@ -5,6 +5,7 @@ | ||||
| package dan200.computercraft.data; | ||||
| 
 | ||||
| import com.mojang.serialization.Codec; | ||||
| import net.minecraft.core.HolderLookup; | ||||
| import net.minecraft.data.DataProvider; | ||||
| import net.minecraft.data.PackOutput; | ||||
| import net.minecraft.data.loot.LootTableProvider.SubProviderEntry; | ||||
| @@ -17,7 +18,9 @@ import net.minecraft.world.item.Item; | ||||
| import net.minecraft.world.level.block.Block; | ||||
| 
 | ||||
| import java.util.List; | ||||
| import java.util.concurrent.CompletableFuture; | ||||
| import java.util.function.BiConsumer; | ||||
| import java.util.function.BiFunction; | ||||
| import java.util.function.Consumer; | ||||
| 
 | ||||
| /** | ||||
| @@ -31,7 +34,7 @@ public final class DataProviders { | ||||
|     public static void add(GeneratorSink generator) { | ||||
|         var turtleUpgrades = generator.add(TurtleUpgradeProvider::new); | ||||
|         var pocketUpgrades = generator.add(PocketUpgradeProvider::new); | ||||
|         generator.add(out -> new RecipeProvider(out, turtleUpgrades, pocketUpgrades)); | ||||
|         generator.add((out, registries) -> new RecipeProvider(out, registries, turtleUpgrades, pocketUpgrades)); | ||||
| 
 | ||||
|         var blockTags = generator.blockTags(TagProvider::blockTags); | ||||
|         generator.itemTags(TagProvider::itemTags, blockTags); | ||||
| @@ -55,6 +58,8 @@ public final class DataProviders { | ||||
|     public interface GeneratorSink { | ||||
|         <T extends DataProvider> T add(DataProvider.Factory<T> factory); | ||||
| 
 | ||||
|         <T extends DataProvider> T add(BiFunction<PackOutput, CompletableFuture<HolderLookup.Provider>, T> factory); | ||||
| 
 | ||||
|         <T> void addFromCodec(String name, PackType type, String directory, Codec<T> codec, Consumer<BiConsumer<ResourceLocation, T>> output); | ||||
| 
 | ||||
|         void lootTable(List<SubProviderEntry> tables); | ||||
|   | ||||
| @@ -4,7 +4,6 @@ | ||||
| 
 | ||||
| package dan200.computercraft.data; | ||||
| 
 | ||||
| import dan200.computercraft.api.ComputerCraftAPI; | ||||
| import dan200.computercraft.shared.CommonHooks; | ||||
| import dan200.computercraft.shared.ModRegistry; | ||||
| import dan200.computercraft.shared.data.BlockNamedEntityLootCondition; | ||||
| @@ -13,14 +12,15 @@ 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.critereon.StatePropertiesPredicate; | ||||
| import net.minecraft.core.HolderLookup; | ||||
| import net.minecraft.data.loot.LootTableProvider.SubProviderEntry; | ||||
| import net.minecraft.resources.ResourceLocation; | ||||
| import net.minecraft.resources.ResourceKey; | ||||
| import net.minecraft.world.level.block.Block; | ||||
| import net.minecraft.world.level.storage.loot.LootPool; | ||||
| import net.minecraft.world.level.storage.loot.LootTable; | ||||
| import net.minecraft.world.level.storage.loot.entries.DynamicLoot; | ||||
| import net.minecraft.world.level.storage.loot.entries.LootItem; | ||||
| import net.minecraft.world.level.storage.loot.entries.LootPoolEntryContainer; | ||||
| import net.minecraft.world.level.storage.loot.functions.CopyComponentsFunction; | ||||
| import net.minecraft.world.level.storage.loot.functions.CopyNameFunction; | ||||
| import net.minecraft.world.level.storage.loot.parameters.LootContextParamSets; | ||||
| import net.minecraft.world.level.storage.loot.predicates.AnyOfCondition; | ||||
| @@ -41,7 +41,7 @@ class LootTableProvider { | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     private static void registerBlocks(BiConsumer<ResourceLocation, LootTable.Builder> add) { | ||||
|     private static void registerBlocks(HolderLookup.Provider registries, BiConsumer<ResourceKey<LootTable>, LootTable.Builder> add) { | ||||
|         namedBlockDrop(add, ModRegistry.Blocks.DISK_DRIVE); | ||||
|         selfDrop(add, ModRegistry.Blocks.MONITOR_NORMAL); | ||||
|         selfDrop(add, ModRegistry.Blocks.MONITOR_ADVANCED); | ||||
| @@ -78,15 +78,15 @@ class LootTableProvider { | ||||
|             )); | ||||
|     } | ||||
| 
 | ||||
|     private static void registerGeneric(BiConsumer<ResourceLocation, LootTable.Builder> add) { | ||||
|     private static void registerGeneric(HolderLookup.Provider registries, BiConsumer<ResourceKey<LootTable>, LootTable.Builder> add) { | ||||
|         add.accept(CommonHooks.TREASURE_DISK_LOOT, LootTable.lootTable()); | ||||
|     } | ||||
| 
 | ||||
|     private static void selfDrop(BiConsumer<ResourceLocation, LootTable.Builder> add, Supplier<? extends Block> wrapper) { | ||||
|     private static void selfDrop(BiConsumer<ResourceKey<LootTable>, LootTable.Builder> add, Supplier<? extends Block> wrapper) { | ||||
|         blockDrop(add, wrapper, LootItem.lootTableItem(wrapper.get()), ExplosionCondition.survivesExplosion()); | ||||
|     } | ||||
| 
 | ||||
|     private static void namedBlockDrop(BiConsumer<ResourceLocation, LootTable.Builder> add, Supplier<? extends Block> wrapper) { | ||||
|     private static void namedBlockDrop(BiConsumer<ResourceKey<LootTable>, LootTable.Builder> add, Supplier<? extends Block> wrapper) { | ||||
|         blockDrop( | ||||
|             add, wrapper, | ||||
|             LootItem.lootTableItem(wrapper.get()).apply(CopyNameFunction.copyName(CopyNameFunction.NameSource.BLOCK_ENTITY)), | ||||
| @@ -94,10 +94,10 @@ class LootTableProvider { | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     private static void computerDrop(BiConsumer<ResourceLocation, LootTable.Builder> add, Supplier<? extends Block> block) { | ||||
|     private static void computerDrop(BiConsumer<ResourceKey<LootTable>, LootTable.Builder> add, Supplier<? extends Block> block) { | ||||
|         blockDrop( | ||||
|             add, block, | ||||
|             DynamicLoot.dynamicEntry(new ResourceLocation(ComputerCraftAPI.MOD_ID, "computer")), | ||||
|             LootItem.lootTableItem(block.get()).apply(CopyComponentsFunction.copyComponents(CopyComponentsFunction.Source.BLOCK_ENTITY)), | ||||
|             AnyOfCondition.anyOf( | ||||
|                 BlockNamedEntityLootCondition.BUILDER, | ||||
|                 HasComputerIdLootCondition.BUILDER, | ||||
| @@ -107,7 +107,7 @@ class LootTableProvider { | ||||
|     } | ||||
| 
 | ||||
|     private static void blockDrop( | ||||
|         BiConsumer<ResourceLocation, LootTable.Builder> add, Supplier<? extends Block> wrapper, | ||||
|         BiConsumer<ResourceKey<LootTable>, LootTable.Builder> add, Supplier<? extends Block> wrapper, | ||||
|         LootPoolEntryContainer.Builder<?> drop, | ||||
|         LootItemCondition.Builder condition | ||||
|     ) { | ||||
|   | ||||
| @@ -5,7 +5,6 @@ | ||||
| package dan200.computercraft.data; | ||||
| 
 | ||||
| import com.google.gson.JsonObject; | ||||
| import com.google.gson.JsonParseException; | ||||
| import com.mojang.authlib.GameProfile; | ||||
| import com.mojang.serialization.JsonOps; | ||||
| import dan200.computercraft.api.ComputerCraftAPI; | ||||
| @@ -19,26 +18,25 @@ import dan200.computercraft.impl.RegistryHelper; | ||||
| import dan200.computercraft.shared.ModRegistry; | ||||
| import dan200.computercraft.shared.common.ClearColourRecipe; | ||||
| import dan200.computercraft.shared.common.ColourableRecipe; | ||||
| import dan200.computercraft.shared.computer.recipe.ComputerUpgradeRecipe; | ||||
| import dan200.computercraft.shared.media.items.DiskItem; | ||||
| import dan200.computercraft.shared.computer.recipe.ComputerConvertRecipe; | ||||
| import dan200.computercraft.shared.media.recipes.DiskRecipe; | ||||
| import dan200.computercraft.shared.media.recipes.PrintoutRecipe; | ||||
| import dan200.computercraft.shared.platform.PlatformHelper; | ||||
| import dan200.computercraft.shared.platform.RecipeIngredients; | ||||
| import dan200.computercraft.shared.pocket.items.PocketComputerItem; | ||||
| import dan200.computercraft.shared.pocket.recipes.PocketComputerUpgradeRecipe; | ||||
| import dan200.computercraft.shared.recipe.CustomShapelessRecipe; | ||||
| import dan200.computercraft.shared.recipe.ImpostorShapedRecipe; | ||||
| import dan200.computercraft.shared.recipe.ImpostorShapelessRecipe; | ||||
| import dan200.computercraft.shared.turtle.items.TurtleItem; | ||||
| import dan200.computercraft.shared.turtle.recipes.TurtleOverlayRecipe; | ||||
| import dan200.computercraft.shared.turtle.recipes.TurtleRecipe; | ||||
| import dan200.computercraft.shared.turtle.recipes.TurtleUpgradeRecipe; | ||||
| import dan200.computercraft.shared.util.ColourUtils; | ||||
| import net.minecraft.Util; | ||||
| import dan200.computercraft.shared.util.DataComponentUtil; | ||||
| import net.minecraft.advancements.Criterion; | ||||
| import net.minecraft.advancements.critereon.InventoryChangeTrigger; | ||||
| import net.minecraft.advancements.critereon.ItemPredicate; | ||||
| import net.minecraft.core.HolderLookup; | ||||
| import net.minecraft.core.component.DataComponents; | ||||
| import net.minecraft.core.registries.BuiltInRegistries; | ||||
| import net.minecraft.core.registries.Registries; | ||||
| import net.minecraft.data.PackOutput; | ||||
| @@ -46,12 +44,13 @@ import net.minecraft.data.recipes.RecipeCategory; | ||||
| import net.minecraft.data.recipes.RecipeOutput; | ||||
| import net.minecraft.data.recipes.ShapedRecipeBuilder; | ||||
| import net.minecraft.data.recipes.ShapelessRecipeBuilder; | ||||
| import net.minecraft.nbt.CompoundTag; | ||||
| import net.minecraft.nbt.NbtUtils; | ||||
| import net.minecraft.resources.ResourceLocation; | ||||
| import net.minecraft.tags.ItemTags; | ||||
| import net.minecraft.tags.TagKey; | ||||
| import net.minecraft.util.GsonHelper; | ||||
| import net.minecraft.world.item.*; | ||||
| import net.minecraft.world.item.component.DyedItemColor; | ||||
| import net.minecraft.world.item.component.ResolvableProfile; | ||||
| import net.minecraft.world.item.crafting.CraftingBookCategory; | ||||
| import net.minecraft.world.item.crafting.Ingredient; | ||||
| import net.minecraft.world.item.crafting.Recipe; | ||||
| @@ -60,6 +59,7 @@ import net.minecraft.world.level.block.Blocks; | ||||
| 
 | ||||
| import java.util.List; | ||||
| import java.util.UUID; | ||||
| import java.util.concurrent.CompletableFuture; | ||||
| import java.util.function.Consumer; | ||||
| 
 | ||||
| import static dan200.computercraft.api.ComputerCraftTags.Items.COMPUTER; | ||||
| @@ -70,8 +70,8 @@ final class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider { | ||||
|     private final TurtleUpgradeDataProvider turtleUpgrades; | ||||
|     private final PocketUpgradeDataProvider pocketUpgrades; | ||||
| 
 | ||||
|     RecipeProvider(PackOutput output, TurtleUpgradeDataProvider turtleUpgrades, PocketUpgradeDataProvider pocketUpgrades) { | ||||
|         super(output); | ||||
|     RecipeProvider(PackOutput output, CompletableFuture<HolderLookup.Provider> registries, TurtleUpgradeDataProvider turtleUpgrades, PocketUpgradeDataProvider pocketUpgrades) { | ||||
|         super(output, registries); | ||||
|         this.turtleUpgrades = turtleUpgrades; | ||||
|         this.pocketUpgrades = pocketUpgrades; | ||||
|     } | ||||
| @@ -100,7 +100,7 @@ final class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider { | ||||
|     private void diskColours(RecipeOutput output) { | ||||
|         for (var colour : Colour.VALUES) { | ||||
|             ShapelessSpecBuilder | ||||
|                 .shapeless(RecipeCategory.REDSTONE, DiskItem.createFromIDAndColour(-1, null, colour.getHex())) | ||||
|                 .shapeless(RecipeCategory.REDSTONE, DataComponentUtil.createStack(ModRegistry.Items.DISK.get(), DataComponents.DYED_COLOR, new DyedItemColor(colour.getHex(), false))) | ||||
|                 .requires(ingredients.redstone()) | ||||
|                 .requires(Items.PAPER) | ||||
|                 .requires(DyeItem.byColor(ofColour(colour))) | ||||
| @@ -122,17 +122,16 @@ final class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider { | ||||
|      */ | ||||
|     private void turtleUpgrades(RecipeOutput add) { | ||||
|         for (var turtleItem : turtleItems()) { | ||||
|             var base = turtleItem.create(-1, null, -1, null, null, 0, null); | ||||
|             var name = RegistryHelper.getKeyOrThrow(BuiltInRegistries.ITEM, turtleItem); | ||||
| 
 | ||||
|             for (var upgrade : turtleUpgrades.getGeneratedUpgrades()) { | ||||
|                 ShapedSpecBuilder | ||||
|                     .shaped(RecipeCategory.REDSTONE, turtleItem.create(-1, null, -1, null, UpgradeData.ofDefault(upgrade), -1, null)) | ||||
|                     .shaped(RecipeCategory.REDSTONE, DataComponentUtil.createStack(turtleItem, ModRegistry.DataComponents.RIGHT_TURTLE_UPGRADE.get(), UpgradeData.ofDefault(upgrade))) | ||||
|                     .group(name.toString()) | ||||
|                     .pattern("#T") | ||||
|                     .define('T', base.getItem()) | ||||
|                     .define('T', turtleItem) | ||||
|                     .define('#', upgrade.getCraftingItem().getItem()) | ||||
|                     .unlockedBy("has_items", inventoryChange(base.getItem(), upgrade.getCraftingItem().getItem())) | ||||
|                     .unlockedBy("has_items", inventoryChange(turtleItem, upgrade.getCraftingItem().getItem())) | ||||
|                     .build(ImpostorShapedRecipe::new) | ||||
|                     .save( | ||||
|                         add, | ||||
| @@ -153,18 +152,17 @@ final class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider { | ||||
|      */ | ||||
|     private void pocketUpgrades(RecipeOutput add) { | ||||
|         for (var pocket : pocketComputerItems()) { | ||||
|             var base = pocket.create(-1, null, -1, null); | ||||
|             var name = RegistryHelper.getKeyOrThrow(BuiltInRegistries.ITEM, pocket).withPath(x -> x.replace("pocket_computer_", "pocket_")); | ||||
| 
 | ||||
|             for (var upgrade : pocketUpgrades.getGeneratedUpgrades()) { | ||||
|                 ShapedSpecBuilder | ||||
|                     .shaped(RecipeCategory.REDSTONE, pocket.create(-1, null, -1, UpgradeData.ofDefault(upgrade))) | ||||
|                     .shaped(RecipeCategory.REDSTONE, DataComponentUtil.createStack(pocket, ModRegistry.DataComponents.POCKET_UPGRADE.get(), UpgradeData.ofDefault(upgrade))) | ||||
|                     .group(name.toString()) | ||||
|                     .pattern("#") | ||||
|                     .pattern("P") | ||||
|                     .define('P', base.getItem()) | ||||
|                     .define('P', pocket) | ||||
|                     .define('#', upgrade.getCraftingItem().getItem()) | ||||
|                     .unlockedBy("has_items", inventoryChange(base.getItem(), upgrade.getCraftingItem().getItem())) | ||||
|                     .unlockedBy("has_items", inventoryChange(pocket, upgrade.getCraftingItem().getItem())) | ||||
|                     .build(ImpostorShapedRecipe::new) | ||||
|                     .save( | ||||
|                         add, | ||||
| @@ -197,15 +195,14 @@ final class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider { | ||||
| 
 | ||||
|     private void turtleOverlay(RecipeOutput add, String overlay, Consumer<ShapelessSpecBuilder> build) { | ||||
|         for (var turtleItem : turtleItems()) { | ||||
|             var base = turtleItem.create(-1, null, -1, null, null, 0, null); | ||||
|             var name = RegistryHelper.getKeyOrThrow(BuiltInRegistries.ITEM, turtleItem); | ||||
| 
 | ||||
|             var builder = ShapelessSpecBuilder.shapeless(RecipeCategory.REDSTONE, base) | ||||
|             var builder = ShapelessSpecBuilder.shapeless(RecipeCategory.REDSTONE, new ItemStack(turtleItem)) | ||||
|                 .group(name.withSuffix("_overlay").toString()) | ||||
|                 .unlockedBy("has_turtle", inventoryChange(base.getItem())); | ||||
|                 .unlockedBy("has_turtle", inventoryChange(turtleItem)); | ||||
|             build.accept(builder); | ||||
|             builder | ||||
|                 .requires(base.getItem()) | ||||
|                 .requires(turtleItem) | ||||
|                 .build(s -> new TurtleOverlayRecipe(s, new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/" + overlay))) | ||||
|                 .save(add, name.withSuffix("_overlays/" + overlay)); | ||||
|         } | ||||
| @@ -254,7 +251,7 @@ final class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider { | ||||
|             .define('#', ingredients.goldIngot()) | ||||
|             .define('C', ModRegistry.Items.COMPUTER_NORMAL.get()) | ||||
|             .unlockedBy("has_components", inventoryChange(itemPredicate(ModRegistry.Items.COMPUTER_NORMAL.get()), itemPredicate(ingredients.goldIngot()))) | ||||
|             .build(ComputerUpgradeRecipe::new) | ||||
|             .build(ComputerConvertRecipe::new) | ||||
|             .save(add, new ResourceLocation(ComputerCraftAPI.MOD_ID, "computer_advanced_upgrade")); | ||||
| 
 | ||||
|         ShapedRecipeBuilder | ||||
| @@ -277,7 +274,7 @@ final class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider { | ||||
|             .define('C', ModRegistry.Items.COMPUTER_NORMAL.get()) | ||||
|             .define('I', ingredients.woodenChest()) | ||||
|             .unlockedBy("has_computer", inventoryChange(ModRegistry.Items.COMPUTER_NORMAL.get())) | ||||
|             .buildOrThrow(TurtleRecipe::of) | ||||
|             .build(ComputerConvertRecipe::new) | ||||
|             .save(add); | ||||
| 
 | ||||
|         ShapedSpecBuilder | ||||
| @@ -289,7 +286,7 @@ final class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider { | ||||
|             .define('C', ModRegistry.Items.COMPUTER_ADVANCED.get()) | ||||
|             .define('I', ingredients.woodenChest()) | ||||
|             .unlockedBy("has_computer", inventoryChange(ModRegistry.Items.COMPUTER_NORMAL.get())) | ||||
|             .buildOrThrow(TurtleRecipe::of) | ||||
|             .build(ComputerConvertRecipe::new) | ||||
|             .save(add); | ||||
| 
 | ||||
|         ShapedSpecBuilder | ||||
| @@ -301,7 +298,7 @@ final class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider { | ||||
|             .define('C', ModRegistry.Items.TURTLE_NORMAL.get()) | ||||
|             .define('B', ingredients.goldBlock()) | ||||
|             .unlockedBy("has_components", inventoryChange(itemPredicate(ModRegistry.Items.TURTLE_NORMAL.get()), itemPredicate(ingredients.goldIngot()))) | ||||
|             .build(ComputerUpgradeRecipe::new) | ||||
|             .build(ComputerConvertRecipe::new) | ||||
|             .save(add, new ResourceLocation(ComputerCraftAPI.MOD_ID, "turtle_advanced_upgrade")); | ||||
| 
 | ||||
|         ShapedRecipeBuilder | ||||
| @@ -366,7 +363,7 @@ final class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider { | ||||
|             .define('#', ingredients.goldIngot()) | ||||
|             .define('C', ModRegistry.Items.POCKET_COMPUTER_NORMAL.get()) | ||||
|             .unlockedBy("has_components", inventoryChange(itemPredicate(ModRegistry.Items.POCKET_COMPUTER_NORMAL.get()), itemPredicate(ingredients.goldIngot()))) | ||||
|             .build(ComputerUpgradeRecipe::new) | ||||
|             .build(ComputerConvertRecipe::new) | ||||
|             .save(add, new ResourceLocation(ComputerCraftAPI.MOD_ID, "pocket_computer_advanced_upgrade")); | ||||
| 
 | ||||
|         ShapedRecipeBuilder | ||||
| @@ -436,18 +433,18 @@ final class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider { | ||||
| 
 | ||||
|         ShapelessSpecBuilder | ||||
|             .shapeless(RecipeCategory.DECORATIONS, playerHead("Cloudhunter", "6d074736-b1e9-4378-a99b-bd8777821c9c")) | ||||
|             .requires(ingredients.head()) | ||||
|             .requires(ItemTags.SKULLS) | ||||
|             .requires(ModRegistry.Items.MONITOR_NORMAL.get()) | ||||
|             .unlockedBy("has_monitor", inventoryChange(ModRegistry.Items.MONITOR_NORMAL.get())) | ||||
|             .build(CustomShapelessRecipe::new) | ||||
|             .build() | ||||
|             .save(add, new ResourceLocation(ComputerCraftAPI.MOD_ID, "skull_cloudy")); | ||||
| 
 | ||||
|         ShapelessSpecBuilder | ||||
|             .shapeless(RecipeCategory.DECORATIONS, playerHead("dan200", "f3c8d69b-0776-4512-8434-d1b2165909eb")) | ||||
|             .requires(ingredients.head()) | ||||
|             .requires(ItemTags.SKULLS) | ||||
|             .requires(ModRegistry.Items.COMPUTER_ADVANCED.get()) | ||||
|             .unlockedBy("has_computer", inventoryChange(ModRegistry.Items.COMPUTER_ADVANCED.get())) | ||||
|             .build(CustomShapelessRecipe::new) | ||||
|             .build() | ||||
|             .save(add, new ResourceLocation(ComputerCraftAPI.MOD_ID, "skull_dan200")); | ||||
| 
 | ||||
|         ShapelessSpecBuilder | ||||
| @@ -493,11 +490,11 @@ final class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider { | ||||
|     } | ||||
| 
 | ||||
|     private static ItemPredicate itemPredicate(Ingredient ingredient) { | ||||
|         var json = Util.getOrThrow(Ingredient.CODEC_NONEMPTY.encodeStart(JsonOps.INSTANCE, ingredient), JsonParseException::new); | ||||
|         var json = Ingredient.CODEC_NONEMPTY.encodeStart(JsonOps.INSTANCE, ingredient).getOrThrow(); | ||||
|         if (!(json instanceof JsonObject object)) throw new IllegalStateException("Unknown ingredient " + json); | ||||
| 
 | ||||
|         if (object.has("item")) { | ||||
|             var item = Util.getOrThrow(ItemStack.ITEM_WITH_COUNT_CODEC.parse(JsonOps.INSTANCE, object), JsonParseException::new); | ||||
|             var item = ItemStack.SIMPLE_ITEM_CODEC.parse(JsonOps.INSTANCE, object).getOrThrow(); | ||||
|             return itemPredicate(item.getItem()); | ||||
|         } else if (object.has("tag")) { | ||||
|             return itemPredicate(TagKey.create(Registries.ITEM, new ResourceLocation(GsonHelper.getAsString(object, "tag")))); | ||||
| @@ -507,10 +504,7 @@ final class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider { | ||||
|     } | ||||
| 
 | ||||
|     private static ItemStack playerHead(String name, String uuid) { | ||||
|         var item = new ItemStack(Items.PLAYER_HEAD); | ||||
|         var owner = NbtUtils.writeGameProfile(new CompoundTag(), new GameProfile(UUID.fromString(uuid), name)); | ||||
|         item.getOrCreateTag().put(PlayerHeadItem.TAG_SKULL_OWNER, owner); | ||||
|         return item; | ||||
|         return DataComponentUtil.createStack(Items.PLAYER_HEAD, DataComponents.PROFILE, new ResolvableProfile(new GameProfile(UUID.fromString(uuid), name))); | ||||
|     } | ||||
| 
 | ||||
|     private static void addSpecial(RecipeOutput add, Recipe<?> recipe) { | ||||
|   | ||||
| @@ -7,8 +7,8 @@ package dan200.computercraft.data; | ||||
| import dan200.computercraft.api.ComputerCraftTags; | ||||
| import dan200.computercraft.impl.RegistryHelper; | ||||
| import dan200.computercraft.shared.ModRegistry; | ||||
| import net.minecraft.core.Registry; | ||||
| import dan200.computercraft.shared.integration.ExternalModTags; | ||||
| import net.minecraft.core.Registry; | ||||
| import net.minecraft.data.tags.ItemTagsProvider; | ||||
| import net.minecraft.data.tags.TagsProvider; | ||||
| import net.minecraft.tags.BlockTags; | ||||
| @@ -97,6 +97,10 @@ class TagProvider { | ||||
|         tags.tag(ComputerCraftTags.Items.WIRED_MODEM).add(ModRegistry.Items.WIRED_MODEM.get(), ModRegistry.Items.WIRED_MODEM_FULL.get()); | ||||
|         tags.copy(ComputerCraftTags.Blocks.MONITOR, ComputerCraftTags.Items.MONITOR); | ||||
| 
 | ||||
|         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()); | ||||
| 
 | ||||
|         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(), | ||||
|   | ||||
| @@ -6,7 +6,6 @@ package dan200.computercraft.data.recipe; | ||||
| 
 | ||||
| import com.mojang.serialization.DataResult; | ||||
| import dan200.computercraft.shared.recipe.RecipeProperties; | ||||
| import net.minecraft.Util; | ||||
| import net.minecraft.advancements.AdvancementRequirements; | ||||
| import net.minecraft.advancements.AdvancementRewards; | ||||
| import net.minecraft.advancements.Criterion; | ||||
| @@ -90,7 +89,7 @@ public abstract class AbstractRecipeBuilder<S extends AbstractRecipeBuilder<S, O | ||||
|      * @return The "built" recipe. | ||||
|      */ | ||||
|     public final FinishedRecipe buildOrThrow(Function<O, DataResult<? extends Recipe<?>>> factory) { | ||||
|         return build(s -> Util.getOrThrow(factory.apply(s), IllegalStateException::new)); | ||||
|         return build(s -> factory.apply(s).getOrThrow()); | ||||
|     } | ||||
| 
 | ||||
|     @SuppressWarnings("unchecked") | ||||
|   | ||||
| @@ -13,6 +13,7 @@ import net.minecraft.tags.TagKey; | ||||
| import net.minecraft.world.item.Item; | ||||
| import net.minecraft.world.item.ItemStack; | ||||
| import net.minecraft.world.item.crafting.Ingredient; | ||||
| import net.minecraft.world.item.crafting.ShapelessRecipe; | ||||
| import net.minecraft.world.level.ItemLike; | ||||
| 
 | ||||
| /** | ||||
| @@ -58,4 +59,8 @@ public final class ShapelessSpecBuilder extends AbstractRecipeBuilder<ShapelessS | ||||
|     protected ShapelessRecipeSpec build(RecipeProperties properties) { | ||||
|         return new ShapelessRecipeSpec(properties, ingredients, result); | ||||
|     } | ||||
| 
 | ||||
|     public FinishedRecipe build() { | ||||
|         return build(spec -> new ShapelessRecipe(spec.properties().group(), spec.properties().category(), spec.result(), spec.ingredients())); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -5,12 +5,19 @@ | ||||
| package dan200.computercraft.impl; | ||||
| 
 | ||||
| import com.google.gson.*; | ||||
| import com.mojang.serialization.Codec; | ||||
| import com.mojang.serialization.DataResult; | ||||
| import com.mojang.serialization.codecs.RecordCodecBuilder; | ||||
| import dan200.computercraft.api.ComputerCraftAPI; | ||||
| import dan200.computercraft.api.upgrades.UpgradeBase; | ||||
| import dan200.computercraft.api.upgrades.UpgradeData; | ||||
| import dan200.computercraft.api.upgrades.UpgradeSerialiser; | ||||
| import dan200.computercraft.shared.platform.PlatformHelper; | ||||
| import io.netty.buffer.ByteBuf; | ||||
| import net.minecraft.core.Registry; | ||||
| import net.minecraft.core.component.DataComponentPatch; | ||||
| import net.minecraft.network.RegistryFriendlyByteBuf; | ||||
| import net.minecraft.network.codec.StreamCodec; | ||||
| import net.minecraft.resources.ResourceKey; | ||||
| import net.minecraft.resources.ResourceLocation; | ||||
| import net.minecraft.server.packs.resources.ResourceManager; | ||||
| @@ -40,16 +47,46 @@ public class UpgradeManager<T extends UpgradeBase> extends SimpleJsonResourceRel | ||||
|     private static final Gson GSON = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create(); | ||||
| 
 | ||||
|     public record UpgradeWrapper<T extends UpgradeBase>( | ||||
|         String id, T upgrade, UpgradeSerialiser<? extends T> serialiser, String modId | ||||
|         ResourceLocation id, T upgrade, UpgradeSerialiser<? extends T> serialiser, String modId | ||||
|     ) { | ||||
|     } | ||||
| 
 | ||||
|     private final String kind; | ||||
|     private final ResourceKey<Registry<UpgradeSerialiser<? extends T>>> registry; | ||||
| 
 | ||||
|     private Map<String, UpgradeWrapper<T>> current = Map.of(); | ||||
|     private Map<ResourceLocation, UpgradeWrapper<T>> current = Map.of(); | ||||
|     private Map<T, UpgradeWrapper<T>> currentWrappers = Map.of(); | ||||
| 
 | ||||
|     private final Codec<T> upgradeCodec = ResourceLocation.CODEC.flatXmap( | ||||
|         x -> { | ||||
|             var upgrade = get(x); | ||||
|             return upgrade == null ? DataResult.error(() -> "Unknown upgrade " + x) : DataResult.success(upgrade); | ||||
|         }, | ||||
|         x -> DataResult.success(x.getUpgradeID()) | ||||
|     ); | ||||
| 
 | ||||
|     private final Codec<UpgradeData<T>> fullCodec = RecordCodecBuilder.create(i -> i.group( | ||||
|         upgradeCodec.fieldOf("id").forGetter(UpgradeData::upgrade), | ||||
|         DataComponentPatch.CODEC.optionalFieldOf("components", DataComponentPatch.EMPTY).forGetter(UpgradeData::data) | ||||
|     ).apply(i, UpgradeData::new)); | ||||
| 
 | ||||
|     private final Codec<UpgradeData<T>> codec = Codec.withAlternative(fullCodec, upgradeCodec, UpgradeData::ofDefault); | ||||
| 
 | ||||
|     private final StreamCodec<ByteBuf, T> upgradeStreamCodec = ResourceLocation.STREAM_CODEC.map( | ||||
|         x -> { | ||||
|             var upgrade = get(x); | ||||
|             if (upgrade == null) throw new IllegalStateException("Unknown upgrade " + x); | ||||
|             return upgrade; | ||||
|         }, | ||||
|         UpgradeBase::getUpgradeID | ||||
|     ); | ||||
| 
 | ||||
|     private final StreamCodec<RegistryFriendlyByteBuf, UpgradeData<T>> streamCodec = StreamCodec.composite( | ||||
|         upgradeStreamCodec, UpgradeData::upgrade, | ||||
|         DataComponentPatch.STREAM_CODEC, UpgradeData::data, | ||||
|         UpgradeData::new | ||||
|     ); | ||||
| 
 | ||||
|     public UpgradeManager(String kind, String path, ResourceKey<Registry<UpgradeSerialiser<? extends T>>> registry) { | ||||
|         super(GSON, path); | ||||
|         this.kind = kind; | ||||
| @@ -57,7 +94,7 @@ public class UpgradeManager<T extends UpgradeBase> extends SimpleJsonResourceRel | ||||
|     } | ||||
| 
 | ||||
|     @Nullable | ||||
|     public T get(String id) { | ||||
|     public T get(ResourceLocation id) { | ||||
|         var wrapper = current.get(id); | ||||
|         return wrapper == null ? null : wrapper.upgrade(); | ||||
|     } | ||||
| @@ -91,14 +128,22 @@ public class UpgradeManager<T extends UpgradeBase> extends SimpleJsonResourceRel | ||||
|         return currentWrappers.keySet(); | ||||
|     } | ||||
| 
 | ||||
|     public Map<String, UpgradeWrapper<T>> getUpgradeWrappers() { | ||||
|     public Map<ResourceLocation, UpgradeWrapper<T>> getUpgradeWrappers() { | ||||
|         return current; | ||||
|     } | ||||
| 
 | ||||
|     public Codec<UpgradeData<T>> codec() { | ||||
|         return codec; | ||||
|     } | ||||
| 
 | ||||
|     public StreamCodec<RegistryFriendlyByteBuf, UpgradeData<T>> streamCodec() { | ||||
|         return streamCodec; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected void apply(Map<ResourceLocation, JsonElement> upgrades, ResourceManager manager, ProfilerFiller profiler) { | ||||
|         var registry = RegistryHelper.getRegistry(this.registry); | ||||
|         Map<String, UpgradeWrapper<T>> newUpgrades = new HashMap<>(); | ||||
|         Map<ResourceLocation, UpgradeWrapper<T>> newUpgrades = new HashMap<>(); | ||||
|         for (var element : upgrades.entrySet()) { | ||||
|             try { | ||||
|                 loadUpgrade(registry, newUpgrades, element.getKey(), element.getValue()); | ||||
| @@ -112,7 +157,7 @@ public class UpgradeManager<T extends UpgradeBase> extends SimpleJsonResourceRel | ||||
|         LOG.info("Loaded {} {}s", current.size(), kind); | ||||
|     } | ||||
| 
 | ||||
|     private void loadUpgrade(Registry<UpgradeSerialiser<? extends T>> registry, Map<String, UpgradeWrapper<T>> current, ResourceLocation id, JsonElement json) { | ||||
|     private void loadUpgrade(Registry<UpgradeSerialiser<? extends T>> registry, Map<ResourceLocation, UpgradeWrapper<T>> current, ResourceLocation id, JsonElement json) { | ||||
|         var root = GsonHelper.convertToJsonObject(json, "top element"); | ||||
|         if (!PlatformHelper.get().shouldLoadResource(root)) return; | ||||
| 
 | ||||
| @@ -130,11 +175,11 @@ public class UpgradeManager<T extends UpgradeBase> extends SimpleJsonResourceRel | ||||
|             throw new IllegalArgumentException("Upgrade " + id + " from " + serialiser + " was incorrectly given id " + upgrade.getUpgradeID()); | ||||
|         } | ||||
| 
 | ||||
|         var result = new UpgradeWrapper<T>(id.toString(), upgrade, serialiser, modId); | ||||
|         var result = new UpgradeWrapper<T>(id, upgrade, serialiser, modId); | ||||
|         current.put(result.id(), result); | ||||
|     } | ||||
| 
 | ||||
|     public void loadFromNetwork(Map<String, UpgradeWrapper<T>> newUpgrades) { | ||||
|     public void loadFromNetwork(Map<ResourceLocation, UpgradeWrapper<T>> newUpgrades) { | ||||
|         current = Collections.unmodifiableMap(newUpgrades); | ||||
|         currentWrappers = newUpgrades.values().stream().collect(Collectors.toUnmodifiableMap(UpgradeWrapper::upgrade, x -> x)); | ||||
|     } | ||||
|   | ||||
| @@ -0,0 +1,49 @@ | ||||
| // SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers | ||||
| // | ||||
| // SPDX-License-Identifier: MPL-2.0 | ||||
| 
 | ||||
| package dan200.computercraft.mixin; | ||||
| 
 | ||||
| import com.llamalad7.mixinextras.injector.ModifyReturnValue; | ||||
| import com.mojang.datafixers.DataFix; | ||||
| import com.mojang.datafixers.TypeRewriteRule; | ||||
| import com.mojang.datafixers.schemas.Schema; | ||||
| import com.mojang.serialization.Dynamic; | ||||
| import dan200.computercraft.shared.util.ComponentizationFixers; | ||||
| import net.minecraft.util.datafix.fixes.ItemStackComponentizationFix; | ||||
| import net.minecraft.util.datafix.fixes.References; | ||||
| import org.spongepowered.asm.mixin.Mixin; | ||||
| import org.spongepowered.asm.mixin.injection.At; | ||||
| import org.spongepowered.asm.mixin.injection.Inject; | ||||
| import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; | ||||
| 
 | ||||
| /** | ||||
|  * Migrates CC's item NBT to use components. | ||||
|  * | ||||
|  * @see V3818_3Mixin | ||||
|  * @see ComponentizationFixers | ||||
|  */ | ||||
| @Mixin(ItemStackComponentizationFix.class) | ||||
| abstract class ItemStackComponentizationFixMixin extends DataFix { | ||||
|     @SuppressWarnings("UnusedMethod") | ||||
|     private ItemStackComponentizationFixMixin(Schema outputSchema, boolean changesType) { | ||||
|         super(outputSchema, changesType); | ||||
|     } | ||||
| 
 | ||||
|     @Inject(method = "fixItemStack", at = @At("TAIL")) | ||||
|     @SuppressWarnings("UnusedMethod") | ||||
|     private static void fixItemStack(ItemStackComponentizationFix.ItemStackData data, Dynamic<?> ops, CallbackInfo ci) { | ||||
|         ComponentizationFixers.fixItemComponents(data, ops); | ||||
|     } | ||||
| 
 | ||||
|     @ModifyReturnValue(method = "makeRule", at = @At("RETURN"), remap = false) | ||||
|     @SuppressWarnings("UnusedMethod") | ||||
|     private TypeRewriteRule wrapMakeRule(TypeRewriteRule existing) { | ||||
|         return TypeRewriteRule.seq(existing, fixTypeEverywhereTyped( | ||||
|             "Turtle upgrade componentization", | ||||
|             getInputSchema().getType(References.BLOCK_ENTITY), | ||||
|             getOutputSchema().getType(References.BLOCK_ENTITY), | ||||
|             ComponentizationFixers.makeBlockEntityRewrites(getInputSchema(), getOutputSchema()) | ||||
|         )); | ||||
|     } | ||||
| } | ||||
| @@ -7,16 +7,17 @@ package dan200.computercraft.mixin; | ||||
| import com.mojang.datafixers.DSL; | ||||
| import com.mojang.datafixers.schemas.Schema; | ||||
| import com.mojang.datafixers.types.templates.TypeTemplate; | ||||
| import dan200.computercraft.shared.ModRegistry; | ||||
| import dan200.computercraft.shared.peripheral.diskdrive.DiskDriveBlockEntity; | ||||
| import dan200.computercraft.shared.peripheral.printer.PrinterBlockEntity; | ||||
| import dan200.computercraft.shared.turtle.blocks.TurtleBlockEntity; | ||||
| import net.minecraft.util.datafix.fixes.References; | ||||
| import net.minecraft.util.datafix.schemas.NamespacedSchema; | ||||
| import net.minecraft.util.datafix.schemas.V1460; | ||||
| import org.spongepowered.asm.mixin.Mixin; | ||||
| import org.spongepowered.asm.mixin.Shadow; | ||||
| 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.Shadow; | ||||
| import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; | ||||
| 
 | ||||
| import java.util.Map; | ||||
| @@ -37,16 +38,34 @@ class V1460Mixin { | ||||
|         var map = ci.getReturnValue(); | ||||
| 
 | ||||
|         // Basic inventories | ||||
|         registerInventory(schema, map, ModRegistry.BlockEntities.TURTLE_NORMAL.id().toString()); | ||||
|         registerInventory(schema, map, ModRegistry.BlockEntities.TURTLE_ADVANCED.id().toString()); | ||||
|         registerInventory(schema, map, ModRegistry.BlockEntities.PRINTER.id().toString()); | ||||
|         registerTurtle(schema, map, "computercraft:turtle_normal"); | ||||
|         registerTurtle(schema, map, "computercraft:turtle_advanced"); | ||||
|         registerInventory(schema, map, "computercraft:printer"); | ||||
| 
 | ||||
|         // Disk drives contain a single item | ||||
|         schema.register(map, ModRegistry.BlockEntities.DISK_DRIVE.id().toString(), () -> DSL.optionalFields( | ||||
|         schema.register(map, "computercraft:disk_drive", () -> DSL.optionalFields( | ||||
|             "Item", References.ITEM_STACK.in(schema) | ||||
|         )); | ||||
|     } | ||||
| 
 | ||||
|     private static TypeTemplate upgradeData(Schema schema) { | ||||
|         return DSL.or( | ||||
|             // Pre-1.20.5 we just use the upgrade ID. | ||||
|             DSL.constType(NamespacedSchema.namespacedString()), | ||||
|             // In newer versions this is represented as a component. | ||||
|             DSL.optionalFields("components", References.DATA_COMPONENTS.in(schema)) | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     @Unique | ||||
|     private static void registerTurtle(Schema schema, Map<String, Supplier<TypeTemplate>> map, String name) { | ||||
|         schema.register(map, name, () -> DSL.optionalFields( | ||||
|             "LeftUpgrade", upgradeData(schema), | ||||
|             "RightUpgrade", upgradeData(schema), | ||||
|             "Items", DSL.list(References.ITEM_STACK.in(schema)) | ||||
|         )); | ||||
|     } | ||||
| 
 | ||||
|     @Shadow | ||||
|     protected static void registerInventory(Schema schema, Map<String, Supplier<TypeTemplate>> map, String name) { | ||||
|     } | ||||
|   | ||||
| @@ -0,0 +1,57 @@ | ||||
| // SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers | ||||
| // | ||||
| // SPDX-License-Identifier: MPL-2.0 | ||||
| 
 | ||||
| package dan200.computercraft.mixin; | ||||
| 
 | ||||
| import com.llamalad7.mixinextras.injector.ModifyReturnValue; | ||||
| import com.mojang.datafixers.DSL; | ||||
| import com.mojang.datafixers.schemas.Schema; | ||||
| import com.mojang.datafixers.types.templates.TypeTemplate; | ||||
| import com.mojang.datafixers.util.Pair; | ||||
| import dan200.computercraft.impl.UpgradeManager; | ||||
| import dan200.computercraft.shared.ModRegistry.DataComponents; | ||||
| import net.minecraft.util.datafix.fixes.References; | ||||
| import net.minecraft.util.datafix.schemas.V3818_3; | ||||
| import org.spongepowered.asm.mixin.Mixin; | ||||
| import org.spongepowered.asm.mixin.injection.At; | ||||
| 
 | ||||
| import java.util.Arrays; | ||||
| import java.util.stream.Stream; | ||||
| 
 | ||||
| /** | ||||
|  * Add our custom data components to the datafixer system. | ||||
|  * | ||||
|  * @see UpgradeManager#codec() | ||||
|  * @see DataComponents#POCKET_UPGRADE | ||||
|  * @see DataComponents#LEFT_TURTLE_UPGRADE | ||||
|  * @see DataComponents#RIGHT_TURTLE_UPGRADE | ||||
|  * @see ItemStackComponentizationFixMixin | ||||
|  */ | ||||
| @Mixin(V3818_3.class) | ||||
| class V3818_3Mixin { | ||||
|     @ModifyReturnValue( | ||||
|         method = "method_57277", | ||||
|         at = @At("TAIL") | ||||
|     ) | ||||
|     @SuppressWarnings("UnusedMethod") | ||||
|     private static TypeTemplate addExtraTypes(TypeTemplate type, Schema schema) { | ||||
|         // Create a codec for UpgradeData | ||||
|         var upgradeData = DSL.optionalFields("components", References.DATA_COMPONENTS.in(schema)); | ||||
| 
 | ||||
|         return extraOptionalFields(type, | ||||
|             Pair.of("computercraft:pocket_upgrade", upgradeData), | ||||
|             Pair.of("computercraft:left_turtle_upgrade", upgradeData), | ||||
|             Pair.of("computercraft:right_turtle_upgrade", upgradeData) | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     @SafeVarargs | ||||
|     @SuppressWarnings("varargs") | ||||
|     private static TypeTemplate extraOptionalFields(TypeTemplate base, Pair<String, TypeTemplate>... fields) { | ||||
|         return DSL.and(Stream.concat( | ||||
|             Arrays.stream(fields).map(entry -> DSL.optional(DSL.field(entry.getFirst(), entry.getSecond()))), | ||||
|             Stream.of(base) | ||||
|         ).toList()); | ||||
|     } | ||||
| } | ||||
| @@ -14,6 +14,7 @@ import dan200.computercraft.shared.computer.metrics.ComputerMBean; | ||||
| import dan200.computercraft.shared.peripheral.monitor.MonitorWatcher; | ||||
| import dan200.computercraft.shared.util.DropConsumer; | ||||
| import dan200.computercraft.shared.util.TickScheduler; | ||||
| import net.minecraft.core.registries.Registries; | ||||
| import net.minecraft.resources.ResourceKey; | ||||
| import net.minecraft.resources.ResourceLocation; | ||||
| import net.minecraft.server.MinecraftServer; | ||||
| @@ -28,7 +29,8 @@ import net.minecraft.world.item.ItemStack; | ||||
| import net.minecraft.world.level.chunk.LevelChunk; | ||||
| import net.minecraft.world.level.storage.loot.BuiltInLootTables; | ||||
| import net.minecraft.world.level.storage.loot.LootPool; | ||||
| import net.minecraft.world.level.storage.loot.entries.LootTableReference; | ||||
| import net.minecraft.world.level.storage.loot.LootTable; | ||||
| import net.minecraft.world.level.storage.loot.entries.NestedLootTable; | ||||
| import net.minecraft.world.level.storage.loot.providers.number.ConstantValue; | ||||
| 
 | ||||
| import javax.annotation.Nullable; | ||||
| @@ -92,9 +94,9 @@ public final class CommonHooks { | ||||
|         TickScheduler.onChunkTicketChanged(level, chunkPos, oldLevel, newLevel); | ||||
|     } | ||||
| 
 | ||||
|     public static final ResourceLocation TREASURE_DISK_LOOT = new ResourceLocation(ComputerCraftAPI.MOD_ID, "treasure_disk"); | ||||
|     public static final ResourceKey<LootTable> TREASURE_DISK_LOOT = ResourceKey.create(Registries.LOOT_TABLE, new ResourceLocation(ComputerCraftAPI.MOD_ID, "treasure_disk")); | ||||
| 
 | ||||
|     private static final Set<ResourceLocation> TREASURE_DISK_LOOT_TABLES = Set.of( | ||||
|     private static final Set<ResourceKey<LootTable>> TREASURE_DISK_LOOT_TABLES = Set.of( | ||||
|         BuiltInLootTables.SIMPLE_DUNGEON, | ||||
|         BuiltInLootTables.ABANDONED_MINESHAFT, | ||||
|         BuiltInLootTables.STRONGHOLD_CORRIDOR, | ||||
| @@ -107,13 +109,13 @@ public final class CommonHooks { | ||||
|         BuiltInLootTables.VILLAGE_CARTOGRAPHER | ||||
|     ); | ||||
| 
 | ||||
|     public static @Nullable LootPool.Builder getExtraLootPool(ResourceLocation lootTable) { | ||||
|         if (!lootTable.getNamespace().equals("minecraft") || !TREASURE_DISK_LOOT_TABLES.contains(lootTable)) { | ||||
|     public static @Nullable LootPool.Builder getExtraLootPool(ResourceKey<LootTable> lootTable) { | ||||
|         if (!TREASURE_DISK_LOOT_TABLES.contains(lootTable)) { | ||||
|             return null; | ||||
|         } | ||||
| 
 | ||||
|         return LootPool.lootPool() | ||||
|             .add(LootTableReference.lootTableReference(TREASURE_DISK_LOOT)) | ||||
|             .add(NestedLootTable.lootTableReference(TREASURE_DISK_LOOT)) | ||||
|             .setRolls(ConstantValue.exactly(1)); | ||||
|     } | ||||
| 
 | ||||
|   | ||||
| @@ -6,6 +6,7 @@ package dan200.computercraft.shared; | ||||
| 
 | ||||
| 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.detail.DetailProvider; | ||||
| import dan200.computercraft.api.detail.VanillaDetailRegistries; | ||||
| @@ -32,9 +33,11 @@ import dan200.computercraft.shared.computer.blocks.ComputerBlockEntity; | ||||
| import dan200.computercraft.shared.computer.core.ComputerFamily; | ||||
| import dan200.computercraft.shared.computer.inventory.ComputerMenuWithoutInventory; | ||||
| import dan200.computercraft.shared.computer.inventory.ViewComputerMenu; | ||||
| import dan200.computercraft.shared.computer.items.AbstractComputerItem; | ||||
| import dan200.computercraft.shared.computer.items.CommandComputerItem; | ||||
| import dan200.computercraft.shared.computer.items.ComputerItem; | ||||
| import dan200.computercraft.shared.computer.recipe.ComputerUpgradeRecipe; | ||||
| import dan200.computercraft.shared.computer.items.ServerComputerReference; | ||||
| import dan200.computercraft.shared.computer.recipe.ComputerConvertRecipe; | ||||
| import dan200.computercraft.shared.config.Config; | ||||
| import dan200.computercraft.shared.data.BlockNamedEntityLootCondition; | ||||
| import dan200.computercraft.shared.data.HasComputerIdLootCondition; | ||||
| @@ -42,10 +45,7 @@ import dan200.computercraft.shared.data.PlayerCreativeLootCondition; | ||||
| import dan200.computercraft.shared.details.BlockDetails; | ||||
| import dan200.computercraft.shared.details.ItemDetails; | ||||
| import dan200.computercraft.shared.integration.PermissionRegistry; | ||||
| import dan200.computercraft.shared.media.items.DiskItem; | ||||
| import dan200.computercraft.shared.media.items.PrintoutItem; | ||||
| import dan200.computercraft.shared.media.items.RecordMedia; | ||||
| import dan200.computercraft.shared.media.items.TreasureDiskItem; | ||||
| import dan200.computercraft.shared.media.items.*; | ||||
| import dan200.computercraft.shared.media.recipes.DiskRecipe; | ||||
| import dan200.computercraft.shared.media.recipes.PrintoutRecipe; | ||||
| import dan200.computercraft.shared.network.container.ComputerContainerData; | ||||
| @@ -81,19 +81,23 @@ import dan200.computercraft.shared.turtle.blocks.TurtleBlockEntity; | ||||
| import dan200.computercraft.shared.turtle.inventory.TurtleMenu; | ||||
| import dan200.computercraft.shared.turtle.items.TurtleItem; | ||||
| import dan200.computercraft.shared.turtle.recipes.TurtleOverlayRecipe; | ||||
| import dan200.computercraft.shared.turtle.recipes.TurtleRecipe; | ||||
| import dan200.computercraft.shared.turtle.recipes.TurtleUpgradeRecipe; | ||||
| import dan200.computercraft.shared.turtle.upgrades.*; | ||||
| import dan200.computercraft.shared.util.DataComponentUtil; | ||||
| import dan200.computercraft.shared.util.NonNegativeId; | ||||
| import net.minecraft.commands.CommandSourceStack; | ||||
| import net.minecraft.commands.synchronization.ArgumentTypeInfo; | ||||
| import net.minecraft.commands.synchronization.SingletonArgumentInfo; | ||||
| import net.minecraft.core.BlockPos; | ||||
| import net.minecraft.core.cauldron.CauldronInteraction; | ||||
| import net.minecraft.core.component.DataComponentType; | ||||
| import net.minecraft.core.registries.Registries; | ||||
| import net.minecraft.network.chat.Component; | ||||
| import net.minecraft.network.codec.ByteBufCodecs; | ||||
| import net.minecraft.resources.ResourceLocation; | ||||
| import net.minecraft.world.flag.FeatureFlags; | ||||
| import net.minecraft.world.inventory.MenuType; | ||||
| import net.minecraft.world.item.*; | ||||
| import net.minecraft.world.item.component.DyedItemColor; | ||||
| import net.minecraft.world.item.crafting.CustomRecipe; | ||||
| import net.minecraft.world.item.crafting.RecipeSerializer; | ||||
| import net.minecraft.world.item.crafting.SimpleCraftingRecipeSerializer; | ||||
| @@ -101,12 +105,12 @@ import net.minecraft.world.level.block.Block; | ||||
| import net.minecraft.world.level.block.entity.BlockEntity; | ||||
| import net.minecraft.world.level.block.entity.BlockEntityType; | ||||
| import net.minecraft.world.level.block.state.BlockBehaviour; | ||||
| import net.minecraft.world.level.block.state.BlockState; | ||||
| import net.minecraft.world.level.material.MapColor; | ||||
| import net.minecraft.world.level.storage.loot.predicates.LootItemConditionType; | ||||
| 
 | ||||
| import java.util.function.BiFunction; | ||||
| import java.util.function.Predicate; | ||||
| import java.util.function.UnaryOperator; | ||||
| 
 | ||||
| /** | ||||
|  * Registers ComputerCraft's registry entries and additional objects, such as {@link CauldronInteraction}s and | ||||
| @@ -174,8 +178,8 @@ public final class ModRegistry { | ||||
|     public static class BlockEntities { | ||||
|         static final RegistrationHelper<BlockEntityType<?>> REGISTRY = PlatformHelper.get().createRegistrationHelper(Registries.BLOCK_ENTITY_TYPE); | ||||
| 
 | ||||
|         private static <T extends BlockEntity> RegistryEntry<BlockEntityType<T>> ofBlock(RegistryEntry<? extends Block> block, BiFunction<BlockPos, BlockState, T> factory) { | ||||
|             return REGISTRY.register(block.id().getPath(), () -> PlatformHelper.get().createBlockEntityType(factory, block.get())); | ||||
|         private static <T extends BlockEntity> RegistryEntry<BlockEntityType<T>> ofBlock(RegistryEntry<? extends Block> block, BlockEntityType.BlockEntitySupplier<T> factory) { | ||||
|             return REGISTRY.register(block.id().getPath(), () -> BlockEntityType.Builder.of(factory, block.get()).build(null)); | ||||
|         } | ||||
| 
 | ||||
|         public static final RegistryEntry<BlockEntityType<MonitorBlockEntity>> MONITOR_NORMAL = | ||||
| @@ -262,6 +266,114 @@ public final class ModRegistry { | ||||
|             () -> new CableBlockItem.WiredModem(Blocks.CABLE.get(), properties())); | ||||
|     } | ||||
| 
 | ||||
|     public static final class DataComponents { | ||||
|         static final RegistrationHelper<DataComponentType<?>> REGISTRY = PlatformHelper.get().createRegistrationHelper(Registries.DATA_COMPONENT_TYPE); | ||||
| 
 | ||||
|         private static <T> RegistryEntry<DataComponentType<T>> register(String name, UnaryOperator<DataComponentType.Builder<T>> unaryOperator) { | ||||
|             return REGISTRY.register(name, () -> unaryOperator.apply(DataComponentType.builder()).build()); | ||||
|         } | ||||
| 
 | ||||
|         /** | ||||
|          * The id of a computer. | ||||
|          * | ||||
|          * @see AbstractComputerItem | ||||
|          * @see PocketComputerItem | ||||
|          */ | ||||
|         public static final RegistryEntry<DataComponentType<NonNegativeId>> COMPUTER_ID = register("computer_id", b -> b | ||||
|             .persistent(NonNegativeId.CODEC).networkSynchronized(NonNegativeId.STREAM_CODEC) | ||||
|         ); | ||||
| 
 | ||||
|         /** | ||||
|          * The left upgrade of a turtle. | ||||
|          * | ||||
|          * @see TurtleItem | ||||
|          */ | ||||
|         public static final RegistryEntry<DataComponentType<UpgradeData<ITurtleUpgrade>>> LEFT_TURTLE_UPGRADE = register("left_turtle_upgrade", b -> b | ||||
|             .persistent(TurtleUpgrades.instance().codec()).networkSynchronized(TurtleUpgrades.instance().streamCodec()) | ||||
|         ); | ||||
| 
 | ||||
|         /** | ||||
|          * The right upgrade of a turtle. | ||||
|          * | ||||
|          * @see TurtleItem | ||||
|          */ | ||||
|         public static final RegistryEntry<DataComponentType<UpgradeData<ITurtleUpgrade>>> RIGHT_TURTLE_UPGRADE = register("right_turtle_upgrade", b -> b | ||||
|             .persistent(TurtleUpgrades.instance().codec()).networkSynchronized(TurtleUpgrades.instance().streamCodec()) | ||||
|         ); | ||||
| 
 | ||||
|         /** | ||||
|          * The fuel level of a turtle. | ||||
|          */ | ||||
|         public static final RegistryEntry<DataComponentType<Integer>> FUEL = register("fuel", b -> b | ||||
|             .persistent(Codec.INT).networkSynchronized(ByteBufCodecs.VAR_INT) | ||||
|         ); | ||||
| 
 | ||||
|         /** | ||||
|          * The overlay on a turtle. | ||||
|          */ | ||||
|         public static final RegistryEntry<DataComponentType<ResourceLocation>> OVERLAY = register("overlay", b -> b | ||||
|             .persistent(ResourceLocation.CODEC).networkSynchronized(ResourceLocation.STREAM_CODEC) | ||||
|         ); | ||||
| 
 | ||||
|         /** | ||||
|          * The back upgrade of a pocket computer. | ||||
|          * | ||||
|          * @see PocketComputerItem | ||||
|          */ | ||||
|         public static final RegistryEntry<DataComponentType<UpgradeData<IPocketUpgrade>>> POCKET_UPGRADE = register("pocket_upgrade", b -> b | ||||
|             .persistent(PocketUpgrades.instance().codec()).networkSynchronized(PocketUpgrades.instance().streamCodec()) | ||||
|         ); | ||||
| 
 | ||||
|         /** | ||||
|          * A reference to the currently running {@link dan200.computercraft.shared.computer.core.ServerComputer}. | ||||
|          * | ||||
|          * @see ServerComputerReference | ||||
|          * @see PocketComputerItem | ||||
|          */ | ||||
|         public static final RegistryEntry<DataComponentType<ServerComputerReference>> COMPUTER = register("computer", b -> b | ||||
|             .persistent(ServerComputerReference.CODEC).networkSynchronized(ServerComputerReference.STREAM_CODEC) | ||||
|         ); | ||||
| 
 | ||||
|         /** | ||||
|          * Whether this item is currently on. | ||||
|          * | ||||
|          * @see PocketComputerItem | ||||
|          * @see TurtleModem | ||||
|          */ | ||||
|         public static final RegistryEntry<DataComponentType<Boolean>> ON = register("on", b -> b | ||||
|             .persistent(Codec.BOOL).networkSynchronized(ByteBufCodecs.BOOL) | ||||
|         ); | ||||
| 
 | ||||
|         /** | ||||
|          * Information about a treasure disk's mount. | ||||
|          * | ||||
|          * @see TreasureDiskItem | ||||
|          * @see TreasureDisk | ||||
|          */ | ||||
|         public static final RegistryEntry<DataComponentType<TreasureDisk>> TREASURE_DISK = register("treasure_disk", b -> b | ||||
|             .persistent(TreasureDisk.CODEC).networkSynchronized(TreasureDisk.STREAM_CODEC) | ||||
|         ); | ||||
| 
 | ||||
|         /** | ||||
|          * The id of a disk. | ||||
|          * | ||||
|          * @see DiskItem | ||||
|          */ | ||||
|         public static final RegistryEntry<DataComponentType<NonNegativeId>> DISK_ID = register("disk_id", b -> b | ||||
|             .persistent(NonNegativeId.CODEC).networkSynchronized(NonNegativeId.STREAM_CODEC) | ||||
|         ); | ||||
| 
 | ||||
|         /** | ||||
|          * The contents of a printed page/printed pages. | ||||
|          * | ||||
|          * @see PrintoutItem | ||||
|          * @see PrintoutData | ||||
|          */ | ||||
|         public static final RegistryEntry<DataComponentType<PrintoutData>> PRINTOUT = register("printout", b -> b | ||||
|             .persistent(PrintoutData.CODEC).networkSynchronized(PrintoutData.STREAM_CODEC) | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     public static class TurtleSerialisers { | ||||
|         static final RegistrationHelper<UpgradeSerialiser<? extends ITurtleUpgrade>> REGISTRY = PlatformHelper.get().createRegistrationHelper(ITurtleUpgrade.serialiserRegistryKey()); | ||||
| 
 | ||||
| @@ -292,16 +404,16 @@ public final class ModRegistry { | ||||
|         static final RegistrationHelper<MenuType<?>> REGISTRY = PlatformHelper.get().createRegistrationHelper(Registries.MENU); | ||||
| 
 | ||||
|         public static final RegistryEntry<MenuType<ComputerMenuWithoutInventory>> COMPUTER = REGISTRY.register("computer", | ||||
|             () -> ContainerData.toType(ComputerContainerData::new, (id, inv, data) -> new ComputerMenuWithoutInventory(Menus.COMPUTER.get(), id, inv, data))); | ||||
|             () -> ContainerData.toType(ComputerContainerData.STREAM_CODEC, (id, inv, data) -> new ComputerMenuWithoutInventory(Menus.COMPUTER.get(), id, inv, data))); | ||||
| 
 | ||||
|         public static final RegistryEntry<MenuType<ComputerMenuWithoutInventory>> POCKET_COMPUTER = REGISTRY.register("pocket_computer", | ||||
|             () -> ContainerData.toType(ComputerContainerData::new, (id, inv, data) -> new ComputerMenuWithoutInventory(Menus.POCKET_COMPUTER.get(), id, inv, data))); | ||||
|             () -> ContainerData.toType(ComputerContainerData.STREAM_CODEC, (id, inv, data) -> new ComputerMenuWithoutInventory(Menus.POCKET_COMPUTER.get(), id, inv, data))); | ||||
| 
 | ||||
|         public static final RegistryEntry<MenuType<ComputerMenuWithoutInventory>> POCKET_COMPUTER_NO_TERM = REGISTRY.register("pocket_computer_no_term", | ||||
|             () -> ContainerData.toType(ComputerContainerData::new, (id, inv, data) -> new ComputerMenuWithoutInventory(Menus.POCKET_COMPUTER_NO_TERM.get(), id, inv, data))); | ||||
|             () -> ContainerData.toType(ComputerContainerData.STREAM_CODEC, (id, inv, data) -> new ComputerMenuWithoutInventory(Menus.POCKET_COMPUTER_NO_TERM.get(), id, inv, data))); | ||||
| 
 | ||||
|         public static final RegistryEntry<MenuType<TurtleMenu>> TURTLE = REGISTRY.register("turtle", | ||||
|             () -> ContainerData.toType(ComputerContainerData::new, TurtleMenu::ofMenuData)); | ||||
|             () -> ContainerData.toType(ComputerContainerData.STREAM_CODEC, TurtleMenu::ofMenuData)); | ||||
| 
 | ||||
|         public static final RegistryEntry<MenuType<DiskDriveMenu>> DISK_DRIVE = REGISTRY.register("disk_drive", | ||||
|             () -> new MenuType<>(DiskDriveMenu::new, FeatureFlags.VANILLA_SET)); | ||||
| @@ -311,12 +423,12 @@ public final class ModRegistry { | ||||
| 
 | ||||
|         public static final RegistryEntry<MenuType<HeldItemMenu>> PRINTOUT = REGISTRY.register("printout", | ||||
|             () -> ContainerData.toType( | ||||
|                 HeldItemContainerData::new, | ||||
|                 (id, inventory, data) -> new HeldItemMenu(Menus.PRINTOUT.get(), id, inventory.player, data.getHand()) | ||||
|                 HeldItemContainerData.STREAM_CODEC, | ||||
|                 (id, inventory, data) -> new HeldItemMenu(Menus.PRINTOUT.get(), id, inventory.player, data.hand()) | ||||
|             )); | ||||
| 
 | ||||
|         public static final RegistryEntry<MenuType<ViewComputerMenu>> VIEW_COMPUTER = REGISTRY.register("view_computer", | ||||
|             () -> ContainerData.toType(ComputerContainerData::new, ViewComputerMenu::new)); | ||||
|             () -> ContainerData.toType(ComputerContainerData.STREAM_CODEC, ViewComputerMenu::new)); | ||||
|     } | ||||
| 
 | ||||
|     static class ArgumentTypes { | ||||
| @@ -346,13 +458,13 @@ public final class ModRegistry { | ||||
|         static final RegistrationHelper<LootItemConditionType> REGISTRY = PlatformHelper.get().createRegistrationHelper(Registries.LOOT_CONDITION_TYPE); | ||||
| 
 | ||||
|         public static final RegistryEntry<LootItemConditionType> BLOCK_NAMED = REGISTRY.register("block_named", | ||||
|             () -> new LootItemConditionType(Codec.unit(BlockNamedEntityLootCondition.INSTANCE))); | ||||
|             () -> new LootItemConditionType(MapCodec.unit(BlockNamedEntityLootCondition.INSTANCE))); | ||||
| 
 | ||||
|         public static final RegistryEntry<LootItemConditionType> PLAYER_CREATIVE = REGISTRY.register("player_creative", | ||||
|             () -> new LootItemConditionType(Codec.unit(PlayerCreativeLootCondition.INSTANCE))); | ||||
|             () -> new LootItemConditionType(MapCodec.unit(PlayerCreativeLootCondition.INSTANCE))); | ||||
| 
 | ||||
|         public static final RegistryEntry<LootItemConditionType> HAS_ID = REGISTRY.register("has_id", | ||||
|             () -> new LootItemConditionType(Codec.unit(HasComputerIdLootCondition.INSTANCE))); | ||||
|             () -> new LootItemConditionType(MapCodec.unit(HasComputerIdLootCondition.INSTANCE))); | ||||
|     } | ||||
| 
 | ||||
|     public static class RecipeSerializers { | ||||
| @@ -362,21 +474,17 @@ public final class ModRegistry { | ||||
|             return REGISTRY.register(name, () -> new SimpleCraftingRecipeSerializer<>(factory)); | ||||
|         } | ||||
| 
 | ||||
|         public static final RegistryEntry<RecipeSerializer<CustomShapedRecipe>> SHAPED = REGISTRY.register("shaped", () -> CustomShapedRecipe.serialiser(CustomShapedRecipe::new)); | ||||
|         public static final RegistryEntry<RecipeSerializer<CustomShapelessRecipe>> SHAPELESS = REGISTRY.register("shapeless", () -> CustomShapelessRecipe.serialiser(CustomShapelessRecipe::new)); | ||||
| 
 | ||||
|         public static final RegistryEntry<RecipeSerializer<ImpostorShapedRecipe>> IMPOSTOR_SHAPED = REGISTRY.register("impostor_shaped", () -> CustomShapedRecipe.serialiser(ImpostorShapedRecipe::new)); | ||||
|         public static final RegistryEntry<RecipeSerializer<ImpostorShapelessRecipe>> IMPOSTOR_SHAPELESS = REGISTRY.register("impostor_shapeless", () -> CustomShapelessRecipe.serialiser(ImpostorShapelessRecipe::new)); | ||||
| 
 | ||||
|         public static final RegistryEntry<SimpleCraftingRecipeSerializer<ColourableRecipe>> DYEABLE_ITEM = simple("colour", ColourableRecipe::new); | ||||
|         public static final RegistryEntry<SimpleCraftingRecipeSerializer<ClearColourRecipe>> DYEABLE_ITEM_CLEAR = simple("clear_colour", ClearColourRecipe::new); | ||||
|         public static final RegistryEntry<RecipeSerializer<TurtleRecipe>> TURTLE = REGISTRY.register("turtle", () -> TurtleRecipe.validatingSerialiser(TurtleRecipe::of)); | ||||
|         public static final RegistryEntry<SimpleCraftingRecipeSerializer<TurtleUpgradeRecipe>> TURTLE_UPGRADE = simple("turtle_upgrade", TurtleUpgradeRecipe::new); | ||||
|         public static final RegistryEntry<RecipeSerializer<TurtleOverlayRecipe>> TURTLE_OVERLAY = REGISTRY.register("turtle_overlay", TurtleOverlayRecipe.Serialiser::new); | ||||
|         public static final RegistryEntry<RecipeSerializer<TurtleOverlayRecipe>> TURTLE_OVERLAY = REGISTRY.register("turtle_overlay", TurtleOverlayRecipe::serialiser); | ||||
|         public static final RegistryEntry<SimpleCraftingRecipeSerializer<PocketComputerUpgradeRecipe>> POCKET_COMPUTER_UPGRADE = simple("pocket_computer_upgrade", PocketComputerUpgradeRecipe::new); | ||||
|         public static final RegistryEntry<SimpleCraftingRecipeSerializer<PrintoutRecipe>> PRINTOUT = simple("printout", PrintoutRecipe::new); | ||||
|         public static final RegistryEntry<SimpleCraftingRecipeSerializer<DiskRecipe>> DISK = simple("disk", DiskRecipe::new); | ||||
|         public static final RegistryEntry<RecipeSerializer<ComputerUpgradeRecipe>> COMPUTER_UPGRADE = REGISTRY.register("computer_upgrade", () -> CustomShapedRecipe.validatingSerialiser(ComputerUpgradeRecipe::of)); | ||||
|         public static final RegistryEntry<RecipeSerializer<ComputerConvertRecipe>> COMPUTER_CONVERT = REGISTRY.register("computer_convert", () -> CustomShapedRecipe.serialiser(ComputerConvertRecipe::new)); | ||||
|     } | ||||
| 
 | ||||
|     public static class Permissions { | ||||
| @@ -425,7 +533,7 @@ public final class ModRegistry { | ||||
| 
 | ||||
|                 out.accept(Items.DISK_DRIVE.get()); | ||||
|                 for (var colour = 0; colour < 16; colour++) { | ||||
|                     out.accept(DiskItem.createFromIDAndColour(-1, null, Colour.VALUES[colour].getHex())); | ||||
|                     out.accept(DataComponentUtil.createStack(Items.DISK.get(), net.minecraft.core.component.DataComponents.DYED_COLOR, new DyedItemColor(Colour.VALUES[colour].getHex(), false))); | ||||
|                 } | ||||
|             }) | ||||
|             .build()); | ||||
| @@ -438,6 +546,7 @@ public final class ModRegistry { | ||||
|         Blocks.REGISTRY.register(); | ||||
|         BlockEntities.REGISTRY.register(); | ||||
|         Items.REGISTRY.register(); | ||||
|         DataComponents.REGISTRY.register(); | ||||
|         TurtleSerialisers.REGISTRY.register(); | ||||
|         PocketUpgradeSerialisers.REGISTRY.register(); | ||||
|         Menus.REGISTRY.register(); | ||||
| @@ -470,14 +579,14 @@ public final class ModRegistry { | ||||
|     } | ||||
| 
 | ||||
|     private static void addTurtle(CreativeModeTab.Output out, TurtleItem turtle) { | ||||
|         out.accept(turtle.create(-1, null, -1, null, null, 0, null)); | ||||
|         out.accept(new ItemStack(turtle)); | ||||
|         TurtleUpgrades.getVanillaUpgrades() | ||||
|             .map(x -> turtle.create(-1, null, -1, null, UpgradeData.ofDefault(x), 0, null)) | ||||
|             .map(x -> DataComponentUtil.createStack(turtle, DataComponents.RIGHT_TURTLE_UPGRADE.get(), UpgradeData.ofDefault(x))) | ||||
|             .forEach(out::accept); | ||||
|     } | ||||
| 
 | ||||
|     private static void addPocket(CreativeModeTab.Output out, PocketComputerItem pocket) { | ||||
|         out.accept(pocket.create(-1, null, -1, null)); | ||||
|         PocketUpgrades.getVanillaUpgrades().map(x -> pocket.create(-1, null, -1, UpgradeData.ofDefault(x))).forEach(out::accept); | ||||
|         out.accept(new ItemStack(pocket)); | ||||
|         PocketUpgrades.getVanillaUpgrades().map(x -> DataComponentUtil.createStack(pocket, DataComponents.POCKET_UPGRADE.get(), UpgradeData.ofDefault(x))).forEach(out::accept); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -260,7 +260,7 @@ public final class CommandComputerCraft { | ||||
|      */ | ||||
|     private static int view(CommandSourceStack source, ServerComputer computer) throws CommandSyntaxException { | ||||
|         var player = source.getPlayerOrException(); | ||||
|         new ComputerContainerData(computer, ItemStack.EMPTY).open(player, new MenuProvider() { | ||||
|         new ComputerContainerData(computer, new ItemStack(ModRegistry.Items.COMPUTER_NORMAL.get())).open(player, new MenuProvider() { | ||||
|             @Override | ||||
|             public Component getDisplayName() { | ||||
|                 return Component.translatable("gui.computercraft.view_computer"); | ||||
|   | ||||
| @@ -14,8 +14,6 @@ import net.minecraft.core.registries.BuiltInRegistries; | ||||
| import net.minecraft.network.FriendlyByteBuf; | ||||
| import net.minecraft.network.chat.Component; | ||||
| 
 | ||||
| import java.util.Objects; | ||||
| 
 | ||||
| /** | ||||
|  * Utilities for working with arguments. | ||||
|  * | ||||
| @@ -45,13 +43,12 @@ public class ArgumentUtils { | ||||
| 
 | ||||
|     @SuppressWarnings("unchecked") | ||||
|     private static <A extends ArgumentType<?>, T extends ArgumentTypeInfo.Template<A>> void serializeToNetwork(FriendlyByteBuf buffer, ArgumentTypeInfo<A, T> type, ArgumentTypeInfo.Template<A> template) { | ||||
|         buffer.writeId(BuiltInRegistries.COMMAND_ARGUMENT_TYPE, type); | ||||
|         buffer.writeVarInt(BuiltInRegistries.COMMAND_ARGUMENT_TYPE.getIdOrThrow(type)); | ||||
|         type.serializeToNetwork((T) template, buffer); | ||||
|     } | ||||
| 
 | ||||
|     public static ArgumentTypeInfo.Template<?> deserialize(FriendlyByteBuf buffer) { | ||||
|         var type = buffer.readById(BuiltInRegistries.COMMAND_ARGUMENT_TYPE); | ||||
|         Objects.requireNonNull(type, "Unknown argument type"); | ||||
|         var type = BuiltInRegistries.COMMAND_ARGUMENT_TYPE.byIdOrThrow(buffer.readVarInt()); | ||||
|         return type.deserializeFromNetwork(buffer); | ||||
|     } | ||||
| 
 | ||||
|   | ||||
| @@ -15,8 +15,10 @@ import com.mojang.brigadier.suggestion.SuggestionsBuilder; | ||||
| import net.minecraft.commands.CommandBuildContext; | ||||
| import net.minecraft.commands.synchronization.ArgumentTypeInfo; | ||||
| import net.minecraft.commands.synchronization.ArgumentTypeInfos; | ||||
| import net.minecraft.core.RegistryAccess; | ||||
| import net.minecraft.network.FriendlyByteBuf; | ||||
| import net.minecraft.network.chat.Component; | ||||
| import net.minecraft.network.chat.ComponentSerialization; | ||||
| 
 | ||||
| import java.util.ArrayList; | ||||
| import java.util.Collection; | ||||
| @@ -114,14 +116,14 @@ public final class RepeatArgumentType<T, U> implements ArgumentType<List<T>> { | ||||
|         public void serializeToNetwork(RepeatArgumentType.Template arg, FriendlyByteBuf buf) { | ||||
|             buf.writeBoolean(arg.flatten); | ||||
|             ArgumentUtils.serializeToNetwork(buf, arg.child); | ||||
|             buf.writeComponent(ArgumentUtils.getMessage(arg.some)); | ||||
|             ComponentSerialization.TRUSTED_CONTEXT_FREE_STREAM_CODEC.encode(buf, ArgumentUtils.getMessage(arg.some)); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public RepeatArgumentType.Template deserializeFromNetwork(FriendlyByteBuf buf) { | ||||
|             var isList = buf.readBoolean(); | ||||
|             var child = ArgumentUtils.deserialize(buf); | ||||
|             var message = buf.readComponent(); | ||||
|             var message = ComponentSerialization.TRUSTED_CONTEXT_FREE_STREAM_CODEC.decode(buf); | ||||
|             return new RepeatArgumentType.Template(this, child, isList, new SimpleCommandExceptionType(message)); | ||||
|         } | ||||
| 
 | ||||
| @@ -134,7 +136,7 @@ public final class RepeatArgumentType<T, U> implements ArgumentType<List<T>> { | ||||
|         public void serializeToJson(RepeatArgumentType.Template arg, JsonObject json) { | ||||
|             json.addProperty("flatten", arg.flatten); | ||||
|             json.add("child", ArgumentUtils.serializeToJson(arg.child)); | ||||
|             json.addProperty("error", Component.Serializer.toJson(ArgumentUtils.getMessage(arg.some))); | ||||
|             json.addProperty("error", Component.Serializer.toJson(ArgumentUtils.getMessage(arg.some), RegistryAccess.EMPTY)); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|   | ||||
| @@ -4,11 +4,8 @@ | ||||
| 
 | ||||
| package dan200.computercraft.shared.common; | ||||
| 
 | ||||
| import dan200.computercraft.shared.container.BasicContainer; | ||||
| import net.minecraft.core.BlockPos; | ||||
| import net.minecraft.network.chat.Component; | ||||
| import net.minecraft.world.Container; | ||||
| import net.minecraft.world.entity.player.Player; | ||||
| import net.minecraft.world.level.block.entity.BaseContainerBlockEntity; | ||||
| import net.minecraft.world.level.block.entity.BlockEntity; | ||||
| import net.minecraft.world.level.block.entity.BlockEntityType; | ||||
| @@ -17,7 +14,7 @@ import net.minecraft.world.level.block.state.BlockState; | ||||
| /** | ||||
|  * A {@link BlockEntity} which exposes an inventory. | ||||
|  */ | ||||
| public abstract class AbstractContainerBlockEntity extends BaseContainerBlockEntity implements BasicContainer { | ||||
| public abstract class AbstractContainerBlockEntity extends BaseContainerBlockEntity { | ||||
|     protected AbstractContainerBlockEntity(BlockEntityType<?> type, BlockPos pos, BlockState state) { | ||||
|         super(type, pos, state); | ||||
|     } | ||||
| @@ -26,9 +23,4 @@ public abstract class AbstractContainerBlockEntity extends BaseContainerBlockEnt | ||||
|     protected final Component getDefaultName() { | ||||
|         return Component.translatable(getBlockState().getBlock().getDescriptionId()); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public boolean stillValid(Player player) { | ||||
|         return Container.stillValidBlockEntity(this, player); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -4,9 +4,12 @@ | ||||
| 
 | ||||
| package dan200.computercraft.shared.common; | ||||
| 
 | ||||
| import dan200.computercraft.api.ComputerCraftTags; | ||||
| import dan200.computercraft.shared.ModRegistry; | ||||
| import dan200.computercraft.shared.util.DataComponentUtil; | ||||
| import net.minecraft.core.HolderLookup; | ||||
| import net.minecraft.core.NonNullList; | ||||
| import net.minecraft.core.RegistryAccess; | ||||
| import net.minecraft.core.component.DataComponents; | ||||
| import net.minecraft.world.inventory.CraftingContainer; | ||||
| import net.minecraft.world.item.ItemStack; | ||||
| import net.minecraft.world.item.Items; | ||||
| @@ -16,7 +19,7 @@ import net.minecraft.world.item.crafting.RecipeSerializer; | ||||
| import net.minecraft.world.level.Level; | ||||
| 
 | ||||
| /** | ||||
|  * Craft a wet sponge with a {@linkplain IColouredItem dyable item} to remove its dye. | ||||
|  * Craft a wet sponge with a {@linkplain ComputerCraftTags.Items#DYEABLE dyable item} to remove its dye. | ||||
|  */ | ||||
| public final class ClearColourRecipe extends CustomRecipe { | ||||
|     public ClearColourRecipe(CraftingBookCategory category) { | ||||
| @@ -31,9 +34,9 @@ public final class ClearColourRecipe extends CustomRecipe { | ||||
|             var stack = inv.getItem(i); | ||||
|             if (stack.isEmpty()) continue; | ||||
| 
 | ||||
|             if (stack.getItem() instanceof IColouredItem colourable) { | ||||
|             if (stack.is(ComputerCraftTags.Items.DYEABLE)) { | ||||
|                 if (hasColourable) return false; | ||||
|                 if (colourable.getColour(stack) == -1) return false; | ||||
|                 if (!stack.has(DataComponents.DYED_COLOR)) return false; | ||||
|                 hasColourable = true; | ||||
|             } else if (stack.getItem() == Items.WET_SPONGE) { | ||||
|                 if (hasSponge) return false; | ||||
| @@ -47,19 +50,17 @@ public final class ClearColourRecipe extends CustomRecipe { | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public ItemStack assemble(CraftingContainer inv, RegistryAccess registryAccess) { | ||||
|     public ItemStack assemble(CraftingContainer inv, HolderLookup.Provider registryAccess) { | ||||
|         var colourable = ItemStack.EMPTY; | ||||
| 
 | ||||
|         for (var i = 0; i < inv.getContainerSize(); i++) { | ||||
|             var stack = inv.getItem(i); | ||||
|             if (stack.getItem() instanceof IColouredItem) colourable = stack; | ||||
|             if (stack.is(ComputerCraftTags.Items.DYEABLE)) colourable = stack; | ||||
|         } | ||||
| 
 | ||||
|         if (colourable.isEmpty()) return ItemStack.EMPTY; | ||||
| 
 | ||||
|         var stack = ((IColouredItem) colourable.getItem()).withColour(colourable, -1); | ||||
|         stack.setCount(1); | ||||
|         return stack; | ||||
|         return DataComponentUtil.createResult(colourable, DataComponents.DYED_COLOR, null); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|   | ||||
| @@ -4,12 +4,16 @@ | ||||
| 
 | ||||
| package dan200.computercraft.shared.common; | ||||
| 
 | ||||
| import dan200.computercraft.api.ComputerCraftTags; | ||||
| import dan200.computercraft.shared.ModRegistry; | ||||
| import dan200.computercraft.shared.util.ColourTracker; | ||||
| import dan200.computercraft.shared.util.ColourUtils; | ||||
| import net.minecraft.core.RegistryAccess; | ||||
| import dan200.computercraft.shared.util.DataComponentUtil; | ||||
| import net.minecraft.core.HolderLookup; | ||||
| import net.minecraft.core.component.DataComponents; | ||||
| import net.minecraft.world.inventory.CraftingContainer; | ||||
| import net.minecraft.world.item.ItemStack; | ||||
| import net.minecraft.world.item.component.DyedItemColor; | ||||
| import net.minecraft.world.item.crafting.CraftingBookCategory; | ||||
| import net.minecraft.world.item.crafting.CustomRecipe; | ||||
| import net.minecraft.world.item.crafting.RecipeSerializer; | ||||
| @@ -28,7 +32,7 @@ public final class ColourableRecipe extends CustomRecipe { | ||||
|             var stack = inv.getItem(i); | ||||
|             if (stack.isEmpty()) continue; | ||||
| 
 | ||||
|             if (stack.getItem() instanceof IColouredItem) { | ||||
|             if (stack.is(ComputerCraftTags.Items.DYEABLE)) { | ||||
|                 if (hasColourable) return false; | ||||
|                 hasColourable = true; | ||||
|             } else if (ColourUtils.getStackColour(stack) != null) { | ||||
| @@ -42,7 +46,7 @@ public final class ColourableRecipe extends CustomRecipe { | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public ItemStack assemble(CraftingContainer inv, RegistryAccess registryAccess) { | ||||
|     public ItemStack assemble(CraftingContainer inv, HolderLookup.Provider registryAccess) { | ||||
|         var colourable = ItemStack.EMPTY; | ||||
| 
 | ||||
|         var tracker = new ColourTracker(); | ||||
| @@ -52,7 +56,7 @@ public final class ColourableRecipe extends CustomRecipe { | ||||
| 
 | ||||
|             if (stack.isEmpty()) continue; | ||||
| 
 | ||||
|             if (stack.getItem() instanceof IColouredItem) { | ||||
|             if (stack.is(ComputerCraftTags.Items.DYEABLE)) { | ||||
|                 colourable = stack; | ||||
|             } else { | ||||
|                 var dye = ColourUtils.getStackColour(stack); | ||||
| @@ -60,11 +64,10 @@ public final class ColourableRecipe extends CustomRecipe { | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         if (colourable.isEmpty()) return ItemStack.EMPTY; | ||||
|         return colourable.isEmpty() | ||||
|             ? ItemStack.EMPTY | ||||
|             : DataComponentUtil.createResult(colourable, DataComponents.DYED_COLOR, new DyedItemColor(tracker.getColour(), false)); | ||||
| 
 | ||||
|         var stack = ((IColouredItem) colourable.getItem()).withColour(colourable, tracker.getColour()); | ||||
|         stack.setCount(1); | ||||
|         return stack; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|   | ||||
| @@ -6,12 +6,9 @@ package dan200.computercraft.shared.common; | ||||
| 
 | ||||
| import net.minecraft.core.BlockPos; | ||||
| import net.minecraft.world.Containers; | ||||
| import net.minecraft.world.InteractionHand; | ||||
| import net.minecraft.world.InteractionResult; | ||||
| import net.minecraft.world.entity.LivingEntity; | ||||
| import net.minecraft.world.entity.player.Player; | ||||
| import net.minecraft.world.inventory.AbstractContainerMenu; | ||||
| import net.minecraft.world.item.ItemStack; | ||||
| import net.minecraft.world.item.context.BlockPlaceContext; | ||||
| import net.minecraft.world.level.Level; | ||||
| import net.minecraft.world.level.block.BaseEntityBlock; | ||||
| @@ -24,7 +21,6 @@ import net.minecraft.world.level.block.state.properties.BlockStateProperties; | ||||
| import net.minecraft.world.level.block.state.properties.DirectionProperty; | ||||
| import net.minecraft.world.phys.BlockHitResult; | ||||
| 
 | ||||
| import javax.annotation.Nullable; | ||||
| 
 | ||||
| /** | ||||
|  * A block which has a container and can be placed in a horizontal direction. | ||||
| @@ -44,20 +40,17 @@ public abstract class HorizontalContainerBlock extends BaseEntityBlock { | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     @Deprecated | ||||
|     public final BlockState mirror(BlockState state, Mirror mirrorIn) { | ||||
|     protected final BlockState mirror(BlockState state, Mirror mirrorIn) { | ||||
|         return state.rotate(mirrorIn.getRotation(state.getValue(FACING))); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     @Deprecated | ||||
|     public final BlockState rotate(BlockState state, Rotation rot) { | ||||
|     protected final BlockState rotate(BlockState state, Rotation rot) { | ||||
|         return state.setValue(FACING, rot.rotate(state.getValue(FACING))); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     @Deprecated | ||||
|     public InteractionResult use(BlockState state, Level level, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) { | ||||
|     protected InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, BlockHitResult hit) { | ||||
|         if (level.isClientSide) return InteractionResult.SUCCESS; | ||||
| 
 | ||||
|         if (level.getBlockEntity(pos) instanceof BaseContainerBlockEntity container) { | ||||
| @@ -68,8 +61,7 @@ public abstract class HorizontalContainerBlock extends BaseEntityBlock { | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     @Deprecated | ||||
|     public final void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean isMoving) { | ||||
|     protected final void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean isMoving) { | ||||
|         if (state.is(newState.getBlock())) return; | ||||
| 
 | ||||
|         if (level.getBlockEntity(pos) instanceof BaseContainerBlockEntity container) { | ||||
| @@ -81,27 +73,17 @@ public abstract class HorizontalContainerBlock extends BaseEntityBlock { | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public final void setPlacedBy(Level world, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) { | ||||
|         if (stack.hasCustomHoverName() && world.getBlockEntity(pos) instanceof BaseContainerBlockEntity container) { | ||||
|             container.setCustomName(stack.getHoverName()); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     @Deprecated | ||||
|     public final boolean hasAnalogOutputSignal(BlockState pState) { | ||||
|     protected final boolean hasAnalogOutputSignal(BlockState pState) { | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     @Deprecated | ||||
|     public final int getAnalogOutputSignal(BlockState pBlockState, Level pLevel, BlockPos pPos) { | ||||
|     protected final int getAnalogOutputSignal(BlockState pBlockState, Level pLevel, BlockPos pPos) { | ||||
|         return AbstractContainerMenu.getRedstoneSignalFromBlockEntity(pLevel.getBlockEntity(pPos)); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     @Deprecated | ||||
|     public RenderShape getRenderShape(BlockState state) { | ||||
|     protected RenderShape getRenderShape(BlockState state) { | ||||
|         return RenderShape.MODEL; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,35 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: 2017 The CC: Tweaked Developers | ||||
| // | ||||
| // SPDX-License-Identifier: MPL-2.0 | ||||
| 
 | ||||
| package dan200.computercraft.shared.common; | ||||
| 
 | ||||
| import net.minecraft.world.item.ItemStack; | ||||
| 
 | ||||
| public interface IColouredItem { | ||||
|     String NBT_COLOUR = "Color"; | ||||
| 
 | ||||
|     default int getColour(ItemStack stack) { | ||||
|         return getColourBasic(stack); | ||||
|     } | ||||
| 
 | ||||
|     default ItemStack withColour(ItemStack stack, int colour) { | ||||
|         var copy = stack.copy(); | ||||
|         setColourBasic(copy, colour); | ||||
|         return copy; | ||||
|     } | ||||
| 
 | ||||
|     static int getColourBasic(ItemStack stack) { | ||||
|         var tag = stack.getTag(); | ||||
|         return tag != null && tag.contains(NBT_COLOUR) ? tag.getInt(NBT_COLOUR) : -1; | ||||
|     } | ||||
| 
 | ||||
|     static void setColourBasic(ItemStack stack, int colour) { | ||||
|         if (colour == -1) { | ||||
|             var tag = stack.getTag(); | ||||
|             if (tag != null) tag.remove(NBT_COLOUR); | ||||
|         } else { | ||||
|             stack.getOrCreateTag().putInt(NBT_COLOUR, colour); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -78,7 +78,7 @@ public class CommandAPI implements ILuaAPI { | ||||
|         var table = VanillaDetailRegistries.BLOCK_IN_WORLD.getDetails(block); | ||||
| 
 | ||||
|         var tile = block.blockEntity(); | ||||
|         if (tile != null) table.put("nbt", NBTUtil.toLua(tile.saveWithFullMetadata())); | ||||
|         if (tile != null) table.put("nbt", NBTUtil.toLua(tile.saveWithFullMetadata(world.registryAccess()))); | ||||
| 
 | ||||
|         return table; | ||||
|     } | ||||
|   | ||||
| @@ -7,19 +7,14 @@ package dan200.computercraft.shared.computer.blocks; | ||||
| import dan200.computercraft.annotations.ForgeOverride; | ||||
| import dan200.computercraft.api.ComputerCraftAPI; | ||||
| import dan200.computercraft.shared.common.IBundledRedstoneBlock; | ||||
| import dan200.computercraft.shared.computer.items.IComputerItem; | ||||
| import dan200.computercraft.shared.network.container.ComputerContainerData; | ||||
| import dan200.computercraft.shared.platform.RegistryEntry; | ||||
| import dan200.computercraft.shared.util.BlockEntityHelpers; | ||||
| import net.minecraft.core.BlockPos; | ||||
| import net.minecraft.core.Direction; | ||||
| import net.minecraft.resources.ResourceLocation; | ||||
| import net.minecraft.server.level.ServerLevel; | ||||
| import net.minecraft.stats.Stats; | ||||
| import net.minecraft.world.InteractionHand; | ||||
| import net.minecraft.world.InteractionResult; | ||||
| import net.minecraft.world.MenuProvider; | ||||
| import net.minecraft.world.entity.LivingEntity; | ||||
| import net.minecraft.world.entity.player.Player; | ||||
| import net.minecraft.world.item.ItemStack; | ||||
| import net.minecraft.world.level.BlockGetter; | ||||
| @@ -27,16 +22,14 @@ import net.minecraft.world.level.Level; | ||||
| import net.minecraft.world.level.LevelAccessor; | ||||
| import net.minecraft.world.level.LevelReader; | ||||
| import net.minecraft.world.level.block.Block; | ||||
| import net.minecraft.world.level.block.Blocks; | ||||
| import net.minecraft.world.level.block.EntityBlock; | ||||
| import net.minecraft.world.level.block.HorizontalDirectionalBlock; | ||||
| import net.minecraft.world.level.block.entity.BlockEntity; | ||||
| import net.minecraft.world.level.block.entity.BlockEntityTicker; | ||||
| import net.minecraft.world.level.block.entity.BlockEntityType; | ||||
| import net.minecraft.world.level.block.state.BlockState; | ||||
| import net.minecraft.world.level.storage.loot.LootParams; | ||||
| import net.minecraft.world.level.storage.loot.parameters.LootContextParams; | ||||
| import net.minecraft.world.phys.BlockHitResult; | ||||
| import net.minecraft.world.phys.Vec3; | ||||
| 
 | ||||
| import javax.annotation.Nullable; | ||||
| 
 | ||||
| @@ -52,8 +45,7 @@ public abstract class AbstractComputerBlock<T extends AbstractComputerBlockEntit | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     @Deprecated | ||||
|     public void onPlace(BlockState state, Level world, BlockPos pos, BlockState oldState, boolean isMoving) { | ||||
|     protected void onPlace(BlockState state, Level world, BlockPos pos, BlockState oldState, boolean isMoving) { | ||||
|         super.onPlace(state, world, pos, oldState, isMoving); | ||||
| 
 | ||||
|         var tile = world.getBlockEntity(pos); | ||||
| @@ -61,14 +53,12 @@ public abstract class AbstractComputerBlock<T extends AbstractComputerBlockEntit | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     @Deprecated | ||||
|     public boolean isSignalSource(BlockState state) { | ||||
|     protected boolean isSignalSource(BlockState state) { | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     @Deprecated | ||||
|     public int getDirectSignal(BlockState state, BlockGetter world, BlockPos pos, Direction incomingSide) { | ||||
|     protected int getDirectSignal(BlockState state, BlockGetter world, BlockPos pos, Direction incomingSide) { | ||||
|         var entity = world.getBlockEntity(pos); | ||||
|         if (!(entity instanceof AbstractComputerBlockEntity computerEntity)) return 0; | ||||
| 
 | ||||
| @@ -79,11 +69,14 @@ public abstract class AbstractComputerBlock<T extends AbstractComputerBlockEntit | ||||
|         return computer.getRedstoneOutput(localSide); | ||||
|     } | ||||
| 
 | ||||
|     protected abstract ItemStack getItem(AbstractComputerBlockEntity tile); | ||||
|     private ItemStack getItem(AbstractComputerBlockEntity tile) { | ||||
|         var stack = new ItemStack(this); | ||||
|         stack.applyComponents(tile.collectComponents()); | ||||
|         return stack; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     @Deprecated | ||||
|     public int getSignal(BlockState state, BlockGetter world, BlockPos pos, Direction incomingSide) { | ||||
|     protected int getSignal(BlockState state, BlockGetter world, BlockPos pos, Direction incomingSide) { | ||||
|         return getDirectSignal(state, world, pos, incomingSide); | ||||
|     } | ||||
| 
 | ||||
| @@ -100,7 +93,6 @@ public abstract class AbstractComputerBlock<T extends AbstractComputerBlockEntit | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     @Deprecated | ||||
|     public ItemStack getCloneItemStack(LevelReader world, BlockPos pos, BlockState state) { | ||||
|         var tile = world.getBlockEntity(pos); | ||||
|         if (tile instanceof AbstractComputerBlockEntity computer) { | ||||
| @@ -113,55 +105,21 @@ public abstract class AbstractComputerBlock<T extends AbstractComputerBlockEntit | ||||
| 
 | ||||
|     @Override | ||||
|     public void playerDestroy(Level world, Player player, BlockPos pos, BlockState state, @Nullable BlockEntity tile, ItemStack tool) { | ||||
|         // Don't drop blocks here - see onBlockHarvested. | ||||
|         player.awardStat(Stats.BLOCK_MINED.get(this)); | ||||
|         player.causeFoodExhaustion(0.005F); | ||||
|         // Use the same trick as DoublePlantBlock, to skip dropping items. See playerWillDestroy. | ||||
|         super.playerDestroy(world, player, pos, Blocks.AIR.defaultBlockState(), tile, tool); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public BlockState playerWillDestroy(Level world, BlockPos pos, BlockState state, Player player) { | ||||
|         var result = super.playerWillDestroy(world, pos, state, player); | ||||
|         if (!(world instanceof ServerLevel serverWorld)) return result; | ||||
| 
 | ||||
|         // We drop the item here instead of doing it in the harvest method, as we should | ||||
|         // drop computers for creative players too. | ||||
|         Block.dropResources(state, world, pos, world.getBlockEntity(pos), player, player.getMainHandItem()); | ||||
| 
 | ||||
|         var tile = world.getBlockEntity(pos); | ||||
|         if (tile instanceof AbstractComputerBlockEntity computer) { | ||||
|             var context = new LootParams.Builder(serverWorld) | ||||
|                 .withParameter(LootContextParams.ORIGIN, Vec3.atCenterOf(pos)) | ||||
|                 .withParameter(LootContextParams.TOOL, player.getMainHandItem()) | ||||
|                 .withParameter(LootContextParams.THIS_ENTITY, player) | ||||
|                 .withParameter(LootContextParams.BLOCK_ENTITY, tile) | ||||
|                 .withDynamicDrop(DROP, out -> out.accept(getItem(computer))); | ||||
|             for (var item : state.getDrops(context)) { | ||||
|                 popResource(world, pos, item); | ||||
|             } | ||||
| 
 | ||||
|             state.spawnAfterBreak(serverWorld, pos, player.getMainHandItem(), true); | ||||
|         } | ||||
| 
 | ||||
|         return result; | ||||
|         return super.playerWillDestroy(world, pos, state, player); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void setPlacedBy(Level world, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) { | ||||
|         super.setPlacedBy(world, pos, state, placer, stack); | ||||
| 
 | ||||
|         var tile = world.getBlockEntity(pos); | ||||
|         if (!world.isClientSide && tile instanceof IComputerBlockEntity computer && stack.getItem() instanceof IComputerItem item) { | ||||
| 
 | ||||
|             var id = item.getComputerID(stack); | ||||
|             if (id != -1) computer.setComputerID(id); | ||||
| 
 | ||||
|             var label = item.getLabel(stack); | ||||
|             if (label != null) computer.setLabel(label); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     @Deprecated | ||||
|     public InteractionResult use(BlockState state, Level level, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) { | ||||
|     protected InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, BlockHitResult hit) { | ||||
|         if (!player.isCrouching() && level.getBlockEntity(pos) instanceof AbstractComputerBlockEntity computer) { | ||||
|             // Regular right click to activate computer | ||||
|             if (!level.isClientSide && computer.isUsable(player)) { | ||||
| @@ -173,12 +131,11 @@ public abstract class AbstractComputerBlock<T extends AbstractComputerBlockEntit | ||||
|             return InteractionResult.sidedSuccess(level.isClientSide); | ||||
|         } | ||||
| 
 | ||||
|         return super.use(state, level, pos, player, hand, hit); | ||||
|         return super.useWithoutItem(state, level, pos, player, hit); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     @Deprecated | ||||
|     public final void neighborChanged(BlockState state, Level world, BlockPos pos, Block neighbourBlock, BlockPos neighbourPos, boolean isMoving) { | ||||
|     protected final void neighborChanged(BlockState state, Level world, BlockPos pos, Block neighbourBlock, BlockPos neighbourPos, boolean isMoving) { | ||||
|         var be = world.getBlockEntity(pos); | ||||
|         if (be instanceof AbstractComputerBlockEntity computer) computer.neighborChanged(neighbourPos); | ||||
|     } | ||||
| @@ -190,8 +147,7 @@ public abstract class AbstractComputerBlock<T extends AbstractComputerBlockEntit | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     @Deprecated | ||||
|     public BlockState updateShape(BlockState state, Direction direction, BlockState neighborState, LevelAccessor level, BlockPos pos, BlockPos neighborPos) { | ||||
|     protected BlockState updateShape(BlockState state, Direction direction, BlockState neighborState, LevelAccessor level, BlockPos pos, BlockPos neighborPos) { | ||||
|         var be = level.getBlockEntity(pos); | ||||
|         if (be instanceof AbstractComputerBlockEntity computer) computer.neighbourShapeChanged(direction); | ||||
| 
 | ||||
| @@ -200,8 +156,7 @@ public abstract class AbstractComputerBlock<T extends AbstractComputerBlockEntit | ||||
| 
 | ||||
|     @Nullable | ||||
|     @Override | ||||
|     @Deprecated | ||||
|     public MenuProvider getMenuProvider(BlockState state, Level level, BlockPos pos) { | ||||
|     protected MenuProvider getMenuProvider(BlockState state, Level level, BlockPos pos) { | ||||
|         return level.getBlockEntity(pos) instanceof AbstractComputerBlockEntity computer ? computer : null; | ||||
|     } | ||||
| 
 | ||||
|   | ||||
| @@ -9,18 +9,19 @@ import dan200.computercraft.api.ComputerCraftAPI; | ||||
| import dan200.computercraft.api.peripheral.IPeripheral; | ||||
| import dan200.computercraft.core.computer.ComputerSide; | ||||
| import dan200.computercraft.impl.BundledRedstone; | ||||
| import dan200.computercraft.shared.ModRegistry; | ||||
| import dan200.computercraft.shared.computer.core.ComputerFamily; | ||||
| import dan200.computercraft.shared.computer.core.ComputerState; | ||||
| import dan200.computercraft.shared.computer.core.ServerComputer; | ||||
| import dan200.computercraft.shared.computer.core.ServerContext; | ||||
| import dan200.computercraft.shared.platform.ComponentAccess; | ||||
| import dan200.computercraft.shared.platform.PlatformHelper; | ||||
| import dan200.computercraft.shared.util.BlockEntityHelpers; | ||||
| import dan200.computercraft.shared.util.DirectionUtil; | ||||
| import dan200.computercraft.shared.util.IDAssigner; | ||||
| import dan200.computercraft.shared.util.RedstoneUtil; | ||||
| import dan200.computercraft.shared.util.*; | ||||
| import net.minecraft.core.BlockPos; | ||||
| import net.minecraft.core.Direction; | ||||
| import net.minecraft.core.HolderLookup; | ||||
| import net.minecraft.core.component.DataComponentMap; | ||||
| import net.minecraft.core.component.DataComponents; | ||||
| import net.minecraft.nbt.CompoundTag; | ||||
| import net.minecraft.network.chat.Component; | ||||
| import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket; | ||||
| @@ -76,8 +77,8 @@ public abstract class AbstractComputerBlockEntity extends BlockEntity implements | ||||
|         unload(); | ||||
|     } | ||||
| 
 | ||||
|     protected int getInteractRange() { | ||||
|         return Container.DEFAULT_DISTANCE_LIMIT; | ||||
|     protected float getInteractRange() { | ||||
|         return Container.DEFAULT_DISTANCE_BUFFER; | ||||
|     } | ||||
| 
 | ||||
|     public boolean isUsable(Player player) { | ||||
| @@ -137,7 +138,7 @@ public abstract class AbstractComputerBlockEntity extends BlockEntity implements | ||||
|     protected abstract void updateBlockState(ComputerState newState); | ||||
| 
 | ||||
|     @Override | ||||
|     public void saveAdditional(CompoundTag nbt) { | ||||
|     public void saveAdditional(CompoundTag nbt, HolderLookup.Provider registries) { | ||||
|         // Save ID, label and power state | ||||
|         if (computerID >= 0) nbt.putInt(NBT_ID, computerID); | ||||
|         if (label != null) nbt.putString(NBT_LABEL, label); | ||||
| @@ -145,20 +146,20 @@ public abstract class AbstractComputerBlockEntity extends BlockEntity implements | ||||
| 
 | ||||
|         lockCode.addToTag(nbt); | ||||
| 
 | ||||
|         super.saveAdditional(nbt); | ||||
|         super.saveAdditional(nbt, registries); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public final void load(CompoundTag nbt) { | ||||
|         super.load(nbt); | ||||
|     public final void loadAdditional(CompoundTag nbt, HolderLookup.Provider registries) { | ||||
|         super.loadAdditional(nbt, registries); | ||||
|         if (level != null && level.isClientSide) { | ||||
|             loadClient(nbt); | ||||
|             loadClient(nbt, registries); | ||||
|         } else { | ||||
|             loadServer(nbt); | ||||
|             loadServer(nbt, registries); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     protected void loadServer(CompoundTag nbt) { | ||||
|     protected void loadServer(CompoundTag nbt, HolderLookup.Provider registries) { | ||||
|         // Load ID, label and power state | ||||
|         computerID = nbt.contains(NBT_ID) ? nbt.getInt(NBT_ID) : -1; | ||||
|         label = nbt.contains(NBT_LABEL) ? nbt.getString(NBT_LABEL) : null; | ||||
| @@ -167,6 +168,31 @@ public abstract class AbstractComputerBlockEntity extends BlockEntity implements | ||||
|         lockCode = LockCode.fromTag(nbt); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected void applyImplicitComponents(DataComponentInput component) { | ||||
|         super.applyImplicitComponents(component); | ||||
|         label = DataComponentUtil.getCustomName(component.get(DataComponents.CUSTOM_NAME)); | ||||
|         computerID = NonNegativeId.getId(component.get(ModRegistry.DataComponents.COMPUTER_ID.get())); | ||||
|         lockCode = component.getOrDefault(DataComponents.LOCK, LockCode.NO_LOCK); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected void collectImplicitComponents(DataComponentMap.Builder builder) { | ||||
|         super.collectImplicitComponents(builder); | ||||
|         builder.set(ModRegistry.DataComponents.COMPUTER_ID.get(), NonNegativeId.of(computerID)); | ||||
|         builder.set(DataComponents.CUSTOM_NAME, label == null ? null : Component.literal(label)); | ||||
|         if (lockCode != LockCode.NO_LOCK) builder.set(DataComponents.LOCK, lockCode); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     @Deprecated | ||||
|     public void removeComponentsFromTag(CompoundTag tag) { | ||||
|         super.removeComponentsFromTag(tag); | ||||
|         tag.remove(NBT_ID); | ||||
|         tag.remove(NBT_LABEL); | ||||
|         tag.remove(LockCode.TAG_LOCK); | ||||
|     } | ||||
| 
 | ||||
|     protected boolean isPeripheralBlockedOnSide(ComputerSide localSide) { | ||||
|         return false; | ||||
|     } | ||||
| @@ -370,15 +396,15 @@ public abstract class AbstractComputerBlockEntity extends BlockEntity implements | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public CompoundTag getUpdateTag() { | ||||
|     public CompoundTag getUpdateTag(HolderLookup.Provider registries) { | ||||
|         // We need this for pick block on the client side. | ||||
|         var nbt = super.getUpdateTag(); | ||||
|         var nbt = super.getUpdateTag(registries); | ||||
|         if (label != null) nbt.putString(NBT_LABEL, label); | ||||
|         if (computerID >= 0) nbt.putInt(NBT_ID, computerID); | ||||
|         return nbt; | ||||
|     } | ||||
| 
 | ||||
|     protected void loadClient(CompoundTag nbt) { | ||||
|     protected void loadClient(CompoundTag nbt, HolderLookup.Provider registries) { | ||||
|         label = nbt.contains(NBT_LABEL) ? nbt.getString(NBT_LABEL) : null; | ||||
|         computerID = nbt.contains(NBT_ID) ? nbt.getInt(NBT_ID) : -1; | ||||
|     } | ||||
|   | ||||
| @@ -7,11 +7,9 @@ package dan200.computercraft.shared.computer.blocks; | ||||
| import com.mojang.serialization.MapCodec; | ||||
| import com.mojang.serialization.codecs.RecordCodecBuilder; | ||||
| import dan200.computercraft.shared.computer.core.ComputerState; | ||||
| import dan200.computercraft.shared.computer.items.ComputerItem; | ||||
| import dan200.computercraft.shared.platform.RegistryEntry; | ||||
| import dan200.computercraft.shared.util.BlockCodecs; | ||||
| import net.minecraft.core.Direction; | ||||
| import net.minecraft.world.item.ItemStack; | ||||
| import net.minecraft.world.item.context.BlockPlaceContext; | ||||
| import net.minecraft.world.level.block.Block; | ||||
| import net.minecraft.world.level.block.entity.BlockEntityType; | ||||
| @@ -55,12 +53,4 @@ public class ComputerBlock<T extends ComputerBlockEntity> extends AbstractComput | ||||
|     public BlockState getStateForPlacement(BlockPlaceContext placement) { | ||||
|         return defaultBlockState().setValue(FACING, placement.getHorizontalDirection().getOpposite()); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected ItemStack getItem(AbstractComputerBlockEntity tile) { | ||||
|         if (!(tile instanceof ComputerBlockEntity computer)) return ItemStack.EMPTY; | ||||
|         if (!(asItem() instanceof ComputerItem item)) return ItemStack.EMPTY; | ||||
| 
 | ||||
|         return item.create(computer.getComputerID(), computer.getLabel()); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -7,30 +7,32 @@ package dan200.computercraft.shared.computer.items; | ||||
| import dan200.computercraft.api.ComputerCraftAPI; | ||||
| import dan200.computercraft.api.filesystem.Mount; | ||||
| import dan200.computercraft.api.media.IMedia; | ||||
| import dan200.computercraft.shared.ModRegistry; | ||||
| import dan200.computercraft.shared.computer.blocks.AbstractComputerBlock; | ||||
| import dan200.computercraft.shared.config.Config; | ||||
| import dan200.computercraft.shared.util.DataComponentUtil; | ||||
| import net.minecraft.ChatFormatting; | ||||
| import net.minecraft.core.component.DataComponents; | ||||
| import net.minecraft.network.chat.Component; | ||||
| import net.minecraft.server.level.ServerLevel; | ||||
| import net.minecraft.world.item.BlockItem; | ||||
| import net.minecraft.world.item.ItemStack; | ||||
| import net.minecraft.world.item.TooltipFlag; | ||||
| import net.minecraft.world.level.Level; | ||||
| 
 | ||||
| import javax.annotation.Nullable; | ||||
| import java.util.List; | ||||
| 
 | ||||
| public abstract class AbstractComputerItem extends BlockItem implements IComputerItem, IMedia { | ||||
| public class AbstractComputerItem extends BlockItem implements IMedia { | ||||
|     public AbstractComputerItem(AbstractComputerBlock<?> block, Properties settings) { | ||||
|         super(block, settings); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void appendHoverText(ItemStack stack, @Nullable Level world, List<Component> list, TooltipFlag options) { | ||||
|         if (options.isAdvanced() || getLabel(stack) == null) { | ||||
|             var id = getComputerID(stack); | ||||
|             if (id >= 0) { | ||||
|                 list.add(Component.translatable("gui.computercraft.tooltip.computer_id", id) | ||||
|     public void appendHoverText(ItemStack stack, TooltipContext context, List<Component> list, TooltipFlag options) { | ||||
|         if (options.isAdvanced() || !stack.has(DataComponents.CUSTOM_NAME)) { | ||||
|             var id = stack.get(ModRegistry.DataComponents.COMPUTER_ID.get()); | ||||
|             if (id != null) { | ||||
|                 list.add(Component.translatable("gui.computercraft.tooltip.computer_id", id.id()) | ||||
|                     .withStyle(ChatFormatting.GRAY)); | ||||
|             } | ||||
|         } | ||||
| @@ -38,22 +40,18 @@ public abstract class AbstractComputerItem extends BlockItem implements ICompute | ||||
| 
 | ||||
|     @Override | ||||
|     public @Nullable String getLabel(ItemStack stack) { | ||||
|         return IComputerItem.super.getLabel(stack); | ||||
|         return DataComponentUtil.getCustomName(stack); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public boolean setLabel(ItemStack stack, @Nullable String label) { | ||||
|         if (label != null) { | ||||
|             stack.setHoverName(Component.literal(label)); | ||||
|         } else { | ||||
|             stack.resetHoverName(); | ||||
|         } | ||||
|         DataComponentUtil.setCustomName(stack, label); | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public @Nullable Mount createDataMount(ItemStack stack, ServerLevel level) { | ||||
|         var id = getComputerID(stack); | ||||
|         return id >= 0 ? ComputerCraftAPI.createSaveDirMount(level.getServer(), "computer/" + id, Config.computerSpaceLimit) : null; | ||||
|         var id = stack.get(ModRegistry.DataComponents.COMPUTER_ID.get()); | ||||
|         return id != null ? ComputerCraftAPI.createSaveDirMount(level.getServer(), "computer/" + id.id(), Config.computerSpaceLimit) : null; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -5,28 +5,10 @@ | ||||
| package dan200.computercraft.shared.computer.items; | ||||
| 
 | ||||
| import dan200.computercraft.shared.computer.blocks.ComputerBlock; | ||||
| import net.minecraft.network.chat.Component; | ||||
| import net.minecraft.world.item.Item; | ||||
| import net.minecraft.world.item.ItemStack; | ||||
| 
 | ||||
| import javax.annotation.Nullable; | ||||
| 
 | ||||
| public class ComputerItem extends AbstractComputerItem { | ||||
|     // TODO: Do we deprecate this? | ||||
|     public ComputerItem(ComputerBlock<?> block, Properties settings) { | ||||
|         super(block, settings); | ||||
|     } | ||||
| 
 | ||||
|     public ItemStack create(int id, @Nullable String label) { | ||||
|         var result = new ItemStack(this); | ||||
|         if (id >= 0) result.getOrCreateTag().putInt(NBT_ID, id); | ||||
|         if (label != null) result.setHoverName(Component.literal(label)); | ||||
|         return result; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public ItemStack changeItem(ItemStack stack, Item newItem) { | ||||
|         return newItem instanceof ComputerItem computer | ||||
|             ? computer.create(getComputerID(stack), getLabel(stack)) | ||||
|             : ItemStack.EMPTY; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,35 +0,0 @@ | ||||
| // Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. | ||||
| // | ||||
| // SPDX-License-Identifier: LicenseRef-CCPL | ||||
| 
 | ||||
| package dan200.computercraft.shared.computer.items; | ||||
| 
 | ||||
| import net.minecraft.world.item.Item; | ||||
| import net.minecraft.world.item.ItemStack; | ||||
| 
 | ||||
| import javax.annotation.Nullable; | ||||
| 
 | ||||
| public interface IComputerItem { | ||||
|     String NBT_ID = "ComputerId"; | ||||
| 
 | ||||
|     default int getComputerID(ItemStack stack) { | ||||
|         var nbt = stack.getTag(); | ||||
|         return nbt != null && nbt.contains(NBT_ID) ? nbt.getInt(NBT_ID) : -1; | ||||
|     } | ||||
| 
 | ||||
|     default @Nullable String getLabel(ItemStack stack) { | ||||
|         return stack.hasCustomHoverName() ? stack.getHoverName().getString() : null; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Create a new stack, changing the underlying item. | ||||
|      * <p> | ||||
|      * This should copy the computer's data to a different item of the same type (for instance, converting a normal | ||||
|      * computer to an advanced one). | ||||
|      * | ||||
|      * @param stack   The current computer stack. | ||||
|      * @param newItem The new item. | ||||
|      * @return The new stack, possibly {@linkplain ItemStack#EMPTY empty} if {@code newItem} is of the same type. | ||||
|      */ | ||||
|     ItemStack changeItem(ItemStack stack, Item newItem); | ||||
| } | ||||
| @@ -0,0 +1,47 @@ | ||||
| // SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers | ||||
| // | ||||
| // SPDX-License-Identifier: MPL-2.0 | ||||
| 
 | ||||
| package dan200.computercraft.shared.computer.items; | ||||
| 
 | ||||
| import com.mojang.serialization.Codec; | ||||
| import com.mojang.serialization.codecs.RecordCodecBuilder; | ||||
| import dan200.computercraft.shared.ModRegistry; | ||||
| import dan200.computercraft.shared.computer.core.ServerComputer; | ||||
| import dan200.computercraft.shared.computer.core.ServerComputerRegistry; | ||||
| import net.minecraft.core.UUIDUtil; | ||||
| import net.minecraft.core.component.DataComponentHolder; | ||||
| import net.minecraft.network.RegistryFriendlyByteBuf; | ||||
| import net.minecraft.network.codec.ByteBufCodecs; | ||||
| import net.minecraft.network.codec.StreamCodec; | ||||
| 
 | ||||
| import javax.annotation.Nullable; | ||||
| import java.util.UUID; | ||||
| 
 | ||||
| /** | ||||
|  * A reference to a {@link ServerComputer}. | ||||
|  * | ||||
|  * @param session  The current {@linkplain ServerComputerRegistry#getSessionID() session id}. | ||||
|  * @param instance The computer's {@linkplain ServerComputer#getInstanceUUID() instance id}. | ||||
|  */ | ||||
| public record ServerComputerReference(int session, UUID instance) { | ||||
|     public static final Codec<ServerComputerReference> CODEC = RecordCodecBuilder.create(i -> i.group( | ||||
|         Codec.INT.fieldOf("session").forGetter(ServerComputerReference::session), | ||||
|         UUIDUtil.CODEC.fieldOf("instance").forGetter(ServerComputerReference::instance) | ||||
|     ).apply(i, ServerComputerReference::new)); | ||||
| 
 | ||||
|     public static final StreamCodec<RegistryFriendlyByteBuf, ServerComputerReference> STREAM_CODEC = StreamCodec.composite( | ||||
|         ByteBufCodecs.VAR_INT, ServerComputerReference::session, | ||||
|         UUIDUtil.STREAM_CODEC, ServerComputerReference::instance, | ||||
|         ServerComputerReference::new | ||||
|     ); | ||||
| 
 | ||||
|     public @Nullable ServerComputer get(ServerComputerRegistry registry) { | ||||
|         return registry.get(session, this.instance()); | ||||
|     } | ||||
| 
 | ||||
|     public static @Nullable ServerComputer get(DataComponentHolder holder, ServerComputerRegistry registry) { | ||||
|         var reference = holder.get(ModRegistry.DataComponents.COMPUTER.get()); | ||||
|         return reference == null ? null : reference.get(registry); | ||||
|     } | ||||
| } | ||||
| @@ -4,43 +4,50 @@ | ||||
| 
 | ||||
| package dan200.computercraft.shared.computer.recipe; | ||||
| 
 | ||||
| import dan200.computercraft.shared.computer.items.IComputerItem; | ||||
| import dan200.computercraft.shared.ModRegistry; | ||||
| import dan200.computercraft.shared.computer.items.AbstractComputerItem; | ||||
| import dan200.computercraft.shared.pocket.items.PocketComputerItem; | ||||
| import dan200.computercraft.shared.recipe.CustomShapedRecipe; | ||||
| import dan200.computercraft.shared.recipe.ShapedRecipeSpec; | ||||
| import net.minecraft.core.RegistryAccess; | ||||
| import net.minecraft.core.HolderLookup; | ||||
| import net.minecraft.world.inventory.CraftingContainer; | ||||
| import net.minecraft.world.item.Item; | ||||
| import net.minecraft.world.item.ItemStack; | ||||
| import net.minecraft.world.level.Level; | ||||
| import net.minecraft.world.item.crafting.RecipeSerializer; | ||||
| 
 | ||||
| /** | ||||
|  * A recipe which converts a computer from one form into another. | ||||
|  */ | ||||
| public abstract class ComputerConvertRecipe extends CustomShapedRecipe { | ||||
| public final class ComputerConvertRecipe extends CustomShapedRecipe { | ||||
|     private final Item result; | ||||
| 
 | ||||
|     public ComputerConvertRecipe(ShapedRecipeSpec recipe) { | ||||
|         super(recipe); | ||||
|     } | ||||
| 
 | ||||
|     protected abstract ItemStack convert(IComputerItem item, ItemStack stack); | ||||
| 
 | ||||
|     @Override | ||||
|     public boolean matches(CraftingContainer inventory, Level world) { | ||||
|         if (!super.matches(inventory, world)) return false; | ||||
| 
 | ||||
|         for (var i = 0; i < inventory.getContainerSize(); i++) { | ||||
|             if (inventory.getItem(i).getItem() instanceof IComputerItem) return true; | ||||
|         } | ||||
| 
 | ||||
|         return false; | ||||
|         this.result = recipe.result().getItem(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public ItemStack assemble(CraftingContainer inventory, RegistryAccess registryAccess) { | ||||
|         // Find our computer item and convert it. | ||||
|     public ItemStack assemble(CraftingContainer inventory, HolderLookup.Provider registryAccess) { | ||||
|         // Find our computer item and copy the components across. | ||||
|         for (var i = 0; i < inventory.getContainerSize(); i++) { | ||||
|             var stack = inventory.getItem(i); | ||||
|             if (stack.getItem() instanceof IComputerItem) return convert((IComputerItem) stack.getItem(), stack); | ||||
|             if (isComputerItem(stack.getItem())) { | ||||
|                 var newStack = new ItemStack(result); | ||||
|                 newStack.applyComponents(stack.getComponentsPatch()); | ||||
|                 return newStack; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return ItemStack.EMPTY; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public RecipeSerializer<ComputerConvertRecipe> getSerializer() { | ||||
|         return ModRegistry.RecipeSerializers.COMPUTER_CONVERT.get(); | ||||
|     } | ||||
| 
 | ||||
|     private static boolean isComputerItem(Item item) { | ||||
|         // TODO: Make this a little more general. Either with a tag, or a predicate on the recipe itself? | ||||
|         return item instanceof AbstractComputerItem || item instanceof PocketComputerItem; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,46 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: 2019 The CC: Tweaked Developers | ||||
| // | ||||
| // SPDX-License-Identifier: MPL-2.0 | ||||
| 
 | ||||
| package dan200.computercraft.shared.computer.recipe; | ||||
| 
 | ||||
| import com.mojang.serialization.DataResult; | ||||
| import dan200.computercraft.shared.ModRegistry; | ||||
| import dan200.computercraft.shared.computer.items.IComputerItem; | ||||
| import dan200.computercraft.shared.recipe.ShapedRecipeSpec; | ||||
| import net.minecraft.world.item.Item; | ||||
| import net.minecraft.world.item.ItemStack; | ||||
| import net.minecraft.world.item.crafting.RecipeSerializer; | ||||
| 
 | ||||
| /** | ||||
|  * A recipe which "upgrades" a {@linkplain IComputerItem computer}, converting to it a new item (for instance a normal | ||||
|  * turtle to an advanced one). | ||||
|  * | ||||
|  * @see IComputerItem#changeItem(ItemStack, Item) | ||||
|  */ | ||||
| public final class ComputerUpgradeRecipe extends ComputerConvertRecipe { | ||||
|     private final Item result; | ||||
| 
 | ||||
|     public ComputerUpgradeRecipe(ShapedRecipeSpec recipe) { | ||||
|         super(recipe); | ||||
|         this.result = recipe.result().getItem(); | ||||
|     } | ||||
| 
 | ||||
|     public static DataResult<ComputerUpgradeRecipe> of(ShapedRecipeSpec recipe) { | ||||
|         if (!(recipe.result().getItem() instanceof IComputerItem)) { | ||||
|             return DataResult.error(() -> recipe.result().getItem() + " is not a computer item"); | ||||
|         } | ||||
| 
 | ||||
|         return DataResult.success(new ComputerUpgradeRecipe(recipe)); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected ItemStack convert(IComputerItem item, ItemStack stack) { | ||||
|         return item.changeItem(stack, result); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public RecipeSerializer<ComputerUpgradeRecipe> getSerializer() { | ||||
|         return ModRegistry.RecipeSerializers.COMPUTER_UPGRADE.get(); | ||||
|     } | ||||
| } | ||||
| @@ -7,6 +7,7 @@ package dan200.computercraft.shared.computer.terminal; | ||||
| import io.netty.buffer.ByteBuf; | ||||
| import io.netty.buffer.Unpooled; | ||||
| import net.minecraft.network.FriendlyByteBuf; | ||||
| import net.minecraft.network.codec.StreamCodec; | ||||
| import org.jetbrains.annotations.Contract; | ||||
| 
 | ||||
| import javax.annotation.Nullable; | ||||
| @@ -19,6 +20,8 @@ import javax.annotation.Nullable; | ||||
|  * states, etc... | ||||
|  */ | ||||
| public class TerminalState { | ||||
|     public static final StreamCodec<FriendlyByteBuf, TerminalState> STREAM_CODEC = StreamCodec.ofMember(TerminalState::write, TerminalState::new); | ||||
| 
 | ||||
|     private final boolean colour; | ||||
|     private final int width; | ||||
|     private final int height; | ||||
| @@ -38,7 +41,7 @@ public class TerminalState { | ||||
|         return terminal == null ? null : new TerminalState(terminal); | ||||
|     } | ||||
| 
 | ||||
|     public TerminalState(FriendlyByteBuf buf) { | ||||
|     private TerminalState(FriendlyByteBuf buf) { | ||||
|         colour = buf.readBoolean(); | ||||
|         width = buf.readVarInt(); | ||||
|         height = buf.readVarInt(); | ||||
| @@ -47,7 +50,7 @@ public class TerminalState { | ||||
|         buffer = buf.readBytes(length); | ||||
|     } | ||||
| 
 | ||||
|     public void write(FriendlyByteBuf buf) { | ||||
|     private void write(FriendlyByteBuf buf) { | ||||
|         buf.writeBoolean(colour); | ||||
|         buf.writeVarInt(width); | ||||
|         buf.writeVarInt(height); | ||||
|   | ||||
| @@ -10,19 +10,19 @@ import net.minecraft.world.ContainerHelper; | ||||
| import net.minecraft.world.item.ItemStack; | ||||
| 
 | ||||
| /** | ||||
|  * A basic implementation of {@link Container} which operates on a {@linkplain #getContents() list of stacks}. | ||||
|  * A basic implementation of {@link Container} which operates on a {@linkplain #getItems() list of stacks}. | ||||
|  */ | ||||
| public interface BasicContainer extends Container { | ||||
|     NonNullList<ItemStack> getContents(); | ||||
|     NonNullList<ItemStack> getItems(); | ||||
| 
 | ||||
|     @Override | ||||
|     default int getContainerSize() { | ||||
|         return getContents().size(); | ||||
|         return getItems().size(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     default boolean isEmpty() { | ||||
|         for (var stack : getContents()) { | ||||
|         for (var stack : getItems()) { | ||||
|             if (!stack.isEmpty()) return false; | ||||
|         } | ||||
| 
 | ||||
| @@ -31,27 +31,33 @@ public interface BasicContainer extends Container { | ||||
| 
 | ||||
|     @Override | ||||
|     default ItemStack getItem(int slot) { | ||||
|         var contents = getContents(); | ||||
|         var contents = getItems(); | ||||
|         return slot >= 0 && slot < contents.size() ? contents.get(slot) : ItemStack.EMPTY; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     default ItemStack removeItemNoUpdate(int slot) { | ||||
|         return ContainerHelper.takeItem(getContents(), slot); | ||||
|         return ContainerHelper.takeItem(getItems(), slot); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     default ItemStack removeItem(int slot, int count) { | ||||
|         return ContainerHelper.removeItem(getContents(), slot, count); | ||||
|         return ContainerHelper.removeItem(getItems(), slot, count); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     default void setItem(int slot, ItemStack itemStack) { | ||||
|         getContents().set(slot, itemStack); | ||||
|         getItems().set(slot, itemStack); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     default void clearContent() { | ||||
|         getContents().clear(); | ||||
|         getItems().clear(); | ||||
|     } | ||||
| 
 | ||||
|     static void defaultSetItems(NonNullList<ItemStack> inventory, NonNullList<ItemStack> items) { | ||||
|         var i = 0; | ||||
|         for (; i < items.size(); i++) inventory.set(i, items.get(i)); | ||||
|         for (; i < inventory.size(); i++) inventory.set(i, ItemStack.EMPTY); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -11,7 +11,7 @@ import net.minecraft.world.item.ItemStack; | ||||
| import javax.annotation.Nullable; | ||||
| 
 | ||||
| /** | ||||
|  * A basic implementation of {@link WorldlyContainer} which operates on a {@linkplain #getContents() list of stacks}. | ||||
|  * A basic implementation of {@link WorldlyContainer} which operates on a {@linkplain #getItems() list of stacks}. | ||||
|  */ | ||||
| public interface BasicWorldlyContainer extends BasicContainer, WorldlyContainer { | ||||
|     @Override | ||||
|   | ||||
| @@ -27,7 +27,7 @@ public final class PlayerCreativeLootCondition implements LootItemCondition { | ||||
|     @Override | ||||
|     public boolean test(LootContext lootContext) { | ||||
|         var entity = lootContext.getParamOrNull(LootContextParams.THIS_ENTITY); | ||||
|         return entity instanceof Player player && player.getAbilities().instabuild; | ||||
|         return entity instanceof Player player && player.isCreative(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|   | ||||
| @@ -4,20 +4,23 @@ | ||||
| 
 | ||||
| package dan200.computercraft.shared.details; | ||||
| 
 | ||||
| import com.google.gson.JsonParseException; | ||||
| import dan200.computercraft.shared.util.NBTUtil; | ||||
| import net.minecraft.core.component.DataComponentPatch; | ||||
| import net.minecraft.core.component.DataComponents; | ||||
| import net.minecraft.core.registries.BuiltInRegistries; | ||||
| import net.minecraft.nbt.ListTag; | ||||
| import net.minecraft.nbt.Tag; | ||||
| import net.minecraft.nbt.NbtOps; | ||||
| import net.minecraft.network.chat.Component; | ||||
| import net.minecraft.world.item.CreativeModeTab; | ||||
| import net.minecraft.world.item.CreativeModeTabs; | ||||
| import net.minecraft.world.item.EnchantedBookItem; | ||||
| import net.minecraft.world.item.ItemStack; | ||||
| import net.minecraft.world.item.enchantment.EnchantmentHelper; | ||||
| import net.minecraft.world.item.enchantment.ItemEnchantments; | ||||
| 
 | ||||
| import javax.annotation.Nullable; | ||||
| import java.util.*; | ||||
| import java.util.ArrayList; | ||||
| import java.util.HashMap; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| 
 | ||||
| /** | ||||
|  * Data providers for items. | ||||
| @@ -26,7 +29,9 @@ public class ItemDetails { | ||||
|     public static void fillBasic(Map<? super String, Object> data, ItemStack stack) { | ||||
|         data.put("name", DetailHelpers.getId(BuiltInRegistries.ITEM, stack.getItem())); | ||||
|         data.put("count", stack.getCount()); | ||||
|         var hash = NBTUtil.getNBTHash(stack.getTag()); | ||||
| 
 | ||||
|         var components = stack.getComponentsPatch(); | ||||
|         var hash = components.isEmpty() ? null : NBTUtil.getNBTHash(DataComponentPatch.CODEC.encodeStart(NbtOps.INSTANCE, components).result().orElse(null)); | ||||
|         if (hash != null) data.put("nbt", hash); | ||||
|     } | ||||
| 
 | ||||
| @@ -46,41 +51,14 @@ public class ItemDetails { | ||||
|         data.put("tags", DetailHelpers.getTags(stack.getTags())); | ||||
|         data.put("itemGroups", getItemGroups(stack)); | ||||
| 
 | ||||
|         var tag = stack.getTag(); | ||||
|         if (tag != null && tag.contains("display", Tag.TAG_COMPOUND)) { | ||||
|             var displayTag = tag.getCompound("display"); | ||||
|             if (displayTag.contains("Lore", Tag.TAG_LIST)) { | ||||
|                 var loreTag = displayTag.getList("Lore", Tag.TAG_STRING); | ||||
|                 data.put("lore", loreTag.stream() | ||||
|                     .map(ItemDetails::parseTextComponent) | ||||
|                     .filter(Objects::nonNull) | ||||
|                     .map(Component::getString) | ||||
|                     .toList()); | ||||
|             } | ||||
|         } | ||||
|         var lore = stack.get(DataComponents.LORE); | ||||
|         if (lore != null) data.put("lore", lore.lines().stream().map(Component::getString).toList()); | ||||
| 
 | ||||
|         /* | ||||
|          * Used to hide some data from ItemStack tooltip. | ||||
|          * @see https://minecraft.wiki/w/Tutorials/Command_NBT_tags | ||||
|          * @see ItemStack#getTooltip | ||||
|          */ | ||||
|         var hideFlags = tag != null ? tag.getInt("HideFlags") : 0; | ||||
| 
 | ||||
|         var enchants = getAllEnchants(stack, hideFlags); | ||||
|         var enchants = getAllEnchants(stack); | ||||
|         if (!enchants.isEmpty()) data.put("enchantments", enchants); | ||||
| 
 | ||||
|         if (tag != null && tag.getBoolean("Unbreakable") && (hideFlags & 4) == 0) { | ||||
|             data.put("unbreakable", true); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Nullable | ||||
|     private static Component parseTextComponent(Tag x) { | ||||
|         try { | ||||
|             return Component.Serializer.fromJson(x.getAsString()); | ||||
|         } catch (JsonParseException e) { | ||||
|             return null; | ||||
|         } | ||||
|         var unbreakable = stack.get(DataComponents.UNBREAKABLE); | ||||
|         if (unbreakable != null && unbreakable.showInTooltip()) data.put("unbreakable", true); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @@ -107,26 +85,13 @@ public class ItemDetails { | ||||
|     /** | ||||
|      * Retrieve all visible enchantments from given stack. Try to follow all tooltip rules : order and visibility. | ||||
|      * | ||||
|      * @param stack     Stack to analyse | ||||
|      * @param hideFlags An int used as bit field to provide visibility rules. | ||||
|      * @param stack Stack to analyse | ||||
|      * @return A filled list that contain all visible enchantments. | ||||
|      */ | ||||
|     private static List<Map<String, Object>> getAllEnchants(ItemStack stack, int hideFlags) { | ||||
|     private static List<Map<String, Object>> getAllEnchants(ItemStack stack) { | ||||
|         var enchants = new ArrayList<Map<String, Object>>(0); | ||||
| 
 | ||||
|         if (stack.getItem() instanceof EnchantedBookItem && (hideFlags & 32) == 0) { | ||||
|             addEnchantments(EnchantedBookItem.getEnchantments(stack), enchants); | ||||
|         } | ||||
| 
 | ||||
|         if (stack.isEnchanted() && (hideFlags & 1) == 0) { | ||||
|             /* | ||||
|              * Mimic the EnchantmentHelper.getEnchantments(ItemStack stack) behavior without special case for Enchanted book. | ||||
|              * I'll do that to have the same data than ones displayed in tooltip. | ||||
|              * @see EnchantmentHelper.getEnchantments(ItemStack stack) | ||||
|              */ | ||||
|             addEnchantments(stack.getEnchantmentTags(), enchants); | ||||
|         } | ||||
| 
 | ||||
|         addEnchantments(stack.get(DataComponents.STORED_ENCHANTMENTS), enchants); | ||||
|         addEnchantments(stack.get(DataComponents.ENCHANTMENTS), enchants); | ||||
|         return enchants; | ||||
|     } | ||||
| 
 | ||||
| @@ -138,18 +103,18 @@ public class ItemDetails { | ||||
|      * @see EnchantmentHelper | ||||
|      */ | ||||
|     @SuppressWarnings("NonApiType") | ||||
|     private static void addEnchantments(ListTag rawEnchants, ArrayList<Map<String, Object>> enchants) { | ||||
|         if (rawEnchants.isEmpty()) return; | ||||
|     private static void addEnchantments(@Nullable ItemEnchantments rawEnchants, ArrayList<Map<String, Object>> enchants) { | ||||
|         if (rawEnchants == null || rawEnchants.isEmpty()) return; | ||||
| 
 | ||||
|         enchants.ensureCapacity(enchants.size() + rawEnchants.size()); | ||||
| 
 | ||||
|         for (var entry : EnchantmentHelper.deserializeEnchantments(rawEnchants).entrySet()) { | ||||
|         for (var entry : rawEnchants.entrySet()) { | ||||
|             var enchantment = entry.getKey(); | ||||
|             var level = entry.getValue(); | ||||
|             var level = entry.getIntValue(); | ||||
|             var enchant = new HashMap<String, Object>(3); | ||||
|             enchant.put("name", DetailHelpers.getId(BuiltInRegistries.ENCHANTMENT, enchantment)); | ||||
|             enchant.put("name", DetailHelpers.getId(BuiltInRegistries.ENCHANTMENT, enchantment.value())); | ||||
|             enchant.put("level", level); | ||||
|             enchant.put("displayName", enchantment.getFullname(level).getString()); | ||||
|             enchant.put("displayName", enchantment.value().getFullname(level).getString()); | ||||
|             enchants.add(enchant); | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -11,6 +11,7 @@ import dan200.computercraft.impl.TurtleUpgrades; | ||||
| import dan200.computercraft.shared.ModRegistry; | ||||
| import dan200.computercraft.shared.pocket.items.PocketComputerItem; | ||||
| import dan200.computercraft.shared.turtle.items.TurtleItem; | ||||
| import dan200.computercraft.shared.util.DataComponentUtil; | ||||
| import net.minecraft.resources.ResourceLocation; | ||||
| import net.minecraft.world.item.ItemStack; | ||||
| 
 | ||||
| @@ -54,14 +55,14 @@ public final class RecipeModHelpers { | ||||
|         for (var turtleSupplier : TURTLES) { | ||||
|             var turtle = turtleSupplier.get(); | ||||
|             for (var upgrade : TurtleUpgrades.instance().getUpgrades()) { | ||||
|                 upgradeItems.add(turtle.create(-1, null, -1, null, UpgradeData.ofDefault(upgrade), 0, null)); | ||||
|                 upgradeItems.add(DataComponentUtil.createStack(turtle, ModRegistry.DataComponents.RIGHT_TURTLE_UPGRADE.get(), UpgradeData.ofDefault(upgrade))); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         for (var pocketSupplier : POCKET_COMPUTERS) { | ||||
|             var pocket = pocketSupplier.get(); | ||||
|             for (var upgrade : PocketUpgrades.instance().getUpgrades()) { | ||||
|                 upgradeItems.add(pocket.create(-1, null, -1, UpgradeData.ofDefault(upgrade))); | ||||
|                 upgradeItems.add(DataComponentUtil.createStack(pocket, ModRegistry.DataComponents.POCKET_UPGRADE.get(), UpgradeData.ofDefault(upgrade))); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|   | ||||
| @@ -11,8 +11,10 @@ import dan200.computercraft.api.upgrades.UpgradeBase; | ||||
| import dan200.computercraft.api.upgrades.UpgradeData; | ||||
| import dan200.computercraft.impl.PocketUpgrades; | ||||
| import dan200.computercraft.impl.TurtleUpgrades; | ||||
| import dan200.computercraft.shared.ModRegistry; | ||||
| import dan200.computercraft.shared.pocket.items.PocketComputerItem; | ||||
| import dan200.computercraft.shared.turtle.items.TurtleItem; | ||||
| import dan200.computercraft.shared.util.DataComponentUtil; | ||||
| import net.minecraft.core.NonNullList; | ||||
| import net.minecraft.world.item.Item; | ||||
| import net.minecraft.world.item.ItemStack; | ||||
| @@ -105,10 +107,10 @@ public class UpgradeRecipeGenerator<T> { | ||||
|     public List<T> findRecipesWithInput(ItemStack stack) { | ||||
|         setupCache(); | ||||
| 
 | ||||
|         if (stack.getItem() instanceof TurtleItem item) { | ||||
|         if (stack.getItem() instanceof TurtleItem) { | ||||
|             // Suggest possible upgrades which can be applied to this turtle | ||||
|             var left = item.getUpgradeWithData(stack, TurtleSide.LEFT); | ||||
|             var right = item.getUpgradeWithData(stack, TurtleSide.RIGHT); | ||||
|             var left = TurtleItem.getUpgradeWithData(stack, TurtleSide.LEFT); | ||||
|             var right = TurtleItem.getUpgradeWithData(stack, TurtleSide.RIGHT); | ||||
|             if (left != null && right != null) return List.of(); | ||||
| 
 | ||||
|             List<T> recipes = new ArrayList<>(); | ||||
| @@ -176,11 +178,11 @@ public class UpgradeRecipeGenerator<T> { | ||||
|      */ | ||||
|     public List<T> findRecipesWithOutput(ItemStack stack) { | ||||
|         // Find which upgrade this item currently has, and so how we could build it. | ||||
|         if (stack.getItem() instanceof TurtleItem item) { | ||||
|         if (stack.getItem() instanceof TurtleItem) { | ||||
|             List<T> recipes = new ArrayList<>(0); | ||||
| 
 | ||||
|             var left = item.getUpgradeWithData(stack, TurtleSide.LEFT); | ||||
|             var right = item.getUpgradeWithData(stack, TurtleSide.RIGHT); | ||||
|             var left = TurtleItem.getUpgradeWithData(stack, TurtleSide.LEFT); | ||||
|             var right = TurtleItem.getUpgradeWithData(stack, TurtleSide.RIGHT); | ||||
| 
 | ||||
|             // The turtle is facing towards us, so upgrades on the left are actually crafted on the right. | ||||
|             if (left != null) { | ||||
| @@ -215,18 +217,16 @@ public class UpgradeRecipeGenerator<T> { | ||||
|     } | ||||
| 
 | ||||
|     private static ItemStack turtleWith(ItemStack stack, @Nullable UpgradeData<ITurtleUpgrade> left, @Nullable UpgradeData<ITurtleUpgrade> right) { | ||||
|         var item = (TurtleItem) stack.getItem(); | ||||
|         return item.create( | ||||
|             item.getComputerID(stack), item.getLabel(stack), item.getColour(stack), | ||||
|             left, right, item.getFuelLevel(stack), item.getOverlay(stack) | ||||
|         ); | ||||
|         var newStack = stack.copyWithCount(1); | ||||
|         newStack.set(ModRegistry.DataComponents.LEFT_TURTLE_UPGRADE.get(), left); | ||||
|         newStack.set(ModRegistry.DataComponents.RIGHT_TURTLE_UPGRADE.get(), right); | ||||
|         return stack; | ||||
|     } | ||||
| 
 | ||||
|     private static ItemStack pocketWith(ItemStack stack, @Nullable UpgradeData<IPocketUpgrade> back) { | ||||
|         var item = (PocketComputerItem) stack.getItem(); | ||||
|         return item.create( | ||||
|             item.getComputerID(stack), item.getLabel(stack), item.getColour(stack), back | ||||
|         ); | ||||
|         var newStack = stack.copyWithCount(1); | ||||
|         newStack.set(ModRegistry.DataComponents.POCKET_UPGRADE.get(), back); | ||||
|         return stack; | ||||
|     } | ||||
| 
 | ||||
|     private T pocket(Ingredient upgrade, Ingredient pocketComputer, ItemStack result) { | ||||
| @@ -278,8 +278,8 @@ public class UpgradeRecipeGenerator<T> { | ||||
|                     var turtleItem = turtleSupplier.get(); | ||||
|                     recipes.add(turtle( | ||||
|                         ingredient, // Right upgrade, recipe on left | ||||
|                         Ingredient.of(turtleItem.create(-1, null, -1, null, null, 0, null)), | ||||
|                         turtleItem.create(-1, null, -1, null, UpgradeData.ofDefault(turtle), 0, null) | ||||
|                         Ingredient.of(new ItemStack(turtleItem)), | ||||
|                         DataComponentUtil.createStack(turtleItem, ModRegistry.DataComponents.RIGHT_TURTLE_UPGRADE.get(), UpgradeData.ofDefault(turtle)) | ||||
|                     )); | ||||
|                 } | ||||
|             } | ||||
| @@ -289,8 +289,8 @@ public class UpgradeRecipeGenerator<T> { | ||||
|                     var pocketItem = pocketSupplier.get(); | ||||
|                     recipes.add(pocket( | ||||
|                         ingredient, | ||||
|                         Ingredient.of(pocketItem.create(-1, null, -1, null)), | ||||
|                         pocketItem.create(-1, null, -1, UpgradeData.ofDefault(pocket)) | ||||
|                         Ingredient.of(pocketItem), | ||||
|                         DataComponentUtil.createStack(pocketItem, ModRegistry.DataComponents.POCKET_UPGRADE.get(), UpgradeData.ofDefault(pocket)) | ||||
|                     )); | ||||
|                 } | ||||
|             } | ||||
|   | ||||
| @@ -70,14 +70,11 @@ public class JEIComputerCraft implements IModPlugin { | ||||
|      * Distinguishes turtles by upgrades and family. | ||||
|      */ | ||||
|     private static final IIngredientSubtypeInterpreter<ItemStack> turtleSubtype = (stack, ctx) -> { | ||||
|         var item = stack.getItem(); | ||||
|         if (!(item instanceof TurtleItem turtle)) return IIngredientSubtypeInterpreter.NONE; | ||||
| 
 | ||||
|         var name = new StringBuilder("turtle:"); | ||||
| 
 | ||||
|         // Add left and right upgrades to the identifier | ||||
|         var left = turtle.getUpgrade(stack, TurtleSide.LEFT); | ||||
|         var right = turtle.getUpgrade(stack, TurtleSide.RIGHT); | ||||
|         var left = TurtleItem.getUpgrade(stack, TurtleSide.LEFT); | ||||
|         var right = TurtleItem.getUpgrade(stack, TurtleSide.RIGHT); | ||||
|         if (left != null) name.append(left.getUpgradeID()); | ||||
|         if (left != null && right != null) name.append('|'); | ||||
|         if (right != null) name.append(right.getUpgradeID()); | ||||
| @@ -89,9 +86,6 @@ public class JEIComputerCraft implements IModPlugin { | ||||
|      * Distinguishes pocket computers by upgrade and family. | ||||
|      */ | ||||
|     private static final IIngredientSubtypeInterpreter<ItemStack> pocketSubtype = (stack, ctx) -> { | ||||
|         var item = stack.getItem(); | ||||
|         if (!(item instanceof PocketComputerItem)) return IIngredientSubtypeInterpreter.NONE; | ||||
| 
 | ||||
|         var name = new StringBuilder("pocket:"); | ||||
| 
 | ||||
|         // Add the upgrade to the identifier | ||||
| @@ -104,11 +98,5 @@ public class JEIComputerCraft implements IModPlugin { | ||||
|     /** | ||||
|      * Distinguishes disks by colour. | ||||
|      */ | ||||
|     private static final IIngredientSubtypeInterpreter<ItemStack> diskSubtype = (stack, ctx) -> { | ||||
|         var item = stack.getItem(); | ||||
|         if (!(item instanceof DiskItem disk)) return IIngredientSubtypeInterpreter.NONE; | ||||
| 
 | ||||
|         var colour = disk.getColour(stack); | ||||
|         return colour == -1 ? IIngredientSubtypeInterpreter.NONE : String.format("%06x", colour); | ||||
|     }; | ||||
|     private static final IIngredientSubtypeInterpreter<ItemStack> diskSubtype = (stack, ctx) -> Integer.toString(DiskItem.getColour(stack)); | ||||
| } | ||||
|   | ||||
| @@ -10,43 +10,34 @@ import dan200.computercraft.api.filesystem.Mount; | ||||
| import dan200.computercraft.api.media.IMedia; | ||||
| import dan200.computercraft.core.util.Colour; | ||||
| import dan200.computercraft.shared.ModRegistry; | ||||
| import dan200.computercraft.shared.common.IColouredItem; | ||||
| import dan200.computercraft.shared.config.Config; | ||||
| import dan200.computercraft.shared.util.NonNegativeId; | ||||
| import net.minecraft.ChatFormatting; | ||||
| import net.minecraft.core.BlockPos; | ||||
| import net.minecraft.core.component.DataComponents; | ||||
| import net.minecraft.network.chat.Component; | ||||
| import net.minecraft.server.level.ServerLevel; | ||||
| import net.minecraft.world.entity.player.Player; | ||||
| import net.minecraft.world.item.Item; | ||||
| import net.minecraft.world.item.ItemStack; | ||||
| import net.minecraft.world.item.TooltipFlag; | ||||
| import net.minecraft.world.level.Level; | ||||
| import net.minecraft.world.item.component.DyedItemColor; | ||||
| import net.minecraft.world.level.LevelReader; | ||||
| 
 | ||||
| import javax.annotation.Nullable; | ||||
| import java.util.List; | ||||
| 
 | ||||
| public class DiskItem extends Item implements IMedia, IColouredItem { | ||||
|     private static final String NBT_ID = "DiskId"; | ||||
| 
 | ||||
| public class DiskItem extends Item implements IMedia { | ||||
|     public DiskItem(Properties settings) { | ||||
|         super(settings); | ||||
|     } | ||||
| 
 | ||||
|     public static ItemStack createFromIDAndColour(int id, @Nullable String label, int colour) { | ||||
|         var stack = new ItemStack(ModRegistry.Items.DISK.get()); | ||||
|         setDiskID(stack, id); | ||||
|         ModRegistry.Items.DISK.get().setLabel(stack, label); | ||||
|         IColouredItem.setColourBasic(stack, colour); | ||||
|         return stack; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void appendHoverText(ItemStack stack, @Nullable Level world, List<Component> list, TooltipFlag options) { | ||||
|     public void appendHoverText(ItemStack stack, TooltipContext context, List<Component> list, TooltipFlag options) { | ||||
|         if (options.isAdvanced()) { | ||||
|             var id = getDiskID(stack); | ||||
|             if (id >= 0) { | ||||
|                 list.add(Component.translatable("gui.computercraft.tooltip.disk_id", id) | ||||
|             var id = stack.get(ModRegistry.DataComponents.DISK_ID.get()); | ||||
|             if (id != null) { | ||||
|                 list.add(Component.translatable("gui.computercraft.tooltip.disk_id", id.id()) | ||||
|                     .withStyle(ChatFormatting.GRAY)); | ||||
|             } | ||||
|         } | ||||
| @@ -59,41 +50,27 @@ public class DiskItem extends Item implements IMedia, IColouredItem { | ||||
| 
 | ||||
|     @Override | ||||
|     public @Nullable String getLabel(ItemStack stack) { | ||||
|         return stack.hasCustomHoverName() ? stack.getHoverName().getString() : null; | ||||
|         var label = stack.get(DataComponents.CUSTOM_NAME); | ||||
|         return label != null ? label.getString() : null; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public boolean setLabel(ItemStack stack, @Nullable String label) { | ||||
|         if (label != null) { | ||||
|             stack.setHoverName(Component.literal(label)); | ||||
|         } else { | ||||
|             stack.resetHoverName(); | ||||
|         } | ||||
|         stack.set(DataComponents.CUSTOM_NAME, label != null ? Component.literal(label) : null); | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public @Nullable Mount createDataMount(ItemStack stack, ServerLevel level) { | ||||
|         var diskID = getDiskID(stack); | ||||
|         if (diskID < 0) { | ||||
|             diskID = ComputerCraftAPI.createUniqueNumberedSaveDir(level.getServer(), "disk"); | ||||
|             setDiskID(stack, diskID); | ||||
|         } | ||||
|         var diskID = NonNegativeId.getOrCreate(level.getServer(), stack, ModRegistry.DataComponents.DISK_ID.get(), "disk"); | ||||
|         return ComputerCraftAPI.createSaveDirMount(level.getServer(), "disk/" + diskID, Config.floppySpaceLimit); | ||||
|     } | ||||
| 
 | ||||
|     public static int getDiskID(ItemStack stack) { | ||||
|         var nbt = stack.getTag(); | ||||
|         return nbt != null && nbt.contains(NBT_ID) ? nbt.getInt(NBT_ID) : -1; | ||||
|         return NonNegativeId.getId(stack.get(ModRegistry.DataComponents.DISK_ID.get())); | ||||
|     } | ||||
| 
 | ||||
|     private static void setDiskID(ItemStack stack, int id) { | ||||
|         if (id >= 0) stack.getOrCreateTag().putInt(NBT_ID, id); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public int getColour(ItemStack stack) { | ||||
|         var colour = IColouredItem.getColourBasic(stack); | ||||
|         return colour == -1 ? Colour.WHITE.getHex() : colour; | ||||
|     public static int getColour(ItemStack stack) { | ||||
|         return DyedItemColor.getOrDefault(stack, Colour.WHITE.getARGB()); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -0,0 +1,104 @@ | ||||
| // SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers | ||||
| // | ||||
| // SPDX-License-Identifier: MPL-2.0 | ||||
| 
 | ||||
| package dan200.computercraft.shared.media.items; | ||||
| 
 | ||||
| import com.mojang.serialization.Codec; | ||||
| import com.mojang.serialization.DataResult; | ||||
| import com.mojang.serialization.codecs.RecordCodecBuilder; | ||||
| import dan200.computercraft.core.terminal.Terminal; | ||||
| import io.netty.buffer.ByteBuf; | ||||
| import net.minecraft.network.codec.ByteBufCodecs; | ||||
| import net.minecraft.network.codec.StreamCodec; | ||||
| 
 | ||||
| import java.util.Arrays; | ||||
| import java.util.List; | ||||
| 
 | ||||
| /** | ||||
|  * The contents of a printout. | ||||
|  * | ||||
|  * @param title The title of this printout. | ||||
|  * @param lines A list of lines for this printout. | ||||
|  * @see PrintoutItem | ||||
|  * @see dan200.computercraft.shared.ModRegistry.DataComponents#PRINTOUT | ||||
|  */ | ||||
| public record PrintoutData(String title, List<Line> lines) { | ||||
|     public static final int LINE_LENGTH = 25; | ||||
|     public static final int LINES_PER_PAGE = 21; | ||||
|     public static final int MAX_PAGES = 16; | ||||
| 
 | ||||
|     /** | ||||
|      * An empty printout. This has no title, and is a single page of empty lines. | ||||
|      */ | ||||
|     public static final PrintoutData EMPTY; | ||||
| 
 | ||||
|     static { | ||||
|         var lines = new Line[LINES_PER_PAGE]; | ||||
|         Arrays.fill(lines, Line.EMPTY); | ||||
|         EMPTY = new PrintoutData("", List.of(lines)); | ||||
|     } | ||||
| 
 | ||||
|     private static final Codec<String> LINE_TEXT = Codec.STRING.validate(x -> x.length() == LINE_LENGTH | ||||
|         ? DataResult.success(x) | ||||
|         : DataResult.error(() -> "Expected string of length " + LINE_LENGTH)); | ||||
| 
 | ||||
|     private static final Codec<Line> LINE_CODEC = RecordCodecBuilder.<Line>create(s -> s.group( | ||||
|         LINE_TEXT.fieldOf("text").forGetter(Line::text), | ||||
|         LINE_TEXT.fieldOf("foreground").forGetter(Line::foreground) | ||||
|     ).apply(s, Line::new)); | ||||
| 
 | ||||
|     private static final StreamCodec<ByteBuf, Line> LINE_STREAM_CODEC = StreamCodec.composite( | ||||
|         ByteBufCodecs.STRING_UTF8, Line::text, | ||||
|         ByteBufCodecs.STRING_UTF8, Line::foreground, | ||||
|         Line::new | ||||
|     ); | ||||
| 
 | ||||
|     public static final Codec<PrintoutData> CODEC = RecordCodecBuilder.<PrintoutData>create(s -> s.group( | ||||
|         Codec.STRING.optionalFieldOf("title", "").forGetter(PrintoutData::title), | ||||
|         LINE_CODEC.listOf(1, MAX_PAGES * LINES_PER_PAGE) | ||||
|             .validate(PrintoutData::validateLines) | ||||
|             .fieldOf("lines").forGetter(PrintoutData::lines) | ||||
|     ).apply(s, PrintoutData::new)); | ||||
| 
 | ||||
|     public static final StreamCodec<ByteBuf, PrintoutData> STREAM_CODEC = StreamCodec.composite( | ||||
|         ByteBufCodecs.STRING_UTF8, PrintoutData::title, | ||||
|         LINE_STREAM_CODEC.apply(ByteBufCodecs.list(MAX_PAGES * LINES_PER_PAGE)), PrintoutData::lines, | ||||
|         PrintoutData::new | ||||
|     ); | ||||
| 
 | ||||
|     /** | ||||
|      * A single line on our printed pages. | ||||
|      * | ||||
|      * @param text       The text for this line. | ||||
|      * @param foreground The foreground colour of this line, in a format equivalent to {@link Terminal#getTextColourLine(int)}. | ||||
|      */ | ||||
|     public record Line(String text, String foreground) { | ||||
|         public static final Line EMPTY = new Line(" ".repeat(LINE_LENGTH), "f".repeat(LINE_LENGTH)); | ||||
| 
 | ||||
|         public Line { | ||||
|             if (text.length() != LINE_LENGTH) throw new IllegalArgumentException("text is of wrong length"); | ||||
|             if (foreground.length() != LINE_LENGTH) throw new IllegalArgumentException("foreground is of wrong length"); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public PrintoutData { | ||||
|         validateLines(lines).getOrThrow(IllegalArgumentException::new); | ||||
|     } | ||||
| 
 | ||||
|     private static DataResult<List<Line>> validateLines(List<Line> lines) { | ||||
|         if (lines.isEmpty()) return DataResult.error(() -> "Expected non-empty list of lines"); | ||||
|         if ((lines.size() % LINES_PER_PAGE) != 0) return DataResult.error(() -> "Not enough lines for a page"); | ||||
|         if (lines.size() > LINES_PER_PAGE * MAX_PAGES) return DataResult.error(() -> "Too many pages"); | ||||
|         return DataResult.success(lines); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the number of pages in this printout. | ||||
|      * | ||||
|      * @return The number of pages. | ||||
|      */ | ||||
|     public int pages() { | ||||
|         return Math.ceilDiv(lines.size(), LINES_PER_PAGE); | ||||
|     } | ||||
| } | ||||
| @@ -17,19 +17,9 @@ import net.minecraft.world.item.ItemStack; | ||||
| import net.minecraft.world.item.TooltipFlag; | ||||
| import net.minecraft.world.level.Level; | ||||
| 
 | ||||
| import javax.annotation.Nullable; | ||||
| import java.util.List; | ||||
| 
 | ||||
| public class PrintoutItem extends Item { | ||||
|     private static final String NBT_TITLE = "Title"; | ||||
|     private static final String NBT_PAGES = "Pages"; | ||||
|     private static final String NBT_LINE_TEXT = "Text"; | ||||
|     private static final String NBT_LINE_COLOUR = "Color"; | ||||
| 
 | ||||
|     public static final int LINES_PER_PAGE = 21; | ||||
|     public static final int LINE_MAX_LENGTH = 25; | ||||
|     public static final int MAX_PAGES = 16; | ||||
| 
 | ||||
|     public enum Type { | ||||
|         PAGE, | ||||
|         PAGES, | ||||
| @@ -44,9 +34,9 @@ public class PrintoutItem extends Item { | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void appendHoverText(ItemStack stack, @Nullable Level world, List<Component> list, TooltipFlag options) { | ||||
|     public void appendHoverText(ItemStack stack, TooltipContext context, List<Component> list, TooltipFlag options) { | ||||
|         var title = getTitle(stack); | ||||
|         if (title != null && !title.isEmpty()) list.add(Component.literal(title)); | ||||
|         if (!title.isEmpty()) list.add(Component.literal(title)); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
| @@ -58,70 +48,17 @@ public class PrintoutItem extends Item { | ||||
|         return new InteractionResultHolder<>(InteractionResult.sidedSuccess(world.isClientSide), player.getItemInHand(hand)); | ||||
|     } | ||||
| 
 | ||||
|     private ItemStack createFromTitleAndText(@Nullable String title, @Nullable String[] text, @Nullable String[] colours) { | ||||
|         var stack = new ItemStack(this); | ||||
| 
 | ||||
|         // Build NBT | ||||
|         if (title != null) stack.getOrCreateTag().putString(NBT_TITLE, title); | ||||
|         if (text != null) { | ||||
|             var tag = stack.getOrCreateTag(); | ||||
|             tag.putInt(NBT_PAGES, text.length / LINES_PER_PAGE); | ||||
|             for (var i = 0; i < text.length; i++) { | ||||
|                 if (text[i] != null) tag.putString(NBT_LINE_TEXT + i, text[i]); | ||||
|             } | ||||
|         } | ||||
|         if (colours != null) { | ||||
|             var tag = stack.getOrCreateTag(); | ||||
|             for (var i = 0; i < colours.length; i++) { | ||||
|                 if (colours[i] != null) tag.putString(NBT_LINE_COLOUR + i, colours[i]); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         return stack; | ||||
|     } | ||||
| 
 | ||||
|     public static ItemStack createSingleFromTitleAndText(@Nullable String title, @Nullable String[] text, @Nullable String[] colours) { | ||||
|         return ModRegistry.Items.PRINTED_PAGE.get().createFromTitleAndText(title, text, colours); | ||||
|     } | ||||
| 
 | ||||
|     public static ItemStack createMultipleFromTitleAndText(@Nullable String title, @Nullable String[] text, @Nullable String[] colours) { | ||||
|         return ModRegistry.Items.PRINTED_PAGES.get().createFromTitleAndText(title, text, colours); | ||||
|     } | ||||
| 
 | ||||
|     public static ItemStack createBookFromTitleAndText(@Nullable String title, @Nullable String[] text, @Nullable String[] colours) { | ||||
|         return ModRegistry.Items.PRINTED_BOOK.get().createFromTitleAndText(title, text, colours); | ||||
|     } | ||||
| 
 | ||||
|     public Type getType() { | ||||
|         return type; | ||||
|     } | ||||
| 
 | ||||
|     public static String getTitle(ItemStack stack) { | ||||
|         var nbt = stack.getTag(); | ||||
|         return nbt != null && nbt.contains(NBT_TITLE) ? nbt.getString(NBT_TITLE) : ""; | ||||
|         var nbt = stack.get(ModRegistry.DataComponents.PRINTOUT.get()); | ||||
|         return nbt == null ? "" : nbt.title(); | ||||
|     } | ||||
| 
 | ||||
|     public static int getPageCount(ItemStack stack) { | ||||
|         var nbt = stack.getTag(); | ||||
|         return nbt != null && nbt.contains(NBT_PAGES) ? nbt.getInt(NBT_PAGES) : 1; | ||||
|     } | ||||
| 
 | ||||
|     public static String[] getText(ItemStack stack) { | ||||
|         return getLines(stack, NBT_LINE_TEXT); | ||||
|     } | ||||
| 
 | ||||
|     public static String[] getColours(ItemStack stack) { | ||||
|         return getLines(stack, NBT_LINE_COLOUR); | ||||
|     } | ||||
| 
 | ||||
|     private static String[] getLines(ItemStack stack, String prefix) { | ||||
|         var nbt = stack.getTag(); | ||||
|         var numLines = getPageCount(stack) * LINES_PER_PAGE; | ||||
|         var lines = new String[numLines]; | ||||
|         for (var i = 0; i < lines.length; i++) { | ||||
|             lines[i] = nbt != null ? nbt.getString(prefix + i) : ""; | ||||
|         } | ||||
|         return lines; | ||||
|         var nbt = stack.get(ModRegistry.DataComponents.PRINTOUT.get()); | ||||
|         return nbt == null ? 1 : nbt.pages(); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -0,0 +1,38 @@ | ||||
| // SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers | ||||
| // | ||||
| // SPDX-License-Identifier: MPL-2.0 | ||||
| 
 | ||||
| package dan200.computercraft.shared.media.items; | ||||
| 
 | ||||
| import com.mojang.serialization.Codec; | ||||
| import com.mojang.serialization.codecs.RecordCodecBuilder; | ||||
| import dan200.computercraft.shared.ModRegistry; | ||||
| import net.minecraft.core.component.DataComponentHolder; | ||||
| import net.minecraft.network.FriendlyByteBuf; | ||||
| import net.minecraft.network.codec.ByteBufCodecs; | ||||
| import net.minecraft.network.codec.StreamCodec; | ||||
| 
 | ||||
| /** | ||||
|  * Stores information about a {@linkplain TreasureDiskItem treasure disk's} mount. | ||||
|  * | ||||
|  * @param name The name/title of the disk. | ||||
|  * @param path The subpath to the resource | ||||
|  * @see ModRegistry.DataComponents#TREASURE_DISK | ||||
|  */ | ||||
| public record TreasureDisk(String name, String path) { | ||||
|     public static final Codec<TreasureDisk> CODEC = RecordCodecBuilder.create(i -> i.group( | ||||
|         Codec.STRING.fieldOf("name").forGetter(TreasureDisk::name), | ||||
|         Codec.STRING.fieldOf("path").forGetter(TreasureDisk::path) | ||||
|     ).apply(i, TreasureDisk::new)); | ||||
| 
 | ||||
|     public static final StreamCodec<FriendlyByteBuf, TreasureDisk> STREAM_CODEC = StreamCodec.composite( | ||||
|         ByteBufCodecs.STRING_UTF8, TreasureDisk::name, | ||||
|         ByteBufCodecs.STRING_UTF8, TreasureDisk::path, | ||||
|         TreasureDisk::new | ||||
|     ); | ||||
| 
 | ||||
|     public static String getTitle(DataComponentHolder holder) { | ||||
|         var nbt = holder.get(ModRegistry.DataComponents.TREASURE_DISK.get()); | ||||
|         return nbt != null ? nbt.name() : "'missingno' by how did you get this anyway?"; | ||||
|     } | ||||
| } | ||||
| @@ -9,7 +9,6 @@ import dan200.computercraft.api.ComputerCraftAPI; | ||||
| import dan200.computercraft.api.filesystem.Mount; | ||||
| import dan200.computercraft.api.media.IMedia; | ||||
| import dan200.computercraft.core.filesystem.SubMount; | ||||
| import dan200.computercraft.core.util.Colour; | ||||
| import dan200.computercraft.shared.ModRegistry; | ||||
| import net.minecraft.core.BlockPos; | ||||
| import net.minecraft.network.chat.Component; | ||||
| @@ -18,7 +17,6 @@ import net.minecraft.world.entity.player.Player; | ||||
| import net.minecraft.world.item.Item; | ||||
| import net.minecraft.world.item.ItemStack; | ||||
| import net.minecraft.world.item.TooltipFlag; | ||||
| import net.minecraft.world.level.Level; | ||||
| import net.minecraft.world.level.LevelReader; | ||||
| 
 | ||||
| import javax.annotation.Nullable; | ||||
| @@ -26,18 +24,13 @@ import java.io.IOException; | ||||
| import java.util.List; | ||||
| 
 | ||||
| public class TreasureDiskItem extends Item implements IMedia { | ||||
|     private static final String NBT_TITLE = "Title"; | ||||
|     private static final String NBT_COLOUR = "Colour"; | ||||
|     private static final String NBT_SUB_PATH = "SubPath"; | ||||
| 
 | ||||
|     public TreasureDiskItem(Properties settings) { | ||||
|         super(settings); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void appendHoverText(ItemStack stack, @Nullable Level world, List<Component> list, TooltipFlag tooltipOptions) { | ||||
|         var label = getTitle(stack); | ||||
|         if (!label.isEmpty()) list.add(Component.literal(label)); | ||||
|     public void appendHoverText(ItemStack stack, TooltipContext context, List<Component> list, TooltipFlag tooltipOptions) { | ||||
|         list.add(Component.literal(TreasureDisk.getTitle(stack))); | ||||
|     } | ||||
| 
 | ||||
|     @ForgeOverride | ||||
| @@ -47,7 +40,7 @@ public class TreasureDiskItem extends Item implements IMedia { | ||||
| 
 | ||||
|     @Override | ||||
|     public String getLabel(ItemStack stack) { | ||||
|         return getTitle(stack); | ||||
|         return TreasureDisk.getTitle(stack); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
| @@ -55,7 +48,10 @@ public class TreasureDiskItem extends Item implements IMedia { | ||||
|         var rootTreasure = ComputerCraftAPI.createResourceMount(level.getServer(), "computercraft", "lua/treasure"); | ||||
|         if (rootTreasure == null) return null; | ||||
| 
 | ||||
|         var subPath = getSubPath(stack); | ||||
|         var treasureDisk = stack.get(ModRegistry.DataComponents.TREASURE_DISK.get()); | ||||
|         if (treasureDisk == null) return null; | ||||
| 
 | ||||
|         var subPath = treasureDisk.path(); | ||||
|         try { | ||||
|             if (rootTreasure.exists(subPath)) { | ||||
|                 return new SubMount(rootTreasure, subPath); | ||||
| @@ -68,37 +64,4 @@ public class TreasureDiskItem extends Item implements IMedia { | ||||
|             return null; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public static ItemStack create(String subPath, int colourIndex) { | ||||
|         var result = new ItemStack(ModRegistry.Items.TREASURE_DISK.get()); | ||||
|         var nbt = result.getOrCreateTag(); | ||||
|         nbt.putString(NBT_SUB_PATH, subPath); | ||||
| 
 | ||||
|         var slash = subPath.indexOf('/'); | ||||
|         if (slash >= 0) { | ||||
|             var author = subPath.substring(0, slash); | ||||
|             var title = subPath.substring(slash + 1); | ||||
|             nbt.putString(NBT_TITLE, "\"" + title + "\" by " + author); | ||||
|         } else { | ||||
|             nbt.putString(NBT_TITLE, "untitled"); | ||||
|         } | ||||
|         nbt.putInt(NBT_COLOUR, Colour.values()[colourIndex].getHex()); | ||||
| 
 | ||||
|         return result; | ||||
|     } | ||||
| 
 | ||||
|     private static String getTitle(ItemStack stack) { | ||||
|         var nbt = stack.getTag(); | ||||
|         return nbt != null && nbt.contains(NBT_TITLE) ? nbt.getString(NBT_TITLE) : "'missingno' by how did you get this anyway?"; | ||||
|     } | ||||
| 
 | ||||
|     private static String getSubPath(ItemStack stack) { | ||||
|         var nbt = stack.getTag(); | ||||
|         return nbt != null && nbt.contains(NBT_SUB_PATH) ? nbt.getString(NBT_SUB_PATH) : "dan200/alongtimeago"; | ||||
|     } | ||||
| 
 | ||||
|     public static int getColour(ItemStack stack) { | ||||
|         var nbt = stack.getTag(); | ||||
|         return nbt != null && nbt.contains(NBT_COLOUR) ? nbt.getInt(NBT_COLOUR) : Colour.BLUE.getHex(); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -6,14 +6,16 @@ package dan200.computercraft.shared.media.recipes; | ||||
| 
 | ||||
| import dan200.computercraft.core.util.Colour; | ||||
| import dan200.computercraft.shared.ModRegistry; | ||||
| import dan200.computercraft.shared.media.items.DiskItem; | ||||
| import dan200.computercraft.shared.platform.PlatformHelper; | ||||
| import dan200.computercraft.shared.util.ColourTracker; | ||||
| import dan200.computercraft.shared.util.ColourUtils; | ||||
| import net.minecraft.core.RegistryAccess; | ||||
| import dan200.computercraft.shared.util.DataComponentUtil; | ||||
| import net.minecraft.core.HolderLookup; | ||||
| import net.minecraft.core.component.DataComponents; | ||||
| import net.minecraft.world.inventory.CraftingContainer; | ||||
| import net.minecraft.world.item.ItemStack; | ||||
| import net.minecraft.world.item.Items; | ||||
| import net.minecraft.world.item.component.DyedItemColor; | ||||
| import net.minecraft.world.item.crafting.CraftingBookCategory; | ||||
| import net.minecraft.world.item.crafting.CustomRecipe; | ||||
| import net.minecraft.world.item.crafting.Ingredient; | ||||
| @@ -53,7 +55,7 @@ public class DiskRecipe extends CustomRecipe { | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public ItemStack assemble(CraftingContainer inv, RegistryAccess registryAccess) { | ||||
|     public ItemStack assemble(CraftingContainer inv, HolderLookup.Provider registryAccess) { | ||||
|         var tracker = new ColourTracker(); | ||||
| 
 | ||||
|         for (var i = 0; i < inv.getContainerSize(); i++) { | ||||
| @@ -67,7 +69,7 @@ public class DiskRecipe extends CustomRecipe { | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return DiskItem.createFromIDAndColour(-1, null, tracker.hasColour() ? tracker.getColour() : Colour.BLUE.getHex()); | ||||
|         return DataComponentUtil.createStack(ModRegistry.Items.DISK.get(), DataComponents.DYED_COLOR, new DyedItemColor(tracker.hasColour() ? tracker.getColour() : Colour.BLUE.getHex(), false)); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
| @@ -76,8 +78,8 @@ public class DiskRecipe extends CustomRecipe { | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public ItemStack getResultItem(RegistryAccess registryAccess) { | ||||
|         return DiskItem.createFromIDAndColour(-1, null, Colour.BLUE.getHex()); | ||||
|     public ItemStack getResultItem(HolderLookup.Provider registryAccess) { | ||||
|         return DataComponentUtil.createStack(ModRegistry.Items.DISK.get(), DataComponents.DYED_COLOR, new DyedItemColor(Colour.BLUE.getHex(), false)); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|   | ||||
| @@ -5,9 +5,11 @@ | ||||
| package dan200.computercraft.shared.media.recipes; | ||||
| 
 | ||||
| import dan200.computercraft.shared.ModRegistry; | ||||
| import dan200.computercraft.shared.media.items.PrintoutData; | ||||
| import dan200.computercraft.shared.media.items.PrintoutItem; | ||||
| import dan200.computercraft.shared.platform.PlatformHelper; | ||||
| import net.minecraft.core.RegistryAccess; | ||||
| import dan200.computercraft.shared.util.DataComponentUtil; | ||||
| import net.minecraft.core.HolderLookup; | ||||
| import net.minecraft.world.inventory.CraftingContainer; | ||||
| import net.minecraft.world.item.ItemStack; | ||||
| import net.minecraft.world.item.Items; | ||||
| @@ -17,6 +19,8 @@ import net.minecraft.world.item.crafting.Ingredient; | ||||
| import net.minecraft.world.item.crafting.RecipeSerializer; | ||||
| import net.minecraft.world.level.Level; | ||||
| 
 | ||||
| import java.util.List; | ||||
| 
 | ||||
| public final class PrintoutRecipe extends CustomRecipe { | ||||
|     private final Ingredient leather; | ||||
|     private final Ingredient string; | ||||
| @@ -35,8 +39,8 @@ public final class PrintoutRecipe extends CustomRecipe { | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public ItemStack getResultItem(RegistryAccess registryAccess) { | ||||
|         return PrintoutItem.createMultipleFromTitleAndText(null, null, null); | ||||
|     public ItemStack getResultItem(HolderLookup.Provider registryAccess) { | ||||
|         return new ItemStack(ModRegistry.Items.PRINTED_PAGES.get()); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
| @@ -45,7 +49,7 @@ public final class PrintoutRecipe extends CustomRecipe { | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public ItemStack assemble(CraftingContainer inventory, RegistryAccess registryAccess) { | ||||
|     public ItemStack assemble(CraftingContainer inventory, HolderLookup.Provider registryAccess) { | ||||
|         // See if we match the recipe, and extract the input disk ID and dye colour | ||||
|         var numPages = 0; | ||||
|         var numPrintouts = 0; | ||||
| @@ -82,43 +86,30 @@ public final class PrintoutRecipe extends CustomRecipe { | ||||
|         } | ||||
| 
 | ||||
|         // Build some pages with what was passed in | ||||
|         if (numPages <= PrintoutItem.MAX_PAGES && stringFound && printoutFound && numPrintouts >= (leatherFound ? 1 : 2)) { | ||||
|         if (numPages <= PrintoutData.MAX_PAGES && stringFound && printoutFound && numPrintouts >= (leatherFound ? 1 : 2)) { | ||||
|             if (printouts == null) throw new IllegalStateException("Printouts must be non-null"); | ||||
|             var text = new String[numPages * PrintoutItem.LINES_PER_PAGE]; | ||||
|             var colours = new String[numPages * PrintoutItem.LINES_PER_PAGE]; | ||||
|             var lines = new PrintoutData.Line[numPages * PrintoutData.LINES_PER_PAGE]; | ||||
|             var line = 0; | ||||
| 
 | ||||
|             for (var printout = 0; printout < numPrintouts; printout++) { | ||||
|                 var stack = printouts[printout]; | ||||
|                 if (stack.getItem() instanceof PrintoutItem) { | ||||
|                 var pageText = printouts[printout].get(ModRegistry.DataComponents.PRINTOUT.get()); | ||||
|                 if (pageText != null) { | ||||
|                     // Add a printout | ||||
|                     var pageText = PrintoutItem.getText(printouts[printout]); | ||||
|                     var pageColours = PrintoutItem.getColours(printouts[printout]); | ||||
|                     for (var pageLine = 0; pageLine < pageText.length; pageLine++) { | ||||
|                         text[line] = pageText[pageLine]; | ||||
|                         colours[line] = pageColours[pageLine]; | ||||
|                         line++; | ||||
|                     } | ||||
|                     for (var pageLine : pageText.lines()) lines[line++] = pageLine; | ||||
|                 } else { | ||||
|                     // Add a blank page | ||||
|                     for (var pageLine = 0; pageLine < PrintoutItem.LINES_PER_PAGE; pageLine++) { | ||||
|                         text[line] = ""; | ||||
|                         colours[line] = ""; | ||||
|                         line++; | ||||
|                     for (var pageLine = 0; pageLine < PrintoutData.LINES_PER_PAGE; pageLine++) { | ||||
|                         lines[line++] = PrintoutData.Line.EMPTY; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             String title = null; | ||||
|             if (printouts[0].getItem() instanceof PrintoutItem) { | ||||
|                 title = PrintoutItem.getTitle(printouts[0]); | ||||
|             } | ||||
|             var title = PrintoutItem.getTitle(printouts[0]); | ||||
| 
 | ||||
|             if (leatherFound) { | ||||
|                 return PrintoutItem.createBookFromTitleAndText(title, text, colours); | ||||
|             } else { | ||||
|                 return PrintoutItem.createMultipleFromTitleAndText(title, text, colours); | ||||
|             } | ||||
|             return DataComponentUtil.createStack( | ||||
|                 leatherFound ? ModRegistry.Items.PRINTED_BOOK.get() : ModRegistry.Items.PRINTED_PAGES.get(), | ||||
|                 ModRegistry.DataComponents.PRINTOUT.get(), new PrintoutData(title, List.of(lines)) | ||||
|             ); | ||||
|         } | ||||
| 
 | ||||
|         return ItemStack.EMPTY; | ||||
|   | ||||
| @@ -1,28 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers | ||||
| // | ||||
| // SPDX-License-Identifier: MPL-2.0 | ||||
| 
 | ||||
| package dan200.computercraft.shared.network; | ||||
| 
 | ||||
| import net.minecraft.network.protocol.common.custom.CustomPacketPayload; | ||||
| import net.minecraft.resources.ResourceLocation; | ||||
| 
 | ||||
| /** | ||||
|  * A type of message to send over the network. | ||||
|  * <p> | ||||
|  * Much like recipe or argument serialisers, each type of {@link NetworkMessage} should have a unique type associated | ||||
|  * with it. This holds platform-specific information about how the packet should be sent over the network. | ||||
|  * | ||||
|  * @param <T> The type of message to send | ||||
|  * @see NetworkMessages | ||||
|  * @see NetworkMessage#type() | ||||
|  */ | ||||
| public interface MessageType<T extends NetworkMessage<?>> { | ||||
|     /** | ||||
|      * Get the id of this message type. This will be used as the custom packet channel name. | ||||
|      * | ||||
|      * @return The id of this message type. | ||||
|      * @see CustomPacketPayload#id() | ||||
|      */ | ||||
|     ResourceLocation id(); | ||||
| } | ||||
| @@ -6,8 +6,7 @@ package dan200.computercraft.shared.network; | ||||
| 
 | ||||
| import dan200.computercraft.shared.network.client.ClientNetworkContext; | ||||
| import dan200.computercraft.shared.network.server.ServerNetworkContext; | ||||
| import net.minecraft.network.FriendlyByteBuf; | ||||
| 
 | ||||
| import net.minecraft.network.protocol.common.custom.CustomPacketPayload; | ||||
| 
 | ||||
| /** | ||||
|  * The base interface for any message which will be sent to the client or server. | ||||
| @@ -16,23 +15,7 @@ import net.minecraft.network.FriendlyByteBuf; | ||||
|  * @see ClientNetworkContext | ||||
|  * @see ServerNetworkContext | ||||
|  */ | ||||
| public interface NetworkMessage<T> { | ||||
|     /** | ||||
|      * Get the type of this message. | ||||
|      * | ||||
|      * @return The type of this message. | ||||
|      */ | ||||
|     MessageType<?> type(); | ||||
| 
 | ||||
|     /** | ||||
|      * Write this packet to a buffer. | ||||
|      * <p> | ||||
|      * This may be called on any thread, so this should be a pure operation. | ||||
|      * | ||||
|      * @param buf The buffer to write data to. | ||||
|      */ | ||||
|     void write(FriendlyByteBuf buf); | ||||
| 
 | ||||
| public interface NetworkMessage<T> extends CustomPacketPayload { | ||||
|     /** | ||||
|      * Handle this {@link NetworkMessage}. | ||||
|      * | ||||
|   | ||||
| @@ -8,59 +8,61 @@ import dan200.computercraft.api.ComputerCraftAPI; | ||||
| import dan200.computercraft.shared.network.client.*; | ||||
| import dan200.computercraft.shared.network.server.*; | ||||
| import dan200.computercraft.shared.platform.PlatformHelper; | ||||
| import net.minecraft.network.FriendlyByteBuf; | ||||
| import net.minecraft.network.RegistryFriendlyByteBuf; | ||||
| import net.minecraft.network.codec.StreamCodec; | ||||
| import net.minecraft.network.protocol.common.custom.CustomPacketPayload; | ||||
| import net.minecraft.resources.ResourceLocation; | ||||
| 
 | ||||
| import java.util.*; | ||||
| 
 | ||||
| /** | ||||
|  * List of all {@link MessageType}s provided by CC: Tweaked. | ||||
|  * List of all {@link CustomPacketPayload.Type}s provided by CC: Tweaked. | ||||
|  * | ||||
|  * @see PlatformHelper The platform helper is used to send packets. | ||||
|  */ | ||||
| public final class NetworkMessages { | ||||
|     private static final Set<String> seenChannel = new HashSet<>(); | ||||
|     private static final List<MessageType<? extends NetworkMessage<ServerNetworkContext>>> serverMessages = new ArrayList<>(); | ||||
|     private static final List<MessageType<? extends NetworkMessage<ClientNetworkContext>>> clientMessages = new ArrayList<>(); | ||||
|     private static final List<CustomPacketPayload.TypeAndCodec<RegistryFriendlyByteBuf, ? extends NetworkMessage<ServerNetworkContext>>> serverMessages = new ArrayList<>(); | ||||
|     private static final List<CustomPacketPayload.TypeAndCodec<RegistryFriendlyByteBuf, ? extends NetworkMessage<ClientNetworkContext>>> clientMessages = new ArrayList<>(); | ||||
| 
 | ||||
|     public static final MessageType<ComputerActionServerMessage> COMPUTER_ACTION = registerServerbound("computer_action", ComputerActionServerMessage::new); | ||||
|     public static final MessageType<QueueEventServerMessage> QUEUE_EVENT = registerServerbound("queue_event", QueueEventServerMessage::new); | ||||
|     public static final MessageType<KeyEventServerMessage> KEY_EVENT = registerServerbound("key_event", KeyEventServerMessage::new); | ||||
|     public static final MessageType<MouseEventServerMessage> MOUSE_EVENT = registerServerbound("mouse_event", MouseEventServerMessage::new); | ||||
|     public static final MessageType<UploadFileMessage> UPLOAD_FILE = registerServerbound("upload_file", UploadFileMessage::new); | ||||
|     public static final CustomPacketPayload.Type<ComputerActionServerMessage> COMPUTER_ACTION = registerServerbound("computer_action", ComputerActionServerMessage.STREAM_CODEC); | ||||
|     public static final CustomPacketPayload.Type<QueueEventServerMessage> QUEUE_EVENT = register(serverMessages, "queue_event", QueueEventServerMessage.STREAM_CODEC); | ||||
|     public static final CustomPacketPayload.Type<KeyEventServerMessage> KEY_EVENT = registerServerbound("key_event", KeyEventServerMessage.STREAM_CODEC); | ||||
|     public static final CustomPacketPayload.Type<MouseEventServerMessage> MOUSE_EVENT = registerServerbound("mouse_event", MouseEventServerMessage.STREAM_CODEC); | ||||
|     public static final CustomPacketPayload.Type<UploadFileMessage> UPLOAD_FILE = register(serverMessages, "upload_file", UploadFileMessage.STREAM_CODEC); | ||||
| 
 | ||||
|     public static final MessageType<ChatTableClientMessage> CHAT_TABLE = registerClientbound("chat_table", ChatTableClientMessage::new); | ||||
|     public static final MessageType<PocketComputerDataMessage> POCKET_COMPUTER_DATA = registerClientbound("pocket_computer_data", PocketComputerDataMessage::new); | ||||
|     public static final MessageType<PocketComputerDeletedClientMessage> POCKET_COMPUTER_DELETED = registerClientbound("pocket_computer_deleted", PocketComputerDeletedClientMessage::new); | ||||
|     public static final MessageType<ComputerTerminalClientMessage> COMPUTER_TERMINAL = registerClientbound("computer_terminal", ComputerTerminalClientMessage::new); | ||||
|     public static final MessageType<PlayRecordClientMessage> PLAY_RECORD = registerClientbound("play_record", PlayRecordClientMessage::new); | ||||
|     public static final MessageType<MonitorClientMessage> MONITOR_CLIENT = registerClientbound("monitor_client", MonitorClientMessage::new); | ||||
|     public static final MessageType<SpeakerAudioClientMessage> SPEAKER_AUDIO = registerClientbound("speaker_audio", SpeakerAudioClientMessage::new); | ||||
|     public static final MessageType<SpeakerMoveClientMessage> SPEAKER_MOVE = registerClientbound("speaker_move", SpeakerMoveClientMessage::new); | ||||
|     public static final MessageType<SpeakerPlayClientMessage> SPEAKER_PLAY = registerClientbound("speaker_play", SpeakerPlayClientMessage::new); | ||||
|     public static final MessageType<SpeakerStopClientMessage> SPEAKER_STOP = registerClientbound("speaker_stop", SpeakerStopClientMessage::new); | ||||
|     public static final MessageType<UploadResultMessage> UPLOAD_RESULT = registerClientbound("upload_result", UploadResultMessage::new); | ||||
|     public static final MessageType<UpgradesLoadedMessage> UPGRADES_LOADED = registerClientbound("upgrades_loaded", UpgradesLoadedMessage::new); | ||||
|     public static final CustomPacketPayload.Type<ChatTableClientMessage> CHAT_TABLE = registerClientbound("chat_table", ChatTableClientMessage.STREAM_CODEC); | ||||
|     public static final CustomPacketPayload.Type<PocketComputerDataMessage> POCKET_COMPUTER_DATA = registerClientbound("pocket_computer_data", PocketComputerDataMessage.STREAM_CODEC); | ||||
|     public static final CustomPacketPayload.Type<PocketComputerDeletedClientMessage> POCKET_COMPUTER_DELETED = registerClientbound("pocket_computer_deleted", PocketComputerDeletedClientMessage.STREAM_CODEC); | ||||
|     public static final CustomPacketPayload.Type<ComputerTerminalClientMessage> COMPUTER_TERMINAL = registerClientbound("computer_terminal", ComputerTerminalClientMessage.STREAM_CODEC); | ||||
|     public static final CustomPacketPayload.Type<PlayRecordClientMessage> PLAY_RECORD = registerClientbound("play_record", PlayRecordClientMessage.STREAM_CODEC); | ||||
|     public static final CustomPacketPayload.Type<MonitorClientMessage> MONITOR_CLIENT = registerClientbound("monitor_client", MonitorClientMessage.STREAM_CODEC); | ||||
|     public static final CustomPacketPayload.Type<SpeakerAudioClientMessage> SPEAKER_AUDIO = registerClientbound("speaker_audio", SpeakerAudioClientMessage.STREAM_CODEC); | ||||
|     public static final CustomPacketPayload.Type<SpeakerMoveClientMessage> SPEAKER_MOVE = registerClientbound("speaker_move", SpeakerMoveClientMessage.STREAM_CODEC); | ||||
|     public static final CustomPacketPayload.Type<SpeakerPlayClientMessage> SPEAKER_PLAY = registerClientbound("speaker_play", SpeakerPlayClientMessage.STREAM_CODEC); | ||||
|     public static final CustomPacketPayload.Type<SpeakerStopClientMessage> SPEAKER_STOP = registerClientbound("speaker_stop", SpeakerStopClientMessage.STREAM_CODEC); | ||||
|     public static final CustomPacketPayload.Type<UploadResultMessage> UPLOAD_RESULT = registerClientbound("upload_result", UploadResultMessage.STREAM_CODEC); | ||||
|     public static final CustomPacketPayload.Type<UpgradesLoadedMessage> UPGRADES_LOADED = registerClientbound("upgrades_loaded", UpgradesLoadedMessage.STREAM_CODEC); | ||||
| 
 | ||||
|     private NetworkMessages() { | ||||
|     } | ||||
| 
 | ||||
|     private static <C, T extends NetworkMessage<C>> MessageType<T> register( | ||||
|         List<MessageType<? extends NetworkMessage<C>>> messages, | ||||
|         String channel, FriendlyByteBuf.Reader<T> reader | ||||
|     private static <C, T extends NetworkMessage<C>> CustomPacketPayload.Type<T> register( | ||||
|         List<CustomPacketPayload.TypeAndCodec<RegistryFriendlyByteBuf, ? extends NetworkMessage<C>>> messages, | ||||
|         String channel, StreamCodec<RegistryFriendlyByteBuf, T> codec | ||||
|     ) { | ||||
|         if (!seenChannel.add(channel)) throw new IllegalArgumentException("Duplicate channel " + channel); | ||||
|         var type = PlatformHelper.get().createMessageType(new ResourceLocation(ComputerCraftAPI.MOD_ID, channel), reader); | ||||
|         messages.add(type); | ||||
|         var type = new CustomPacketPayload.Type<T>(new ResourceLocation(ComputerCraftAPI.MOD_ID, channel)); | ||||
|         messages.add(new CustomPacketPayload.TypeAndCodec<>(type, codec)); | ||||
|         return type; | ||||
|     } | ||||
| 
 | ||||
|     private static <T extends NetworkMessage<ServerNetworkContext>> MessageType<T> registerServerbound(String id, FriendlyByteBuf.Reader<T> reader) { | ||||
|         return register(serverMessages, id, reader); | ||||
|     private static <T extends NetworkMessage<ServerNetworkContext>> CustomPacketPayload.Type<T> registerServerbound(String id, StreamCodec<RegistryFriendlyByteBuf, T> codec) { | ||||
|         return register(serverMessages, id, codec); | ||||
|     } | ||||
| 
 | ||||
|     private static <T extends NetworkMessage<ClientNetworkContext>> MessageType<T> registerClientbound(String id, FriendlyByteBuf.Reader<T> reader) { | ||||
|         return register(clientMessages, id, reader); | ||||
|     private static <T extends NetworkMessage<ClientNetworkContext>> CustomPacketPayload.Type<T> registerClientbound(String id, StreamCodec<RegistryFriendlyByteBuf, T> codec) { | ||||
|         return register(clientMessages, id, codec); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @@ -68,7 +70,7 @@ public final class NetworkMessages { | ||||
|      * | ||||
|      * @return An unmodifiable sequence of all serverbound message types. | ||||
|      */ | ||||
|     public static Collection<MessageType<? extends NetworkMessage<ServerNetworkContext>>> getServerbound() { | ||||
|     public static Collection<CustomPacketPayload.TypeAndCodec<RegistryFriendlyByteBuf, ? extends NetworkMessage<ServerNetworkContext>>> getServerbound() { | ||||
|         return Collections.unmodifiableCollection(serverMessages); | ||||
|     } | ||||
| 
 | ||||
| @@ -77,7 +79,7 @@ public final class NetworkMessages { | ||||
|      * | ||||
|      * @return An unmodifiable sequence of all clientbound message types. | ||||
|      */ | ||||
|     public static Collection<MessageType<? extends NetworkMessage<ClientNetworkContext>>> getClientbound() { | ||||
|     public static Collection<CustomPacketPayload.TypeAndCodec<RegistryFriendlyByteBuf, ? extends NetworkMessage<ClientNetworkContext>>> getClientbound() { | ||||
|         return Collections.unmodifiableCollection(clientMessages); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -5,14 +5,18 @@ | ||||
| package dan200.computercraft.shared.network.client; | ||||
| 
 | ||||
| import dan200.computercraft.shared.command.text.TableBuilder; | ||||
| import dan200.computercraft.shared.network.MessageType; | ||||
| import dan200.computercraft.shared.network.NetworkMessage; | ||||
| import dan200.computercraft.shared.network.NetworkMessages; | ||||
| import net.minecraft.network.FriendlyByteBuf; | ||||
| import net.minecraft.network.RegistryFriendlyByteBuf; | ||||
| import net.minecraft.network.chat.Component; | ||||
| import net.minecraft.network.chat.ComponentSerialization; | ||||
| import net.minecraft.network.codec.StreamCodec; | ||||
| import net.minecraft.network.protocol.common.custom.CustomPacketPayload; | ||||
| 
 | ||||
| 
 | ||||
| public class ChatTableClientMessage implements NetworkMessage<ClientNetworkContext> { | ||||
|     public static final StreamCodec<RegistryFriendlyByteBuf, ChatTableClientMessage> STREAM_CODEC = StreamCodec.ofMember(ChatTableClientMessage::write, ChatTableClientMessage::new); | ||||
| 
 | ||||
|     private static final int MAX_LEN = 16; | ||||
|     private final TableBuilder table; | ||||
| 
 | ||||
| @@ -21,13 +25,13 @@ public class ChatTableClientMessage implements NetworkMessage<ClientNetworkConte | ||||
|         this.table = table; | ||||
|     } | ||||
| 
 | ||||
|     public ChatTableClientMessage(FriendlyByteBuf buf) { | ||||
|     private ChatTableClientMessage(RegistryFriendlyByteBuf buf) { | ||||
|         var id = buf.readUtf(MAX_LEN); | ||||
|         var columns = buf.readVarInt(); | ||||
|         TableBuilder table; | ||||
|         if (buf.readBoolean()) { | ||||
|             var headers = new Component[columns]; | ||||
|             for (var i = 0; i < columns; i++) headers[i] = buf.readComponent(); | ||||
|             for (var i = 0; i < columns; i++) headers[i] = ComponentSerialization.STREAM_CODEC.decode(buf); | ||||
|             table = new TableBuilder(id, headers); | ||||
|         } else { | ||||
|             table = new TableBuilder(id); | ||||
| @@ -36,7 +40,7 @@ public class ChatTableClientMessage implements NetworkMessage<ClientNetworkConte | ||||
|         var rows = buf.readVarInt(); | ||||
|         for (var i = 0; i < rows; i++) { | ||||
|             var row = new Component[columns]; | ||||
|             for (var j = 0; j < columns; j++) row[j] = buf.readComponent(); | ||||
|             for (var j = 0; j < columns; j++) row[j] = ComponentSerialization.STREAM_CODEC.decode(buf); | ||||
|             table.row(row); | ||||
|         } | ||||
| 
 | ||||
| @@ -44,18 +48,17 @@ public class ChatTableClientMessage implements NetworkMessage<ClientNetworkConte | ||||
|         this.table = table; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void write(FriendlyByteBuf buf) { | ||||
|     private void write(RegistryFriendlyByteBuf buf) { | ||||
|         buf.writeUtf(table.getId(), MAX_LEN); | ||||
|         buf.writeVarInt(table.getColumns()); | ||||
|         buf.writeBoolean(table.getHeaders() != null); | ||||
|         if (table.getHeaders() != null) { | ||||
|             for (var header : table.getHeaders()) buf.writeComponent(header); | ||||
|             for (var header : table.getHeaders()) ComponentSerialization.STREAM_CODEC.encode(buf, header); | ||||
|         } | ||||
| 
 | ||||
|         buf.writeVarInt(table.getRows().size()); | ||||
|         for (var row : table.getRows()) { | ||||
|             for (var column : row) buf.writeComponent(column); | ||||
|             for (var column : row) ComponentSerialization.STREAM_CODEC.encode(buf, column); | ||||
|         } | ||||
| 
 | ||||
|         buf.writeVarInt(table.getAdditional()); | ||||
| @@ -67,7 +70,7 @@ public class ChatTableClientMessage implements NetworkMessage<ClientNetworkConte | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public MessageType<ChatTableClientMessage> type() { | ||||
|     public CustomPacketPayload.Type<ChatTableClientMessage> type() { | ||||
|         return NetworkMessages.CHAT_TABLE; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -4,32 +4,33 @@ | ||||
| 
 | ||||
| package dan200.computercraft.shared.network.client; | ||||
| 
 | ||||
| import dan200.computercraft.shared.computer.menu.ComputerMenu; | ||||
| import dan200.computercraft.shared.computer.terminal.TerminalState; | ||||
| import dan200.computercraft.shared.network.MessageType; | ||||
| import dan200.computercraft.shared.network.NetworkMessage; | ||||
| import dan200.computercraft.shared.network.NetworkMessages; | ||||
| import net.minecraft.network.FriendlyByteBuf; | ||||
| import net.minecraft.network.RegistryFriendlyByteBuf; | ||||
| import net.minecraft.network.codec.ByteBufCodecs; | ||||
| import net.minecraft.network.codec.StreamCodec; | ||||
| import net.minecraft.network.protocol.common.custom.CustomPacketPayload; | ||||
| import net.minecraft.world.inventory.AbstractContainerMenu; | ||||
| 
 | ||||
| 
 | ||||
| public class ComputerTerminalClientMessage implements NetworkMessage<ClientNetworkContext> { | ||||
|     private final int containerId; | ||||
|     private final TerminalState terminal; | ||||
| /** | ||||
|  * Update the terminal for the currently opened {@link ComputerMenu}. | ||||
|  * | ||||
|  * @param containerId The currently opened container id. | ||||
|  * @param terminal    The new terminal data. | ||||
|  */ | ||||
| public record ComputerTerminalClientMessage( | ||||
|     int containerId, TerminalState terminal | ||||
| ) implements NetworkMessage<ClientNetworkContext> { | ||||
|     public static final StreamCodec<RegistryFriendlyByteBuf, ComputerTerminalClientMessage> STREAM_CODEC = StreamCodec.composite( | ||||
|         ByteBufCodecs.VAR_INT, ComputerTerminalClientMessage::containerId, | ||||
|         TerminalState.STREAM_CODEC, ComputerTerminalClientMessage::terminal, | ||||
|         ComputerTerminalClientMessage::new | ||||
|     ); | ||||
| 
 | ||||
|     public ComputerTerminalClientMessage(AbstractContainerMenu menu, TerminalState terminal) { | ||||
|         containerId = menu.containerId; | ||||
|         this.terminal = terminal; | ||||
|     } | ||||
| 
 | ||||
|     public ComputerTerminalClientMessage(FriendlyByteBuf buf) { | ||||
|         containerId = buf.readVarInt(); | ||||
|         terminal = new TerminalState(buf); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void write(FriendlyByteBuf buf) { | ||||
|         buf.writeVarInt(containerId); | ||||
|         terminal.write(buf); | ||||
|         this(menu.containerId, terminal); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
| @@ -38,7 +39,7 @@ public class ComputerTerminalClientMessage implements NetworkMessage<ClientNetwo | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public MessageType<ComputerTerminalClientMessage> type() { | ||||
|     public CustomPacketPayload.Type<ComputerTerminalClientMessage> type() { | ||||
|         return NetworkMessages.COMPUTER_TERMINAL; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -5,41 +5,38 @@ | ||||
| package dan200.computercraft.shared.network.client; | ||||
| 
 | ||||
| import dan200.computercraft.shared.computer.terminal.TerminalState; | ||||
| import dan200.computercraft.shared.network.MessageType; | ||||
| import dan200.computercraft.shared.network.NetworkMessage; | ||||
| import dan200.computercraft.shared.network.NetworkMessages; | ||||
| import net.minecraft.core.BlockPos; | ||||
| import net.minecraft.network.FriendlyByteBuf; | ||||
| import net.minecraft.network.RegistryFriendlyByteBuf; | ||||
| import net.minecraft.network.codec.ByteBufCodecs; | ||||
| import net.minecraft.network.codec.StreamCodec; | ||||
| import net.minecraft.network.protocol.common.custom.CustomPacketPayload; | ||||
| 
 | ||||
| import javax.annotation.Nullable; | ||||
| import java.util.Optional; | ||||
| 
 | ||||
| public class MonitorClientMessage implements NetworkMessage<ClientNetworkContext> { | ||||
|     private final BlockPos pos; | ||||
|     private final @Nullable TerminalState state; | ||||
| 
 | ||||
|     public MonitorClientMessage(BlockPos pos, @Nullable TerminalState state) { | ||||
|         this.pos = pos; | ||||
|         this.state = state; | ||||
|     } | ||||
| 
 | ||||
|     public MonitorClientMessage(FriendlyByteBuf buf) { | ||||
|         pos = buf.readBlockPos(); | ||||
|         state = buf.readNullable(TerminalState::new); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void write(FriendlyByteBuf buf) { | ||||
|         buf.writeBlockPos(pos); | ||||
|         buf.writeNullable(state, (b, t) -> t.write(b)); | ||||
|     } | ||||
| /** | ||||
|  * Update the terminal contents of a monitor. | ||||
|  * | ||||
|  * @param pos      The position of the origin monitor. | ||||
|  * @param terminal The current monitor terminal. | ||||
|  */ | ||||
| public record MonitorClientMessage( | ||||
|     BlockPos pos, Optional<TerminalState> terminal | ||||
| ) implements NetworkMessage<ClientNetworkContext> { | ||||
|     public static final StreamCodec<RegistryFriendlyByteBuf, MonitorClientMessage> STREAM_CODEC = StreamCodec.composite( | ||||
|         BlockPos.STREAM_CODEC, MonitorClientMessage::pos, | ||||
|         ByteBufCodecs.optional(TerminalState.STREAM_CODEC), MonitorClientMessage::terminal, | ||||
|         MonitorClientMessage::new | ||||
|     ); | ||||
| 
 | ||||
|     @Override | ||||
|     public void handle(ClientNetworkContext context) { | ||||
|         context.handleMonitorData(pos, state); | ||||
|         context.handleMonitorData(pos, terminal.orElse(null)); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public MessageType<MonitorClientMessage> type() { | ||||
|     public CustomPacketPayload.Type<MonitorClientMessage> type() { | ||||
|         return NetworkMessages.MONITOR_CLIENT; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -4,60 +4,50 @@ | ||||
| 
 | ||||
| package dan200.computercraft.shared.network.client; | ||||
| 
 | ||||
| import dan200.computercraft.shared.network.MessageType; | ||||
| import dan200.computercraft.shared.network.NetworkMessage; | ||||
| import dan200.computercraft.shared.network.NetworkMessages; | ||||
| import dan200.computercraft.shared.peripheral.diskdrive.DiskDriveBlockEntity; | ||||
| import net.minecraft.core.BlockPos; | ||||
| import net.minecraft.network.FriendlyByteBuf; | ||||
| import net.minecraft.core.Holder; | ||||
| import net.minecraft.network.RegistryFriendlyByteBuf; | ||||
| import net.minecraft.network.codec.ByteBufCodecs; | ||||
| import net.minecraft.network.codec.StreamCodec; | ||||
| import net.minecraft.network.protocol.common.custom.CustomPacketPayload; | ||||
| import net.minecraft.sounds.SoundEvent; | ||||
| 
 | ||||
| import javax.annotation.Nullable; | ||||
| import java.util.Optional; | ||||
| 
 | ||||
| /** | ||||
|  * Starts or stops a record on the client, depending on if {@link #soundEvent} is {@code null}. | ||||
|  * <p> | ||||
|  * Used by disk drives to play record items. | ||||
|  * | ||||
|  * @param pos        The position of the speaker, where we should play this sound. | ||||
|  * @param soundEvent The sound to play, or {@link Optional#empty()} if we should stop playing. | ||||
|  * @param name       The title of the audio to play. | ||||
|  * @see DiskDriveBlockEntity | ||||
|  */ | ||||
| public class PlayRecordClientMessage implements NetworkMessage<ClientNetworkContext> { | ||||
|     private final BlockPos pos; | ||||
|     private final @Nullable String name; | ||||
|     private final @Nullable SoundEvent soundEvent; | ||||
| 
 | ||||
|     public PlayRecordClientMessage(BlockPos pos, SoundEvent event, @Nullable String name) { | ||||
|         this.pos = pos; | ||||
|         this.name = name; | ||||
|         soundEvent = event; | ||||
|     } | ||||
| public record PlayRecordClientMessage( | ||||
|     BlockPos pos, Optional<Holder<SoundEvent>> soundEvent, Optional<String> name | ||||
| ) implements NetworkMessage<ClientNetworkContext> { | ||||
|     public static final StreamCodec<RegistryFriendlyByteBuf, PlayRecordClientMessage> STREAM_CODEC = StreamCodec.composite( | ||||
|         BlockPos.STREAM_CODEC, PlayRecordClientMessage::pos, | ||||
|         ByteBufCodecs.optional(SoundEvent.STREAM_CODEC), PlayRecordClientMessage::soundEvent, | ||||
|         ByteBufCodecs.optional(ByteBufCodecs.STRING_UTF8), PlayRecordClientMessage::name, | ||||
|         PlayRecordClientMessage::new | ||||
|     ); | ||||
| 
 | ||||
|     public PlayRecordClientMessage(BlockPos pos) { | ||||
|         this.pos = pos; | ||||
|         name = null; | ||||
|         soundEvent = null; | ||||
|     } | ||||
| 
 | ||||
|     public PlayRecordClientMessage(FriendlyByteBuf buf) { | ||||
|         pos = buf.readBlockPos(); | ||||
|         soundEvent = buf.readNullable(SoundEvent::readFromNetwork); | ||||
|         name = buf.readNullable(FriendlyByteBuf::readUtf); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void write(FriendlyByteBuf buf) { | ||||
|         buf.writeBlockPos(pos); | ||||
|         buf.writeNullable(soundEvent, (b, e) -> e.writeToNetwork(b)); | ||||
|         buf.writeNullable(name, FriendlyByteBuf::writeUtf); | ||||
|         this(pos, Optional.empty(), Optional.empty()); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void handle(ClientNetworkContext context) { | ||||
|         context.handlePlayRecord(pos, soundEvent, name); | ||||
|         context.handlePlayRecord(pos, soundEvent.map(Holder::value).orElse(null), name.orElse(null)); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public MessageType<PlayRecordClientMessage> type() { | ||||
|     public CustomPacketPayload.Type<PlayRecordClientMessage> type() { | ||||
|         return NetworkMessages.PLAY_RECORD; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -5,54 +5,56 @@ | ||||
| package dan200.computercraft.shared.network.client; | ||||
| 
 | ||||
| import dan200.computercraft.shared.computer.core.ComputerState; | ||||
| import dan200.computercraft.shared.computer.core.ServerComputer; | ||||
| import dan200.computercraft.shared.computer.terminal.TerminalState; | ||||
| import dan200.computercraft.shared.network.MessageType; | ||||
| import dan200.computercraft.shared.network.NetworkMessage; | ||||
| import dan200.computercraft.shared.network.NetworkMessages; | ||||
| import dan200.computercraft.shared.network.codec.MoreStreamCodecs; | ||||
| import dan200.computercraft.shared.pocket.core.PocketServerComputer; | ||||
| import net.minecraft.network.FriendlyByteBuf; | ||||
| import net.minecraft.core.UUIDUtil; | ||||
| import net.minecraft.network.RegistryFriendlyByteBuf; | ||||
| import net.minecraft.network.codec.ByteBufCodecs; | ||||
| import net.minecraft.network.codec.StreamCodec; | ||||
| import net.minecraft.network.protocol.common.custom.CustomPacketPayload; | ||||
| 
 | ||||
| import javax.annotation.Nullable; | ||||
| import java.util.Optional; | ||||
| import java.util.UUID; | ||||
| 
 | ||||
| /** | ||||
|  * Provides additional data about a client computer, such as its ID and current state. | ||||
|  * | ||||
|  * @param id         The {@linkplain ServerComputer#getInstanceUUID() instance id} of the pocket computer. | ||||
|  * @param state      Whether the computer is on, off, or blinking. | ||||
|  * @param lightState The colour of the light, or {@code -1} if off. | ||||
|  * @param terminal   The computer's terminal. This may be absent, in which case the terminal will not be updated on. | ||||
|  */ | ||||
| public class PocketComputerDataMessage implements NetworkMessage<ClientNetworkContext> { | ||||
|     private final UUID clientId; | ||||
|     private final ComputerState state; | ||||
|     private final int lightState; | ||||
|     private final @Nullable TerminalState terminal; | ||||
| public record PocketComputerDataMessage( | ||||
|     UUID id, ComputerState state, int lightState, Optional<TerminalState> terminal | ||||
| ) implements NetworkMessage<ClientNetworkContext> { | ||||
|     public static final StreamCodec<RegistryFriendlyByteBuf, PocketComputerDataMessage> STREAM_CODEC = StreamCodec.composite( | ||||
|         UUIDUtil.STREAM_CODEC, PocketComputerDataMessage::id, | ||||
|         MoreStreamCodecs.ofEnum(ComputerState.class), PocketComputerDataMessage::state, | ||||
|         ByteBufCodecs.VAR_INT, PocketComputerDataMessage::lightState, | ||||
|         ByteBufCodecs.optional(TerminalState.STREAM_CODEC), PocketComputerDataMessage::terminal, | ||||
|         PocketComputerDataMessage::new | ||||
|     ); | ||||
| 
 | ||||
|     public PocketComputerDataMessage(PocketServerComputer computer, boolean sendTerminal) { | ||||
|         clientId = computer.getInstanceUUID(); | ||||
|         state = computer.getState(); | ||||
|         lightState = computer.getLight(); | ||||
|         terminal = sendTerminal ? computer.getTerminalState() : null; | ||||
|     } | ||||
| 
 | ||||
|     public PocketComputerDataMessage(FriendlyByteBuf buf) { | ||||
|         clientId = buf.readUUID(); | ||||
|         state = buf.readEnum(ComputerState.class); | ||||
|         lightState = buf.readVarInt(); | ||||
|         terminal = buf.readNullable(TerminalState::new); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void write(FriendlyByteBuf buf) { | ||||
|         buf.writeUUID(clientId); | ||||
|         buf.writeEnum(state); | ||||
|         buf.writeVarInt(lightState); | ||||
|         buf.writeNullable(terminal, (b, t) -> t.write(b)); | ||||
|         this( | ||||
|             computer.getInstanceUUID(), | ||||
|             computer.getState(), | ||||
|             computer.getLight(), | ||||
|             sendTerminal ? Optional.of(computer.getTerminalState()) : Optional.empty() | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void handle(ClientNetworkContext context) { | ||||
|         context.handlePocketComputerData(clientId, state, lightState, terminal); | ||||
|         context.handlePocketComputerData(id, state, lightState, terminal.orElse(null)); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public MessageType<PocketComputerDataMessage> type() { | ||||
|     public CustomPacketPayload.Type<PocketComputerDataMessage> type() { | ||||
|         return NetworkMessages.POCKET_COMPUTER_DATA; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -4,29 +4,24 @@ | ||||
| 
 | ||||
| package dan200.computercraft.shared.network.client; | ||||
| 
 | ||||
| import dan200.computercraft.shared.network.MessageType; | ||||
| import dan200.computercraft.shared.network.NetworkMessage; | ||||
| import dan200.computercraft.shared.network.NetworkMessages; | ||||
| import net.minecraft.network.FriendlyByteBuf; | ||||
| import net.minecraft.core.UUIDUtil; | ||||
| import net.minecraft.network.RegistryFriendlyByteBuf; | ||||
| import net.minecraft.network.codec.StreamCodec; | ||||
| import net.minecraft.network.protocol.common.custom.CustomPacketPayload; | ||||
| 
 | ||||
| import java.util.UUID; | ||||
| 
 | ||||
| 
 | ||||
| public class PocketComputerDeletedClientMessage implements NetworkMessage<ClientNetworkContext> { | ||||
|     private final UUID instanceId; | ||||
| 
 | ||||
|     public PocketComputerDeletedClientMessage(UUID instanceId) { | ||||
|         this.instanceId = instanceId; | ||||
|     } | ||||
| 
 | ||||
|     public PocketComputerDeletedClientMessage(FriendlyByteBuf buffer) { | ||||
|         instanceId = buffer.readUUID(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void write(FriendlyByteBuf buf) { | ||||
|         buf.writeUUID(instanceId); | ||||
|     } | ||||
| /** | ||||
|  * Delete any client-side pocket computer state. | ||||
|  * | ||||
|  * @param instanceId The pocket computer's instance id. | ||||
|  */ | ||||
| public record PocketComputerDeletedClientMessage(UUID instanceId) implements NetworkMessage<ClientNetworkContext> { | ||||
|     public static final StreamCodec<RegistryFriendlyByteBuf, PocketComputerDeletedClientMessage> STREAM_CODEC = UUIDUtil.STREAM_CODEC | ||||
|         .map(PocketComputerDeletedClientMessage::new, PocketComputerDeletedClientMessage::instanceId) | ||||
|         .cast(); | ||||
| 
 | ||||
|     @Override | ||||
|     public void handle(ClientNetworkContext context) { | ||||
| @@ -34,7 +29,7 @@ public class PocketComputerDeletedClientMessage implements NetworkMessage<Client | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public MessageType<PocketComputerDeletedClientMessage> type() { | ||||
|     public CustomPacketPayload.Type<PocketComputerDeletedClientMessage> type() { | ||||
|         return NetworkMessages.POCKET_COMPUTER_DELETED; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -4,13 +4,17 @@ | ||||
| 
 | ||||
| package dan200.computercraft.shared.network.client; | ||||
| 
 | ||||
| import dan200.computercraft.shared.network.MessageType; | ||||
| import dan200.computercraft.shared.network.NetworkMessage; | ||||
| import dan200.computercraft.shared.network.NetworkMessages; | ||||
| import dan200.computercraft.shared.peripheral.speaker.EncodedAudio; | ||||
| import dan200.computercraft.shared.peripheral.speaker.SpeakerBlockEntity; | ||||
| import dan200.computercraft.shared.peripheral.speaker.SpeakerPeripheral; | ||||
| import dan200.computercraft.shared.peripheral.speaker.SpeakerPosition; | ||||
| import net.minecraft.network.FriendlyByteBuf; | ||||
| import net.minecraft.core.UUIDUtil; | ||||
| import net.minecraft.network.RegistryFriendlyByteBuf; | ||||
| import net.minecraft.network.codec.ByteBufCodecs; | ||||
| import net.minecraft.network.codec.StreamCodec; | ||||
| import net.minecraft.network.protocol.common.custom.CustomPacketPayload; | ||||
| 
 | ||||
| import java.util.UUID; | ||||
| 
 | ||||
| @@ -19,34 +23,28 @@ import java.util.UUID; | ||||
|  * <p> | ||||
|  * Used by speakers to play sounds. | ||||
|  * | ||||
|  * @param source  The {@linkplain SpeakerPeripheral#getSource() id} of the speaker playing audio. | ||||
|  * @param pos     The position of the speaker. | ||||
|  * @param content The audio to play. | ||||
|  * @param volume  The volume to play the audio at. | ||||
|  * @see SpeakerBlockEntity | ||||
|  */ | ||||
| public class SpeakerAudioClientMessage implements NetworkMessage<ClientNetworkContext> { | ||||
|     private final UUID source; | ||||
|     private final SpeakerPosition.Message pos; | ||||
|     private final EncodedAudio content; | ||||
|     private final float volume; | ||||
| public record SpeakerAudioClientMessage( | ||||
|     UUID source, | ||||
|     SpeakerPosition.Message pos, | ||||
|     EncodedAudio content, | ||||
|     float volume | ||||
| ) implements NetworkMessage<ClientNetworkContext> { | ||||
|     public static final StreamCodec<RegistryFriendlyByteBuf, SpeakerAudioClientMessage> STREAM_CODEC = StreamCodec.composite( | ||||
|         UUIDUtil.STREAM_CODEC, SpeakerAudioClientMessage::source, | ||||
|         SpeakerPosition.Message.STREAM_CODEC, SpeakerAudioClientMessage::pos, | ||||
|         EncodedAudio.STREAM_CODEC, SpeakerAudioClientMessage::content, | ||||
|         ByteBufCodecs.FLOAT, SpeakerAudioClientMessage::volume, | ||||
|         SpeakerAudioClientMessage::new | ||||
|     ); | ||||
| 
 | ||||
|     public SpeakerAudioClientMessage(UUID source, SpeakerPosition pos, float volume, EncodedAudio content) { | ||||
|         this.source = source; | ||||
|         this.pos = pos.asMessage(); | ||||
|         this.content = content; | ||||
|         this.volume = volume; | ||||
|     } | ||||
| 
 | ||||
|     public SpeakerAudioClientMessage(FriendlyByteBuf buf) { | ||||
|         source = buf.readUUID(); | ||||
|         pos = SpeakerPosition.Message.read(buf); | ||||
|         volume = buf.readFloat(); | ||||
|         content = EncodedAudio.read(buf); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void write(FriendlyByteBuf buf) { | ||||
|         buf.writeUUID(source); | ||||
|         pos.write(buf); | ||||
|         buf.writeFloat(volume); | ||||
|         content.write(buf); | ||||
|         this(source, pos.asMessage(), content, volume); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
| @@ -55,7 +53,7 @@ public class SpeakerAudioClientMessage implements NetworkMessage<ClientNetworkCo | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public MessageType<SpeakerAudioClientMessage> type() { | ||||
|     public CustomPacketPayload.Type<SpeakerAudioClientMessage> type() { | ||||
|         return NetworkMessages.SPEAKER_AUDIO; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -4,12 +4,15 @@ | ||||
| 
 | ||||
| package dan200.computercraft.shared.network.client; | ||||
| 
 | ||||
| import dan200.computercraft.shared.network.MessageType; | ||||
| import dan200.computercraft.shared.network.NetworkMessage; | ||||
| import dan200.computercraft.shared.network.NetworkMessages; | ||||
| import dan200.computercraft.shared.peripheral.speaker.SpeakerBlockEntity; | ||||
| import dan200.computercraft.shared.peripheral.speaker.SpeakerPeripheral; | ||||
| import dan200.computercraft.shared.peripheral.speaker.SpeakerPosition; | ||||
| import net.minecraft.network.FriendlyByteBuf; | ||||
| import net.minecraft.core.UUIDUtil; | ||||
| import net.minecraft.network.RegistryFriendlyByteBuf; | ||||
| import net.minecraft.network.codec.StreamCodec; | ||||
| import net.minecraft.network.protocol.common.custom.CustomPacketPayload; | ||||
| 
 | ||||
| import java.util.UUID; | ||||
| 
 | ||||
| @@ -18,26 +21,21 @@ import java.util.UUID; | ||||
|  * <p> | ||||
|  * Used by speakers to play sounds. | ||||
|  * | ||||
|  * @param source The {@linkplain SpeakerPeripheral#getSource() id} of the speaker playing audio. | ||||
|  * @param pos    The new position of the speaker. | ||||
|  * @see SpeakerBlockEntity | ||||
|  */ | ||||
| public class SpeakerMoveClientMessage implements NetworkMessage<ClientNetworkContext> { | ||||
|     private final UUID source; | ||||
|     private final SpeakerPosition.Message pos; | ||||
| public record SpeakerMoveClientMessage( | ||||
|     UUID source, SpeakerPosition.Message pos | ||||
| ) implements NetworkMessage<ClientNetworkContext> { | ||||
|     public static final StreamCodec<RegistryFriendlyByteBuf, SpeakerMoveClientMessage> STREAM_CODEC = StreamCodec.composite( | ||||
|         UUIDUtil.STREAM_CODEC, SpeakerMoveClientMessage::source, | ||||
|         SpeakerPosition.Message.STREAM_CODEC, SpeakerMoveClientMessage::pos, | ||||
|         SpeakerMoveClientMessage::new | ||||
|     ); | ||||
| 
 | ||||
|     public SpeakerMoveClientMessage(UUID source, SpeakerPosition pos) { | ||||
|         this.source = source; | ||||
|         this.pos = pos.asMessage(); | ||||
|     } | ||||
| 
 | ||||
|     public SpeakerMoveClientMessage(FriendlyByteBuf buf) { | ||||
|         source = buf.readUUID(); | ||||
|         pos = SpeakerPosition.Message.read(buf); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void write(FriendlyByteBuf buf) { | ||||
|         buf.writeUUID(source); | ||||
|         pos.write(buf); | ||||
|         this(source, pos.asMessage()); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
| @@ -46,7 +44,7 @@ public class SpeakerMoveClientMessage implements NetworkMessage<ClientNetworkCon | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public MessageType<SpeakerMoveClientMessage> type() { | ||||
|     public CustomPacketPayload.Type<SpeakerMoveClientMessage> type() { | ||||
|         return NetworkMessages.SPEAKER_MOVE; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -4,12 +4,16 @@ | ||||
| 
 | ||||
| package dan200.computercraft.shared.network.client; | ||||
| 
 | ||||
| import dan200.computercraft.shared.network.MessageType; | ||||
| import dan200.computercraft.shared.network.NetworkMessage; | ||||
| import dan200.computercraft.shared.network.NetworkMessages; | ||||
| import dan200.computercraft.shared.peripheral.speaker.SpeakerBlockEntity; | ||||
| import dan200.computercraft.shared.peripheral.speaker.SpeakerPeripheral; | ||||
| import dan200.computercraft.shared.peripheral.speaker.SpeakerPosition; | ||||
| import net.minecraft.network.FriendlyByteBuf; | ||||
| import net.minecraft.core.UUIDUtil; | ||||
| import net.minecraft.network.RegistryFriendlyByteBuf; | ||||
| import net.minecraft.network.codec.ByteBufCodecs; | ||||
| import net.minecraft.network.codec.StreamCodec; | ||||
| import net.minecraft.network.protocol.common.custom.CustomPacketPayload; | ||||
| import net.minecraft.resources.ResourceLocation; | ||||
| 
 | ||||
| import java.util.UUID; | ||||
| @@ -19,38 +23,31 @@ import java.util.UUID; | ||||
|  * <p> | ||||
|  * Used by speakers to play sounds. | ||||
|  * | ||||
|  * @param source The {@linkplain SpeakerPeripheral#getSource() id} of the speaker playing audio. | ||||
|  * @param pos    The position of the speaker. | ||||
|  * @param sound  The sound to play. | ||||
|  * @param volume The volume to play the sound at. | ||||
|  * @param pitch  The pitch to play the sound at. | ||||
|  * @see SpeakerBlockEntity | ||||
|  */ | ||||
| public class SpeakerPlayClientMessage implements NetworkMessage<ClientNetworkContext> { | ||||
|     private final UUID source; | ||||
|     private final SpeakerPosition.Message pos; | ||||
|     private final ResourceLocation sound; | ||||
|     private final float volume; | ||||
|     private final float pitch; | ||||
| public record SpeakerPlayClientMessage( | ||||
|     UUID source, | ||||
|     SpeakerPosition.Message pos, | ||||
|     ResourceLocation sound, | ||||
|     float volume, | ||||
|     float pitch | ||||
| ) implements NetworkMessage<ClientNetworkContext> { | ||||
|     public static final StreamCodec<RegistryFriendlyByteBuf, SpeakerPlayClientMessage> STREAM_CODEC = StreamCodec.composite( | ||||
|         UUIDUtil.STREAM_CODEC, SpeakerPlayClientMessage::source, | ||||
|         SpeakerPosition.Message.STREAM_CODEC, SpeakerPlayClientMessage::pos, | ||||
|         ResourceLocation.STREAM_CODEC, SpeakerPlayClientMessage::sound, | ||||
|         ByteBufCodecs.FLOAT, SpeakerPlayClientMessage::volume, | ||||
|         ByteBufCodecs.FLOAT, SpeakerPlayClientMessage::pitch, | ||||
|         SpeakerPlayClientMessage::new | ||||
|     ); | ||||
| 
 | ||||
|     public SpeakerPlayClientMessage(UUID source, SpeakerPosition pos, ResourceLocation sound, float volume, float pitch) { | ||||
|         this.source = source; | ||||
|         this.pos = pos.asMessage(); | ||||
|         this.sound = sound; | ||||
|         this.volume = volume; | ||||
|         this.pitch = pitch; | ||||
|     } | ||||
| 
 | ||||
|     public SpeakerPlayClientMessage(FriendlyByteBuf buf) { | ||||
|         source = buf.readUUID(); | ||||
|         pos = SpeakerPosition.Message.read(buf); | ||||
|         sound = buf.readResourceLocation(); | ||||
|         volume = buf.readFloat(); | ||||
|         pitch = buf.readFloat(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void write(FriendlyByteBuf buf) { | ||||
|         buf.writeUUID(source); | ||||
|         pos.write(buf); | ||||
|         buf.writeResourceLocation(sound); | ||||
|         buf.writeFloat(volume); | ||||
|         buf.writeFloat(pitch); | ||||
|         this(source, pos.asMessage(), sound, volume, pitch); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
| @@ -59,7 +56,7 @@ public class SpeakerPlayClientMessage implements NetworkMessage<ClientNetworkCon | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public MessageType<SpeakerPlayClientMessage> type() { | ||||
|     public CustomPacketPayload.Type<SpeakerPlayClientMessage> type() { | ||||
|         return NetworkMessages.SPEAKER_PLAY; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -4,11 +4,14 @@ | ||||
| 
 | ||||
| package dan200.computercraft.shared.network.client; | ||||
| 
 | ||||
| import dan200.computercraft.shared.network.MessageType; | ||||
| import dan200.computercraft.shared.network.NetworkMessage; | ||||
| import dan200.computercraft.shared.network.NetworkMessages; | ||||
| import dan200.computercraft.shared.peripheral.speaker.SpeakerBlockEntity; | ||||
| import net.minecraft.network.FriendlyByteBuf; | ||||
| import dan200.computercraft.shared.peripheral.speaker.SpeakerPeripheral; | ||||
| import net.minecraft.core.UUIDUtil; | ||||
| import net.minecraft.network.RegistryFriendlyByteBuf; | ||||
| import net.minecraft.network.codec.StreamCodec; | ||||
| import net.minecraft.network.protocol.common.custom.CustomPacketPayload; | ||||
| 
 | ||||
| import java.util.UUID; | ||||
| 
 | ||||
| @@ -17,23 +20,13 @@ import java.util.UUID; | ||||
|  * <p> | ||||
|  * Called when a speaker is broken. | ||||
|  * | ||||
|  * @param source The {@linkplain SpeakerPeripheral#getSource() id} of the speaker playing audio. | ||||
|  * @see SpeakerBlockEntity | ||||
|  */ | ||||
| public class SpeakerStopClientMessage implements NetworkMessage<ClientNetworkContext> { | ||||
|     private final UUID source; | ||||
| 
 | ||||
|     public SpeakerStopClientMessage(UUID source) { | ||||
|         this.source = source; | ||||
|     } | ||||
| 
 | ||||
|     public SpeakerStopClientMessage(FriendlyByteBuf buf) { | ||||
|         source = buf.readUUID(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void write(FriendlyByteBuf buf) { | ||||
|         buf.writeUUID(source); | ||||
|     } | ||||
| public record SpeakerStopClientMessage(UUID source) implements NetworkMessage<ClientNetworkContext> { | ||||
|     public static final StreamCodec<RegistryFriendlyByteBuf, SpeakerStopClientMessage> STREAM_CODEC = UUIDUtil.STREAM_CODEC | ||||
|         .map(SpeakerStopClientMessage::new, SpeakerStopClientMessage::source) | ||||
|         .cast(); | ||||
| 
 | ||||
|     @Override | ||||
|     public void handle(ClientNetworkContext context) { | ||||
| @@ -41,7 +34,7 @@ public class SpeakerStopClientMessage implements NetworkMessage<ClientNetworkCon | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public MessageType<SpeakerStopClientMessage> type() { | ||||
|     public CustomPacketPayload.Type<SpeakerStopClientMessage> type() { | ||||
|         return NetworkMessages.SPEAKER_STOP; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -12,11 +12,12 @@ import dan200.computercraft.impl.PocketUpgrades; | ||||
| import dan200.computercraft.impl.RegistryHelper; | ||||
| import dan200.computercraft.impl.TurtleUpgrades; | ||||
| import dan200.computercraft.impl.UpgradeManager; | ||||
| import dan200.computercraft.shared.network.MessageType; | ||||
| import dan200.computercraft.shared.network.NetworkMessage; | ||||
| import dan200.computercraft.shared.network.NetworkMessages; | ||||
| import net.minecraft.core.Registry; | ||||
| import net.minecraft.network.FriendlyByteBuf; | ||||
| import net.minecraft.network.RegistryFriendlyByteBuf; | ||||
| import net.minecraft.network.codec.StreamCodec; | ||||
| import net.minecraft.network.protocol.common.custom.CustomPacketPayload; | ||||
| import net.minecraft.resources.ResourceKey; | ||||
| import net.minecraft.resources.ResourceLocation; | ||||
| 
 | ||||
| @@ -27,34 +28,36 @@ import java.util.Map; | ||||
|  * Syncs turtle and pocket upgrades to the client. | ||||
|  */ | ||||
| public final class UpgradesLoadedMessage implements NetworkMessage<ClientNetworkContext> { | ||||
|     private final Map<String, UpgradeManager.UpgradeWrapper<ITurtleUpgrade>> turtleUpgrades; | ||||
|     private final Map<String, UpgradeManager.UpgradeWrapper<IPocketUpgrade>> pocketUpgrades; | ||||
|     public static final StreamCodec<RegistryFriendlyByteBuf, UpgradesLoadedMessage> STREAM_CODEC = StreamCodec.ofMember(UpgradesLoadedMessage::write, UpgradesLoadedMessage::new); | ||||
| 
 | ||||
|     private final Map<ResourceLocation, UpgradeManager.UpgradeWrapper<ITurtleUpgrade>> turtleUpgrades; | ||||
|     private final Map<ResourceLocation, UpgradeManager.UpgradeWrapper<IPocketUpgrade>> pocketUpgrades; | ||||
| 
 | ||||
|     public UpgradesLoadedMessage() { | ||||
|         turtleUpgrades = TurtleUpgrades.instance().getUpgradeWrappers(); | ||||
|         pocketUpgrades = PocketUpgrades.instance().getUpgradeWrappers(); | ||||
|     } | ||||
| 
 | ||||
|     public UpgradesLoadedMessage(FriendlyByteBuf buf) { | ||||
|     private UpgradesLoadedMessage(RegistryFriendlyByteBuf buf) { | ||||
|         turtleUpgrades = fromBytes(buf, ITurtleUpgrade.serialiserRegistryKey()); | ||||
|         pocketUpgrades = fromBytes(buf, IPocketUpgrade.serialiserRegistryKey()); | ||||
|     } | ||||
| 
 | ||||
|     private <T extends UpgradeBase> Map<String, UpgradeManager.UpgradeWrapper<T>> fromBytes( | ||||
|         FriendlyByteBuf buf, ResourceKey<Registry<UpgradeSerialiser<? extends T>>> registryKey | ||||
|     private <T extends UpgradeBase> Map<ResourceLocation, UpgradeManager.UpgradeWrapper<T>> fromBytes( | ||||
|         RegistryFriendlyByteBuf buf, ResourceKey<Registry<UpgradeSerialiser<? extends T>>> registryKey | ||||
|     ) { | ||||
|         var registry = RegistryHelper.getRegistry(registryKey); | ||||
| 
 | ||||
|         var size = buf.readVarInt(); | ||||
|         Map<String, UpgradeManager.UpgradeWrapper<T>> upgrades = new HashMap<>(size); | ||||
|         Map<ResourceLocation, UpgradeManager.UpgradeWrapper<T>> upgrades = new HashMap<>(size); | ||||
|         for (var i = 0; i < size; i++) { | ||||
|             var id = buf.readUtf(); | ||||
|             var id = buf.readResourceLocation(); | ||||
| 
 | ||||
|             var serialiserId = buf.readResourceLocation(); | ||||
|             var serialiser = registry.get(serialiserId); | ||||
|             if (serialiser == null) throw new IllegalStateException("Unknown serialiser " + serialiserId); | ||||
| 
 | ||||
|             var upgrade = serialiser.fromNetwork(new ResourceLocation(id), buf); | ||||
|             var upgrade = serialiser.fromNetwork(id, buf); | ||||
|             var modId = buf.readUtf(); | ||||
| 
 | ||||
|             upgrades.put(id, new UpgradeManager.UpgradeWrapper<T>(id, upgrade, serialiser, modId)); | ||||
| @@ -63,20 +66,19 @@ public final class UpgradesLoadedMessage implements NetworkMessage<ClientNetwork | ||||
|         return upgrades; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void write(FriendlyByteBuf buf) { | ||||
|     private void write(RegistryFriendlyByteBuf buf) { | ||||
|         toBytes(buf, ITurtleUpgrade.serialiserRegistryKey(), turtleUpgrades); | ||||
|         toBytes(buf, IPocketUpgrade.serialiserRegistryKey(), pocketUpgrades); | ||||
|     } | ||||
| 
 | ||||
|     private <T extends UpgradeBase> void toBytes( | ||||
|         FriendlyByteBuf buf, ResourceKey<Registry<UpgradeSerialiser<? extends T>>> registryKey, Map<String, UpgradeManager.UpgradeWrapper<T>> upgrades | ||||
|         RegistryFriendlyByteBuf buf, ResourceKey<Registry<UpgradeSerialiser<? extends T>>> registryKey, Map<ResourceLocation, UpgradeManager.UpgradeWrapper<T>> upgrades | ||||
|     ) { | ||||
|         var registry = RegistryHelper.getRegistry(registryKey); | ||||
| 
 | ||||
|         buf.writeVarInt(upgrades.size()); | ||||
|         for (var entry : upgrades.entrySet()) { | ||||
|             buf.writeUtf(entry.getKey()); | ||||
|             buf.writeResourceLocation(entry.getKey()); | ||||
| 
 | ||||
|             var serialiser = entry.getValue().serialiser(); | ||||
|             @SuppressWarnings("unchecked") | ||||
| @@ -96,7 +98,7 @@ public final class UpgradesLoadedMessage implements NetworkMessage<ClientNetwork | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public MessageType<UpgradesLoadedMessage> type() { | ||||
|     public CustomPacketPayload.Type<UpgradesLoadedMessage> type() { | ||||
|         return NetworkMessages.UPGRADES_LOADED; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -4,60 +4,57 @@ | ||||
| 
 | ||||
| package dan200.computercraft.shared.network.client; | ||||
| 
 | ||||
| import dan200.computercraft.core.util.Nullability; | ||||
| import dan200.computercraft.shared.computer.upload.UploadResult; | ||||
| import dan200.computercraft.shared.network.MessageType; | ||||
| import dan200.computercraft.shared.network.NetworkMessage; | ||||
| import dan200.computercraft.shared.network.NetworkMessages; | ||||
| import net.minecraft.network.FriendlyByteBuf; | ||||
| import dan200.computercraft.shared.network.codec.MoreStreamCodecs; | ||||
| import net.minecraft.network.RegistryFriendlyByteBuf; | ||||
| import net.minecraft.network.chat.Component; | ||||
| import net.minecraft.network.chat.ComponentSerialization; | ||||
| import net.minecraft.network.codec.ByteBufCodecs; | ||||
| import net.minecraft.network.codec.StreamCodec; | ||||
| import net.minecraft.network.protocol.common.custom.CustomPacketPayload; | ||||
| import net.minecraft.world.inventory.AbstractContainerMenu; | ||||
| 
 | ||||
| import javax.annotation.Nullable; | ||||
| import java.util.Optional; | ||||
| 
 | ||||
| public final class UploadResultMessage implements NetworkMessage<ClientNetworkContext> { | ||||
|     public static final StreamCodec<RegistryFriendlyByteBuf, UploadResultMessage> STREAM_CODEC = StreamCodec.composite( | ||||
|         ByteBufCodecs.VAR_INT, x -> x.containerId, | ||||
|         MoreStreamCodecs.ofEnum(UploadResult.class), x -> x.result, | ||||
|         ComponentSerialization.OPTIONAL_STREAM_CODEC, x -> x.errorMessage, | ||||
|         UploadResultMessage::new | ||||
|     ); | ||||
| 
 | ||||
| public class UploadResultMessage implements NetworkMessage<ClientNetworkContext> { | ||||
|     private final int containerId; | ||||
|     private final UploadResult result; | ||||
|     private final @Nullable Component errorMessage; | ||||
|     private final Optional<Component> errorMessage; | ||||
| 
 | ||||
|     private UploadResultMessage(AbstractContainerMenu container, UploadResult result, @Nullable Component errorMessage) { | ||||
|         containerId = container.containerId; | ||||
|     private UploadResultMessage(int containerId, UploadResult result, Optional<Component> errorMessage) { | ||||
|         this.containerId = containerId; | ||||
|         this.result = result; | ||||
|         this.errorMessage = errorMessage; | ||||
|     } | ||||
| 
 | ||||
|     public static UploadResultMessage queued(AbstractContainerMenu container) { | ||||
|         return new UploadResultMessage(container, UploadResult.QUEUED, null); | ||||
|         return new UploadResultMessage(container.containerId, UploadResult.QUEUED, Optional.empty()); | ||||
|     } | ||||
| 
 | ||||
|     public static UploadResultMessage consumed(AbstractContainerMenu container) { | ||||
|         return new UploadResultMessage(container, UploadResult.CONSUMED, null); | ||||
|         return new UploadResultMessage(container.containerId, UploadResult.CONSUMED, Optional.empty()); | ||||
|     } | ||||
| 
 | ||||
|     public static UploadResultMessage error(AbstractContainerMenu container, Component errorMessage) { | ||||
|         return new UploadResultMessage(container, UploadResult.ERROR, errorMessage); | ||||
|     } | ||||
| 
 | ||||
|     public UploadResultMessage(FriendlyByteBuf buf) { | ||||
|         containerId = buf.readVarInt(); | ||||
|         result = buf.readEnum(UploadResult.class); | ||||
|         errorMessage = result == UploadResult.ERROR ? buf.readComponent() : null; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void write(FriendlyByteBuf buf) { | ||||
|         buf.writeVarInt(containerId); | ||||
|         buf.writeEnum(result); | ||||
|         if (result == UploadResult.ERROR) buf.writeComponent(Nullability.assertNonNull(errorMessage)); | ||||
|         return new UploadResultMessage(container.containerId, UploadResult.ERROR, Optional.of(errorMessage)); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void handle(ClientNetworkContext context) { | ||||
|         context.handleUploadResult(containerId, result, errorMessage); | ||||
|         context.handleUploadResult(containerId, result, errorMessage.orElse(null)); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public MessageType<UploadResultMessage> type() { | ||||
|     public CustomPacketPayload.Type<UploadResultMessage> type() { | ||||
|         return NetworkMessages.UPLOAD_RESULT; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -0,0 +1,107 @@ | ||||
| // SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers | ||||
| // | ||||
| // SPDX-License-Identifier: MPL-2.0 | ||||
| 
 | ||||
| package dan200.computercraft.shared.network.codec; | ||||
| 
 | ||||
| import io.netty.buffer.ByteBuf; | ||||
| import io.netty.handler.codec.DecoderException; | ||||
| import net.minecraft.core.NonNullList; | ||||
| import net.minecraft.network.FriendlyByteBuf; | ||||
| import net.minecraft.network.VarInt; | ||||
| import net.minecraft.network.codec.ByteBufCodecs; | ||||
| import net.minecraft.network.codec.StreamCodec; | ||||
| import net.minecraft.world.phys.Vec3; | ||||
| 
 | ||||
| import java.nio.ByteBuffer; | ||||
| import java.util.OptionalInt; | ||||
| 
 | ||||
| /** | ||||
|  * Additional {@link StreamCodec}s. | ||||
|  * | ||||
|  * @see ByteBufCodecs | ||||
|  */ | ||||
| public class MoreStreamCodecs { | ||||
|     public static <B extends FriendlyByteBuf, C extends Enum<C>> StreamCodec<B, C> ofEnum(Class<C> klass) { | ||||
|         return new StreamCodec<>() { | ||||
|             @Override | ||||
|             public C decode(B buffer) { | ||||
|                 return buffer.readEnum(klass); | ||||
|             } | ||||
| 
 | ||||
|             @Override | ||||
|             public void encode(B buffer, C value) { | ||||
|                 buffer.writeEnum(value); | ||||
|             } | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     public static <B extends FriendlyByteBuf, C> StreamCodec<B, NonNullList<C>> nonNullList(StreamCodec<B, C> codec, C empty) { | ||||
|         return new StreamCodec<>() { | ||||
|             @Override | ||||
|             public NonNullList<C> decode(B buffer) { | ||||
|                 var count = buffer.readVarInt(); | ||||
|                 var result = NonNullList.withSize(count, empty); | ||||
|                 for (var i = 0; i < result.size(); i++) result.set(i, codec.decode(buffer)); | ||||
|                 return result; | ||||
|             } | ||||
| 
 | ||||
|             @Override | ||||
|             public void encode(B buffer, NonNullList<C> list) { | ||||
|                 var count = buffer.writeVarInt(list.size()); | ||||
|                 for (var entry : list) codec.encode(buffer, entry); | ||||
|             } | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     public static final StreamCodec<ByteBuf, Vec3> VEC3 = StreamCodec.composite( | ||||
|         ByteBufCodecs.DOUBLE, Vec3::x, | ||||
|         ByteBufCodecs.DOUBLE, Vec3::y, | ||||
|         ByteBufCodecs.DOUBLE, Vec3::z, | ||||
|         Vec3::new | ||||
|     ); | ||||
| 
 | ||||
|     /** | ||||
|      * A codec for {@link OptionalInt}. This uses the same wire format as {@link ByteBufCodecs#optional(StreamCodec)} | ||||
|      * and {@link ByteBufCodecs#VAR_INT}. | ||||
|      */ | ||||
|     public static final StreamCodec<ByteBuf, OptionalInt> OPTIONAL_INT = new StreamCodec<>() { | ||||
|         @Override | ||||
|         public OptionalInt decode(ByteBuf buf) { | ||||
|             return buf.readBoolean() ? OptionalInt.of(ByteBufCodecs.VAR_INT.decode(buf)) : OptionalInt.empty(); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public void encode(ByteBuf buf, OptionalInt optional) { | ||||
|             if (optional.isPresent()) { | ||||
|                 buf.writeBoolean(true); | ||||
|                 ByteBufCodecs.VAR_INT.encode(buf, optional.getAsInt()); | ||||
|             } else { | ||||
|                 buf.writeBoolean(false); | ||||
|             } | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     /** | ||||
|      * Equivalent to {@link ByteBufCodecs#BYTE_ARRAY}, but into an immutable {@link ByteBuffer}. | ||||
|      */ | ||||
|     public static final StreamCodec<ByteBuf, ByteBuffer> BYTE_BUFFER = new StreamCodec<>() { | ||||
|         @Override | ||||
|         public ByteBuffer decode(ByteBuf buf) { | ||||
|             var toRead = VarInt.read(buf); | ||||
|             if (toRead > buf.readableBytes()) { | ||||
|                 throw new DecoderException("ByteArray with size " + toRead + " is bigger than allowed"); | ||||
|             } | ||||
| 
 | ||||
|             var bytes = new byte[toRead]; | ||||
|             buf.readBytes(bytes); | ||||
|             return ByteBuffer.wrap(bytes).asReadOnlyBuffer(); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public void encode(ByteBuf buf, ByteBuffer buffer) { | ||||
|             VarInt.write(buf, buffer.remaining()); | ||||
|             buf.writeBytes(buffer.duplicate()); | ||||
|         } | ||||
|     }; | ||||
| } | ||||
| @@ -8,55 +8,37 @@ import dan200.computercraft.shared.computer.core.ComputerFamily; | ||||
| import dan200.computercraft.shared.computer.core.ServerComputer; | ||||
| import dan200.computercraft.shared.computer.terminal.TerminalState; | ||||
| import dan200.computercraft.shared.config.Config; | ||||
| import net.minecraft.network.FriendlyByteBuf; | ||||
| import dan200.computercraft.shared.network.codec.MoreStreamCodecs; | ||||
| import net.minecraft.network.RegistryFriendlyByteBuf; | ||||
| import net.minecraft.network.codec.ByteBufCodecs; | ||||
| import net.minecraft.network.codec.StreamCodec; | ||||
| import net.minecraft.world.item.ItemStack; | ||||
| 
 | ||||
| public class ComputerContainerData implements ContainerData { | ||||
|     private final ComputerFamily family; | ||||
|     private final TerminalState terminal; | ||||
|     private final ItemStack displayStack; | ||||
|     private final int uploadMaxSize; | ||||
| /** | ||||
|  * The data required to open a computer container. | ||||
|  * | ||||
|  * @param family        The computer family. | ||||
|  * @param terminal      The initial terminal contents. | ||||
|  * @param displayStack  The stack associated with this menu. This may be displayed on the client. | ||||
|  * @param uploadMaxSize The maximum size of a file upload. | ||||
|  */ | ||||
| public record ComputerContainerData( | ||||
|     ComputerFamily family, TerminalState terminal, ItemStack displayStack, int uploadMaxSize | ||||
| ) implements ContainerData { | ||||
|     public static final StreamCodec<RegistryFriendlyByteBuf, ComputerContainerData> STREAM_CODEC = StreamCodec.composite( | ||||
|         MoreStreamCodecs.ofEnum(ComputerFamily.class), ComputerContainerData::family, | ||||
|         TerminalState.STREAM_CODEC, ComputerContainerData::terminal, | ||||
|         ItemStack.OPTIONAL_STREAM_CODEC, ComputerContainerData::displayStack, | ||||
|         ByteBufCodecs.VAR_INT, ComputerContainerData::uploadMaxSize, | ||||
|         ComputerContainerData::new | ||||
|     ); | ||||
| 
 | ||||
|     public ComputerContainerData(ServerComputer computer, ItemStack displayStack) { | ||||
|         family = computer.getFamily(); | ||||
|         terminal = computer.getTerminalState(); | ||||
|         this.displayStack = displayStack; | ||||
|         uploadMaxSize = Config.uploadMaxSize; | ||||
|     } | ||||
| 
 | ||||
|     public ComputerContainerData(FriendlyByteBuf buf) { | ||||
|         family = buf.readEnum(ComputerFamily.class); | ||||
|         terminal = new TerminalState(buf); | ||||
|         displayStack = buf.readItem(); | ||||
|         uploadMaxSize = buf.readInt(); | ||||
|         this(computer.getFamily(), computer.getTerminalState(), displayStack, Config.uploadMaxSize); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void toBytes(FriendlyByteBuf buf) { | ||||
|         buf.writeEnum(family); | ||||
|         terminal.write(buf); | ||||
|         buf.writeItem(displayStack); | ||||
|         buf.writeInt(uploadMaxSize); | ||||
|     } | ||||
| 
 | ||||
|     public ComputerFamily family() { | ||||
|         return family; | ||||
|     } | ||||
| 
 | ||||
|     public TerminalState terminal() { | ||||
|         return terminal; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get a stack associated with this menu. This may be displayed on the client. | ||||
|      * | ||||
|      * @return The stack associated with this menu. | ||||
|      */ | ||||
|     public ItemStack displayStack() { | ||||
|         return displayStack; | ||||
|     } | ||||
| 
 | ||||
|     public int uploadMaxSize() { | ||||
|         return uploadMaxSize; | ||||
|     public void toBytes(RegistryFriendlyByteBuf buf) { | ||||
|         STREAM_CODEC.encode(buf, this); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -6,20 +6,19 @@ package dan200.computercraft.shared.network.container; | ||||
| 
 | ||||
| import dan200.computercraft.shared.network.NetworkMessage; | ||||
| import dan200.computercraft.shared.platform.PlatformHelper; | ||||
| import net.minecraft.network.FriendlyByteBuf; | ||||
| import net.minecraft.network.RegistryFriendlyByteBuf; | ||||
| import net.minecraft.network.codec.StreamCodec; | ||||
| import net.minecraft.world.MenuProvider; | ||||
| import net.minecraft.world.entity.player.Inventory; | ||||
| import net.minecraft.world.entity.player.Player; | ||||
| import net.minecraft.world.inventory.AbstractContainerMenu; | ||||
| import net.minecraft.world.inventory.MenuType; | ||||
| 
 | ||||
| import java.util.function.Function; | ||||
| 
 | ||||
| /** | ||||
|  * Additional data to send when opening a menu. Like {@link NetworkMessage}, this should be immutable. | ||||
|  */ | ||||
| public interface ContainerData { | ||||
|     void toBytes(FriendlyByteBuf buf); | ||||
|     void toBytes(RegistryFriendlyByteBuf buf); | ||||
| 
 | ||||
|     /** | ||||
|      * Open a menu for a specific player using this data. | ||||
| @@ -31,8 +30,8 @@ public interface ContainerData { | ||||
|         PlatformHelper.get().openMenu(player, menu, this); | ||||
|     } | ||||
| 
 | ||||
|     static <C extends AbstractContainerMenu, T extends ContainerData> MenuType<C> toType(Function<FriendlyByteBuf, T> reader, Factory<C, T> factory) { | ||||
|         return PlatformHelper.get().createMenuType(reader, factory); | ||||
|     static <C extends AbstractContainerMenu, T extends ContainerData> MenuType<C> toType(StreamCodec<RegistryFriendlyByteBuf, T> codec, Factory<C, T> factory) { | ||||
|         return PlatformHelper.get().createMenuType(codec, factory); | ||||
|     } | ||||
| 
 | ||||
|     interface Factory<C extends AbstractContainerMenu, T extends ContainerData> { | ||||
|   | ||||
| @@ -6,32 +6,26 @@ package dan200.computercraft.shared.network.container; | ||||
| 
 | ||||
| import dan200.computercraft.shared.common.HeldItemMenu; | ||||
| import dan200.computercraft.shared.media.items.PrintoutItem; | ||||
| import net.minecraft.network.FriendlyByteBuf; | ||||
| import dan200.computercraft.shared.network.codec.MoreStreamCodecs; | ||||
| import net.minecraft.network.RegistryFriendlyByteBuf; | ||||
| import net.minecraft.network.codec.StreamCodec; | ||||
| import net.minecraft.world.InteractionHand; | ||||
| 
 | ||||
| /** | ||||
|  * Opens a printout GUI based on the currently held item. | ||||
|  * | ||||
|  * @param hand The hand holding this item. | ||||
|  * @see HeldItemMenu | ||||
|  * @see PrintoutItem | ||||
|  */ | ||||
| public class HeldItemContainerData implements ContainerData { | ||||
|     private final InteractionHand hand; | ||||
| 
 | ||||
|     public HeldItemContainerData(InteractionHand hand) { | ||||
|         this.hand = hand; | ||||
|     } | ||||
| 
 | ||||
|     public HeldItemContainerData(FriendlyByteBuf buffer) { | ||||
|         hand = buffer.readEnum(InteractionHand.class); | ||||
|     } | ||||
| public record HeldItemContainerData(InteractionHand hand) implements ContainerData { | ||||
|     public static final StreamCodec<RegistryFriendlyByteBuf, HeldItemContainerData> STREAM_CODEC = StreamCodec.composite( | ||||
|         MoreStreamCodecs.ofEnum(InteractionHand.class), HeldItemContainerData::hand, | ||||
|         HeldItemContainerData::new | ||||
|     ); | ||||
| 
 | ||||
|     @Override | ||||
|     public void toBytes(FriendlyByteBuf buf) { | ||||
|         buf.writeEnum(hand); | ||||
|     } | ||||
| 
 | ||||
|     public InteractionHand getHand() { | ||||
|         return hand; | ||||
|     public void toBytes(RegistryFriendlyByteBuf buf) { | ||||
|         STREAM_CODEC.encode(buf, this); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -5,31 +5,35 @@ | ||||
| package dan200.computercraft.shared.network.server; | ||||
| 
 | ||||
| import dan200.computercraft.shared.computer.menu.ComputerMenu; | ||||
| import dan200.computercraft.shared.network.MessageType; | ||||
| import dan200.computercraft.shared.network.NetworkMessages; | ||||
| import net.minecraft.network.FriendlyByteBuf; | ||||
| import dan200.computercraft.shared.network.codec.MoreStreamCodecs; | ||||
| import net.minecraft.network.RegistryFriendlyByteBuf; | ||||
| import net.minecraft.network.codec.ByteBufCodecs; | ||||
| import net.minecraft.network.codec.StreamCodec; | ||||
| import net.minecraft.network.protocol.common.custom.CustomPacketPayload; | ||||
| import net.minecraft.world.inventory.AbstractContainerMenu; | ||||
| 
 | ||||
| /** | ||||
|  * Turn on, shutdown, or reboot the currently open computer. | ||||
|  */ | ||||
| public final class ComputerActionServerMessage extends ComputerServerMessage { | ||||
|     public static final StreamCodec<RegistryFriendlyByteBuf, ComputerActionServerMessage> STREAM_CODEC = StreamCodec.composite( | ||||
|         ByteBufCodecs.VAR_INT, ComputerActionServerMessage::containerId, | ||||
|         MoreStreamCodecs.ofEnum(Action.class), x -> x.action, | ||||
|         ComputerActionServerMessage::new | ||||
|     ); | ||||
| 
 | ||||
| public class ComputerActionServerMessage extends ComputerServerMessage { | ||||
|     private final Action action; | ||||
| 
 | ||||
|     public ComputerActionServerMessage(AbstractContainerMenu menu, Action action) { | ||||
|         this(menu.containerId, action); | ||||
|     } | ||||
| 
 | ||||
|     private ComputerActionServerMessage(int menu, Action action) { | ||||
|         super(menu); | ||||
|         this.action = action; | ||||
|     } | ||||
| 
 | ||||
|     public ComputerActionServerMessage(FriendlyByteBuf buf) { | ||||
|         super(buf); | ||||
|         action = buf.readEnum(Action.class); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void write(FriendlyByteBuf buf) { | ||||
|         super.write(buf); | ||||
|         buf.writeEnum(action); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected void handle(ServerNetworkContext context, ComputerMenu container) { | ||||
|         switch (action) { | ||||
| @@ -40,7 +44,7 @@ public class ComputerActionServerMessage extends ComputerServerMessage { | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public MessageType<ComputerActionServerMessage> type() { | ||||
|     public CustomPacketPayload.Type<ComputerActionServerMessage> type() { | ||||
|         return NetworkMessages.COMPUTER_ACTION; | ||||
|     } | ||||
| 
 | ||||
|   | ||||
| @@ -6,11 +6,7 @@ package dan200.computercraft.shared.network.server; | ||||
| 
 | ||||
| import dan200.computercraft.shared.computer.menu.ComputerMenu; | ||||
| import dan200.computercraft.shared.network.NetworkMessage; | ||||
| import net.minecraft.network.FriendlyByteBuf; | ||||
| import net.minecraft.world.entity.player.Player; | ||||
| import net.minecraft.world.inventory.AbstractContainerMenu; | ||||
| 
 | ||||
| import javax.annotation.OverridingMethodsMustInvokeSuper; | ||||
| 
 | ||||
| /** | ||||
|  * A packet, which performs an action on the currently open {@link ComputerMenu}. | ||||
| @@ -18,18 +14,12 @@ import javax.annotation.OverridingMethodsMustInvokeSuper; | ||||
| public abstract class ComputerServerMessage implements NetworkMessage<ServerNetworkContext> { | ||||
|     private final int containerId; | ||||
| 
 | ||||
|     protected ComputerServerMessage(AbstractContainerMenu menu) { | ||||
|         containerId = menu.containerId; | ||||
|     ComputerServerMessage(int id) { | ||||
|         containerId = id; | ||||
|     } | ||||
| 
 | ||||
|     public ComputerServerMessage(FriendlyByteBuf buffer) { | ||||
|         containerId = buffer.readVarInt(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     @OverridingMethodsMustInvokeSuper | ||||
|     public void write(FriendlyByteBuf buf) { | ||||
|         buf.writeVarInt(containerId); | ||||
|     int containerId() { | ||||
|         return containerId; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|   | ||||
| @@ -5,47 +5,50 @@ | ||||
| package dan200.computercraft.shared.network.server; | ||||
| 
 | ||||
| import dan200.computercraft.shared.computer.menu.ComputerMenu; | ||||
| import dan200.computercraft.shared.network.MessageType; | ||||
| import dan200.computercraft.shared.network.NetworkMessages; | ||||
| import net.minecraft.network.FriendlyByteBuf; | ||||
| import dan200.computercraft.shared.network.codec.MoreStreamCodecs; | ||||
| import net.minecraft.network.RegistryFriendlyByteBuf; | ||||
| import net.minecraft.network.codec.ByteBufCodecs; | ||||
| import net.minecraft.network.codec.StreamCodec; | ||||
| import net.minecraft.network.protocol.common.custom.CustomPacketPayload; | ||||
| import net.minecraft.world.inventory.AbstractContainerMenu; | ||||
| 
 | ||||
| /** | ||||
|  * Queue a key event on the currently opened computer. | ||||
|  */ | ||||
| public final class KeyEventServerMessage extends ComputerServerMessage { | ||||
|     public static final StreamCodec<RegistryFriendlyByteBuf, KeyEventServerMessage> STREAM_CODEC = StreamCodec.composite( | ||||
|         ByteBufCodecs.VAR_INT, KeyEventServerMessage::containerId, | ||||
|         MoreStreamCodecs.ofEnum(Action.class), x -> x.action, | ||||
|         ByteBufCodecs.INT, x -> x.key, | ||||
|         KeyEventServerMessage::new | ||||
|     ); | ||||
| 
 | ||||
| public class KeyEventServerMessage extends ComputerServerMessage { | ||||
|     private final Action type; | ||||
|     private final Action action; | ||||
|     private final int key; | ||||
| 
 | ||||
|     public KeyEventServerMessage(AbstractContainerMenu menu, Action type, int key) { | ||||
|         super(menu); | ||||
|         this.type = type; | ||||
|     public KeyEventServerMessage(AbstractContainerMenu menu, Action action, int key) { | ||||
|         this(menu.containerId, action, key); | ||||
|     } | ||||
| 
 | ||||
|     private KeyEventServerMessage(int id, Action action, int key) { | ||||
|         super(id); | ||||
|         this.action = action; | ||||
|         this.key = key; | ||||
|     } | ||||
| 
 | ||||
|     public KeyEventServerMessage(FriendlyByteBuf buf) { | ||||
|         super(buf); | ||||
|         type = buf.readEnum(Action.class); | ||||
|         key = buf.readVarInt(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void write(FriendlyByteBuf buf) { | ||||
|         super.write(buf); | ||||
|         buf.writeEnum(type); | ||||
|         buf.writeVarInt(key); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected void handle(ServerNetworkContext context, ComputerMenu container) { | ||||
|         var input = container.getInput(); | ||||
|         if (type == Action.UP) { | ||||
|             input.keyUp(key); | ||||
|         } else { | ||||
|             input.keyDown(key, type == Action.REPEAT); | ||||
|         switch (action) { | ||||
|             case UP -> input.keyUp(key); | ||||
|             case DOWN -> input.keyDown(key, false); | ||||
|             case REPEAT -> input.keyDown(key, true); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public MessageType<KeyEventServerMessage> type() { | ||||
|     public CustomPacketPayload.Type<KeyEventServerMessage> type() { | ||||
|         return NetworkMessages.KEY_EVENT; | ||||
|     } | ||||
| 
 | ||||
|   | ||||
| @@ -5,47 +5,48 @@ | ||||
| package dan200.computercraft.shared.network.server; | ||||
| 
 | ||||
| import dan200.computercraft.shared.computer.menu.ComputerMenu; | ||||
| import dan200.computercraft.shared.network.MessageType; | ||||
| import dan200.computercraft.shared.network.NetworkMessages; | ||||
| import net.minecraft.network.FriendlyByteBuf; | ||||
| import dan200.computercraft.shared.network.codec.MoreStreamCodecs; | ||||
| import net.minecraft.network.RegistryFriendlyByteBuf; | ||||
| import net.minecraft.network.codec.ByteBufCodecs; | ||||
| import net.minecraft.network.codec.StreamCodec; | ||||
| import net.minecraft.network.protocol.common.custom.CustomPacketPayload; | ||||
| import net.minecraft.world.inventory.AbstractContainerMenu; | ||||
| 
 | ||||
| /** | ||||
|  * Queue a mouse event on the currently opened computer. | ||||
|  */ | ||||
| public final class MouseEventServerMessage extends ComputerServerMessage { | ||||
|     public static final StreamCodec<RegistryFriendlyByteBuf, MouseEventServerMessage> STREAM_CODEC = StreamCodec.composite( | ||||
|         ByteBufCodecs.VAR_INT, MouseEventServerMessage::containerId, | ||||
|         MoreStreamCodecs.ofEnum(Action.class), x -> x.action, | ||||
|         ByteBufCodecs.VAR_INT, x -> x.arg, | ||||
|         ByteBufCodecs.VAR_INT, x -> x.x, | ||||
|         ByteBufCodecs.VAR_INT, x -> x.y, | ||||
|         MouseEventServerMessage::new | ||||
|     ); | ||||
| 
 | ||||
| public class MouseEventServerMessage extends ComputerServerMessage { | ||||
|     private final Action type; | ||||
|     private final Action action; | ||||
|     private final int arg; | ||||
|     private final int x; | ||||
|     private final int y; | ||||
|     private final int arg; | ||||
| 
 | ||||
|     public MouseEventServerMessage(AbstractContainerMenu menu, Action type, int arg, int x, int y) { | ||||
|         super(menu); | ||||
|         this.type = type; | ||||
|         this.arg = arg; | ||||
|     public MouseEventServerMessage(AbstractContainerMenu menu, Action action, int arg, int x, int y) { | ||||
|         this(menu.containerId, action, arg, x, y); | ||||
|     } | ||||
| 
 | ||||
|     private MouseEventServerMessage(int id, Action action, int arg, int x, int y) { | ||||
|         super(id); | ||||
|         this.action = action; | ||||
|         this.x = x; | ||||
|         this.y = y; | ||||
|     } | ||||
| 
 | ||||
|     public MouseEventServerMessage(FriendlyByteBuf buf) { | ||||
|         super(buf); | ||||
|         type = buf.readEnum(Action.class); | ||||
|         arg = buf.readVarInt(); | ||||
|         x = buf.readVarInt(); | ||||
|         y = buf.readVarInt(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void write(FriendlyByteBuf buf) { | ||||
|         super.write(buf); | ||||
|         buf.writeEnum(type); | ||||
|         buf.writeVarInt(arg); | ||||
|         buf.writeVarInt(x); | ||||
|         buf.writeVarInt(y); | ||||
|         this.arg = arg; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected void handle(ServerNetworkContext context, ComputerMenu container) { | ||||
|         var input = container.getInput(); | ||||
|         switch (type) { | ||||
|         switch (action) { | ||||
|             case CLICK -> input.mouseClick(arg, x, y); | ||||
|             case DRAG -> input.mouseDrag(arg, x, y); | ||||
|             case UP -> input.mouseUp(arg, x, y); | ||||
| @@ -54,7 +55,7 @@ public class MouseEventServerMessage extends ComputerServerMessage { | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public MessageType<MouseEventServerMessage> type() { | ||||
|     public CustomPacketPayload.Type<MouseEventServerMessage> type() { | ||||
|         return NetworkMessages.MOUSE_EVENT; | ||||
|     } | ||||
| 
 | ||||
|   | ||||
| @@ -7,10 +7,12 @@ package dan200.computercraft.shared.network.server; | ||||
| import dan200.computercraft.shared.computer.core.ServerComputer; | ||||
| import dan200.computercraft.shared.computer.menu.ComputerMenu; | ||||
| import dan200.computercraft.shared.computer.menu.ServerInputHandler; | ||||
| import dan200.computercraft.shared.network.MessageType; | ||||
| import dan200.computercraft.shared.network.NetworkMessages; | ||||
| import dan200.computercraft.shared.util.NBTUtil; | ||||
| import net.minecraft.network.FriendlyByteBuf; | ||||
| import net.minecraft.network.RegistryFriendlyByteBuf; | ||||
| import net.minecraft.network.codec.StreamCodec; | ||||
| import net.minecraft.network.protocol.common.custom.CustomPacketPayload; | ||||
| import net.minecraft.world.inventory.AbstractContainerMenu; | ||||
| 
 | ||||
| import javax.annotation.Nullable; | ||||
| @@ -20,27 +22,28 @@ import javax.annotation.Nullable; | ||||
|  * | ||||
|  * @see ServerInputHandler#queueEvent(String) | ||||
|  */ | ||||
| public class QueueEventServerMessage extends ComputerServerMessage { | ||||
| public final class QueueEventServerMessage extends ComputerServerMessage { | ||||
|     public static final StreamCodec<RegistryFriendlyByteBuf, QueueEventServerMessage> STREAM_CODEC = StreamCodec.ofMember(QueueEventServerMessage::write, QueueEventServerMessage::new); | ||||
| 
 | ||||
|     private final String event; | ||||
|     private final @Nullable Object[] args; | ||||
| 
 | ||||
|     public QueueEventServerMessage(AbstractContainerMenu menu, String event, @Nullable Object[] args) { | ||||
|         super(menu); | ||||
|         super(menu.containerId); | ||||
|         this.event = event; | ||||
|         this.args = args; | ||||
|     } | ||||
| 
 | ||||
|     public QueueEventServerMessage(FriendlyByteBuf buf) { | ||||
|         super(buf); | ||||
|     private QueueEventServerMessage(FriendlyByteBuf buf) { | ||||
|         super(buf.readVarInt()); | ||||
|         event = buf.readUtf(Short.MAX_VALUE); | ||||
| 
 | ||||
|         var args = buf.readNbt(); | ||||
|         this.args = args == null ? null : NBTUtil.decodeObjects(args); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void write(FriendlyByteBuf buf) { | ||||
|         super.write(buf); | ||||
|     private void write(RegistryFriendlyByteBuf buf) { | ||||
|         buf.writeVarInt(containerId()); | ||||
|         buf.writeUtf(event); | ||||
|         buf.writeNbt(args == null ? null : NBTUtil.encodeObjects(args)); | ||||
|     } | ||||
| @@ -51,7 +54,7 @@ public class QueueEventServerMessage extends ComputerServerMessage { | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public MessageType<QueueEventServerMessage> type() { | ||||
|     public CustomPacketPayload.Type<QueueEventServerMessage> type() { | ||||
|         return NetworkMessages.QUEUE_EVENT; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -6,7 +6,9 @@ package dan200.computercraft.shared.network.server; | ||||
| 
 | ||||
| import dan200.computercraft.shared.network.NetworkMessage; | ||||
| import dan200.computercraft.shared.network.client.ClientNetworkContext; | ||||
| import dan200.computercraft.shared.platform.PlatformHelper; | ||||
| import net.minecraft.network.protocol.Packet; | ||||
| import net.minecraft.network.protocol.common.ClientCommonPacketListener; | ||||
| import net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket; | ||||
| import net.minecraft.server.MinecraftServer; | ||||
| import net.minecraft.server.level.ServerChunkCache; | ||||
| import net.minecraft.server.level.ServerLevel; | ||||
| @@ -30,7 +32,7 @@ public final class ServerNetworking { | ||||
|      * @param player  The player to send it to. | ||||
|      */ | ||||
|     public static void sendToPlayer(NetworkMessage<ClientNetworkContext> message, ServerPlayer player) { | ||||
|         player.connection.send(PlatformHelper.get().createPacket(message)); | ||||
|         player.connection.send(createPacket(message)); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @@ -41,7 +43,7 @@ public final class ServerNetworking { | ||||
|      */ | ||||
|     public static void sendToPlayers(NetworkMessage<ClientNetworkContext> message, Collection<ServerPlayer> players) { | ||||
|         if (players.isEmpty()) return; | ||||
|         var packet = PlatformHelper.get().createPacket(message); | ||||
|         var packet = createPacket(message); | ||||
|         for (var player : players) player.connection.send(packet); | ||||
|     } | ||||
| 
 | ||||
| @@ -52,7 +54,7 @@ public final class ServerNetworking { | ||||
|      * @param server  The current server. | ||||
|      */ | ||||
|     public static void sendToAllPlayers(NetworkMessage<ClientNetworkContext> message, MinecraftServer server) { | ||||
|         server.getPlayerList().broadcastAll(PlatformHelper.get().createPacket(message)); | ||||
|         server.getPlayerList().broadcastAll(createPacket(message)); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @@ -64,7 +66,7 @@ public final class ServerNetworking { | ||||
|      * @param distance The distance to the centre players must be within. | ||||
|      */ | ||||
|     public static void sendToAllAround(NetworkMessage<ClientNetworkContext> message, ServerLevel level, Vec3 pos, float distance) { | ||||
|         level.getServer().getPlayerList().broadcast(null, pos.x, pos.y, pos.z, distance, level.dimension(), PlatformHelper.get().createPacket(message)); | ||||
|         level.getServer().getPlayerList().broadcast(null, pos.x, pos.y, pos.z, distance, level.dimension(), createPacket(message)); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @@ -74,9 +76,19 @@ public final class ServerNetworking { | ||||
|      * @param chunk   The chunk players must be tracking. | ||||
|      */ | ||||
|     public static void sendToAllTracking(NetworkMessage<ClientNetworkContext> message, LevelChunk chunk) { | ||||
|         var packet = PlatformHelper.get().createPacket(message); | ||||
|         var packet = createPacket(message); | ||||
|         for (var player : ((ServerChunkCache) chunk.getLevel().getChunkSource()).chunkMap.getPlayers(chunk.getPos(), false)) { | ||||
|             player.connection.send(packet); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Convert a clientbound {@link NetworkMessage} to a Minecraft {@link Packet}. | ||||
|      * | ||||
|      * @param message The message to convert. | ||||
|      * @return The converted message. | ||||
|      */ | ||||
|     private static Packet<ClientCommonPacketListener> createPacket(NetworkMessage<ClientNetworkContext> message) { | ||||
|         return new ClientboundCustomPayloadPacket(message); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -9,10 +9,12 @@ import dan200.computercraft.shared.computer.menu.ComputerMenu; | ||||
| import dan200.computercraft.shared.computer.upload.FileSlice; | ||||
| import dan200.computercraft.shared.computer.upload.FileUpload; | ||||
| import dan200.computercraft.shared.config.Config; | ||||
| import dan200.computercraft.shared.network.MessageType; | ||||
| import dan200.computercraft.shared.network.NetworkMessages; | ||||
| import io.netty.handler.codec.DecoderException; | ||||
| import net.minecraft.network.FriendlyByteBuf; | ||||
| import net.minecraft.network.RegistryFriendlyByteBuf; | ||||
| import net.minecraft.network.codec.StreamCodec; | ||||
| import net.minecraft.network.protocol.common.custom.CustomPacketPayload; | ||||
| import net.minecraft.world.inventory.AbstractContainerMenu; | ||||
| 
 | ||||
| import javax.annotation.Nullable; | ||||
| @@ -25,6 +27,8 @@ import java.util.function.Consumer; | ||||
| import static dan200.computercraft.core.util.Nullability.assertNonNull; | ||||
| 
 | ||||
| public class UploadFileMessage extends ComputerServerMessage { | ||||
|     public static final StreamCodec<RegistryFriendlyByteBuf, UploadFileMessage> STREAM_CODEC = StreamCodec.ofMember(UploadFileMessage::write, UploadFileMessage::new); | ||||
| 
 | ||||
|     static final int MAX_PACKET_SIZE = 30 * 1024; // Max packet size is 32767. | ||||
|     private static final int HEADER_SIZE = 16 + 1; // 16 bytes for the UUID, 4 for the flag. | ||||
| 
 | ||||
| @@ -41,15 +45,15 @@ public class UploadFileMessage extends ComputerServerMessage { | ||||
|     final @VisibleForTesting List<FileSlice> slices; | ||||
| 
 | ||||
|     UploadFileMessage(AbstractContainerMenu menu, UUID uuid, int flag, @Nullable List<FileUpload> files, List<FileSlice> slices) { | ||||
|         super(menu); | ||||
|         super(menu.containerId); | ||||
|         this.uuid = uuid; | ||||
|         this.flag = flag; | ||||
|         this.files = files; | ||||
|         this.slices = slices; | ||||
|     } | ||||
| 
 | ||||
|     public UploadFileMessage(FriendlyByteBuf buf) { | ||||
|         super(buf); | ||||
|     private UploadFileMessage(FriendlyByteBuf buf) { | ||||
|         super(buf.readVarInt()); | ||||
|         uuid = buf.readUUID(); | ||||
|         var flag = this.flag = buf.readByte(); | ||||
| 
 | ||||
| @@ -92,9 +96,8 @@ public class UploadFileMessage extends ComputerServerMessage { | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void write(FriendlyByteBuf buf) { | ||||
|         super.write(buf); | ||||
|     private void write(FriendlyByteBuf buf) { | ||||
|         buf.writeVarInt(containerId()); | ||||
|         buf.writeUUID(uuid); | ||||
|         buf.writeByte(flag); | ||||
| 
 | ||||
| @@ -170,7 +173,7 @@ public class UploadFileMessage extends ComputerServerMessage { | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public MessageType<UploadFileMessage> type() { | ||||
|     public CustomPacketPayload.Type<UploadFileMessage> type() { | ||||
|         return NetworkMessages.UPLOAD_FILE; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -11,8 +11,9 @@ import dan200.computercraft.shared.common.HorizontalContainerBlock; | ||||
| import net.minecraft.core.BlockPos; | ||||
| import net.minecraft.core.Direction; | ||||
| import net.minecraft.world.InteractionHand; | ||||
| import net.minecraft.world.InteractionResult; | ||||
| import net.minecraft.world.ItemInteractionResult; | ||||
| import net.minecraft.world.entity.player.Player; | ||||
| import net.minecraft.world.item.ItemStack; | ||||
| import net.minecraft.world.level.Level; | ||||
| import net.minecraft.world.level.block.BaseEntityBlock; | ||||
| import net.minecraft.world.level.block.Block; | ||||
| @@ -40,7 +41,6 @@ public class DiskDriveBlock extends HorizontalContainerBlock { | ||||
|             .setValue(STATE, DiskDriveState.EMPTY)); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     @Override | ||||
|     protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> properties) { | ||||
|         properties.add(FACING, STATE); | ||||
| @@ -52,20 +52,18 @@ public class DiskDriveBlock extends HorizontalContainerBlock { | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     @Deprecated | ||||
|     public InteractionResult use(BlockState state, Level level, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) { | ||||
|     protected ItemInteractionResult useItemOn(ItemStack stack, BlockState state, Level level, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) { | ||||
|         if (player.isCrouching() && level.getBlockEntity(pos) instanceof DiskDriveBlockEntity drive) { | ||||
|             // Try to put a disk into the drive | ||||
|             var disk = player.getItemInHand(hand); | ||||
|             if (disk.isEmpty()) return InteractionResult.PASS; | ||||
|             if (stack.isEmpty()) return ItemInteractionResult.SKIP_DEFAULT_BLOCK_INTERACTION; | ||||
| 
 | ||||
|             if (!level.isClientSide && drive.getDiskStack().isEmpty() && MediaProviders.get(disk) != null) { | ||||
|                 drive.setDiskStack(disk.split(1)); | ||||
|             if (!level.isClientSide && drive.getDiskStack().isEmpty() && MediaProviders.get(stack) != null) { | ||||
|                 drive.setDiskStack(stack.split(1)); | ||||
|             } | ||||
|             return InteractionResult.sidedSuccess(level.isClientSide); | ||||
|             return ItemInteractionResult.sidedSuccess(level.isClientSide); | ||||
|         } | ||||
| 
 | ||||
|         return super.use(state, level, pos, player, hand, hit); | ||||
|         return super.useItemOn(stack, state, level, pos, player, hand, hit); | ||||
|     } | ||||
| 
 | ||||
|     @Nullable | ||||
|   | ||||
| @@ -11,12 +11,11 @@ import dan200.computercraft.api.media.IMedia; | ||||
| import dan200.computercraft.api.peripheral.IComputerAccess; | ||||
| import dan200.computercraft.api.peripheral.IPeripheral; | ||||
| import dan200.computercraft.shared.common.AbstractContainerBlockEntity; | ||||
| import dan200.computercraft.shared.container.BasicContainer; | ||||
| import dan200.computercraft.shared.network.client.PlayRecordClientMessage; | ||||
| import dan200.computercraft.shared.network.server.ServerNetworking; | ||||
| import dan200.computercraft.shared.util.WorldUtil; | ||||
| import net.minecraft.core.BlockPos; | ||||
| import net.minecraft.core.Direction; | ||||
| import net.minecraft.core.NonNullList; | ||||
| import net.minecraft.core.*; | ||||
| import net.minecraft.nbt.CompoundTag; | ||||
| import net.minecraft.server.level.ServerLevel; | ||||
| import net.minecraft.world.entity.player.Inventory; | ||||
| @@ -30,6 +29,7 @@ import net.minecraft.world.phys.Vec3; | ||||
| import javax.annotation.Nullable; | ||||
| import java.util.HashMap; | ||||
| import java.util.Map; | ||||
| import java.util.Optional; | ||||
| import java.util.concurrent.atomic.AtomicBoolean; | ||||
| import java.util.concurrent.atomic.AtomicReference; | ||||
| 
 | ||||
| @@ -55,7 +55,7 @@ import java.util.concurrent.atomic.AtomicReference; | ||||
|  * | ||||
|  * @see DiskDrivePeripheral | ||||
|  */ | ||||
| public final class DiskDriveBlockEntity extends AbstractContainerBlockEntity { | ||||
| public final class DiskDriveBlockEntity extends AbstractContainerBlockEntity implements BasicContainer { | ||||
|     private static final String NBT_ITEM = "Item"; | ||||
| 
 | ||||
|     private static final class MountInfo { | ||||
| @@ -109,17 +109,17 @@ public final class DiskDriveBlockEntity extends AbstractContainerBlockEntity { | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void load(CompoundTag nbt) { | ||||
|         super.load(nbt); | ||||
|         setDiskStack(nbt.contains(NBT_ITEM) ? ItemStack.of(nbt.getCompound(NBT_ITEM)) : ItemStack.EMPTY); | ||||
|     public void loadAdditional(CompoundTag nbt, HolderLookup.Provider registries) { | ||||
|         super.loadAdditional(nbt, registries); | ||||
|         setDiskStack(nbt.contains(NBT_ITEM) ? ItemStack.parseOptional(registries, nbt.getCompound(NBT_ITEM)) : ItemStack.EMPTY); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void saveAdditional(CompoundTag tag) { | ||||
|         super.saveAdditional(tag); | ||||
|     public void saveAdditional(CompoundTag tag, HolderLookup.Provider registries) { | ||||
|         super.saveAdditional(tag, registries); | ||||
| 
 | ||||
|         var stack = getDiskStack(); | ||||
|         if (!stack.isEmpty()) tag.put(NBT_ITEM, stack.save(new CompoundTag())); | ||||
|         if (!stack.isEmpty()) tag.put(NBT_ITEM, stack.save(registries)); | ||||
|     } | ||||
| 
 | ||||
|     void serverTick() { | ||||
| @@ -135,7 +135,7 @@ public final class DiskDriveBlockEntity extends AbstractContainerBlockEntity { | ||||
|                     if (record != null) { | ||||
|                         recordPlaying = true; | ||||
|                         var title = media.getAudioTitle(); | ||||
|                         sendMessage(new PlayRecordClientMessage(getBlockPos(), record, title)); | ||||
|                         sendMessage(new PlayRecordClientMessage(getBlockPos(), Optional.of(Holder.direct(record)), Optional.ofNullable(title))); | ||||
|                     } | ||||
|                 } | ||||
|                 case STOP -> { | ||||
| @@ -147,10 +147,15 @@ public final class DiskDriveBlockEntity extends AbstractContainerBlockEntity { | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public NonNullList<ItemStack> getContents() { | ||||
|     public NonNullList<ItemStack> getItems() { | ||||
|         return inventory; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void setItems(NonNullList<ItemStack> items) { | ||||
|         BasicContainer.defaultSetItems(inventory, items); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void setChanged() { | ||||
|         if (level != null && !level.isClientSide) updateMedia(); | ||||
| @@ -162,7 +167,7 @@ public final class DiskDriveBlockEntity extends AbstractContainerBlockEntity { | ||||
|      */ | ||||
|     private synchronized void updateMedia() { | ||||
|         var newStack = getDiskStack(); | ||||
|         if (ItemStack.isSameItemSameTags(newStack, media.stack())) return; | ||||
|         if (ItemStack.isSameItemSameComponents(newStack, media.stack())) return; | ||||
| 
 | ||||
|         var newMedia = MediaStack.of(newStack); | ||||
| 
 | ||||
| @@ -233,7 +238,7 @@ public final class DiskDriveBlockEntity extends AbstractContainerBlockEntity { | ||||
|      */ | ||||
|     @GuardedBy("this") | ||||
|     private void updateMediaStack(ItemStack stack, boolean immediate) { | ||||
|         if (ItemStack.isSameItemSameTags(media.stack(), stack)) return; | ||||
|         if (ItemStack.isSameItemSameComponents(media.stack(), stack)) return; | ||||
|         media = new MediaStack(stack, media.media()); | ||||
| 
 | ||||
|         if (immediate) { | ||||
|   | ||||
| @@ -15,7 +15,6 @@ import net.minecraft.core.BlockPos; | ||||
| import net.minecraft.core.Direction; | ||||
| import net.minecraft.server.level.ServerLevel; | ||||
| import net.minecraft.util.RandomSource; | ||||
| import net.minecraft.world.InteractionHand; | ||||
| import net.minecraft.world.InteractionResult; | ||||
| import net.minecraft.world.entity.LivingEntity; | ||||
| import net.minecraft.world.entity.player.Player; | ||||
| @@ -91,8 +90,7 @@ public class CableBlock extends Block implements SimpleWaterloggedBlock, EntityB | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     @Deprecated | ||||
|     public VoxelShape getShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext context) { | ||||
|     protected VoxelShape getShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext context) { | ||||
|         return CableShapes.getShape(state); | ||||
|     } | ||||
| 
 | ||||
| @@ -139,7 +137,6 @@ public class CableBlock extends Block implements SimpleWaterloggedBlock, EntityB | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     @Deprecated | ||||
|     public ItemStack getCloneItemStack(LevelReader world, BlockPos pos, BlockState state) { | ||||
|         return state.getValue(CABLE) ? new ItemStack(ModRegistry.Items.CABLE.get()) : new ItemStack(ModRegistry.Items.WIRED_MODEM.get()); | ||||
|     } | ||||
| @@ -167,14 +164,12 @@ public class CableBlock extends Block implements SimpleWaterloggedBlock, EntityB | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     @Deprecated | ||||
|     public FluidState getFluidState(BlockState state) { | ||||
|     protected FluidState getFluidState(BlockState state) { | ||||
|         return WaterloggableHelpers.getFluidState(state); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     @Deprecated | ||||
|     public BlockState updateShape(BlockState state, Direction side, BlockState otherState, LevelAccessor level, BlockPos pos, BlockPos otherPos) { | ||||
|     protected BlockState updateShape(BlockState state, Direction side, BlockState otherState, LevelAccessor level, BlockPos pos, BlockPos otherPos) { | ||||
|         WaterloggableHelpers.updateShape(state, level, pos); | ||||
| 
 | ||||
|         // Should never happen, but handle the case where we've no modem or cable. | ||||
| @@ -208,8 +203,7 @@ public class CableBlock extends Block implements SimpleWaterloggedBlock, EntityB | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     @Deprecated | ||||
|     public boolean canSurvive(BlockState state, LevelReader world, BlockPos pos) { | ||||
|     protected boolean canSurvive(BlockState state, LevelReader world, BlockPos pos) { | ||||
|         var facing = state.getValue(MODEM).getFacing(); | ||||
|         if (facing == null) return true; | ||||
| 
 | ||||
| @@ -248,15 +242,13 @@ public class CableBlock extends Block implements SimpleWaterloggedBlock, EntityB | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     @Deprecated | ||||
|     public final InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) { | ||||
|     protected final InteractionResult useWithoutItem(BlockState state, Level world, BlockPos pos, Player player, BlockHitResult hit) { | ||||
|         if (player.isCrouching() || !player.mayBuild()) return InteractionResult.PASS; | ||||
|         return world.getBlockEntity(pos) instanceof CableBlockEntity modem ? modem.use(player) : InteractionResult.PASS; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     @Deprecated | ||||
|     public final void neighborChanged(BlockState state, Level world, BlockPos pos, Block neighbourBlock, BlockPos neighbourPos, boolean isMoving) { | ||||
|     protected final void neighborChanged(BlockState state, Level world, BlockPos pos, Block neighbourBlock, BlockPos neighbourPos, boolean isMoving) { | ||||
|         if (world.getBlockEntity(pos) instanceof CableBlockEntity modem) modem.neighborChanged(neighbourPos); | ||||
|     } | ||||
| 
 | ||||
| @@ -266,8 +258,7 @@ public class CableBlock extends Block implements SimpleWaterloggedBlock, EntityB | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     @Deprecated | ||||
|     public void tick(BlockState state, ServerLevel world, BlockPos pos, RandomSource rand) { | ||||
|     protected void tick(BlockState state, ServerLevel world, BlockPos pos, RandomSource rand) { | ||||
|         if (world.getBlockEntity(pos) instanceof CableBlockEntity modem) modem.blockTick(); | ||||
|     } | ||||
| 
 | ||||
|   | ||||
| @@ -15,6 +15,7 @@ import dan200.computercraft.shared.util.DirectionUtil; | ||||
| import dan200.computercraft.shared.util.TickScheduler; | ||||
| import net.minecraft.core.BlockPos; | ||||
| import net.minecraft.core.Direction; | ||||
| import net.minecraft.core.HolderLookup; | ||||
| import net.minecraft.nbt.CompoundTag; | ||||
| import net.minecraft.network.chat.Component; | ||||
| import net.minecraft.world.InteractionResult; | ||||
| @@ -145,15 +146,15 @@ public class CableBlockEntity extends BlockEntity { | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void load(CompoundTag nbt) { | ||||
|         super.load(nbt); | ||||
|     public void loadAdditional(CompoundTag nbt, HolderLookup.Provider registries) { | ||||
|         super.loadAdditional(nbt, registries); | ||||
|         peripheral.read(nbt, ""); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void saveAdditional(CompoundTag nbt) { | ||||
|     public void saveAdditional(CompoundTag nbt, HolderLookup.Provider registries) { | ||||
|         peripheral.write(nbt, ""); | ||||
|         super.saveAdditional(nbt); | ||||
|         super.saveAdditional(nbt, registries); | ||||
|     } | ||||
| 
 | ||||
|     private void updateBlockState() { | ||||
|   | ||||
| @@ -32,7 +32,7 @@ public abstract class CableBlockItem extends BlockItem { | ||||
|         if (!state.canSurvive(world, pos)) return false; | ||||
| 
 | ||||
|         world.setBlockAndUpdate(pos, state); | ||||
|         var soundType = state.getBlock().getSoundType(state); | ||||
|         var soundType = state.getSoundType(); | ||||
|         world.playSound(null, pos, soundType.getPlaceSound(), SoundSource.BLOCKS, (soundType.getVolume() + 1.0F) / 2.0F, soundType.getPitch() * 0.8F); | ||||
| 
 | ||||
|         var tile = world.getBlockEntity(pos); | ||||
|   | ||||
| @@ -10,7 +10,6 @@ import net.minecraft.core.BlockPos; | ||||
| import net.minecraft.core.Direction; | ||||
| import net.minecraft.server.level.ServerLevel; | ||||
| import net.minecraft.util.RandomSource; | ||||
| import net.minecraft.world.InteractionHand; | ||||
| import net.minecraft.world.InteractionResult; | ||||
| import net.minecraft.world.entity.player.Player; | ||||
| import net.minecraft.world.level.Level; | ||||
| @@ -44,14 +43,12 @@ public class WiredModemFullBlock extends Block implements EntityBlock { | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     @Deprecated | ||||
|     public final InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) { | ||||
|     protected final InteractionResult useWithoutItem(BlockState state, Level world, BlockPos pos, Player player, BlockHitResult hit) { | ||||
|         return world.getBlockEntity(pos) instanceof WiredModemFullBlockEntity modem ? modem.use(player) : InteractionResult.PASS; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     @Deprecated | ||||
|     public BlockState updateShape(BlockState state, Direction direction, BlockState neighborState, LevelAccessor level, BlockPos pos, BlockPos neighborPos) { | ||||
|     protected BlockState updateShape(BlockState state, Direction direction, BlockState neighborState, LevelAccessor level, BlockPos pos, BlockPos neighborPos) { | ||||
|         if (state.getValue(PERIPHERAL_ON) && level.getBlockEntity(pos) instanceof WiredModemFullBlockEntity modem) { | ||||
|             modem.queueRefreshPeripheral(direction); | ||||
|         } | ||||
| @@ -60,8 +57,7 @@ public class WiredModemFullBlock extends Block implements EntityBlock { | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     @Deprecated | ||||
|     public final void neighborChanged(BlockState state, Level level, BlockPos pos, Block neighbourBlock, BlockPos neighbourPos, boolean isMoving) { | ||||
|     protected final void neighborChanged(BlockState state, Level level, BlockPos pos, Block neighbourBlock, BlockPos neighbourPos, boolean isMoving) { | ||||
|         if (state.getValue(PERIPHERAL_ON) && level.getBlockEntity(pos) instanceof WiredModemFullBlockEntity modem) { | ||||
|             modem.neighborChanged(neighbourPos); | ||||
|         } | ||||
| @@ -75,8 +71,7 @@ public class WiredModemFullBlock extends Block implements EntityBlock { | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     @Deprecated | ||||
|     public void tick(BlockState state, ServerLevel world, BlockPos pos, RandomSource rand) { | ||||
|     protected void tick(BlockState state, ServerLevel world, BlockPos pos, RandomSource rand) { | ||||
|         if (world.getBlockEntity(pos) instanceof WiredModemFullBlockEntity modem) modem.blockTick(); | ||||
|     } | ||||
| 
 | ||||
|   | ||||
| @@ -15,6 +15,7 @@ import dan200.computercraft.shared.util.DirectionUtil; | ||||
| import dan200.computercraft.shared.util.TickScheduler; | ||||
| import net.minecraft.core.BlockPos; | ||||
| import net.minecraft.core.Direction; | ||||
| import net.minecraft.core.HolderLookup; | ||||
| import net.minecraft.nbt.CompoundTag; | ||||
| import net.minecraft.network.chat.Component; | ||||
| import net.minecraft.world.InteractionResult; | ||||
| @@ -155,15 +156,15 @@ public class WiredModemFullBlockEntity extends BlockEntity { | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void load(CompoundTag nbt) { | ||||
|         super.load(nbt); | ||||
|     public void loadAdditional(CompoundTag nbt, HolderLookup.Provider registries) { | ||||
|         super.loadAdditional(nbt, registries); | ||||
|         for (var i = 0; i < peripherals.length; i++) peripherals[i].read(nbt, Integer.toString(i)); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void saveAdditional(CompoundTag nbt) { | ||||
|     public void saveAdditional(CompoundTag nbt, HolderLookup.Provider registries) { | ||||
|         for (var i = 0; i < peripherals.length; i++) peripherals[i].write(nbt, Integer.toString(i)); | ||||
|         super.saveAdditional(nbt); | ||||
|         super.saveAdditional(nbt, registries); | ||||
|     } | ||||
| 
 | ||||
|     void blockTick() { | ||||
|   | ||||
| @@ -60,20 +60,17 @@ public class WirelessModemBlock extends DirectionalBlock implements SimpleWaterl | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     @Deprecated | ||||
|     public VoxelShape getShape(BlockState blockState, BlockGetter blockView, BlockPos blockPos, CollisionContext context) { | ||||
|     protected VoxelShape getShape(BlockState blockState, BlockGetter blockView, BlockPos blockPos, CollisionContext context) { | ||||
|         return ModemShapes.getBounds(blockState.getValue(FACING)); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     @Deprecated | ||||
|     public FluidState getFluidState(BlockState state) { | ||||
|     protected FluidState getFluidState(BlockState state) { | ||||
|         return WaterloggableHelpers.getFluidState(state); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     @Deprecated | ||||
|     public BlockState updateShape(BlockState state, Direction side, BlockState otherState, LevelAccessor world, BlockPos pos, BlockPos otherPos) { | ||||
|     protected BlockState updateShape(BlockState state, Direction side, BlockState otherState, LevelAccessor world, BlockPos pos, BlockPos otherPos) { | ||||
|         WaterloggableHelpers.updateShape(state, world, pos); | ||||
|         return side == state.getValue(FACING) && !state.canSurvive(world, pos) | ||||
|             ? state.getFluidState().createLegacyBlock() | ||||
| @@ -81,8 +78,7 @@ public class WirelessModemBlock extends DirectionalBlock implements SimpleWaterl | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     @Deprecated | ||||
|     public boolean canSurvive(BlockState state, LevelReader world, BlockPos pos) { | ||||
|     protected boolean canSurvive(BlockState state, LevelReader world, BlockPos pos) { | ||||
|         var facing = state.getValue(FACING); | ||||
|         return ModemShapes.canSupport(world, pos.relative(facing), facing.getOpposite()); | ||||
|     } | ||||
| @@ -96,20 +92,17 @@ public class WirelessModemBlock extends DirectionalBlock implements SimpleWaterl | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     @Deprecated | ||||
|     public BlockState mirror(BlockState state, Mirror mirrorIn) { | ||||
|     protected BlockState mirror(BlockState state, Mirror mirrorIn) { | ||||
|         return state.rotate(mirrorIn.getRotation(state.getValue(FACING))); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     @Deprecated | ||||
|     public BlockState rotate(BlockState state, Rotation rot) { | ||||
|     protected BlockState rotate(BlockState state, Rotation rot) { | ||||
|         return state.setValue(FACING, rot.rotate(state.getValue(FACING))); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     @Deprecated | ||||
|     public void tick(BlockState state, ServerLevel level, BlockPos pos, RandomSource rand) { | ||||
|     protected void tick(BlockState state, ServerLevel level, BlockPos pos, RandomSource rand) { | ||||
|         var te = level.getBlockEntity(pos); | ||||
|         if (te instanceof WirelessModemBlockEntity modem) modem.blockTick(); | ||||
|     } | ||||
|   | ||||
| @@ -13,7 +13,6 @@ import net.minecraft.core.Direction; | ||||
| import net.minecraft.server.level.ServerLevel; | ||||
| import net.minecraft.server.level.ServerPlayer; | ||||
| import net.minecraft.util.RandomSource; | ||||
| import net.minecraft.world.InteractionHand; | ||||
| import net.minecraft.world.InteractionResult; | ||||
| import net.minecraft.world.entity.LivingEntity; | ||||
| import net.minecraft.world.entity.player.Player; | ||||
| @@ -87,8 +86,7 @@ public class MonitorBlock extends HorizontalDirectionalBlock implements EntityBl | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     @Deprecated | ||||
|     public final void onRemove(BlockState block, Level world, BlockPos pos, BlockState replace, boolean bool) { | ||||
|     protected final void onRemove(BlockState block, Level world, BlockPos pos, BlockState replace, boolean bool) { | ||||
|         if (block.getBlock() == replace.getBlock()) return; | ||||
| 
 | ||||
|         var tile = world.getBlockEntity(pos); | ||||
| @@ -97,15 +95,13 @@ public class MonitorBlock extends HorizontalDirectionalBlock implements EntityBl | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     @Deprecated | ||||
|     public void tick(BlockState state, ServerLevel world, BlockPos pos, RandomSource rand) { | ||||
|     protected void tick(BlockState state, ServerLevel world, BlockPos pos, RandomSource rand) { | ||||
|         var te = world.getBlockEntity(pos); | ||||
|         if (te instanceof MonitorBlockEntity monitor) monitor.blockTick(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     @Deprecated | ||||
|     public final InteractionResult use(BlockState state, Level level, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) { | ||||
|     protected final InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, BlockHitResult hit) { | ||||
|         if (player.isCrouching() || !(level.getBlockEntity(pos) instanceof MonitorBlockEntity monitor) || monitor.getFront() != hit.getDirection()) { | ||||
|             return InteractionResult.PASS; | ||||
|         } | ||||
|   | ||||
| @@ -14,6 +14,7 @@ import dan200.computercraft.shared.util.BlockEntityHelpers; | ||||
| import dan200.computercraft.shared.util.TickScheduler; | ||||
| import net.minecraft.core.BlockPos; | ||||
| import net.minecraft.core.Direction; | ||||
| import net.minecraft.core.HolderLookup; | ||||
| import net.minecraft.nbt.CompoundTag; | ||||
| import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket; | ||||
| import net.minecraft.world.level.block.entity.BlockEntity; | ||||
| @@ -98,17 +99,17 @@ public class MonitorBlockEntity extends BlockEntity { | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void saveAdditional(CompoundTag tag) { | ||||
|     public void saveAdditional(CompoundTag tag, HolderLookup.Provider registries) { | ||||
|         tag.putInt(NBT_X, xIndex); | ||||
|         tag.putInt(NBT_Y, yIndex); | ||||
|         tag.putInt(NBT_WIDTH, width); | ||||
|         tag.putInt(NBT_HEIGHT, height); | ||||
|         super.saveAdditional(tag); | ||||
|         super.saveAdditional(tag, registries); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void load(CompoundTag nbt) { | ||||
|         super.load(nbt); | ||||
|     public void loadAdditional(CompoundTag nbt, HolderLookup.Provider registries) { | ||||
|         super.loadAdditional(nbt, registries); | ||||
| 
 | ||||
|         var oldXIndex = xIndex; | ||||
|         var oldYIndex = yIndex; | ||||
| @@ -202,8 +203,8 @@ public class MonitorBlockEntity extends BlockEntity { | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public final CompoundTag getUpdateTag() { | ||||
|         var nbt = super.getUpdateTag(); | ||||
|     public final CompoundTag getUpdateTag(HolderLookup.Provider registries) { | ||||
|         var nbt = super.getUpdateTag(registries); | ||||
|         nbt.putInt(NBT_X, xIndex); | ||||
|         nbt.putInt(NBT_Y, yIndex); | ||||
|         nbt.putInt(NBT_WIDTH, width); | ||||
|   | ||||
| @@ -14,6 +14,7 @@ import net.minecraft.world.level.chunk.LevelChunk; | ||||
| 
 | ||||
| import javax.annotation.Nullable; | ||||
| import java.util.ArrayDeque; | ||||
| import java.util.Optional; | ||||
| import java.util.Queue; | ||||
| 
 | ||||
| public final class MonitorWatcher { | ||||
| @@ -40,7 +41,7 @@ public final class MonitorWatcher { | ||||
|             if (serverMonitor == null || monitor.enqueued) continue; | ||||
| 
 | ||||
|             var state = getState(monitor, serverMonitor); | ||||
|             ServerNetworking.sendToPlayer(new MonitorClientMessage(monitor.getBlockPos(), state), player); | ||||
|             ServerNetworking.sendToPlayer(new MonitorClientMessage(monitor.getBlockPos(), Optional.ofNullable(state)), player); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| @@ -66,7 +67,7 @@ public final class MonitorWatcher { | ||||
|             } | ||||
| 
 | ||||
|             var state = getState(tile, monitor); | ||||
|             ServerNetworking.sendToAllTracking(new MonitorClientMessage(pos, state), chunk); | ||||
|             ServerNetworking.sendToAllTracking(new MonitorClientMessage(pos, Optional.ofNullable(state)), chunk); | ||||
| 
 | ||||
|             limit -= state == null ? 0 : state.size(); | ||||
|         } | ||||
|   | ||||
| @@ -5,13 +5,18 @@ | ||||
| package dan200.computercraft.shared.peripheral.printer; | ||||
| 
 | ||||
| import dan200.computercraft.api.peripheral.IPeripheral; | ||||
| import dan200.computercraft.shared.ModRegistry; | ||||
| import dan200.computercraft.shared.common.AbstractContainerBlockEntity; | ||||
| import dan200.computercraft.shared.computer.terminal.NetworkedTerminal; | ||||
| import dan200.computercraft.shared.container.BasicContainer; | ||||
| import dan200.computercraft.shared.container.BasicWorldlyContainer; | ||||
| import dan200.computercraft.shared.media.items.PrintoutData; | ||||
| import dan200.computercraft.shared.media.items.PrintoutItem; | ||||
| import dan200.computercraft.shared.util.ColourUtils; | ||||
| import dan200.computercraft.shared.util.DataComponentUtil; | ||||
| import net.minecraft.core.BlockPos; | ||||
| import net.minecraft.core.Direction; | ||||
| import net.minecraft.core.HolderLookup; | ||||
| import net.minecraft.core.NonNullList; | ||||
| import net.minecraft.nbt.CompoundTag; | ||||
| import net.minecraft.world.ContainerHelper; | ||||
| @@ -23,6 +28,7 @@ import net.minecraft.world.level.block.entity.BlockEntityType; | ||||
| import net.minecraft.world.level.block.state.BlockState; | ||||
| 
 | ||||
| import javax.annotation.Nullable; | ||||
| import java.util.List; | ||||
| 
 | ||||
| public final class PrinterBlockEntity extends AbstractContainerBlockEntity implements BasicWorldlyContainer { | ||||
|     private static final String NBT_PRINTING = "Printing"; | ||||
| @@ -37,7 +43,7 @@ public final class PrinterBlockEntity extends AbstractContainerBlockEntity imple | ||||
|     private final PrinterPeripheral peripheral = new PrinterPeripheral(this); | ||||
|     private final NonNullList<ItemStack> inventory = NonNullList.withSize(SLOTS, ItemStack.EMPTY); | ||||
| 
 | ||||
|     private final NetworkedTerminal page = new NetworkedTerminal(PrintoutItem.LINE_MAX_LENGTH, PrintoutItem.LINES_PER_PAGE, true); | ||||
|     private final NetworkedTerminal page = new NetworkedTerminal(PrintoutData.LINE_LENGTH, PrintoutData.LINES_PER_PAGE, true); | ||||
|     private String pageTitle = ""; | ||||
|     private boolean printing = false; | ||||
| 
 | ||||
| @@ -50,8 +56,8 @@ public final class PrinterBlockEntity extends AbstractContainerBlockEntity imple | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void load(CompoundTag nbt) { | ||||
|         super.load(nbt); | ||||
|     public void loadAdditional(CompoundTag nbt, HolderLookup.Provider registries) { | ||||
|         super.loadAdditional(nbt, registries); | ||||
| 
 | ||||
|         // Read page | ||||
|         synchronized (page) { | ||||
| @@ -61,11 +67,11 @@ public final class PrinterBlockEntity extends AbstractContainerBlockEntity imple | ||||
|         } | ||||
| 
 | ||||
|         // Read inventory | ||||
|         ContainerHelper.loadAllItems(nbt, inventory); | ||||
|         ContainerHelper.loadAllItems(nbt, inventory, registries); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void saveAdditional(CompoundTag tag) { | ||||
|     public void saveAdditional(CompoundTag tag, HolderLookup.Provider registries) { | ||||
|         // Write page | ||||
|         synchronized (page) { | ||||
|             tag.putBoolean(NBT_PRINTING, printing); | ||||
| @@ -74,9 +80,9 @@ public final class PrinterBlockEntity extends AbstractContainerBlockEntity imple | ||||
|         } | ||||
| 
 | ||||
|         // Write inventory | ||||
|         ContainerHelper.saveAllItems(tag, inventory); | ||||
|         ContainerHelper.saveAllItems(tag, inventory, registries); | ||||
| 
 | ||||
|         super.saveAdditional(tag); | ||||
|         super.saveAdditional(tag, registries); | ||||
|     } | ||||
| 
 | ||||
|     boolean isPrinting() { | ||||
| @@ -84,10 +90,15 @@ public final class PrinterBlockEntity extends AbstractContainerBlockEntity imple | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public NonNullList<ItemStack> getContents() { | ||||
|     public NonNullList<ItemStack> getItems() { | ||||
|         return inventory; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void setItems(NonNullList<ItemStack> items) { | ||||
|         BasicContainer.defaultSetItems(inventory, items); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void setChanged() { | ||||
|         super.setChanged(); | ||||
| @@ -183,12 +194,13 @@ public final class PrinterBlockEntity extends AbstractContainerBlockEntity imple | ||||
|             page.setTextColour(dye.getId()); | ||||
| 
 | ||||
|             page.clear(); | ||||
|             if (paperStack.getItem() instanceof PrintoutItem) { | ||||
|                 pageTitle = PrintoutItem.getTitle(paperStack); | ||||
|                 var text = PrintoutItem.getText(paperStack); | ||||
|                 var textColour = PrintoutItem.getColours(paperStack); | ||||
| 
 | ||||
|             var printout = paperStack.get(ModRegistry.DataComponents.PRINTOUT.get()); | ||||
|             if (printout != null) { | ||||
|                 pageTitle = printout.title(); | ||||
|                 for (var y = 0; y < page.getHeight(); y++) { | ||||
|                     page.setLine(y, text[y], textColour[y], ""); | ||||
|                     var line = printout.lines().get(y); | ||||
|                     page.setLine(y, line.text(), line.foreground(), ""); | ||||
|                 } | ||||
|             } else { | ||||
|                 pageTitle = ""; | ||||
| @@ -215,14 +227,16 @@ public final class PrinterBlockEntity extends AbstractContainerBlockEntity imple | ||||
| 
 | ||||
|     private boolean outputPage() { | ||||
|         var height = page.getHeight(); | ||||
|         var lines = new String[height]; | ||||
|         var colours = new String[height]; | ||||
|         var lines = new PrintoutData.Line[height]; | ||||
|         for (var i = 0; i < height; i++) { | ||||
|             lines[i] = page.getLine(i).toString(); | ||||
|             colours[i] = page.getTextColourLine(i).toString(); | ||||
|             lines[i] = new PrintoutData.Line(page.getLine(i).toString(), page.getTextColourLine(i).toString()); | ||||
|         } | ||||
| 
 | ||||
|         var stack = PrintoutItem.createSingleFromTitleAndText(pageTitle, lines, colours); | ||||
|         var stack = DataComponentUtil.createStack( | ||||
|             ModRegistry.Items.PRINTED_PAGE.get(), | ||||
|             ModRegistry.DataComponents.PRINTOUT.get(), new PrintoutData(pageTitle, List.of(lines)) | ||||
|         ); | ||||
| 
 | ||||
|         for (var slot : BOTTOM_SLOTS) { | ||||
|             if (inventory.get(slot).isEmpty()) { | ||||
|                 inventory.set(slot, stack); | ||||
|   | ||||
| @@ -4,7 +4,10 @@ | ||||
| 
 | ||||
| package dan200.computercraft.shared.peripheral.speaker; | ||||
| 
 | ||||
| import net.minecraft.network.FriendlyByteBuf; | ||||
| import dan200.computercraft.shared.network.codec.MoreStreamCodecs; | ||||
| import io.netty.buffer.ByteBuf; | ||||
| import net.minecraft.network.codec.ByteBufCodecs; | ||||
| import net.minecraft.network.codec.StreamCodec; | ||||
| 
 | ||||
| import java.nio.ByteBuffer; | ||||
| 
 | ||||
| @@ -17,23 +20,11 @@ import java.nio.ByteBuffer; | ||||
|  * @param audio       The block of encoded audio. | ||||
|  */ | ||||
| public record EncodedAudio(int charge, int strength, boolean previousBit, ByteBuffer audio) { | ||||
|     public void write(FriendlyByteBuf buf) { | ||||
|         buf.writeVarInt(charge()); | ||||
|         buf.writeVarInt(strength()); | ||||
|         buf.writeBoolean(previousBit()); | ||||
|         buf.writeVarInt(audio.remaining()); | ||||
|         buf.writeBytes(audio().duplicate()); | ||||
|     } | ||||
| 
 | ||||
|     public static EncodedAudio read(FriendlyByteBuf buf) { | ||||
|         var charge = buf.readVarInt(); | ||||
|         var strength = buf.readVarInt(); | ||||
|         var previousBit = buf.readBoolean(); | ||||
| 
 | ||||
|         var length = buf.readVarInt(); | ||||
|         var bytes = new byte[length]; | ||||
|         buf.readBytes(bytes); | ||||
| 
 | ||||
|         return new EncodedAudio(charge, strength, previousBit, ByteBuffer.wrap(bytes)); | ||||
|     } | ||||
|     public static final StreamCodec<ByteBuf, EncodedAudio> STREAM_CODEC = StreamCodec.composite( | ||||
|         ByteBufCodecs.VAR_INT, EncodedAudio::charge, | ||||
|         ByteBufCodecs.VAR_INT, EncodedAudio::strength, | ||||
|         ByteBufCodecs.BOOL, EncodedAudio::previousBit, | ||||
|         MoreStreamCodecs.BYTE_BUFFER, EncodedAudio::audio, | ||||
|         EncodedAudio::new | ||||
|     ); | ||||
| } | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user
	 Jonathan Coates
					Jonathan Coates