Advanced Modding - GUI Container

Goal

I want to show you how to create a GUI Container - this will be used to make a GUI with an inventory.

Difficulty

4/10 - Moderately 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!


Creating a GUI Container

A GUI container is used to track inventory slots on client and server side when they are accessed with a GUI. The container contains several slots linked to a slot in an inventory. These slots are then displayed in the GUI with their ItemStack inside.

 

Each GUI needs a special container, unless two GUIs share the exact same slot configuration. In this example, I'll create a GUI container for the ModTileEntity from the TileEntity with Inventory tutorial with a slot configuration similar to a dispenser or dropper. Therefore, we first create a new class which extends net.minecraft.inventory.Container. I named the class ContainerModTileEntity and placed it in the package com.bedrockminer.tutorial.guicontainer.

ContainerModTileEntity.java:
package com.bedrockminer.tutorial.guicontainer;

import net.minecraft.inventory.Container;

public class ContainerModTileEntity extends Container {
}

The class Container is abstract and requires the method canInteractWith to be implemented in our class. Additionally, we should override the constructor because there we are going to initialise our slots.

ContainerModTileEntity.java:
public class ContainerModTileEntity extends Container {

    public ContainerModTileEntity() {
    }

    @Override
    public boolean canInteractWith(EntityPlayer playerIn) {
        return false;
    }
}

The constructor should have two arguments, namely the player using the container and the TileEntity we are creating the Inventory for. If you want to keep your constructor more generic, you can use IInventory instead of your TileEntity class. However, then you cannot guarantee you'll get your TileEntity.


The method canInteractWith should simply call the method isUsableByPlayer of the TileEntity (or IInventory). Therefore, we need to save it in a variable.

ContainerModTileEntity.java:
public class ContainerModTileEntity extends Container {

    private ModTileEntity te;

    public ContainerModTileEntity(IInventory playerInv, ModTileEntity te) {
        this.te = te;
    }

    @Override
    public boolean canInteractWith(EntityPlayer playerIn) {
        return this.te.isUseableByPlayer(playerIn);
    }
}

Now, we're going to add our Inventory slots to the container. This is done in the constructor with the method addSlotForContainer. This method accepts a slot as argument. The constructor of Slot (net.minecraft.inventory.Slot) takes these arguments:

new Slot(IInventory inventory, int slot, int x, int y);

inventory is the inventory accessed by the slot.

slot is the slot ID inside the inventory. This is the ID passed to the inventory's methods.

x and y are the slot's coordinates in the GUI. They should match the slot's pixel position on the GUI's texture.

Every slot is assigned a unique ID when it's added to the container. The IDs start at 0 and increase with every new slot. They cannot be modified and only depend on the registration order.


This is the code I use for the dispenser-like inventory:

ContainerModTileEntity.java:
public ContainerModTileEntity(IInventory playerInv, ModTileEntity te) {
    this.te = te;

    // Tile Entity, Slot 0-8, Slot IDs 0-8
    for (int y = 0; y < 3; ++y) {
        for (int x = 0; x < 3; ++x) {
            this.addSlotToContainer(new Slot(te, x + y * 3, 62 + x * 18, 17 + y * 18));
        }
    }

    // Player Inventory, Slot 9-35, Slot IDs 9-35
    for (int y = 0; y < 3; ++y) {
        for (int x = 0; x < 9; ++x) {
            this.addSlotToContainer(new Slot(playerInv, x + y * 9 + 9, 8 + x * 18, 84 + y * 18));
        }
    }

    // Player Inventory, Slot 0-8, Slot IDs 36-44
    for (int x = 0; x < 9; ++x) {
        this.addSlotToContainer(new Slot(playerInv, x, 8 + x * 18, 142));
    }
}

The first for-construct adds our TileEntity's slots to the container. They are arranged in a 3*3 grid. The first one is located in 62 | 17, the next one 18 pixels further and so on.

The slot IDs within the TileEntity's inventory reach from 0 to 8 (calculated using the x and y coordinates). The generated slot IDs match these values because the slot ids are used in ascending order.


The next for-construct adds the player's main inventory. Here, the inventory IDs range from 9 to 35 because the hotbar is done separate. This is simply copied from another GUI. If the GUI's width is equal to vanilla GUIs (176 pixels) only the height needs to be adjusted.

The players hotbar is added last. Here, the inventory IDs range from 0 to 8 again.


This construction is a bit messy, so it's always a good idea to keep track of the slot's IDs. The best way is to write a comment which contains the information:

ContainerModTileEntity.java:
/*
 * SLOTS:
 * 
 * Tile Entity 0-8 ........ 0  - 8
 * Player Inventory 9-35 .. 9  - 35
 * Player Inventory 0-8 ... 36 - 44
 */

Keeping track of this information is also very useful in the next step where we define the behaviour on Shift-Right Click.

But first, I want to show you how our slot configuration looks like now:

GUI Size Analysis - Show

Shift - Right Click and transferStackInSlot

transferStackInSlot is the next method we need to override. This method is called when a Shift Right Click is performed on a slot in the container. This should move the complete stack to another slot in the container. However, this behaviour needs to be implemented on our own. Therefore, we override transferStackInSlot.

ContainerModTileEntity.java:
@Override
public ItemStack transferStackInSlot(EntityPlayer playerIn, int fromSlot) {
}

The method's content is basically a "frame" which is similar in every implementation and the transfer procedure itself.

The frame looks like this. You can copy it for every implementation of this method:

ContainerModTileEntity.java:
@Override
public ItemStack transferStackInSlot(EntityPlayer playerIn, int fromSlot) {
    ItemStack previous = null;
    Slot slot = (Slot) this.inventorySlots.get(fromSlot);

    if (slot != null && slot.getHasStack()) {
        ItemStack current = slot.getStack();
        previous = current.copy();

        // [...] Custom behaviour

        if (current.stackSize == 0)
            slot.putStack((ItemStack) null);
        else
            slot.onSlotChanged();

        if (current.stackSize == previous.stackSize)
            return null;
        slot.onPickupFromSlot(playerIn, current);
    }
    return previous;
}

Our custom implementation needs to be placed at the comment's position.

We differentiate between different slots and run the mergeItemStack method with different arguments. This method does the real merging process.

 

mergeItemStack(ItemStack stack, int start, int end, boolean backwards)

stack is the ItemStack that should be merged.

start is the slot index where the merge destination range starts (inclusive).

end is the slot index where the merge destination range ends (exclusive!).

If backwards is true, the search for a valid destination is run backwards.

The method returns true if merging was successful.

The method does not check if the Item can be placed in the slot (isItemValidForSlot), remember this when setting up the ranges.


The behaviour I want to achieve is the following:

  • Shift Right Clicks in the TileEntity's inventory merge the stack to the player's inventory.
  • Shift Right Clicks in the player's inventory merge the stack to the TileEntity's inventory.

It's possible to do more complicated merges, but this is enough for now.


The code used here is the following:

ContainerModTileEntity.java:
@Override
public ItemStack transferStackInSlot(EntityPlayer playerIn, int fromSlot) {
    ItemStack previous = null;
    Slot slot = (Slot) this.inventorySlots.get(fromSlot);

    if (slot != null && slot.getHasStack()) {
        ItemStack current = slot.getStack();
        previous = current.copy();

        if (fromSlot < 9) {
            // From TE Inventory to Player Inventory
            if (!this.mergeItemStack(current, 9, 45, true))
                return null;
        } else {
            // From Player Inventory to TE Inventory
            if (!this.mergeItemStack(current, 0, 9, false))
                return null;
        }

        if (current.stackSize == 0)
            slot.putStack((ItemStack) null);
        else
            slot.onSlotChanged();

        if (current.stackSize == previous.stackSize)
            return null;
        slot.onPickupFromSlot(playerIn, current);
    }
    return previous;
}

Remember the slot configuration I showed above:

  • Tile Entity 0-8: 0-8
  • Player Inventory 9-35: 9-35
  • Player Inventory 0-8: 36-44

The Tile Entity slots have an ID less than 9. That's what we're looking for in the if statement. If we have a TileEntity stack, we call the mergeItemStack method with these arguments:

mergeItemStack(current, 9, 45, true);

This method now tries to place the stack current in one of the slots from 9 to 44 (45 is not included). The checks if the stack can be placed in the slot start at 44 and go down to 9 because the last argument is true. Normally (in vanilla Minecraft) the argument is only true if the stack is merged into the main inventory. I'm actually not sure why they even implemented this, but I just copied the behaviour.

If the merging fails (return: false) the transferStackInSlot method must return null. It's important that we add an if-check around every merge method.

If the shift right click was performed on an other stack, it is merged from the player's inventory to the TileEntity's inventory as you can see above.

It is possible to add several different merging ways, you can even change the behaviour based on the item that should be merged.


Now, our inventory container is finished! We now need to create a GUI that can work with a container.

Allowing less items than 64 in a slot

Sometimes, you may want to have less than 64 items in a certain slot.

To do this, you need to create a new slot class. I just drop the code here, you should understand this on your own.

SingleItemSlot.java:
public class SingleItemSlot extends Slot {

    public SingleItemSlot(IInventory inventory, int index, int xPosition, int yPosition) {
        super(inventory, index, xPosition, yPosition);
    }

    @Override
    public int getSlotStackLimit() {
        return 1;
    }
}

If you want to use the mergeItemStack method together with those slots, you need to use the following advanced version of it, because the normal one doesn't care for stack limits. Just copy this one into your container code:

Container_.java:
@Override
protected boolean mergeItemStack(ItemStack stack, int startIndex, int endIndex, boolean useEndIndex) {
    boolean success = false;
    int index = startIndex;

    if (useEndIndex)
        index = endIndex - 1;

    Slot slot;
    ItemStack stackinslot;

    if (stack.isStackable()) {
        while (stack.stackSize > 0 && (!useEndIndex && index < endIndex || useEndIndex && index >= startIndex)) {
            slot = (Slot) this.inventorySlots.get(index);
            stackinslot = slot.getStack();

            if (stackinslot != null && stackinslot.getItem() == stack.getItem() && (!stack.getHasSubtypes() || stack.getMetadata() == stackinslot.getMetadata()) && ItemStack.areItemStackTagsEqual(stack, stackinslot)) {
                int l = stackinslot.stackSize + stack.stackSize;
                int maxsize = Math.min(stack.getMaxStackSize(), slot.getItemStackLimit(stack));

                if (l <= maxsize) {
                    stack.stackSize = 0;
                    stackinslot.stackSize = l;
                    slot.onSlotChanged();
                    success = true;
                } else if (stackinslot.stackSize < maxsize) {
                    stack.stackSize -= stack.getMaxStackSize() - stackinslot.stackSize;
                    stackinslot.stackSize = stack.getMaxStackSize();
                    slot.onSlotChanged();
                    success = true;
                }
            }

            if (useEndIndex) {
                --index;
            } else {
                ++index;
            }
        }
    }

    if (stack.stackSize > 0) {
        if (useEndIndex) {
            index = endIndex - 1;
        } else {
            index = startIndex;
        }

        while (!useEndIndex && index < endIndex || useEndIndex && index >= startIndex && stack.stackSize > 0) {
            slot = (Slot) this.inventorySlots.get(index);
            stackinslot = slot.getStack();

            // Forge: Make sure to respect isItemValid in the slot.
            if (stackinslot == null && slot.isItemValid(stack)) {
                if (stack.stackSize < slot.getItemStackLimit(stack)) {
                    slot.putStack(stack.copy());
                    stack.stackSize = 0;
                    success = true;
                    break;
                } else {
                    ItemStack newstack = stack.copy();
                    newstack.stackSize = slot.getItemStackLimit(stack);
                    slot.putStack(newstack);
                    stack.stackSize -= slot.getItemStackLimit(stack);
                    success = true;
                }
            }

            if (useEndIndex) {
                --index;
            } else {
                ++index;
            }
        }
    }

    return success;
}

The real GUI

Just one thing: This tutorial does not show you how to paint a GUI. This is part of another tutorial because there are multiple different ways. Here I'll just show how to add the GUI and how to get it to work.


The GUI class is where the real magic happens. Here, everything important is drawn on the screen. This means however, that the GUI class is not needed on the server side. Thus, we place it in the client package, in my case com.bedrockminer.tutorial.client.gui. I called the GUI class GuiModTileEntity. It's superclass is GuiContainer.

GuiContainer requires a method and the constructor to be overridden:

GuiModTileEntity.java:
package com.bedrockminer.tutorial.client.gui;

import net.minecraft.client.gui.inventory.GuiContainer;

import com.bedrockminer.tutorial.guicontainer.ContainerModTileEntity;
import com.bedrockminer.tutorial.tileentity.ModTileEntity;

public class GuiModTileEntity extends GuiContainer {

    public GuiModTileEntity(IInventory playerInv, ModTileEntity te) {
        super(new ContainerModTileEntity(playerInv, te));

        this.xSize = 176;
        this.ySize = 166;
    }

    @Override
    protected void drawGuiContainerBackgroundLayer(float partialTicks, int mouseX, int mouseY) {
    }
}

As you can see, the super constructor needs a container. This container is our ContainerModTileEntity. In the constructor we also set the GUI's size. Here, I set it to 176*166. This is the default size which is already set before constructing, but I prefer to set it again in my custom class, just to clarify the size.

The other method is used to draw the GUI (as I said, different tutorial). This class will be changed a little bit when we finally add the rendering code to it.

Adding GUI to the GUI Handler

To finish up we need to add the GUI to the GUI Handler we created in the previous tutorial. There, I already showed how to add a GUI, so we're just adding the corresponding constructor arguments to make it work:

GuiHandler.java:
public static final int MOD_TILE_ENTITY_GUI = 0;

@Override
public Object getServerGuiElement(int ID, EntityPlayer player, World world, int x, int y, int z) {
    if (ID == MOD_TILE_ENTITY_GUI)
        return new ContainerModTileEntity(player.inventory, (ModTileEntity) world.getTileEntity(new BlockPos(x, y, z)));

    return null;
}

@Override
public Object getClientGuiElement(int ID, EntityPlayer player, World world, int x, int y, int z) {
    if (ID == MOD_TILE_ENTITY_GUI)
        return new GuiModTileEntity(new ContainerModTileEntity(player.inventory, (ModTileEntity) world.getTileEntity(new BlockPos(x, y, z))));

    return null;
}

Now we can open our GUI already. It won't look like much, because we have no texture applied to it, but we can see the ItemStacks in the inventory slots.

To open the GUI when right-clicking the TileEntity we add this method to the TileEntity's block class:

ModBlockTileEntity.java:
@Override
public boolean onBlockActivated(World world, BlockPos pos, IBlockState state, EntityPlayer player, EnumFacing side, float hitX, float hitY, float hitZ) {
    if (!world.isRemote) {
        player.openGui(Main.instance, ModGuiHandler.MOD_TILE_ENTITY_GUI, world, pos.getX(), pos.getY(), pos.getZ());
    }
    return true;
}

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.