Advanced Modding - Tile Entity with Inventory

Goal

I want to show you how to create a Tile Entity which has an inventory.

Difficulty

5/10 - Mediocre Difficult

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 an Inventory to a Tile Entity

To add an inventory to an existing Tile Entity, we need to implement a new interface in the Tile Entity class. This interface is IInventory. Let's add it to our Tile Entity class first:

ModTileEntity.java:
public class ModTileEntity extends TileEntity implements IInventory {
}

IInventory contains a whole bunch of methods which need to be implemented. Luckily, most of them are standard methods which can be copied to every implementation of IInventory.

However, before we add those methods, we first add two variables: The storage for the items in the inventory, which is simply an array of ItemStacks and a String variable containing the custom inventory name. This allows us to change the inventory's title by renaming the block in an anvil.

ModTileEntity.java:
public class ModTileEntity extends TileEntity implements IInventory {
    
    private ItemStack[] inventory;
    private String customName;

    public ModTileEntity() {
        this.inventory = new ItemStack[this.getSizeInventory()];
    }

    public String getCustomName() {
        return this.customName;
    }

    public void setCustomName(String customName) {
        this.customName = customName;
    }
}

Along with the variables I added a constructor which creates the array of ItemStacks. The method getSizeInventory() used here is contained in the IInventory interface. We'll implement it in just a second. I also added getter and setter for the custom name, those will be used later.


Let's start with the methods from IInventory:


The first three methods are used to get the name of the inventory. The name is displayed in the GUI for the Inventory (We'll create GUIs in the next tutorials). Here are the methods used:

ModTileEntity.java:
@Override
public String getName() {
    return this.hasCustomName() ? this.customName : "container.tutorial_tile_entity";
}

@Override
public boolean hasCustomName() {
    return this.customName != null && !this.customName.equals("");
}

@Override
public IChatComponent getDisplayName() {
    return this.hasCustomName() ? new ChatComponentText(this.getName()) : new ChatComponentTranslation(this.getName());
}

customName is the String variable we created above. The String "container.tutorial_tile_entity" defines the normal name of the Inventory. This name will be translated, thus it must be mapped in the language file. If you create your own Tile Entity, you'll need to adjust this String to your own inventory name key (By convention, inventory names always start with "container."). The rest of the methods can just be copied.

 

The next method is getSizeInventory(). It returns a constant integer value defining the slot count this inventory has. Here I'll use nine slots, similar to a Minecraft dispenser.

ModTileEntity.java:
@Override
public int getSizeInventory() {
    return 9;
}

The next methods are used to get or modify inventory slots. These are standard methods that work for every inventory, thus they can be copied as they are.

ModTileEntity.java:
@Override
public ItemStack getStackInSlot(int index) {
    if (index < 0 || index >= this.getSizeInventory())
        return null;
    return this.inventory[index];
}

@Override
public ItemStack decrStackSize(int index, int count) {
    if (this.getStackInSlot(index) != null) {
        ItemStack itemstack;

        if (this.getStackInSlot(index).stackSize <= count) {
            itemstack = this.getStackInSlot(index);
            this.setInventorySlotContents(index, null);
            this.markDirty();
            return itemstack;
        } else {
            itemstack = this.getStackInSlot(index).splitStack(count);

            if (this.getStackInSlot(index).stackSize <= 0) {
                this.setInventorySlotContents(index, null);
            } else {
                //Just to show that changes happened
                this.setInventorySlotContents(index, this.getStackInSlot(index));
            }

            this.markDirty();
            return itemstack;
        }
    } else {
        return null;
    }
}

@Override
public ItemStack getStackInSlotOnClosing(int index) {
    ItemStack stack = this.getStackInSlot(index);
    this.setInventorySlotContents(index, null);
    return stack;
}

@Override
public void setInventorySlotContents(int index, ItemStack stack) {
    if (index < 0 || index >= this.getSizeInventory())
        return;

    if (stack != null && stack.stackSize > this.getInventoryStackLimit())
        stack.stackSize = this.getInventoryStackLimit();
        
    if (stack != null && stack.stackSize == 0)
        stack = null;

    this.inventory[index] = stack;
    this.markDirty();
}

The first method getStackInSlot checks whether the stack index is valid and returns the stack within.

The second method decrStackSize decreases the amount of items in the specified slot by the given amount and returns an ItemStack containing the amount of items that has been removed.

The third method getStackInSlotOnClosing clears a slot and returns it's previous content.

Finally, the last method sets the content of a slot. Note that this method does not check whether the item is valid for the given slot (method isItemValidForSlot, will be done in a second). However, this should not be implemented anyway because each GUI must check this on it's own. Just leave this method as it is, its fine this way. Vanilla methods aren't implemented differently in this point.


The next method is getInventoryStackLimit. This method returns the maximum stack size in the inventory. This is 64 in most cases.

ModTileEntity.java:
@Override
public int getInventoryStackLimit() {
    return 64;
}

The next method isUsableByPlayer returns true if the given player has access to the inventory. The default implementation just checks the player's distance to the TileEntity and returns true if it is less than 8 blocks. The method uses the player's method getDistanceSq which returns the squared distance to the given point. If this is less than 64, the real distance is less than 8.

ModTileEntity.java:
@Override
public boolean isUseableByPlayer(EntityPlayer player) {
    return this.worldObj.getTileEntity(this.getPos()) == this && player.getDistanceSq(this.pos.add(0.5, 0.5, 0.5)) <= 64;
}

The next two methods openInventory and closeInventory are not needed in most cases so we leave them empty.

ModTileEntity.java:
@Override
public void openInventory(EntityPlayer player) {
}

@Override
public void closeInventory(EntityPlayer player) {
}

The next method isItemValidForSlot returns whether the given ItemStack can be placed in the given slot. This can be used to create slots in which no or only special items can be placed by the player. Internally, these slots can be changed of course (That's why setInventorySlotContents must not check this method!). By default, this method should return true.

ModTileEntity.java:
@Override
public boolean isItemValidForSlot(int index, ItemStack stack) {
    return true;
}

The next three methods can be used to synchronize data between server and client when the client player is accessing the inventory. The methods allow the user to "publish" several integer values together with an integer ID that will be synchronized with the client. I'll not explain this method here because this would be too long for this tutorial. For now, just leave those methods blank.

ModTileEntity.java:
@Override
public int getField(int id) {
    return 0;
}

@Override
public void setField(int id, int value) {
}

@Override
public int getFieldCount() {
    return 0;
}

Finally, the last method.

ModTileEntity.java:
@Override
public void clear() {
    for (int i = 0; i < this.getSizeInventory(); i++)
        this.setInventorySlotContents(i, null);
}

Now our Tile Entity has an inventory. But we still have some work to do...

First, the inventory needs to be saved. Therefore, we override writeToNBT and readFromNBT. If you haven't read the last tutorial about saving Tile Entity data this is your last chance to do it.

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

    NBTTagList list = new NBTTagList();
    for (int i = 0; i < this.getSizeInventory(); ++i) {
        if (this.getStackInSlot(i) != null) {
            NBTTagCompound stackTag = new NBTTagCompound();
            stackTag.setByte("Slot", (byte) i);
            this.getStackInSlot(i).writeToNBT(stackTag);
            list.appendTag(stackTag);
        }
    }
    nbt.setTag("Items", list);

    if (this.hasCustomName()) {
        nbt.setString("CustomName", this.getCustomName());
    }
}


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

    NBTTagList list = nbt.getTagList("Items", 10);
    for (int i = 0; i < list.tagCount(); ++i) {
        NBTTagCompound stackTag = list.getCompoundTagAt(i);
        int slot = stackTag.getByte("Slot") & 255;
        this.setInventorySlotContents(slot, ItemStack.loadItemStackFromNBT(stackTag));
    }

    if (nbt.hasKey("CustomName", 8)) {
        this.setCustomName(nbt.getString("CustomName"));
    }
}

The writeToNBT method first creates a new NBTTagList. Then, every ItemStack in the inventory that is not null is added to this list. Each ItemStack is written into an NBTTagCompound together with it's slot ID. The NBTTagList is then saved into the main tag with the name "Items". Additionally, the custom name is saved if existent.


The load method loads those tags and reassembles the inventory content.



Now we need to change the block class which contains the Tile Entity. Here we need to add two methods. The first one, breakBlock, needs to take care that the items contained in our inventory are dropped. The second one, onBlockPlacedBy, sets the custom name of our inventory when the item used to place the block is named.

ModBlockTileEntity.java:
@Override
public void breakBlock(World world, BlockPos pos, IBlockState blockstate) {
    ModTileEntity te = (ModTileEntity) world.getTileEntity(pos);
    InventoryHelper.dropInventoryItems(world, pos, te);
    super.breakBlock(world, pos, blockstate);
}


@Override
public void onBlockPlacedBy(World worldIn, BlockPos pos, IBlockState state, EntityLivingBase placer, ItemStack stack) {
    if (stack.hasDisplayName()) {
        ((ModTileEntity) worldIn.getTileEntity(pos)).setCustomName(stack.getDisplayName());
    }
}

Note that the method InventoryHelper.dropInventoryItems only works for our Tile Entity as the third argument because it implements IInventory. Tile Entities that does not implement this interface can't be passed to this method.

Also note that the inventory is not cleared when calling this method. But in this case, this doesn't matter because the Tile Entity is removed anyway when the block is broken.


Now, we can test our Tile Entity with inventory!

However, we don't have a GUI, so we can just test it by filling in Items using a hopper, then saving the world, reloading and destroying the block. If the items we filled in are dropped, everything works fine.


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


Recommended tutorials to continue with

Advanced Modding:


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.