How to use NBT Tag Compound

From Minecraft Forge
Jump to: navigation, search

Contents

Goals

Create a "last 5 visitors" block, that remembers the last 5 people to click on the block.


Prerequisites

Basic blocks

What is NBT?

NBT is the map format made by Notch for saving the Minecraft map. It is a node-based file format, used in region files, player.dat files, and level.dat files.

You can think of the structure as a bit like XML, but in binary.

Any NBT file starts with a root tag. It is of the type "TAG_Compound", therefore the name of this tutorial. A Tag_Compound is a node capable of holding other nodes inside. The root tag normally does not have a name.

Why use NBT?

NBT is very good if you want to store data that cannot be stored in data/metadata of the block/item. For example, you cant save all the item information from a chest in a 4 bit metadata, so you use NBT, which is practically infinite big. The size of blocks on the hard drive does increase a lot though, so don't use NBT in blocks appearing naturally in big quantities.

NBT may also be useful in items. For example, the book uses NBT to save the author and contents of itself.

What is NBT used for in Minecraft?

  • Signs
  • Books
  • All blocks with an inventory(Furnace, Chest, etc...)

Types of tags

There are 12 different types of tags, capable of storing different data:

ID Name Description
0 TAG_End End of a compound tag. Normally never seen.
1 TAG_Byte A tag containing 1 byte of data.
2 TAG_Short A tag containing 1 short(Number value)
3 TAG_Int A tag containing 1 integer
4 TAG_Long A tag containing 1 long
5 TAG_Float A tag containing 1 float
6 TAG_Double A tag containing 1 double
7 TAG_Byte_Array A tag containing an array of bytes
8 TAG_String A tag containing a string
9 TAG_List A list of nameless tags of the same type.
10 TAG_Compound A list of named tag. The start of each NBT file. Can contain other compounds.
11 TAG_Int_Array An array of integers

Information taken from the MinecraftCoalition page on NBT. Please see the bottom of the page under "read more".


Writing the block class

If it was an item we was making, you can get the tag compound from the ItemStack the item resolves in. I might make another tutorial about that. But because we are working with a block, we need to use something called a TileEntity to save NBT values. A TileEntity is basically a entity connected to a block, used to store stuff for the block.

First, we make the constructor. Please note that the class extends BlockContainer.


public BlockVisitor(int id) {
        super(id, 35, Material.circuits);
        // TODO Auto-generated constructor stub
}

I won't go through this in detail, because you should already know this stuff.

Next, the method used for detecting right clicks.

public boolean onBlockActivated(World world, int par2, int par3, int par4, EntityPlayer par5EntityPlayer, int par6, float par7, float par8, float par9)
{
        if(!world.isRemote)
        {
                TileEntityVisitor t = (TileEntityVisitor) world.getBlockTileEntity(par2, par3, par4);
                t.processActivate(par5EntityPlayer, world);
        }
        return true;
}

Lets go through the content line for line...

if(!world.isRemote)

This line checks if it is the server or client calling this. isRemote is true if it is the client, because after Minecraft 1.2.5, single player is run on a local server. Therefore, this is a easy way of detecting server/client.

TileEntityVisitor t = (TileEntityVisitor) world.getBlockTileEntity(par2, par3, par4);

This line gives us the variable t, by getting a tile entity from the world with the getBlockTileEntity method. par2, 3, and 4 are x, y, and z coordinates. Notice how we cast to a TileEntityVisitor.

t.processActivate(par5EntityPlayer, world);

There is a method i made in our tileEntity called processActivate, that we use to do all the stuff in registering, and printing. The arguments are the player, and the world.

Now we need to add a method to the file that tells Minecraft that we are using a tile entity:

public boolean hasTileEntity(int metadata)
{
    return true;
}

The last method in the block file is the createNewTileEntity() method. It is quite simple, it just returns a new instance of a TileEntityVisitor:

public TileEntity createNewTileEntity(World par1World)
{
    try
    {
        return new TileEntityVisitor();
    }
    catch (Exception var3)
    {
        throw new RuntimeException(var3);
    }
}

We are now done with the block file. Lets move on to the tile entity file.

Making the TileEntity

The TileEntity does not need a constructor. In fact, the first thing we do is to declare the variables containing the visitors:

String visitor1="none";
String visitor2="none";
String visitor3="none";
String visitor4="none";
String visitor5="none";

I'm not using an array. Sue me.

The next thing we are going to do is to write the method that loads the information from the NBT tag compound:

@Override
public void readFromNBT(NBTTagCompound nbt)
{
    super.readFromNBT(nbt);
    this.visitor1 = nbt.getString("visitor1");
    this.visitor2 = nbt.getString("visitor2");
    this.visitor3 = nbt.getString("visitor3");
    this.visitor4 = nbt.getString("visitor4");
    this.visitor5 = nbt.getString("visitor5");
}

As you can see, we get a NBTTagComponund to use for reading. The rest of the code is quite self-explaining. On a note, remember to run super.readFromNBT(TagCompound); Because a lot of information is actually saved and loaded my the tileentity itself.

The next method saves to NBT:

@Override
public void writeToNBT(NBTTagCompound nbt)
{
    super.writeToNBT(nbt);
    nbt.setString("visitor1", visitor1);
    nbt.setString("visitor2", visitor2);
    nbt.setString("visitor3", visitor3);
    nbt.setString("visitor4", visitor4);
    nbt.setString("visitor5", visitor5);
}

This code is also very self-explanatory.

The last method to add is the one called in the block.

public void processActivate(EntityPlayer par5EntityPlayer, World world) {
        if(!visitor1.equals(par5EntityPlayer.getEntityName()))
        {
                visitor5=visitor4;
                visitor4=visitor3;
                visitor3=visitor2;
                visitor2=visitor1;
                visitor1=par5EntityPlayer.getEntityName();
        }
        //System.out.println("Visitors: " + visitor1 + ", " + visitor2 + ", " + visitor3 + ", " + visitor4 + ", " + visitor5);
        par5EntityPlayer.addChatMessage("Visitors: " + visitor1 + ", " + visitor2 + ", " + visitor3 + ", " + visitor4 + ", " + visitor5);
        world.notifyBlockChange(xCoord, yCoord, zCoord, 2);
}

This might require some explanation:

if(!visitor1.equals(par5EntityPlayer.getEntityName()))

This makes sure you cant register twice in a row in the guest list.

visitor5=visitor4;
visitor4=visitor3;
visitor3=visitor2;
visitor2=visitor1;
visitor1=par5EntityPlayer.getEntityName();

This part shifts the guest list.

world.notifyBlockChange(xCoord, yCoord, zCoord, 2);

To be safe. This method tells the world to update neighbour blocks. Please note that i also print the visitors to the chat.

All the code

VisitorBlock.java(Main code file)

package com.generic.tutorial.nbt.block;

import net.minecraft.src.*;
import cpw.mods.fml.common.Mod;
import cpw.mods.fml.common.Mod.Init;
import cpw.mods.fml.common.Mod.Instance;
import cpw.mods.fml.common.Mod.PreInit;
import cpw.mods.fml.common.event.FMLInitializationEvent;
import cpw.mods.fml.common.event.FMLPreInitializationEvent;
import cpw.mods.fml.common.network.NetworkMod;
import cpw.mods.fml.common.registry.GameRegistry;

@Mod(modid="VisitorBlock", name="VisitorBlock", version="0.0.1")
@NetworkMod(clientSideRequired=true, serverSideRequired=false)
public class VisitorBlock {
        @Instance("VisitorBlock")
        VisitorBlock instance;
        BlockVisitor visitor;
        int blockID=200;
        @PreInit
        public void preInit(FMLPreInitializationEvent event)
        {
               
        }
        @Init
        public void load(FMLInitializationEvent event) {
                visitor = new BlockVisitor(blockID);
                GameRegistry.registerBlock(visitor);
                GameRegistry.registerTileEntity(TileEntityVisitor.class, "visitorBlock");
                GameRegistry.addRecipe(new ItemStack(visitor, 1), "###", "#%#", "###", '#', Item.redstone, '%', Item.paper);
        }
}

BlockVisitor.java (Block class)

package com.generic.tutorial.nbt.block;

import net.minecraft.src.Block;
import net.minecraft.src.BlockContainer;
import net.minecraft.src.EntityPlayer;
import net.minecraft.src.Material;
import net.minecraft.src.TileEntity;
import net.minecraft.src.World;

public class BlockVisitor extends BlockContainer {

        public BlockVisitor(int id) {
                super(id, 35, Material.circuits);
                // TODO Auto-generated constructor stub
        }
        public boolean onBlockActivated(World world, int par2, int par3, int par4, EntityPlayer par5EntityPlayer, int par6, float par7, float par8, float par9)
    {
                if(!world.isRemote)
                {
                        TileEntityVisitor t = (TileEntityVisitor) world.getBlockTileEntity(par2, par3, par4);
                        t.processActivate(par5EntityPlayer, world);
                }
                return true;
    }
        public boolean hasTileEntity(int metadata)
    {
        return true;
    }
        @Override
        public TileEntity createNewTileEntity(World par1World)
    {
        try
        {
            return new TileEntityVisitor();
        }
        catch (Exception var3)
        {
            throw new RuntimeException(var3);
        }
    }  

}

TileEntityVisitor.java

package com.generic.tutorial.nbt.block;

import net.minecraft.src.*;

public class TileEntityVisitor extends TileEntity{

        String visitor1="none";
        String visitor2="none";
        String visitor3="none";
        String visitor4="none";
        String visitor5="none";
        public void processActivate(EntityPlayer par5EntityPlayer, World world) {
                if(!visitor1.equals(par5EntityPlayer.getEntityName()))
                {
                        visitor5=visitor4;
                        visitor4=visitor3;
                        visitor3=visitor2;
                        visitor2=visitor1;
                        visitor1=par5EntityPlayer.getEntityName();
                }
                //System.out.println("Visitors: " + visitor1 + ", " + visitor2 + ", " + visitor3 + ", " + visitor4 + ", " + visitor5);
                par5EntityPlayer.addChatMessage("Visitors: " + visitor1 + ", " + visitor2 + ", " + visitor3 + ", " + visitor4 + ", " + visitor5);
                world.notifyBlockChange(xCoord, yCoord, zCoord, 2);
        }
       
        @Override
        public void readFromNBT(NBTTagCompound nbt)
    {
        super.readFromNBT(nbt);
        this.visitor1 = nbt.getString("visitor1");
        this.visitor2 = nbt.getString("visitor2");
        this.visitor3 = nbt.getString("visitor3");
        this.visitor4 = nbt.getString("visitor4");
        this.visitor5 = nbt.getString("visitor5");
    }

    @Override
    public void writeToNBT(NBTTagCompound nbt)
    {
        super.writeToNBT(nbt);
        nbt.setString("visitor1", visitor1);
        nbt.setString("visitor2", visitor2);
        nbt.setString("visitor3", visitor3);
        nbt.setString("visitor4", visitor4);
        nbt.setString("visitor5", visitor5);
    }

}


Please note

You have to register the tileEntity with Forge, using:

GameRegistry.registerTileEntity(TileEntityVisitor.class, "visitorBlock");

Read more

NBT page at Minecraft Coalition

Personal tools
Namespaces
Variants
Actions
Navigation
tutorials
Toolbox