Util Creating Book GUIs

Discussion in 'Resources' started by Juancomaster1998, Aug 22, 2015.

Thread Status:
Not open for further replies.
  1. Hello, I've googled once about opening Book GUIs through Bukkit/Spigot, and All that I've found was it wasn't possible, so I've researched by myself through CraftBukkit and Spigot. I've found the way and I'll like to share it with you.
    Also, if anyone is interested, PLEASE make an Api of this, it will be really useful for me and probablly for other devs too. I'm lack of time to make one right now. Made One, check at the bottom of this thread.

    So, when reading NMS files, I've found there is a method called "openBook(net.minecraft.server.ItemStack)" on HumanEntity class. What it does is to open the book the player has in hand.
    Using this, we can easily put an Item on a player's hand for less than a milisec, call the method and then give the player he's hand item back.
    This all seems to be pretty easy, the major issue is when creating the book: Since ItemMeta was implemented, Bukkit's Devs have stopped using NBTTags. Using ItemMeta is all right, but for massive/intensive uses it's best to use NBTTags.

    So, here's an self descriptive code I've made to give you an idea how this will work

    Code:
    import net.minecraft.server.v1_8_R3.EntityHuman;
    import net.minecraft.server.v1_8_R3.Item;
    import net.minecraft.server.v1_8_R3.ItemStack;
    import net.minecraft.server.v1_8_R3.NBTTagCompound;
    import net.minecraft.server.v1_8_R3.NBTTagList;
    import net.minecraft.server.v1_8_R3.NBTTagString;
    import net.minecraft.server.v1_8_R3.StatisticList;
    
    import org.bukkit.Bukkit;
    import org.bukkit.ChatColor;
    import org.bukkit.command.Command;
    import org.bukkit.command.CommandSender;
    import org.bukkit.craftbukkit.v1_8_R3.entity.CraftHumanEntity;
    import org.bukkit.craftbukkit.v1_8_R3.inventory.CraftItemStack;
    import org.bukkit.entity.HumanEntity;
    import org.bukkit.entity.Player;
    import org.bukkit.plugin.java.JavaPlugin;
    
    public class Core extends JavaPlugin {
    
        public boolean onCommand(CommandSender sender, Command cmd, String commandLabel, String[] args) {
            if (args.length > 0) {
                //Getting The Player
                Player p = Bukkit.getPlayer(args[0]);
                HumanEntity he = (HumanEntity) p;
                CraftHumanEntity che = (CraftHumanEntity) he;
                EntityHuman eh = che.getHandle();
          
                //Creating NMS ItemStack and a tag, setting some values.
                ItemStack book = new ItemStack(Item.getById(387));
                NBTTagCompound tag = new NBTTagCompound();
                tag.setString("author", "Notch");
                tag.setString("title", "My Dairy");
                tag.set("display", new NBTTagCompound());
          
                //Adding values to the "display" section (name and lore)
                NBTTagCompound display = tag.getCompound("display");
                display.setString("Name", ChatColor.translateAlternateColorCodes('&', "&cCustom Book GUI"));
                NBTTagList lore = new NBTTagList();
                lore.add(new NBTTagString("Hello There!"));
                lore.add(new NBTTagString("Why Am I adding a lore?"));
                lore.add(new NBTTagString("Nobody will see it!"));
                lore.add(new NBTTagString("Huh, well, it's added already..."));
                display.set("Lore", lore);
          
                //Now to set the pages
                NBTTagList pages = new NBTTagList();
                pages.add(new NBTTagString("How Are you? I'm the first page and I have nothing on special!"));
          
                //You can too add JSON!
                NBTTagCompound json = new NBTTagCompound();
                json.setString("text", "Click Me!");
                json.setString("color", "blue");
                json.setString("italic", "true");
                json.setString("underlined", "true");
                json.set("clickEvent", new NBTTagCompound());
                json.set("hoverEvent", new NBTTagCompound());
                //This includes Events
                NBTTagCompound clickEvent = json.getCompound("clickEvent");
                clickEvent.setString("action", "run_command");
                clickEvent.setString("value", "Vote Please!");
                //Hover Events are events too!
                NBTTagCompound hoverEvent = json.getCompound("hoverEvent");
                hoverEvent.setString("action", "show_text");
                hoverEvent.set("value", new NBTTagCompound());
                hoverEvent.setString("insertion", "what is this?");
                //You can have as many hierarchies as you like
                NBTTagCompound hoverValue = hoverEvent.getCompound("value");
                hoverValue.setString("text", "https://myWebsite.com/vote");
                hoverValue.setString("color", "aqua");
                hoverValue.setString("italic", "true");
                //Let's add this on a new page
                pages.add(new NBTTagString(json.toString()));
          
                //You can change through pages too
                NBTTagCompound returner = new NBTTagCompound();
                returner.setString("text", "Go to the start!");
                returner.set("clickEvent", new NBTTagCompound());
                NBTTagCompound rClickEvent = returner.getCompound("clickEvent");
                rClickEvent.setString("action", "change_page");
                rClickEvent.setInt("value", 1);
                pages.add(new NBTTagString(returner.toString()));
          
                //Add pages to the book
                tag.set("pages", pages);
          
                //Don't forget this
                book.setTag(tag);
          
                //This is IMPORTANT, remember to put the book on the player's hand
                org.bukkit.inventory.ItemStack hand = p.getItemInHand();
                p.setItemInHand(CraftItemStack.asBukkitCopy(book));
          
                //Opening the GUI
                eh.openBook(book);
          
                //Returning whatever was on hand.
                p.setItemInHand(hand);
          
                //Adding Statistics, as it was a normal book and quill.
                eh.b(StatisticList.USE_ITEM_COUNT[387]);
            }
            return true;
        }
    }
    Thanks for Reading

    The API (open)

    Code:
    import java.util.ArrayList;
    import java.util.List;
    
    import net.minecraft.server.v1_8_R3.EntityHuman;
    import net.minecraft.server.v1_8_R3.Item;
    import net.minecraft.server.v1_8_R3.ItemStack;
    import net.minecraft.server.v1_8_R3.NBTTagCompound;
    import net.minecraft.server.v1_8_R3.NBTTagList;
    import net.minecraft.server.v1_8_R3.NBTTagString;
    import net.minecraft.server.v1_8_R3.StatisticList;
    
    import org.bukkit.craftbukkit.v1_8_R3.entity.CraftHumanEntity;
    import org.bukkit.craftbukkit.v1_8_R3.inventory.CraftItemStack;
    import org.bukkit.entity.Player;
    
    public final class Book {
    
        private String title;
        private String author;
        private List<String> pages = new ArrayList<String>();
      
        public Book(String title, String author) {
            this.title = title;
            this.author = author;
        }
      
        public PageBuilder addPage() { return new PageBuilder(this); }
      
        public ItemStack build() {
            ItemStack book = new ItemStack(Item.getById(387));
            NBTTagCompound tag = new NBTTagCompound();
            tag.setString("author", author);
            tag.setString("title", title);
            NBTTagList pages = new NBTTagList();
            for (String page : this.pages) pages.add(new NBTTagString(page));
            tag.set("pages", pages);
            book.setTag(tag);
            return book;
        }
      
        public static void open(Player p, ItemStack book, boolean addStats) {
            EntityHuman player = ((CraftHumanEntity)p).getHandle();
            org.bukkit.inventory.ItemStack hand = p.getItemInHand();
            try {
                p.setItemInHand(CraftItemStack.asBukkitCopy(book));
                player.playerConnection.sendPacket(new PacketPlayOutCustomPayload("MC|BOpen", new PacketDataSerializer(Unpooled.buffer())));
            } catch(Exception ex) { ex.printStackTrace(); }
            finally { p.setItemInHand(hand); }
            if (addStats) player.b(StatisticList.USE_ITEM_COUNT[387]);
        }
      
        public static final class PageBuilder {
            private String page = "{text:\"\", extra:[";
            private boolean first = true;
            private Book book;
          
            public PageBuilder(Book book) { this.book = book; }
    
            public Builder add() { return new Builder(this); }
            public Builder add(String text) { return new Builder(this).setText(text); }
            public PageBuilder newPage() { return add("\n").build(); }
          
            public Book build() { book.pages.add(page += "]}"); return book; }
        }
      
        public static final class Builder {
            private String text = null;
            private ClickAction click = null;
            private HoverAction hover = null;
            private PageBuilder builder;
          
            public Builder(PageBuilder builder) { this.builder = builder; }
          
            public Builder setText(String text) { this.text = text; return this; }
            public Builder clickEvent(ClickAction action) { click = action; return this; }
            public Builder hoverEvent(HoverAction action) { hover = action; return this; }
            public Builder clickEvent(ClickAction action, String value) { click = action; click.value = value; return this; }
            public Builder hoverEvent(HoverAction action, String value) { hover = action; hover.value = value; return this; }
          
            public PageBuilder build() {
                String extra = "{text:\"" + text + "\"";
              
                if (click != null) extra += ", clickEvent:{action:" + click.getString() + ", value:\"" + click.value + "\"}";
                if (hover != null) extra += ", hoverEvent:{action:" + hover.getString() + ", value:\"" + hover.value + "\"}";
              
                extra += "}";
              
                if (builder.first) builder.first = false;
                else extra = ", " + extra;
              
                builder.page += extra;
                return builder;
            }
        }
      
        public static enum ClickAction {
            Run_Command("run_command"), Suggest_Command("suggest_command"), Open_Url("open_url"), Change_Page("change_page");
          
            public String value = null;
            private String str;
            private ClickAction(String str) { this.str = str; }
            public String getString() { return str; }
        }
      
        public static enum HoverAction {
            Show_Text("show_text"), Show_Item("show_item"), Show_Entity("show_entity"), Show_Achievement("show_achievement");
          
            public String value = null;
            private String str;
            private HoverAction(String str) { this.str = str; }
            public String getString() { return str; }
        }
    }
    


    ~Juanco
     
    Last edited: Apr 27, 2016
    caseif likes this.
  2. Offline

    FisheyLP

    But NBT-Tags require nms & cb imports which makes the plugin only work for one version. That's why you should use BookMeta instead
     
  3. @FisheyLP
    Truth, but I'm used to work with nms & cb on my plugins. Anyways you can use it as you like, I'm just sharing what I've found.
     
  4. Offline

    atesin

    very interesting....

    but in mc 1.9 there is no net.minecraft.server.<version>.EntityHuman.openBook(book) method ... anyone knows some workaround?
     
    Desle likes this.
  5. Offline

    DeROvI

    How to place plain text and text with ClickEvent on one page?
     
  6. Haven't coded in 1.9 yet, but I'll do some research.

    Edit: Found a packet that does the same:
    Code:
    playerConnection.sendPacket(new PacketPlayOutCustomPayload("MC|BOpen", new PacketDataSerializer(Unpooled.buffer())));
    Hope it helps.

    using the api:
    Book book = new Book("My Book", "myself");
    PageBuilder page1 = book.addPage();
    page1.add("Plain Text\n").build();
    page1.add("Clickable Text").clickEvent(ClickAction.Run_Command, "/say hello").build();
    page1.build();

    Then to display it:
    Player player;
    Book.open(player, book.build(), false);
     
    Last edited: Apr 27, 2016
  7. Offline

    ChipDev

    Why not just create a book in the unseeable part of the inventory, and open that itemstack/book?
     
  8. I couldn't find the way to do this tbh, if you found it please share it!
    What this packets does is telling minecraft's client to open the book that a player has in it's hand.
    WARNING: If the item in the player's hand isn't a book, the client will crash!
     
    ChipDev likes this.
  9. Offline

    sander_blaadjes

    When you add text which goes over multiple lines, only the first line will have the correct color, the others will be reset.
    Example:
    Code:
    page1.add(ChatColor.GOLD + "[Blah blah blah blah more blah blah blah]").hoverEvent(HoverAction.Show_Text, "Click to go to blahblah").clickEvent(ClickAction.Run_Command, "/blahblah").build();
            
    Result:
    [​IMG]

    @Juancomaster1998
     
  10. This is just an api, meant to make things easier, different of plugins, which should be dumb-prove.
    You could manually add the color, or dig into the lib and look where the string is added, detect if there's any color, and calculate the width (in pixels) of every line, if the width is larger than a line, you can repeat the last color. I personally consider including this as a feature in the lib useless, sorry
     
Thread Status:
Not open for further replies.

Share This Page