Store information persistently on item/itemstack?

Discussion in 'Plugin Development' started by Hanii Puppy, Sep 11, 2013.

Thread Status:
Not open for further replies.
  1. Part of something I'm working on requires that I store my own information, specifically a metadata class containing instructions for certain actions performed with the item in question held, against a non-stackable version of another stackable item. ie a stick or a nether star.

    It's fairly easy to save and load data separately for things like blocks which can uniquely be referenced through a coördinate and world, but items can be stored in a player inventory, in a block inventory, in an ender chest, theoretically in an item's inventory, or in the world as an item entity, and it strikes me as being awkward and finicky to re-apply my data to items, especially ones that may appear in modded inventories.

    Is there a way I can persistently store data, even if it's just a small string that I can then use as a key for more data, across restarts? IE in the same way that item names, enchants, or attributes retain their data. Alternatively, is there a way of accessing a guaranteeably unique key for an item or item-stack that I can use to reference the unique item upon saving and loading data?

    Thankyou very much ^^
     
  2. (Still seeking help ^^; )
     
  3. Offline

    krisdestruction

  4. Offline

    xTrollxDudex

  5. Offline

    Comphenix

    You can always store that additional data in an item's lore section, though it will ordinarily be visible to the user:
    Code:java
    1. ItemStack stack = new ItemStack(Material.GOLD_AXE);
    2. ItemMeta meta = stack.getItemMeta();
    3.  
    4. meta.setLore(Lists.newArrayList("Your custom data. Human readable?"));
    5. stack.setItemMeta(meta);

    However, if you're willing to depend on ProtocolLib, then you can always hide the lore section client-side, while still manipulating it on the server. Take a look at this packet listener for more information (it's basically a condensed version of ItemRenamer).

    I've also added an API to ItemRenamer that makes this much easier, though it's currently only out on the development build. I might release it on BukkitDev soon though (example download):
    Code:java
    1.  
    2. public class ExampleMod extends JavaPlugin {
    3. @Override
    4. public void onEnable() {
    5. RenamerAPI.getAPI().addListener(this, RenamerPriority.POST_NORMAL, new RenamerListener() {
    6. @Override
    7. public void onItemsRenaming(Player player, RenamerSnapshot snapshot) {
    8. for (ItemStack stack : snapshot) {
    9. if (stack != null && stack.hasItemMeta()) {
    10. ItemMeta meta = stack.getItemMeta();
    11.  
    12. // Our marker - hide the lore
    13. if (meta.hasLore() && meta.getLore().get(0).startsWith("[ExampleMod]")) {
    14. meta.setLore(null);
    15. stack.setItemMeta(meta);
    16. }
    17. }
    18. }
    19. }
    20. });
    21. }
    22.  
    23. @Override
    24. public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
    25. if (sender instanceof Player) {
    26. Player player = (Player) sender;
    27. ItemStack stack = new ItemStack(Material.GOLD_AXE);
    28. ItemMeta meta = stack.getItemMeta();
    29.  
    30. meta.setLore(Lists.newArrayList("[ExampleMod]Your custom data. Human readable?"));
    31. stack.setItemMeta(meta);
    32.  
    33. // Testing
    34. player.getInventory().addItem(stack);
    35. }
    36. return true;
    37. }
    38. }


    Finally ... you could, though I don't really recommend it, store this kind of data as hidden color codes. Copy CharCodeFactory in ItemRenamer to your project, and you'll be able to store custom hidden data in an item's display name or lore without resorting to ProtocolLib.

    But ... be careful, especially with display name. It can crash the client if the name gets too long and is put in an anvil. Lore should be safe however, but it will leave an empty lore line.
     
  6. Offline

    metalhedd

  7. Offline

    Comphenix

    Oh, no problem. It's easier to explain this with PortableHorses than ItemRenamer, at least. :p

    Yeah, I've posted a response. Basically, you can do it, but you'll have to compress/decompress the stream yourself OR break compatibility.
     
  8. Offline

    krisdestruction

  9. Offline

    Comphenix

    I just realized that you could also store this kind of information in an attribute's name. Just add one of the attribute library classes here, and add the following class to your project:
    https://gist.github.com/aadnk/6754421

    Then you'll be able to store arbitrary data in an ItemStack without displaying it to the user like so:
    Code:java
    1. public class ExampleMod extends JavaPlugin implements Listener {
    2. // Generate your own ID! Go to uuidgenerator.net
    3. private static final UUID ID = UUID.fromString("52af7459-585d-46cc-84cb-4de6d03b8c8b");
    4.  
    5. @Override
    6. public void onEnable() {
    7. getServer().getPluginManager().registerEvents(this, this);
    8. }
    9.  
    10. @EventHandler
    11. public void onPlayerItemHeldEvent(PlayerItemHeldEvent e) {
    12. ItemStack current = e.getPlayer().getInventory().getItem(e.getNewSlot());
    13.  
    14. if (current != null && current.getTypeId() > 0) {
    15. AttributeStorage storage = AttributeStorage.newTarget(current, ID);
    16.  
    17. // For retreving data
    18. System.out.println("Attribute data: " + storage.getData(null));
    19. }
    20. }
    21.  
    22. @Override
    23. public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
    24. if (sender instanceof Player) {
    25. ItemStack helmet = new ItemStack(Material.GOLD_HELMET);
    26. AttributeStorage storage = AttributeStorage.newTarget(helmet, ID);
    27.  
    28. // Note that you have to use getTarget() here.
    29. storage.setData("Some data ...");
    30. ((Player) sender).getInventory().addItem(storage.getTarget());
    31. }
    32. return true;
    33. }
    34. }

    You do need Minecraft 1.6.2 for this to work, and the data will be limited to 65 535 characters. But it will not be transmitted to the client (the string is the attribute type, not name), so it doesn't consume any significant bandwidth.
     
  10. Offline

    MTN

    Is there a way to do this by updating an existing itemstack? So that it does not have to be given everytime?
     
  11. Offline

    Comphenix

    Yes, as long as the item stack came from an inventory, you don't necessarily have replace it with getTarget(). That's only necessary if you create a new ItemStack from scratch.

    Basically, an ItemStack have to different internal representations. One is a proxy for a NMS ItemStack, and forwards method calls to that instance when you're querying the type or quantity. This is the type you get when you retrieve a stack from an inventory or entity, and can be a target of AttributeStorage.

    The second type is the one native to Bukkit, and simply stores every property in normal fields. You get this type when you construct an ItemStack yourself, or retrieve one from a configuration file. Bukkit will automatically convert it to the first type for you, if its ever inserted into an inventory.

    The problem here is that the attribute API can only read and alter the first type, not the second. You can convert to the first type, but only by making a copy. The problem is that you might insert the old version into the inventory, instead of the new copy. That's why you have to call getTarget() in the example, as its a different object than the ItemStack I created.

    But you don't have to do it if you item stack is already of the first type. Or you can just call setItem() on the inventory, and change the stack that way (just in case).
     
  12. Offline

    gabizou


    So, let me get this straight, if I want to store say Map<String, Map<String, Object>> much like multiple configuration sections, I would do the following:

    Code:java
    1. // Going direct
    2. public void storeMap(Player player, ItemStack item, Map<String, Map<String,Object>> map) {
    3. for (Map.Entry<String, Map<String, Object>> entry : map.getEntrySet()) {
    4. Map<String, Object> attributeMap = entry.getValue();
    5. String serializedMap = //Some serialization happens somehow with some utility, just say it makes a string of a JSON object map)
    6. String itemName = entry.getKey();
    7. AttributeStorage storage = AttributeStorage.newTarget(item, itemName.hashCode);
    8. storage.setData(serializedMap);
    9. player.getInventory().removeItem(item);
    10. player.getInventory().addItem(storage.getTarget());
    11. }
    12. }
    13.  


    From my understanding, this would allow for storing all various data into the ItemStack and would be loaded properly through CraftBukkit (Not caring about MCPC+ quite honestly)?

    Also, I understand that the name hash code isn't so secure and can be foiled, but I have plans to make the item names unique though other means.

    Further note: I am using NbtFactory and AttributeStorage that you've provided.
     
  13. Offline

    Comphenix

    Correct, though depending on the data size, you could serialize the entire map of maps into a single attribute. In practice, it's limited to 32 Kb of data though (more or less).
    I recommend using a hashing function such as MD5 or SHA-128 to generate the UUID, preferably by prefixing with a random salt:
    Code:java
    1. private static UUID computeUUID(String id) {
    2. try {
    3. // TODO: REPLACE PREFIX WITH A RANDOM STRING. See random.org
    4. final byte[] input = ("ce37ca4386d9e77b4f0a60bc069658b5" + id).getBytes(Charsets.UTF_8);
    5. final ByteBuffer output = ByteBuffer.wrap(MessageDigest.getInstance("MD5").digest(input));
    6.  
    7. return new UUID(output.getLong(), output.getLong());
    8.  
    9. // Definitely in violation of the specs
    10. throw new IllegalStateException("Current JVM doesn't support MD5.", e);
    11. }
    12. }
     
  14. Offline

    gabizou

    Nvm, I misread initially. Thank you very much Comphenix!

    So technically, I could make an IteStack into a backpack, storing other ItemStacks, or would storing possible 54 ItemStacks within an attribute be impossible?

    I'm just brainstorming here and coming up with various ideas to use these libraries in awesome ways.
     
  15. Offline

    TerXIII

    I'm bumping this thread because I'm attempting to create a reflection-based Item framework for my server.

    I've got the AttributeStorage concept at least somewhat working in 1.7.5 development build, and have fixed some of the issues that cropped up from upgrading.

    I'm having some trouble figuring out the UUID portion of the storage attributes, though. Is the UUID a per-plugin ID, or a per-item id?
     
Thread Status:
Not open for further replies.

Share This Page