From 40a94c87629191136bca69420ad6f5256c5db2bf Mon Sep 17 00:00:00 2001 From: xcfrg Date: Mon, 6 Jul 2026 12:25:55 -0400 Subject: [PATCH 1/4] book signing --- .../clientcommands/command/BookCommand.java | 106 ++++++++++++------ .../assets/clientcommands/lang/en_us.json | 1 + 2 files changed, 74 insertions(+), 33 deletions(-) diff --git a/src/main/java/net/earthcomputer/clientcommands/command/BookCommand.java b/src/main/java/net/earthcomputer/clientcommands/command/BookCommand.java index 71738c8a3..2097a15d3 100644 --- a/src/main/java/net/earthcomputer/clientcommands/command/BookCommand.java +++ b/src/main/java/net/earthcomputer/clientcommands/command/BookCommand.java @@ -2,6 +2,7 @@ import com.mojang.brigadier.Command; import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; import net.earthcomputer.clientcommands.util.MultiVersionCompat; @@ -26,35 +27,48 @@ import static com.mojang.brigadier.arguments.IntegerArgumentType.*; import static com.mojang.brigadier.arguments.LongArgumentType.*; +import static com.mojang.brigadier.arguments.StringArgumentType.*; import static net.fabricmc.fabric.api.client.command.v2.ClientCommands.*; public class BookCommand { private static final SimpleCommandExceptionType NO_BOOK = new SimpleCommandExceptionType(Component.translatable("commands.cbook.commandException")); + private static final SimpleCommandExceptionType TITLE_TOO_LONG = new SimpleCommandExceptionType(Component.translatable("commands.cbook.titleTooLong")); private static final int DEFAULT_LIMIT = 50; + private static final int TITLE_MAX_LENGTH = 32; public static void register(CommandDispatcher dispatcher) { - if (MultiVersionCompat.INSTANCE.getProtocolVersion() >= MultiVersionCompat.V1_15) { - return; // chunk savestate fixed in 1.15 + LiteralArgumentBuilder cbook = literal("cbook") + .then(literal("sign") + .executes(ctx -> signBook(ctx.getSource(), "")) + .then(argument("title", greedyString()) + .executes(ctx -> signBook(ctx.getSource(), getString(ctx, "title"))))); + + if (MultiVersionCompat.INSTANCE.getProtocolVersion() < MultiVersionCompat.V1_15) { + cbook.then(literal("fill") + .then(literal("static") + .executes(ctx -> fillBook(ctx.getSource(), fill(), DEFAULT_LIMIT)) + .then(argument("limit", integer(0, getMaxLimit())) + .executes(ctx -> fillBook(ctx.getSource(), fill(), getInteger(ctx, "limit"))))) + .then(literal("random") + .executes(ctx -> fillBook(ctx.getSource(), random(new Random()), DEFAULT_LIMIT)) + .then(argument("limit", integer(0, getMaxLimit())) + .executes(ctx -> fillBook(ctx.getSource(), random(new Random()), getInteger(ctx, "limit"))) + .then(argument("seed", longArg()) + .executes(ctx -> fillBook(ctx.getSource(), + random(new Random(getLong(ctx, "seed"))), + getInteger(ctx, "limit")))))) + .then(literal("ascii") + .executes(ctx -> fillBook(ctx.getSource(), ascii(new Random()), DEFAULT_LIMIT)) + .then(argument("limit", integer(0, getMaxLimit())) + .executes(ctx -> fillBook(ctx.getSource(), ascii(new Random()), getInteger(ctx, "limit"))) + .then(argument("seed", longArg()) + .executes(ctx -> fillBook(ctx.getSource(), + ascii(new Random(getLong(ctx, "seed"))), + getInteger(ctx, "limit"))))))); } - dispatcher.register(literal("cbook") - .then(literal("fill") - .executes(ctx -> fillBook(ctx.getSource(), fill(), DEFAULT_LIMIT)) - .then(argument("limit", integer(0, getMaxLimit())) - .executes(ctx -> fillBook(ctx.getSource(), fill(), getInteger(ctx, "limit"))))) - .then(literal("random") - .executes(ctx -> fillBook(ctx.getSource(), random(new Random()), DEFAULT_LIMIT)) - .then(argument("limit", integer(0, getMaxLimit())) - .executes(ctx -> fillBook(ctx.getSource(), random(new Random()), getInteger(ctx, "limit"))) - .then(argument("seed", longArg()) - .executes(ctx -> fillBook(ctx.getSource(), random(new Random(getLong(ctx, "seed"))), getInteger(ctx, "limit")))))) - .then(literal("ascii") - .executes(ctx -> fillBook(ctx.getSource(), ascii(new Random()), DEFAULT_LIMIT)) - .then(argument("limit", integer(0, getMaxLimit())) - .executes(ctx -> fillBook(ctx.getSource(), ascii(new Random()), getInteger(ctx, "limit"))) - .then(argument("seed", longArg()) - .executes(ctx -> fillBook(ctx.getSource(), ascii(new Random(getLong(ctx, "seed"))), getInteger(ctx, "limit"))))))); + dispatcher.register(cbook); } private static IntStream fill() { @@ -71,20 +85,12 @@ private static IntStream ascii(Random rand) { private static int fillBook(FabricClientCommandSource source, IntStream characterGenerator, int limit) throws CommandSyntaxException { LocalPlayer player = source.getPlayer(); - assert player != null; - ItemStack heldItem = player.getMainHandItem(); - InteractionHand hand = InteractionHand.MAIN_HAND; - if (heldItem.getItem() != Items.WRITABLE_BOOK) { - heldItem = player.getOffhandItem(); - hand = InteractionHand.OFF_HAND; - if (heldItem.getItem() != Items.WRITABLE_BOOK) { - throw NO_BOOK.create(); - } - } - int slot = hand == InteractionHand.MAIN_HAND ? player.getInventory().getSelectedSlot() : Inventory.SLOT_OFFHAND; + HeldBook book = getHeldWritableBook(player); - String joinedPages = characterGenerator.limit((long) getMaxLimit() * 210).mapToObj(i -> String.valueOf((char) i)).collect(Collectors.joining()); + String joinedPages = characterGenerator.limit((long) getMaxLimit() * 210) + .mapToObj(i -> String.valueOf((char) i)) + .collect(Collectors.joining()); List pages = new ArrayList<>(limit); List> filterablePages = new ArrayList<>(); @@ -95,15 +101,49 @@ private static int fillBook(FabricClientCommandSource source, IntStream characte filterablePages.add(Filterable.passThrough(page)); } - heldItem.set(DataComponents.WRITABLE_BOOK_CONTENT, new WritableBookContent(filterablePages)); - player.connection.send(new ServerboundEditBookPacket(slot, pages, Optional.empty())); + book.stack().set(DataComponents.WRITABLE_BOOK_CONTENT, new WritableBookContent(filterablePages)); + player.connection.send(new ServerboundEditBookPacket(book.slot(), pages, Optional.empty())); + + source.sendFeedback(Component.translatable("commands.cbook.success")); + + return Command.SINGLE_SUCCESS; + } + + private static int signBook(FabricClientCommandSource source, String title) throws CommandSyntaxException { + LocalPlayer player = source.getPlayer(); + + if (title.length() > TITLE_MAX_LENGTH) { + throw TITLE_TOO_LONG.create(); + } + + HeldBook book = getHeldWritableBook(player); + WritableBookContent content = book.stack().get(DataComponents.WRITABLE_BOOK_CONTENT); + List pages = content == null ? List.of() : content.getPages(false).toList(); + player.connection.send(new ServerboundEditBookPacket(book.slot(), pages, Optional.of(title))); source.sendFeedback(Component.translatable("commands.cbook.success")); return Command.SINGLE_SUCCESS; } + private static HeldBook getHeldWritableBook(LocalPlayer player) throws CommandSyntaxException { + ItemStack heldItem = player.getMainHandItem(); + InteractionHand hand = InteractionHand.MAIN_HAND; + if (heldItem.getItem() != Items.WRITABLE_BOOK) { + heldItem = player.getOffhandItem(); + hand = InteractionHand.OFF_HAND; + if (heldItem.getItem() != Items.WRITABLE_BOOK) { + throw NO_BOOK.create(); + } + } + int slot = hand == InteractionHand.MAIN_HAND ? player.getInventory().getSelectedSlot() : Inventory.SLOT_OFFHAND; + return new HeldBook(heldItem, slot); + } + private static int getMaxLimit() { return MultiVersionCompat.INSTANCE.getProtocolVersion() < MultiVersionCompat.V1_14 ? 50 : 100; } + + private record HeldBook(ItemStack stack, int slot) { + } } diff --git a/src/main/resources/assets/clientcommands/lang/en_us.json b/src/main/resources/assets/clientcommands/lang/en_us.json index a08fcf141..90fe4bd4c 100644 --- a/src/main/resources/assets/clientcommands/lang/en_us.json +++ b/src/main/resources/assets/clientcommands/lang/en_us.json @@ -64,6 +64,7 @@ "commands.cbook.commandException": "You are not holding a book", "commands.cbook.success": "Successfully edited book", + "commands.cbook.titleTooLong": "Book title cannot be longer than 32 characters", "commands.cbuildinfo.success": "Running clientcommands on version %s (%s@{%s})", From f0cdb51c26c0e1d17c8e6b03e347f4caa0ebf036 Mon Sep 17 00:00:00 2001 From: xcfrg Date: Tue, 7 Jul 2026 22:31:32 -0400 Subject: [PATCH 2/4] address feedback --- .../clientcommands/command/BookCommand.java | 55 +++++++++---------- .../assets/clientcommands/lang/en_us.json | 2 +- 2 files changed, 28 insertions(+), 29 deletions(-) diff --git a/src/main/java/net/earthcomputer/clientcommands/command/BookCommand.java b/src/main/java/net/earthcomputer/clientcommands/command/BookCommand.java index 2097a15d3..a94001c4f 100644 --- a/src/main/java/net/earthcomputer/clientcommands/command/BookCommand.java +++ b/src/main/java/net/earthcomputer/clientcommands/command/BookCommand.java @@ -17,6 +17,7 @@ import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.Items; import net.minecraft.world.item.component.WritableBookContent; +import net.minecraft.world.item.component.WrittenBookContent; import java.util.ArrayList; import java.util.List; @@ -32,41 +33,39 @@ public class BookCommand { private static final SimpleCommandExceptionType NO_BOOK = new SimpleCommandExceptionType(Component.translatable("commands.cbook.commandException")); - private static final SimpleCommandExceptionType TITLE_TOO_LONG = new SimpleCommandExceptionType(Component.translatable("commands.cbook.titleTooLong")); + private static final SimpleCommandExceptionType TITLE_TOO_LONG = new SimpleCommandExceptionType( + Component.translatable("commands.cbook.titleTooLong", WrittenBookContent.TITLE_MAX_LENGTH)); private static final int DEFAULT_LIMIT = 50; - private static final int TITLE_MAX_LENGTH = 32; public static void register(CommandDispatcher dispatcher) { LiteralArgumentBuilder cbook = literal("cbook") .then(literal("sign") .executes(ctx -> signBook(ctx.getSource(), "")) .then(argument("title", greedyString()) - .executes(ctx -> signBook(ctx.getSource(), getString(ctx, "title"))))); - - if (MultiVersionCompat.INSTANCE.getProtocolVersion() < MultiVersionCompat.V1_15) { - cbook.then(literal("fill") - .then(literal("static") - .executes(ctx -> fillBook(ctx.getSource(), fill(), DEFAULT_LIMIT)) - .then(argument("limit", integer(0, getMaxLimit())) - .executes(ctx -> fillBook(ctx.getSource(), fill(), getInteger(ctx, "limit"))))) - .then(literal("random") - .executes(ctx -> fillBook(ctx.getSource(), random(new Random()), DEFAULT_LIMIT)) - .then(argument("limit", integer(0, getMaxLimit())) - .executes(ctx -> fillBook(ctx.getSource(), random(new Random()), getInteger(ctx, "limit"))) - .then(argument("seed", longArg()) - .executes(ctx -> fillBook(ctx.getSource(), - random(new Random(getLong(ctx, "seed"))), - getInteger(ctx, "limit")))))) - .then(literal("ascii") - .executes(ctx -> fillBook(ctx.getSource(), ascii(new Random()), DEFAULT_LIMIT)) - .then(argument("limit", integer(0, getMaxLimit())) - .executes(ctx -> fillBook(ctx.getSource(), ascii(new Random()), getInteger(ctx, "limit"))) - .then(argument("seed", longArg()) - .executes(ctx -> fillBook(ctx.getSource(), - ascii(new Random(getLong(ctx, "seed"))), - getInteger(ctx, "limit"))))))); - } + .executes(ctx -> signBook(ctx.getSource(), getString(ctx, "title"))))) + .then(literal("fill") + .requires(source -> MultiVersionCompat.INSTANCE.getProtocolVersion() < MultiVersionCompat.V1_15) + .then(literal("static") + .executes(ctx -> fillBook(ctx.getSource(), fill(), DEFAULT_LIMIT)) + .then(argument("limit", integer(0, getMaxLimit())) + .executes(ctx -> fillBook(ctx.getSource(), fill(), getInteger(ctx, "limit"))))) + .then(literal("random") + .executes(ctx -> fillBook(ctx.getSource(), random(new Random()), DEFAULT_LIMIT)) + .then(argument("limit", integer(0, getMaxLimit())) + .executes(ctx -> fillBook(ctx.getSource(), random(new Random()), getInteger(ctx, "limit"))) + .then(argument("seed", longArg()) + .executes(ctx -> fillBook(ctx.getSource(), + random(new Random(getLong(ctx, "seed"))), + getInteger(ctx, "limit")))))) + .then(literal("ascii") + .executes(ctx -> fillBook(ctx.getSource(), ascii(new Random()), DEFAULT_LIMIT)) + .then(argument("limit", integer(0, getMaxLimit())) + .executes(ctx -> fillBook(ctx.getSource(), ascii(new Random()), getInteger(ctx, "limit"))) + .then(argument("seed", longArg()) + .executes(ctx -> fillBook(ctx.getSource(), + ascii(new Random(getLong(ctx, "seed"))), + getInteger(ctx, "limit"))))))); dispatcher.register(cbook); } @@ -112,7 +111,7 @@ private static int fillBook(FabricClientCommandSource source, IntStream characte private static int signBook(FabricClientCommandSource source, String title) throws CommandSyntaxException { LocalPlayer player = source.getPlayer(); - if (title.length() > TITLE_MAX_LENGTH) { + if (title.length() > WrittenBookContent.TITLE_MAX_LENGTH) { throw TITLE_TOO_LONG.create(); } diff --git a/src/main/resources/assets/clientcommands/lang/en_us.json b/src/main/resources/assets/clientcommands/lang/en_us.json index 90fe4bd4c..71bc76bb7 100644 --- a/src/main/resources/assets/clientcommands/lang/en_us.json +++ b/src/main/resources/assets/clientcommands/lang/en_us.json @@ -64,7 +64,7 @@ "commands.cbook.commandException": "You are not holding a book", "commands.cbook.success": "Successfully edited book", - "commands.cbook.titleTooLong": "Book title cannot be longer than 32 characters", + "commands.cbook.titleTooLong": "Book title cannot be longer than %s characters", "commands.cbuildinfo.success": "Running clientcommands on version %s (%s@{%s})", From 8379531eedde1e25f1b930adf3516a3d2339407f Mon Sep 17 00:00:00 2001 From: Frederik van der Els Date: Tue, 21 Jul 2026 19:43:39 +0200 Subject: [PATCH 3/4] Restore old syntax of /cbook --- .../clientcommands/command/BookCommand.java | 58 ++++++++----------- 1 file changed, 24 insertions(+), 34 deletions(-) diff --git a/src/main/java/net/earthcomputer/clientcommands/command/BookCommand.java b/src/main/java/net/earthcomputer/clientcommands/command/BookCommand.java index a94001c4f..2b2f9f3bd 100644 --- a/src/main/java/net/earthcomputer/clientcommands/command/BookCommand.java +++ b/src/main/java/net/earthcomputer/clientcommands/command/BookCommand.java @@ -2,7 +2,6 @@ import com.mojang.brigadier.Command; import com.mojang.brigadier.CommandDispatcher; -import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; import net.earthcomputer.clientcommands.util.MultiVersionCompat; @@ -33,41 +32,32 @@ public class BookCommand { private static final SimpleCommandExceptionType NO_BOOK = new SimpleCommandExceptionType(Component.translatable("commands.cbook.commandException")); - private static final SimpleCommandExceptionType TITLE_TOO_LONG = new SimpleCommandExceptionType( - Component.translatable("commands.cbook.titleTooLong", WrittenBookContent.TITLE_MAX_LENGTH)); + private static final SimpleCommandExceptionType TITLE_TOO_LONG = new SimpleCommandExceptionType(Component.translatable("commands.cbook.titleTooLong", WrittenBookContent.TITLE_MAX_LENGTH)); private static final int DEFAULT_LIMIT = 50; public static void register(CommandDispatcher dispatcher) { - LiteralArgumentBuilder cbook = literal("cbook") - .then(literal("sign") - .executes(ctx -> signBook(ctx.getSource(), "")) - .then(argument("title", greedyString()) - .executes(ctx -> signBook(ctx.getSource(), getString(ctx, "title"))))) - .then(literal("fill") - .requires(source -> MultiVersionCompat.INSTANCE.getProtocolVersion() < MultiVersionCompat.V1_15) - .then(literal("static") - .executes(ctx -> fillBook(ctx.getSource(), fill(), DEFAULT_LIMIT)) - .then(argument("limit", integer(0, getMaxLimit())) - .executes(ctx -> fillBook(ctx.getSource(), fill(), getInteger(ctx, "limit"))))) - .then(literal("random") - .executes(ctx -> fillBook(ctx.getSource(), random(new Random()), DEFAULT_LIMIT)) - .then(argument("limit", integer(0, getMaxLimit())) - .executes(ctx -> fillBook(ctx.getSource(), random(new Random()), getInteger(ctx, "limit"))) - .then(argument("seed", longArg()) - .executes(ctx -> fillBook(ctx.getSource(), - random(new Random(getLong(ctx, "seed"))), - getInteger(ctx, "limit")))))) - .then(literal("ascii") - .executes(ctx -> fillBook(ctx.getSource(), ascii(new Random()), DEFAULT_LIMIT)) - .then(argument("limit", integer(0, getMaxLimit())) - .executes(ctx -> fillBook(ctx.getSource(), ascii(new Random()), getInteger(ctx, "limit"))) - .then(argument("seed", longArg()) - .executes(ctx -> fillBook(ctx.getSource(), - ascii(new Random(getLong(ctx, "seed"))), - getInteger(ctx, "limit"))))))); - - dispatcher.register(cbook); + dispatcher.register(literal("cbook") + .then(literal("sign") + .executes(ctx -> signBook(ctx.getSource(), "")) + .then(argument("title", greedyString()) + .executes(ctx -> signBook(ctx.getSource(), getString(ctx, "title"))))) + .then(literal("fill") + .executes(ctx -> fillBook(ctx.getSource(), fill(), DEFAULT_LIMIT)) + .then(argument("limit", integer(0, getMaxLimit())) + .executes(ctx -> fillBook(ctx.getSource(), fill(), getInteger(ctx, "limit"))))) + .then(literal("random") + .executes(ctx -> fillBook(ctx.getSource(), random(new Random()), DEFAULT_LIMIT)) + .then(argument("limit", integer(0, getMaxLimit())) + .executes(ctx -> fillBook(ctx.getSource(), random(new Random()), getInteger(ctx, "limit"))) + .then(argument("seed", longArg()) + .executes(ctx -> fillBook(ctx.getSource(), random(new Random(getLong(ctx, "seed"))), getInteger(ctx, "limit")))))) + .then(literal("ascii") + .executes(ctx -> fillBook(ctx.getSource(), ascii(new Random()), DEFAULT_LIMIT)) + .then(argument("limit", integer(0, getMaxLimit())) + .executes(ctx -> fillBook(ctx.getSource(), ascii(new Random()), getInteger(ctx, "limit"))) + .then(argument("seed", longArg()) + .executes(ctx -> fillBook(ctx.getSource(), ascii(new Random(getLong(ctx, "seed"))), getInteger(ctx, "limit"))))))); } private static IntStream fill() { @@ -88,8 +78,8 @@ private static int fillBook(FabricClientCommandSource source, IntStream characte HeldBook book = getHeldWritableBook(player); String joinedPages = characterGenerator.limit((long) getMaxLimit() * 210) - .mapToObj(i -> String.valueOf((char) i)) - .collect(Collectors.joining()); + .mapToObj(i -> String.valueOf((char) i)) + .collect(Collectors.joining()); List pages = new ArrayList<>(limit); List> filterablePages = new ArrayList<>(); From cd865dabb750a0e611cf2e6b451a5cdbbe4a0629 Mon Sep 17 00:00:00 2001 From: Frederik van der Els Date: Tue, 21 Jul 2026 19:45:48 +0200 Subject: [PATCH 4/4] Add back version check --- .../net/earthcomputer/clientcommands/command/BookCommand.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/net/earthcomputer/clientcommands/command/BookCommand.java b/src/main/java/net/earthcomputer/clientcommands/command/BookCommand.java index 2b2f9f3bd..4dad46022 100644 --- a/src/main/java/net/earthcomputer/clientcommands/command/BookCommand.java +++ b/src/main/java/net/earthcomputer/clientcommands/command/BookCommand.java @@ -43,16 +43,19 @@ public static void register(CommandDispatcher dispatc .then(argument("title", greedyString()) .executes(ctx -> signBook(ctx.getSource(), getString(ctx, "title"))))) .then(literal("fill") + .requires(_ -> MultiVersionCompat.INSTANCE.getProtocolVersion() < MultiVersionCompat.V1_15) .executes(ctx -> fillBook(ctx.getSource(), fill(), DEFAULT_LIMIT)) .then(argument("limit", integer(0, getMaxLimit())) .executes(ctx -> fillBook(ctx.getSource(), fill(), getInteger(ctx, "limit"))))) .then(literal("random") + .requires(_ -> MultiVersionCompat.INSTANCE.getProtocolVersion() < MultiVersionCompat.V1_15) .executes(ctx -> fillBook(ctx.getSource(), random(new Random()), DEFAULT_LIMIT)) .then(argument("limit", integer(0, getMaxLimit())) .executes(ctx -> fillBook(ctx.getSource(), random(new Random()), getInteger(ctx, "limit"))) .then(argument("seed", longArg()) .executes(ctx -> fillBook(ctx.getSource(), random(new Random(getLong(ctx, "seed"))), getInteger(ctx, "limit")))))) .then(literal("ascii") + .requires(_ -> MultiVersionCompat.INSTANCE.getProtocolVersion() < MultiVersionCompat.V1_15) .executes(ctx -> fillBook(ctx.getSource(), ascii(new Random()), DEFAULT_LIMIT)) .then(argument("limit", integer(0, getMaxLimit())) .executes(ctx -> fillBook(ctx.getSource(), ascii(new Random()), getInteger(ctx, "limit")))