Containers and GUIs

From Minecraft Forge
Jump to: navigation, search

How-To Icon.png

This is a How-To guide or Tutorial detailing a practice or process for Minecraft Forge or related software.

Information Icon.png

This page was made with Minecraft Forge 6.5.0.467. It might not work with other versions.

Warning Icon.png

This page is marked as Outdated.

It was made for older content and may cause problems.

Please improve this article if you can.


Contents

Prerequisites

  • An understanding of java
  • An understanding of vanilla minecraft and a basic understanding of forge
  • A Block
  • A TileEntity for said block that implements IInventory
  • A GUI image (see the /gui/ folder in minecraft.jar for references)

In this tutorial, we will create a block that holds a stack of items (a "tiny chest").

Example TileEntity and Block classes (for reference)

TileEntityTiny.java

package tco.tiny;

import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.inventory.IInventory;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
import net.minecraft.tileentity.TileEntity;

public class TileEntityTiny extends TileEntity implements IInventory {

        private ItemStack[] inv;

        public TileEntityTiny(){
                inv = new ItemStack[9];
        }
       
        @Override
        public int getSizeInventory() {
                return inv.length;
        }

        @Override
        public ItemStack getStackInSlot(int slot) {
                return inv[slot];
        }
       
        @Override
        public void setInventorySlotContents(int slot, ItemStack stack) {
                inv[slot] = stack;
                if (stack != null && stack.stackSize > getInventoryStackLimit()) {
                        stack.stackSize = getInventoryStackLimit();
                }              
        }

        @Override
        public ItemStack decrStackSize(int slot, int amt) {
                ItemStack stack = getStackInSlot(slot);
                if (stack != null) {
                        if (stack.stackSize <= amt) {
                                setInventorySlotContents(slot, null);
                        } else {
                                stack = stack.splitStack(amt);
                                if (stack.stackSize == 0) {
                                        setInventorySlotContents(slot, null);
                                }
                        }
                }
                return stack;
        }

        @Override
        public ItemStack getStackInSlotOnClosing(int slot) {
                ItemStack stack = getStackInSlot(slot);
                if (stack != null) {
                        setInventorySlotContents(slot, null);
                }
                return stack;
        }
       
        @Override
        public int getInventoryStackLimit() {
                return 64;
        }

        @Override
        public boolean isUseableByPlayer(EntityPlayer player) {
                return worldObj.getBlockTileEntity(xCoord, yCoord, zCoord) == this &&
                player.getDistanceSq(xCoord + 0.5, yCoord + 0.5, zCoord + 0.5) < 64;
        }

        @Override
        public void openChest() {}

        @Override
        public void closeChest() {}
       
        @Override
        public void readFromNBT(NBTTagCompound tagCompound) {
                super.readFromNBT(tagCompound);
               
                NBTTagList tagList = tagCompound.getTagList("Inventory");
                for (int i = 0; i < tagList.tagCount(); i++) {
                        NBTTagCompound tag = (NBTTagCompound) tagList.tagAt(i);
                        byte slot = tag.getByte("Slot");
                        if (slot >= 0 && slot < inv.length) {
                                inv[slot] = ItemStack.loadItemStackFromNBT(tag);
                        }
                }
        }

        @Override
        public void writeToNBT(NBTTagCompound tagCompound) {
                super.writeToNBT(tagCompound);
                               
                NBTTagList itemList = new NBTTagList();
                for (int i = 0; i < inv.length; i++) {
                        ItemStack stack = inv[i];
                        if (stack != null) {
                                NBTTagCompound tag = new NBTTagCompound();
                                tag.setByte("Slot", (byte) i);
                                stack.writeToNBT(tag);
                                itemList.appendTag(tag);
                        }
                }
                tagCompound.setTag("Inventory", itemList);
        }

                @Override
                public String getInvName() {
                        return "tco.tileentitytiny";
                }
}

BlockTiny.java

package tco.tiny;

import java.util.Random;

import net.minecraft.block.BlockContainer;
import net.minecraft.block.material.Material;
import net.minecraft.creativetab.CreativeTabs;
import net.minecraft.entity.item.EntityItem;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.inventory.IInventory;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.world.World;

public class BlockTiny extends BlockContainer {

        protected BlockTiny (int id) {
                super(id, Material.wood);
                setHardness(2.0F);
                setResistance(5.0F);
                setBlockName("blockTiny");
                setCreativeTab(CreativeTabs.tabDecorations);
        }

        @Override
        public boolean onBlockActivated(World world, int x, int y, int z,
                        EntityPlayer player, int metadata, float what, float these, float are) {
                TileEntity tileEntity = world.getBlockTileEntity(x, y, z);
                if (tileEntity == null || player.isSneaking()) {
                        return false;
                }
        //code to open gui explained later
        player.openGui(Tiny.instance, 0, world, x, y, z);
                return true;
        }

        @Override
        public void breakBlock(World world, int x, int y, int z, int par5, int par6) {
                dropItems(world, x, y, z);
                super.breakBlock(world, x, y, z, par5, par6);
        }

        private void dropItems(World world, int x, int y, int z){
                Random rand = new Random();

                TileEntity tileEntity = world.getBlockTileEntity(x, y, z);
                if (!(tileEntity instanceof IInventory)) {
                        return;
                }
                IInventory inventory = (IInventory) tileEntity;

                for (int i = 0; i < inventory.getSizeInventory(); i++) {
                        ItemStack item = inventory.getStackInSlot(i);

                        if (item != null && item.stackSize > 0) {
                                float rx = rand.nextFloat() * 0.8F + 0.1F;
                                float ry = rand.nextFloat() * 0.8F + 0.1F;
                                float rz = rand.nextFloat() * 0.8F + 0.1F;

                                EntityItem entityItem = new EntityItem(world,
                                                x + rx, y + ry, z + rz,
                                                new ItemStack(item.itemID, item.stackSize, item.getItemDamage()));

                                if (item.hasTagCompound()) {
                                        entityItem.getEntityItem().setTagCompound((NBTTagCompound) item.getTagCompound().copy());
                                }

                                float factor = 0.05F;
                                entityItem.motionX = rand.nextGaussian() * factor;
                                entityItem.motionY = rand.nextGaussian() * factor + 0.2F;
                                entityItem.motionZ = rand.nextGaussian() * factor;
                                world.spawnEntityInWorld(entityItem);
                                item.stackSize = 0;
                        }
                }
        }

        @Override
        public TileEntity createNewTileEntity(World world) {
                return new TileEntityTiny();
        }

}

Creating the container

A container is what connects the inventories of the player and tileentity to the GUI. In the class definition, we will define the position on-screen and contents of each slot. We will also define Shift-click behaviour for the player's convenience. Take note of:

  • Slot(IInventory, int, int, int)
  • addSlotToContainer(Slot)
  • transferStackInSlot(EntityPlayer, int)
package tco.tiny;

import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.InventoryPlayer;
import net.minecraft.inventory.Container;
import net.minecraft.inventory.Slot;
import net.minecraft.item.ItemStack;

public class ContainerTiny extends Container {

        protected TileEntityTiny tileEntity;

        public ContainerTiny (InventoryPlayer inventoryPlayer, TileEntityTiny te){
                tileEntity = te;

                //the Slot constructor takes the IInventory and the slot number in that it binds to
                //and the x-y coordinates it resides on-screen
                for (int i = 0; i < 3; i++) {
                        for (int j = 0; j < 3; j++) {
                                addSlotToContainer(new Slot(tileEntity, j + i * 3, 62 + j * 18, 17 + i * 18));
                        }
                }

                //commonly used vanilla code that adds the player's inventory
                bindPlayerInventory(inventoryPlayer);
        }

        @Override
        public boolean canInteractWith(EntityPlayer player) {
                return tileEntity.isUseableByPlayer(player);
        }


        protected void bindPlayerInventory(InventoryPlayer inventoryPlayer) {
                for (int i = 0; i < 3; i++) {
                        for (int j = 0; j < 9; j++) {
                                addSlotToContainer(new Slot(inventoryPlayer, j + i * 9 + 9,
                                                8 + j * 18, 84 + i * 18));
                        }
                }

                for (int i = 0; i < 9; i++) {
                        addSlotToContainer(new Slot(inventoryPlayer, i, 8 + i * 18, 142));
                }
        }

        @Override
        public ItemStack transferStackInSlot(EntityPlayer player, int slot) {
                ItemStack stack = null;
                Slot slotObject = (Slot) inventorySlots.get(slot);

                //null checks and checks if the item can be stacked (maxStackSize > 1)
                if (slotObject != null && slotObject.getHasStack()) {
                        ItemStack stackInSlot = slotObject.getStack();
                        stack = stackInSlot.copy();

                        //merges the item into player inventory since its in the tileEntity
                        if (slot < 9) {
                                if (!this.mergeItemStack(stackInSlot, 0, 35, true)) {
                                        return null;
                                }
                        }
                        //places it into the tileEntity is possible since its in the player inventory
                        else if (!this.mergeItemStack(stackInSlot, 0, 9, false)) {
                                return null;
                        }

                        if (stackInSlot.stackSize == 0) {
                                slotObject.putStack(null);
                        } else {
                                slotObject.onSlotChanged();
                        }

                        if (stackInSlot.stackSize == stack.stackSize) {
                                return null;
                        }
                        slotObject.onPickupFromSlot(player, stackInSlot);
                }
                return stack;
        }
}

Now we add the transferStackInSlot method. Without this method properly overridden, Shift-clicking will almost always cause massive errors. This bit of code is quite confusing. There are 2 important parts of the code we must understand: the "slot" and the method mergeItemStack.

First, the "slot" parameter is the slot number of the Container. Each call to the addSlotToContainer method increases the number of slots in the Container by 1. They can be accessed in the order that we added them. i.e. slot 0 would be the single inventory slot of TileEntityTiny.

Secondly, here's my (guessed) documentation of the mergeItemStack method:

/**
 * @param itemStack ItemStack to merge into inventory
 * @param start minimum slot to attempt fill
 * @param end maximum slot to attempt fill
 * @param backwards go backwards
 * @return true if stacks merged successfully
 */

public boolean mergeItemStack(itemStack, start, end, backwards)

Basically, this method will attempt to place the given itemStack within the Container slots between start and end. It will try to fill from end to start if backwards=true, and vice versa. You can use the transferStackInSlot method for "intelligent" shift-clicking, like putting smeltable items in the top slot of a furnance and fuel in the bottom slot.

Drawing the GUI

Now we will create the Gui for the Container. We won't mess around that much with OpenGL because it is big and scary.

package tco.tiny;

import net.minecraft.client.gui.inventory.GuiContainer;
import net.minecraft.entity.player.InventoryPlayer;
import net.minecraft.util.StatCollector;

import org.lwjgl.opengl.GL11;

public class GuiTiny extends GuiContainer {

        public GuiTiny (InventoryPlayer inventoryPlayer,
                        TileEntityTiny tileEntity) {
                //the container is instanciated and passed to the superclass for handling
                super(new ContainerTiny(inventoryPlayer, tileEntity));
        }

        @Override
        protected void drawGuiContainerForegroundLayer(int param1, int param2) {
                //draw text and stuff here
                //the parameters for drawString are: string, x, y, color
                fontRenderer.drawString("Tiny", 8, 6, 4210752);
                //draws "Inventory" or your regional equivalent
                fontRenderer.drawString(StatCollector.translateToLocal("container.inventory"), 8, ySize - 96 + 2, 4210752);
        }

        @Override
        protected void drawGuiContainerBackgroundLayer(float par1, int par2,
                        int par3) {
                //draw your Gui here, only thing you need to change is the path
                int texture = mc.renderEngine.getTexture("/gui/trap.png");
                GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F);
                this.mc.renderEngine.bindTexture(texture);
                int x = (width - xSize) / 2;
                int y = (height - ySize) / 2;
                this.drawTexturedModalRect(x, y, 0, 0, xSize, ySize);
        }

}

Opening the GUI

Now we can open the Gui. First, you need a class that implements IGuiHandler. There are 2 methods you need: getClientGuiElement and getServerGuiElement. Note that the client returns a Gui while the server returns a Container

package tco.tiny;

import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.world.World;
import cpw.mods.fml.common.network.IGuiHandler;

public class GuiHandler implements IGuiHandler {
        //returns an instance of the Container you made earlier
        @Override
        public Object getServerGuiElement(int id, EntityPlayer player, World world,
                        int x, int y, int z) {
                TileEntity tileEntity = world.getBlockTileEntity(x, y, z);
                if(tileEntity instanceof TileEntityTiny){
                        return new ContainerTiny(player.inventory, (TileEntityTiny) tileEntity);
                }
                return null;
        }

        //returns an instance of the Gui you made earlier
        @Override
        public Object getClientGuiElement(int id, EntityPlayer player, World world,
                        int x, int y, int z) {
                TileEntity tileEntity = world.getBlockTileEntity(x, y, z);
                if(tileEntity instanceof TileEntityTiny){
                        return new GuiTiny(player.inventory, (TileEntityTiny) tileEntity);
                }
                return null;

        }
}

Then, register the gui handler in your mod file.

        //"this" is an instance of the @Mod
        NetworkRegistry.instance().registerGuiHandler(this, new GuiHandler());

Now we just need to open the Gui. Heres the completed player.openGui:

        player.openGui(Tiny.instance, 0, world, x, y, z);

Syncing Progress Bars / Integer Data

  • Look at ContainerFurnace for info on doing this.*

Buttons

Sending button clicks to the server is more complicated. We need to send a packet with the necessary info and handle that packet in the PacketHandler. Here's an example of initGui and actionPerformed in the gui class. Note that a corresponding packet handler is required

        @Override
        public void initGui() {
                super.initGui();
                //make buttons
                                        //id, x, y, width, height, text
                controlList.add(new GuiButton(1, 10, 52, 20, 20, "+"));
                controlList.add(new GuiButton(2, 40, 72, 20, 20, "-"));
        }

        protected void actionPerformed(GuiButton guibutton) {
                //id is the id you give your button
                switch(guibutton.id) {
                case 1:
                        i += 1;
                        break;
                case 2:
                        i -= 1;
                }
                //Packet code here
                //PacketDispatcher.sendPacketToServer(packet); //send packet
        }

Multiple Guis

You may have noticed the id parameter for the methods in IGuiHandler. That is your GUI Id, which is required for multiple guis within one mod. Simply add switch or if/else statements in the IGuiHandler methods to check the id and act accordingly. To open a gui with a specific id, use:

        //id = your gui id
        player.openGui(YourMod.instance, id, world, x, y, z);

Here's the my test @Mod file. (testing only)

package tco.tiny;

import net.minecraft.block.Block;
import net.minecraft.item.ItemBlock;
import cpw.mods.fml.common.Mod;
import cpw.mods.fml.common.Mod.*;
import cpw.mods.fml.common.event.FMLInitializationEvent;
import cpw.mods.fml.common.network.NetworkMod;
import cpw.mods.fml.common.network.NetworkRegistry;
import cpw.mods.fml.common.registry.GameRegistry;

@Mod(modid="Tiny", name="Tiny", version="1.0")
@NetworkMod(clientSideRequired=true)
public class Tiny {

        @Instance("Tiny")
        public static Tiny instance;
       
        public static Block blockTiny;

        @EventHandler
        public void init(FMLInitializationEvent event) {
                blockTiny = new BlockTiny(1337);
                GameRegistry.registerBlock(blockTiny, ItemBlock.class, "blockTiny");
                GameRegistry.registerTileEntity(TileEntityTiny.class, "containerTiny");
                NetworkRegistry.instance().registerGuiHandler(this, new GuiHandler());
        }

}
Personal tools
Namespaces
Variants
Actions
Navigation
tutorials
Toolbox