1.15.2 Is it possible to store ItemStack data into the bookmeta of a book?

Discussion in 'Spigot Plugin Development' started by DapperNurd, Jan 23, 2020.

  1. I don't know if I worded that correctly.

    Basically what I want to do is have it so when you die, it spawns only book that when you click the text in the book, it restores all your items you dropped on death. I already kind of have this, but this is my issue:

    When you die, a book spawns. Clicking the text simply runs a command that keeps track of the items you had when you last died. The problem is that if you died before getting the book, now there are two books that do the same thing. What I really want to have happen is each book to keep track of the items from the death that caused it to spawn. This way, you could have two books from the same player at once that hold different items.

    Does anyone know if this is possible. And if so, how could I go about it? Thanks
     
  2. Well... what kind of command are you running?
    What you probably (or what you should have) is a Map containing the Items as well as the Player that the items belong to. There is no need to store the ItemStack to a Book. You should instead store the book that this "loot" belongs to this Map. A click on the book would then look up the Item in the Map and return the appropriate stacks to the owner.
     
  3. I'm not sure I understand what you are saying. I do have a item map as well as an armor map that gets set every time the player dies.

    This is my code for creating the book:
    Code (Java):
        public ItemStack createBook(String playerName) {
            ItemStack book = new ItemStack(Material.WRITTEN_BOOK);
            BookMeta bookMeta = (BookMeta) book.getItemMeta();

            BaseComponent[] page = new ComponentBuilder("[Click here to restore dropped items]")
                    .event(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/restoredrops")).create();

            bookMeta.spigot().addPage(page);

            bookMeta.setTitle(playerName);
            bookMeta.setAuthor("OrganizedDeathDrops");
            book.setItemMeta(bookMeta);

            return book;
     
  4. That‘s clear. What I wonder is what you do when you execute /restoredrops ?
     
  5. When I die, this function gets called and all my items get stored:
    Code (Java):
     public void storeContents(Player p, ItemStack[] items, ItemStack[] armor, float newXP) {
            itemsMap.put(p.getName(), items);
            armorMap.put(p.getName(), armor);
            xp = newXP;
        }
    When I do /restoredrops, this gets called (using the sender as the player):
    Code (Java):
        public void restoreInventory(Player p){
            PlayerInventory inventory = p.getInventory();
            inventory.setContents(itemsMap.get(p.getName()));
            inventory.setArmorContents(armorMap.get(p.getName()));
            p.setExp(xp);
        }
     
  6. Ah, I see your problem. You have multiple Solution options:
    1) Use a List with a custom class (that contains the items, armor and xp and loop over the contents. Since a very small amount of players will probably only be active at a time, there are no efficiency issues.
    2) Use Google‘s Multimap. This is a Map, that can contain multiple keys
    3) use another key; for example a combination of a player-attribute and the current time-stamp
    4) ? There are probably more options that you could come up with.

    Just some other considerations:
    You should only use one map and store a custom object that contains all the inventory-contents.
    Don‘t use the player‘s name as key. It is not guaranteed to be persistent, it also provides a worse hash than other options. Instead; use either the UUID, or an OfflinePlayer.
     
  7. Personally, I would have a HashMap of UUIDs and arrays of ItemStacks. Then using this tutorial (which is great once you set it up) you can store UUID data inside of the itemstack (in this case a book, though it can be any item). Then when the player right clicks the book, check if it has a uuid tag and pull up the correct items, and restore them to the player.
     
  8. Would you maybe be able to go over 1 or 2 a little bit more? I don't want to be spoon fed but this is my first plugin and I'm kind of lost.
     
  9. Option 1:
    Code (Text):
    class InventorySnapshot {

        private ItemStack[] contents;
        private ItemStack[] armor;
        private Player/OfflinePlayer/UUID player;
    }

    Code (Java):
    List<InventorySnaphot> snapshots = new ArrayList<>();

    // looking up a player

    for (InventorySnapshot snapshot: snapshots) {
        if (snaphot.belongsTo(player) ...
    }
    You would obviously have to implement constructors and other methods

    Option 2:
    Code (Java):
    Map<OfflinePlayer, List<InventorySnaphot>> snapshots = new HashMap<>();
    you will have to find some way to identify which entry in the list to use, maybe via a variable in InventorySnapshot. This is the same as using
    Code (Java):
    Multimap<OfflinePlayer, InventorySnapshot> snapshots = new HashMultimap();
    , the differences are only marginal.

    Option 3:

    Code (Java):
    class PlayerTimeStamp {
        private UUID uuid;
        private long timeStamp;

        public PlayerTimeStamp(UUID uuid) {
            this.uuid = uuid;
            this. timeStamp = System.currentTimeMilis();
        }
    }
    and then
    Code (Java):
    Map<PlayerTimeStamp, InventorySnapshot> map = new HashMap<>();
    hope nothing is too unclear and this gives you some inspiration!
     
  10. For option 1, how would it be able to have multiple books at once? To me it looks like it is just checking if a certain player owns the items. Would I be able to use the book's UUID instead of the player?
     
  11. A List can contain multiple Entries. Every time a player dies, you would have to create a new InventorySnapshot containing the stuff that the player has. If a player clicks on a book, it would look up that exact player.
    You can use the book’s ID additionally, if you care about the exact order of the books. However, you also need a reference to the player in order to compare the player.
    Obviously, if you give the book to one and only one player, and you can guarantee that that player will only have that book, you could omit the player-Id.
     
  12. I'm really sorry for asking so many questions but I'm not entirely sure I understand why I need to compare the player. I don't think I am going to make it so only the original owner of the book can open it- any player could open the book and get the items.
    So what I'm understanding so far I think is that I can create this class and then, somewhere, create a list of the class. Every time a player dies, I create a new entry to the list that stores their items and such. When I create a book, I also store the book's UUID in the class and so when a player opens that book, they get specifically those items associated with it. Am I correct?
     
  13. Ah, I misunderstood you. I thought you had a book for every player. In that case, yes, you‘ll need to store some reference to that book itself. Could be the UUID, could be the book itself (I didn‘t knew a book even had a UUID)
    In other words; yes, you are correct :D

    but then, you could also make a map Book -> Inventory
     
  14. I don't know if books have UUID's either I was just guessing. They probably don't to be honest.

    My current Death event looks like this:
    Code (Java):
    final Player player =  (Player) e.getEntity();
                player.sendMessage("Death Detected");

                player.getWorld().dropItemNaturally(player.getLocation(), main.createBook(player.getName()));

                main.storeContents(player, player.getInventory().getContents(), player.getInventory().getArmorContents(), e.getNewExp());
                e.getDrops().clear();
    Now I have something like this:
    Code (Java):
    final Player player =  (Player) e.getEntity();
                player.sendMessage("Death Detected");

                ItemStack book = main.createBook(player.getUniqueId(), player.getName());

                player.getWorld().dropItemNaturally(player.getLocation(), book);

                main.individualBooks.put(book, new InventorySnapshot() );

                //main.storeContents(player, player.getInventory().getContents(), player.getInventory().getArmorContents(), e.getNewExp());
                e.getDrops().clear();
    In the main class, I added:
    Code (Java):
        public Map<ItemStack, InventorySnapshot> individualBooks = new HashMap<ItemStack, InventorySnapshot>();
    Is that kind of what you were saying I could do? If so, in the line "main.individualBooks.put(book, new InventorySnapshot());", what should I do in the new InventorySnapshot() part?

    EDIT: the createBook() method uses a UUID and the name now because I switched to UUID for getting the player's inventory but am still using the name for the title of the book.
     
  15. @Schottky
    So, I've got this now:
    Code (Java):

    final Player player =  (Player) e.getEntity();
    player.sendMessage("Death Detected");

    book = main.createBook(player.getUniqueId(), player.getName());

    player.getWorld().dropItemNaturally(player.getLocation(), book);

    invSnap = new InventorySnapshot();
    invSnap.contents = player.getInventory().getContents();
    invSnap.armor = player.getInventory().getArmorContents();
    invSnap.book = book;
    invSnap.player = player.getUniqueId();

    System.out.println(Arrays.toString(invSnap.contents));

    main.individualBooks.put(book, invSnap);

    //main.storeContents(player, player.getInventory().getContents(), player.getInventory().getArmorContents(), e.getNewExp());
    e.getDrops().clear();
     
    It seems to be storing the items, but it isn't really doing what I want. It still isn't making each book be a separate thing, but I'm not sure if that is a problem with the books or the code. When I print the book to console, it always gives me:
    Code (Java):
    ItemStack{WRITTEN_BOOK x 1, BOOK_SIGNED_META:{meta-type=BOOK_SIGNED, title=DapperNurd, author=OrganizedDeathDrops, pages=[[Click here to restore dropped items]]}}
    The books even stack, which makes me think that they don't really have any different data. I'm not really sure what to do about this?
     
  16. Would I be able to use like an NBT tag?
     
  17. You should be careful storing items in NBT, you could easily end up with an item that's so big you're unable to connect to the server. That's why you can't put shulker boxes in shulker boxes.

    Edit: I mean like if you fill your inventory, then die, then take the book and fill your inventory without using the book, die, repeat until you have a massive item.
     
  18. Do you know any other beginner to intermediate friendly way of adding a special code or tag or something onto a book that could make it identifiable from other books?
     
  19. You could apply a random ID to each one, then save the player's inventory under a corresponding ID in a config file. Like
    • Player dies
    • Inventory saved to <uuid>.yml under id 328472
    • Player is issued a book with tag Inventory:328472
    • When book is used, player executes command /restoreinv 328472
    • Inventory is loaded from <uuid>.yml
    • Inventory is restored to player and book is taken
    • ID 328472 is deleted from <uuid>.yml to prevent duping.

    You might want to have a limit to how many books a player can have assigned to them though, for a similar reason: you don't want to run your server out of space if you have more than a few players that like to break things.
     
  20. Well instead of doing
    Code (Java):
    config.set("spigot","awesome");
    you'd do something like
    Code (Java):
    config.set("spigot.is","awesome");
    or
    Code (Java):
    ConfigurationSection spigot = config.getConfigurationSection("spigot");
    spigot.set("is", "awesome");
    The latter two would result in a config that looks like:
    Code (Text):
    spigot:
        is: awesome
    You'd just do something similar with your lists of items. ItemStack is configuration serialize-able so you should be able to just do something similar to
    Code (Java):
    spigot.set("hand", player.getInventory().getItemInMainHand());
    for all your items.
     
    • Like Like x 1