From ef35a39388d718874cbe8bd58f1140c45b8c6e5b Mon Sep 17 00:00:00 2001
From: Matt Worzala <35708499+mworzala@users.noreply.github.com>
Date: Fri, 8 Jan 2021 21:40:22 -0500
Subject: [PATCH] Add Player#openBook (#99)

---
 .../net/minestom/server/entity/Player.java    | 25 +++++++++++
 src/test/java/demo/Main.java                  |  1 +
 src/test/java/demo/commands/BookCommand.java  | 45 +++++++++++++++++++
 3 files changed, 71 insertions(+)
 create mode 100644 src/test/java/demo/commands/BookCommand.java

diff --git a/src/main/java/net/minestom/server/entity/Player.java b/src/main/java/net/minestom/server/entity/Player.java
index 098812de1..4925ced5c 100644
--- a/src/main/java/net/minestom/server/entity/Player.java
+++ b/src/main/java/net/minestom/server/entity/Player.java
@@ -29,6 +29,7 @@ import net.minestom.server.inventory.Inventory;
 import net.minestom.server.inventory.PlayerInventory;
 import net.minestom.server.item.ItemStack;
 import net.minestom.server.item.Material;
+import net.minestom.server.item.metadata.WrittenBookMeta;
 import net.minestom.server.listener.PlayerDiggingListener;
 import net.minestom.server.network.ConnectionManager;
 import net.minestom.server.network.ConnectionState;
@@ -1084,6 +1085,30 @@ public class Player extends LivingEntity implements CommandSender {
         playerConnection.sendPacket(titlePacket);
     }
 
+    /**
+     * Opens a book ui for the player with the given book metadata.
+     *
+     * @param bookMeta The metadata of the book to open
+     */
+    public void openBook(@NotNull WrittenBookMeta bookMeta) {
+        // Set book in offhand
+        final ItemStack writtenBook = new ItemStack(Material.WRITTEN_BOOK, (byte) 1);
+        writtenBook.setItemMeta(bookMeta);
+        final SetSlotPacket setSlotPacket = new SetSlotPacket();
+        setSlotPacket.windowId = 0;
+        setSlotPacket.slot = 45;
+        setSlotPacket.itemStack = writtenBook;
+        this.playerConnection.sendPacket(setSlotPacket);
+
+        // Open the book
+        final OpenBookPacket openBookPacket = new OpenBookPacket();
+        openBookPacket.hand = Hand.OFF;
+        this.playerConnection.sendPacket(openBookPacket);
+
+        // Update inventory to remove book (which the actual inventory does not have)
+        this.inventory.update();
+    }
+
     @Override
     public boolean isImmune(@NotNull DamageType type) {
         if (!getGameMode().canTakeDamage()) {
diff --git a/src/test/java/demo/Main.java b/src/test/java/demo/Main.java
index 1a9f0401f..6970eb89a 100644
--- a/src/test/java/demo/Main.java
+++ b/src/test/java/demo/Main.java
@@ -41,6 +41,7 @@ public class Main {
         commandManager.register(new PlayersCommand());
         commandManager.register(new PotionCommand());
         commandManager.register(new TitleCommand());
+        commandManager.register(new BookCommand());
 
         commandManager.setUnknownCommandCallback((sender, command) -> sender.sendMessage("unknown command"));
 
diff --git a/src/test/java/demo/commands/BookCommand.java b/src/test/java/demo/commands/BookCommand.java
new file mode 100644
index 000000000..ef2a9be7f
--- /dev/null
+++ b/src/test/java/demo/commands/BookCommand.java
@@ -0,0 +1,45 @@
+package demo.commands;
+
+import net.minestom.server.chat.ChatColor;
+import net.minestom.server.chat.ColoredText;
+import net.minestom.server.command.CommandSender;
+import net.minestom.server.command.builder.Arguments;
+import net.minestom.server.command.builder.Command;
+import net.minestom.server.entity.Player;
+import net.minestom.server.item.metadata.WrittenBookMeta;
+
+import java.util.List;
+
+public class BookCommand extends Command {
+    public BookCommand() {
+        super("book");
+
+        setCondition(this::playerCondition);
+
+        setDefaultExecutor(this::execute);
+    }
+
+    private boolean playerCondition(CommandSender sender, String commandString) {
+        if (!sender.isPlayer()) {
+            sender.sendMessage("The command is only available for players");
+            return false;
+        }
+        return true;
+    }
+
+    private void execute(CommandSender sender, Arguments args) {
+        Player player = sender.asPlayer();
+
+        final WrittenBookMeta bookMeta = new WrittenBookMeta();
+        bookMeta.setAuthor(player.getUsername());
+        bookMeta.setGeneration(WrittenBookMeta.WrittenBookGeneration.ORIGINAL);
+        bookMeta.setTitle(player.getUsername() + "'s Book");
+        bookMeta.setPages(List.of(
+                ColoredText.of(ChatColor.RED, "Page one"),
+                ColoredText.of(ChatColor.BRIGHT_GREEN, "Page two"),
+                ColoredText.of(ChatColor.BLUE, "Page three")
+        ));
+
+        player.openBook(bookMeta);
+    }
+}