Resource How to modify NBT

Discussion in 'Spigot Plugin Development' started by CoolJWB, Aug 14, 2019.

?

What should my next resource be about?

  1. Hide players fully (Vanish)

    0 vote(s)
    0.0%
  2. Saving items to files (Item save)

    0 vote(s)
    0.0%
  3. NPCs

    70.0%
  4. Run tasks (Schedulers)

    0 vote(s)
    0.0%
  5. None of the above

    30.0%

  1. How to modify NBT:
    Note: I will be underlining all terms that should be paid attention to as they are important.
    The version will be based on 1.13 but you can follow the tutorial with minimal change for versions 1.12.2 to 1.14.4.
    If you use 1.14+, NBT modification is not always needed (see comments).

    The intent of this resource is not to learn you how to add enchantments/attributes. The intent is to give you a basic understanding of how NBT tags works and how to edit them.


    This resource will have as a goal to give you at least a basic understanding of how to modify existing entities or inventory items NBT. I will also try to answer questions or issues that I myself met along the way.
    If you want to take a look at some more advanced NBT modification code you can take a look at my plugin here.

    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

    Learning the NBT structure:
    Note: When I talk about compounds I will be referring to NBTTagCompounds.

    A good way to learn an items NBT structure is to actually look at it yourself. Here is an example of an item I created which I viewed it in NBTExplorer, something I strongly recommend before continuing which would make it easier to understand the entire "learning the NBT structure part".
    If you do not feel like installing NBTExplorer and only want it explained, then you can continue taking my basic description on how NBT is structured below.

    You can think of NBT as a tree of different types of variables (tags) all nested within each other. Bytes, shorts, integers, doubles, strings, etc.
    The "main" variable type is the compound. It can contain any type of all the "variables", even itself.

    NBTTagCompound:
    Compounds will work as a mix of variables that can be accessed in any order and defined and accessible by their "name".

    Whenever you create a new item (in the inventory) or entity a new item compound will be created to store all information.
    If you take any items compound, it will contain 2 bytes, one for the count (amount of items) and one for the slot (where the item is placed in your inventory). It will also contain a string which will be the items ID (ex. "minecraft:stone"). This is all you need for an item.

    There could also be another compound within the items compound, this is called the item tag. The tag will contain all additional information (such as enchants, attributes, display name, lore, etc).

    NBTTagList:
    What differs lists from compounds is exactly what you would expect. Lists are ordered by in which order they were added. They start at 0 and will also be able to store any type of variable.
    To get a list from a compound we need to know the list type ID which can be found below:

    0 - End
    1 - Byte
    2 - Short
    3 - Integer
    4 - Long
    5 - Float
    6 - Double
    7 - ByteArray
    8 - String
    9 - List
    10 - Compound
    11 - Integer Array
    12 - Long Array

    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

    Writing the code:
    So, you are now familiar with the NBT structure so let's get to writing the code.

    Items:
    I will be covering how to modify enchants and attributes.

    We will first need to get or create the item tag. Luckily, there's an easy way to do this.
    What the code does is to first get the item in the players main hand, then convert it to a NMS ItemStack and lastly get (or create) the item tag.
    Code (Java):

    ItemStack item = Bukkit.getPlayer("CoolJWB").getInventory().getItemInMainHand(); // Get the Bukkit ItemStack from the player.
    net.minecraft.server.v_1_13_R1.ItemStack stack = CraftItemStack.asNMSCopy(item); // Convert the Bukkit ItemStack to a NMS ItemStack.
    NBTTagCompound tag = stack.getOrCreateTag(); // Get the item tag.

    Enchants:
    Now that we have the item tag we can modify/add all types of NBT values that an item can have.
    So, let's print all enchants from an item.
    Let's say that we know for sure that the item has existing enchants (to avoid checks). If you want to check if there is any enchants use the list isEmpty() method.

    We continue on the code we wrote above and say that we already have the item tag.

    So firstly get the enchantments list. As it's a list with compounds we use 10 (since as mentioned above it's the list type ID for compounds).
    Code (Java):

    NBTTagList enchants = tag.getList("Enchantments", 10); // Gets the enchantments.
     
    The rest is simple, iterate over all enchants (and make sure that you use < in the for loop) and print them to the console.
    Code (Java):

    for(int i = 0; i < enchants.size(); i++)
       Bukkit.getConsoleSender().sendMessage(enchants.getCompound(i)); // Prints the enchantment in text form.
     
    But then let's say we want to modify all existing enchants on an item to be level 127. Then we can use what we have written above and instead of printing it to the console, using the compounds set method to modify short values in the item.
    Code (Java):

    for(int i = 0; i < enchants.size(); i++)
       enchants.getCompound(i).setShort("lvl",127); // Set the enchants level to 127.
     
    It becomes a bit trickier to add an enchant as we would need to check if it already has enchantments and if yes get that enchant list. So let's say that we want to overwrite all existing enchants with a new enchantment list.
    Code (Java):

    NBTTagList enchantmentList = new NBTTagList(); // Create a new list for all enchantments to be stored.
    NBTTagCompound enchantment = new NBTTagCompound(); // Make a new enchantment compound.
     
    So, we have now created a new enchantment list and a new enchantment. Let's add some variables to the enchantment.
    Code (Java):

    enchantment.setString("id", "minecraft:sharpness"); // Add sharpness to the enchantment compound.
    enchantment.setShort("lvl", (short) 10); // Set the enchantment level to be 127.
     
    We should now add the enchantment to the enchantment list.
    Code (Java):

    enchantmentList.add(enchantment); // Add the enchantment to the list.
     
    Lastly, we want to add the enchantment list to the item tag and add it to the NMS stack. Then we need to convert the NMS stack to a Bukkit ItemStack.
    Code (Java):

    tag.set("Enchantments", enchantmentList); // Add the enchantment list to the item tag.
    stack.setTag(tag); // Set the NMS ItemStacks item tag.

    ItemStack item = CraftItemStack.asBukkitCopy(stack); // Convert from NMS ItemStack to Bukkit ItemStack.

    Bukkit.getPlayer("CoolJWB").getInventory().setItemInMainHand(item); // Set the mainhand item to the modified item.
     
    Attributes:
    Attributes are based on the same basics as enchants. Let's say that we want to print all attributes.
    So firstly get the attributes list. As it's a list with compounds we use 10 once again.
    Code (Java):

    NBTTagList attributes = tag.getList("AttributeModifiers", 10); // Gets the attributes.
     
    As with enchants, make sure to use < in the for loop.
    Code (Java):

    for(int i = 0; i < attributes.size(); i++)
       Bukkit.getConsoleSender().sendMessage(attributes.getCompound(i)); // Prints the attribute in text form.
     
    Let's set all attributes amounts to 2048.
    Code (Java):

    for(int i = 0; i < attributes.size(); i++)
       attributes.getCompound(i).setDouble("Amount",2048); // Set the attribute amount to 2048.
     
    It becomes harder to add attributes due to UUIDs and Attribute names.
    Code (Java):

    NBTTagList attributeList = new NBTTagList(); // Create a new list for all attributes to be stored.
    NBTTagCompound attribute = new NBTTagCompound(); // Make a new attribute compound.
     
    Attributes are different from enchants as they need an amount, Attribute name, name, operation and UUID.
    Let's start by getting an UUID, this is needed to avoid attributes overlaping.
    Code (Java):

    UUID uuid = UUID.randomUUID(); // Generates a random UUID.
     
    We now need to set the attribute amount. Let's set it to 2048.
    Code (Java):

    attribute.setDouble("Amount",2048); // Set the attribute amount.
     
    You can find all Attribute names by following the link.
    Code (Java):

    [SIZE=4]attribute.setString("AttributeName","generic.armor");
    attribute.setString("Name","generic.armor");
     
    You can read more about operations at the Attribute Wiki. I'm not going to go into much detail more than that you can set the operation value between 0 and 2.
    Code (Java):

    attribute.setInt("Operation",0);
     
    If you will be using multiple attributes on your items, then you must have different UUIDLeast and most values or else the attributes will overlap and not work.
    Code (Java):

    attribute.setLong("UUIDLeast",uuid.getLeastSignificantBits());
    attribute.setLong("UUIDMost",uuid.getMostSignificantBits());
     
    Add the attribute to the attribute list.
    Code (Java):

    attributeList.add(attribute); // Add the attribute to the list.
     

    Code (Java):

    tag.set("AttributeModifiers", attributeList); // Add the attribute list to the item tag.
    stack.setTag(tag); // Set the NMS ItemStacks item tag.

    ItemStack item = CraftItemStack.asBukkitCopy(stack); // Convert from NMS ItemStack to Bukkit ItemStack.

    Bukkit.getPlayer("CoolJWB").getInventory().setItemInMainHand(item); // Set the mainhand item to the modified item.
     

    Entities:
    Entities are easy to modify as they almost always have most data collected in their entity tag.
    You can take notice of entity data here.

    To get an entities entity tag you can use the code below:
    Code (Java):

    Entity entity = Bukkit.getWorld("world").getEntities().get(0); // Get a Bukkit entity.
    net.minecraft.server.v1_14_R1.Entity nmsEntity =((CraftEntity)entity).getHandle(); // Convert it to NMS entity.
    NBTTagCompound tag = new NBTTagCompound(); // Make a new clear tag.
    nmsEntity.save(tag); // Add NMS entity tag to the clear compound above.
     
    Players:
    This is (as you might have guessed) done almost the same as entities.
    You can take notice of player data here.
    Code (Java):

    Player player = Bukkit.getPlayer("CoolJWB");
    net.minecraft.server.v1_14_R1.EntityPlayer nmsPlayer = ((CraftPlayer)player).getHandle();
    NBTTagCompound tag = new NBTTagCompound();
    nmsPlayer.save(tag);
     
    Other info:
    You will find the files to modify with NBTExplorer in either the "world/playerdata/" folder or the "world/region/" folder.
    The playerdata folder will contain all players NBT data (inventory, position, rotation, etc).
    The region folder will contain all chunks which will then contain all entities in that chunk. You can find the chunk from a location with Dinnerbones Coordinate Tool.

    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

    If you have any questions or feel like there are ways to improve this resource (which there is), then please tell me all about it.
    Thanks to you (as a reader) for reading, I hope you liked it and (hopefully) this solved some of your questions.
    Thanks to kowagatte for his guide. This helped me a lot when learning myself NBT modification.

    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
     
    #1 CoolJWB, Aug 14, 2019
    Last edited: Sep 17, 2019 at 4:55 PM
    • Useful Useful x 2
    • Like Like x 1
  2. Why should we use this when we can achieve your examples with the Spigot-API already? An in a fully Multiversion compatible way.
     
  3. Oh, I forgot to say this but my intent of this resource was not to learn others how to add enchants or attributes (even if it might look like so) as I'm well aware that this can be done 10x easier.
    My actual intent was to make it easier for those who want to get started with editing NBT when APIs lack (as this was the issue for me).

    Enchants and attributes were only an example.
     
  4. Choco

    Moderator

    Fun fact: There's an API for custom NBT tags on items, entities and tile entities
    https://hub.spigotmc.org/javadocs/bukkit/org/bukkit/persistence/PersistentDataHolder.html

    Any existing NBT tags should be modifiable with either ItemMeta, the respective Entity interfaces or the respective BlockState interfaces. If not, create a PR.
     
    • Agree Agree x 1
  5. The custom nbt API is available since 1.13 so that can't be your problem.

    The mentioned interfaces are stable for a long time.

    So what do you mean with "The Spigot NBT API changed 3 times." ?
     
  6. The nbt api introduced in 1.13.2 (CustomItemTagContainer) was deprecated in 1.14, yet is still fully functional, even though it is deprecated.
     
  7. That's correct, however I don't like using deprecated stuff so in 1.14 you should be using PersistentDataHolder. In future versions
    What I meant to say is prior to 1.13.2 you had to use NMS way to change NBT data. 1.13.2 introduced CustomItemTagContainer, and then 1.14 introduced PersistentDataHolder. 3 different ways to do it. Each of them still possible but when I am creating a plugin that I want to support 1.8-.14 with I needed something like the NBT API that I linked above.