Advanced Modding - Tile Entity Data

Goal

I want to show you how to add data to a Tile Entity. Then I want to introduce you to the NBT system and explain how to save the data.

Difficulty

3/10 - Relatively Easy

Prerequisites

Forge Version

This Tutorial was created with Forge 11.14.1.1334 for Minecraft 1.8. If anything doesn't work with other versions, please contact me!


Adding data to a TileEntity

Two notes at the beginning: If you only read this because you want to learn something about NBT Data, just scroll down or click here.

The TileEntity we're creating here is pretty useless. But this tutorial is necessary for the following ones, so read it anyway, you'll need it.

 


Adding data to a Tile Entity is as simple as adding variables to classes. Actually, it's exactly what we are doing here. A Tile Entity can have any possible variable or object, they will be usable like in a normal Java class. Note that every block that has a Tile Entity in the world has a new instance of your Tile Entity class. This is different than the normal blocks: There a numeric ID is linked to one single instance of the block class. Thus, you don't have block specific variables if your block doesn't have a Tile Entity.

 

For demonstration I added a few variables to our Tile Entity we created in the "Tile Entity" Tutorial.

ModTileEntity.java:
public class ModTileEntity extends TileEntity {

    private boolean aBoolean;
    private byte aByte;
    private short aShort;
    private int anInt;
    private long aLong;
    private float aFloat;
    private double aDouble;
    private String aString;
    private byte[] aByteArray;
    private int[] anIntArray;

    private ItemStack anItemStack;

    private ArrayList aList = new ArrayList();
}

Again, this Tile Entity does nothing useful, it's just used for demonstration.


These variables now can be changed for the Tile Entity either directly or by using getter and setter.

Saving Tile Entity Data: NBT

How does Minecraft work - NBT Data:
NBT stands for "Named Binary Tag" and is one of the basic Minecraft data storage systems. NBT data is used to store data of items, entities and tile entities. Its also used in commands like /summon or /tellraw
The NBT format is very similar to the common JSON format (Read more about that in this wikipedia article): It supports single named values, arrays or objects. The nbt syntax in text format is similar to JSON and you probably already know it if you worked with Minecraft commands a bit. Anyway, whats interesting here is the code representation of nbt objects.

The "object" in the NBT format is an NBTTagCompound in Java. An NBTTagCompound object has several methods to store variables and sub-objects in it. NBT supports the following different "types", which are internally represented by an ID:

ID Type (NBT Class) Equivalent Java Type Description
0 NBTTagEnd null Used to define the end of an object. Don't use this on your own.
1 NBTTagByte byte Stores a single byte value.
2 NBTTagShort short / char Stores a single short or char value (2 bytes).
3 NBTTagInt int Stores a single int value (4 bytes).
4 NBTTagLong long Stores a single long value (8 bytes).
5 NBTTagFloat float Stores a single float value (4 bytes).
6 NBTTagDouble double Stores a single double value (8 bytes).
7 NBTTagByteArray byte[] Stores an array of byte values. This is a shortcut for an NBTTagList containing NBTTagByte values.
8 NBTTagString String Stores a String value.
9 NBTTagList Object[] Stores an array of individual values of the same type. Values in an array don't have a name but an index where they can be found. It is possible to create arrays of objects or arrays of arrays.
10 NBTTagCompound Object A individual object which can contain several named values or even other objects and arrays.
11 NBTTagIntArray int[] Stores an array of int values. This is a shortcut for an NBTTagList containing NBTTagInt values.

To store data into an NBTTagCompound object, there are several methods that can be used. The most basic method here is setTag(String name, NBTBase tag). This method stores an NBT object (any of the ones above) in the NBTTagCompound along with the given name. There are several specialized methods to store a specific type. These methods will be used later in this tutorial.
To load values, the method getTag(String name) can be used. This returns an NBTBase object. However, there are specialized methods available as well.
NBTTagCompound also provides methods to check if an entry exists ( hasTag(String name, int typeID) ) and to delete entries ( removeTag(String name) ).

Saving Tile Entity Data: Methods

To save Tile Entity data, the methods readFromNBT(NBTTagCompound) and writeToNBT(NBTTagCompound) must be overridden in the TileEntity class. Those methods provide an NBTTagCompound as argument which can be used to store or load our data.

ModTileEntity.java:
    @Override
    public void writeToNBT(NBTTagCompound compound) {
        super.writeToNBT(compound);

        //Primitives:
        compound.setBoolean("aBoolean", this.aBoolean);
        compound.setByte("aByte", this.aByte);
        compound.setShort("aShort", this.aShort);
        compound.setInteger("anInt", this.anInt);
        compound.setLong("aLong", this.aLong);
        compound.setFloat("aFloat", this.aFloat);
        compound.setDouble("aDouble", this.aDouble);
        compound.setString("aString", this.aString);
        compound.setByteArray("aByteArray", this.aByteArray);
        compound.setIntArray("anIntArray", this.anIntArray);

        //Item Stack:
        NBTTagCompound stack = new NBTTagCompound();
        this.anItemStack.writeToNBT(stack);
        compound.setTag("anItemStack", stack);

        //TagList of Integer Tags:
        NBTTagList list = new NBTTagList();
        for (int i = 0; i < this.aList.size(); i++) {
            NBTTagCompound nbt = new NBTTagCompound();
            nbt.setInteger("id", i);
            nbt.setInteger("value", this.aList.get(i));
            list.appendTag(nbt);
        }
        compound.setTag("aList", list);
    }

    @Override
    public void readFromNBT(NBTTagCompound compound) {
        super.readFromNBT(compound);

        //Primitives:
        this.aBoolean = compound.getBoolean("aBoolean");
        this.aByte = compound.getByte("aByte");
        this.aShort = compound.getShort("aShort");
        this.anInt = compound.getInteger("anInt");
        this.aLong = compound.getLong("aLong");
        this.aFloat = compound.getFloat("aFloat");
        this.aDouble = compound.getDouble("aDouble");
        this.aString = compound.getString("aString");
        this.aByteArray = compound.getByteArray("aByteArray");
        this.anIntArray = compound.getIntArray("anIntArray");

        //ItemStack:
        this.anItemStack = ItemStack.loadItemStackFromNBT(compound.getCompoundTag("anItemStack"));

        //TagList of Integer Tags:
        NBTTagList list = compound.getTagList("aList", 10);
        this.aList.clear();
        for (int i = 0; i < list.tagCount(); i++) {
            NBTTagCompound nbt = list.getCompoundTagAt(i);
            int id = nbt.getInteger("id");
            int value = nbt.getInteger("value");
            this.aList.ensureCapacity(id);
            this.aList.set(id, value);
        }
    }

This example shows how to save data in an NBTTagCompound and how to load it again.

The first method saves data:

First the "primitive" data types that have their own method, then an ItemStack using it's special saving method and finally an NBTTagList which is created before and filled with data from an array list. The NBTTagList contains NBTTagCompounds. Each of them has an id which is the index of the element in the list. The value equals the value in the list.

 

The second method loads data:

The order of the load calls is identical to the save calls: First the "primitives", then an ItemStack, finally the List. When loading the list one thing is very important, namely this line:

NBTTagList list = compound.getTagList("aList", 10);

Here, the 10 defines that the TagList contains NBTTagCompounds (10 is the ID of NBTTagCompound). This must be specified whenever a TagList is loaded. If you created a TagList of Integer (ID: 3) tags, the method would be compound.getTagList("name", 3);

 

It's also possible to store a list without the "id" objects, then the loaded elements are just appended to the list using the method this.aList.add(...);.


You can download the code used in this tutorial as a .zip file from here.


Recommended tutorials to continue with


Comments and Questions:

If you want to report modding problems, please make sure to include the code in a pastebin link or something else! Don't just write "It doesn't work", otherwise your post will be deleted. For more complicated problems, please use the troubleshooter form.