Custom Storage Block

I feel there’s a huge lack of tutorials that covers this in an easy-to-read way. I myself really struggled to get my first custom storage code running. After lots of trial and errors, I finally did it, and I decided to share it with you guys.

I will make a new block, it has the same properties as a chest, except it has only 15 slots. I will be sharing my GUI texture, but I won’t be giving out the actual block textures, to give as much wiggle room as possible, for you to actually make your own block. Let’s start.

First, create a block. Mine won’t have any textures like I said before, but you should put your own textures in your project. I will also add some extra code which I’ll explain in a minute,

public class BlockStorage extends BlockContainer
{
    private static final String name = "storage";

    private final Random rand = new Random();

    public BlockStorage()
    {
        super(Material.wood);
        GameRegistry.registerBlock(this, name);
        setBlockName(name);
        setCreativeTab(CreativeTabs.tabDecorations);
    }

    @Override
    public boolean onBlockActivated(World world, int x, int y, int z, EntityPlayer player, int side, float lx, float ly, float lz)
    {
        if (world.isRemote) return true;

        TileEntity te = world.getTileEntity(x, y, z);
        if (te != null && te instanceof TileEntityStorage)
        {
            player.openGui(StorageMod.instance, 0, world, x, y, z);
            return true;
        }
        return false;
    }

    @Override
    public void breakBlock(World world, int x, int y, int z, Block block, int par6)
    {
        if (world.isRemote) return;

        ArrayList drops = new ArrayList();

        TileEntity teRaw = world.getTileEntity(x, y, z);

        if (teRaw != null && teRaw instanceof TileEntityStorage)
        {
            TileEntityStorage te = (TileEntityStorage) teRaw;

            for (int i = 0; i < te.getSizeInventory(); i++)
            {
                ItemStack stack = te.getStackInSlot(i);

                if (stack != null) drops.add(stack.copy());
            }
        }

        for (int i = 0;i < drops.size();i++)
        {
            EntityItem item = new EntityItem(world, x + 0.5, y + 0.5, z + 0.5, drops.get(i));
            item.setVelocity((rand.nextDouble() - 0.5) * 0.25, rand.nextDouble() * 0.5 * 0.25, (rand.nextDouble() - 0.5) * 0.25);
            world.spawnEntityInWorld(item);
        }
    }

    public TileEntity createNewTileEntity(World world, int par2)
    {
        return new TileEntityStorage();
    }
}

Here you can see I extended to BlockContainer and added createNewTileEntity(). I will create the tile entity class after this. I’ve added a few extra methods, onBlockActivated and breakBlock.

onBlockActivated() serves to tell Minecraft to open the GUI on right clicking the block, “activating” it. In it is basically a few checks and then finally an openGui call. It has a few parameters, the first is the mod instance, mine is StorageMod.instance, this should be the instance under the @Mod.Instance or @Instance annotation in your main mod class. The next parameter is a zero (0), it is your Gui ID. It is mod-specific so you won’t have to worry it clashing with other mods or vanilla Minecraft. This has to be the same throughout the block GUI classes, so remember this. Or if you want, you can enumerate it, and use ordinal() to get an integer. But I won’t be covering that in this tutorial.

breakBlock() gives the ability to drop the items inside the block if you break it, so you don’t lose items. It’s mostly copy and paste from vanilla code.

You will get a few errors in that code, but that’s because we have no TileEntityStorage class yet. Now it’s time to make the Tile Entity class.

public class TileEntityStorage extends TileEntity implements IInventory
{
    private ItemStack[] items = new ItemStack[15];

    public int getSizeInventory()
    {
        return items.length;
    }

    public ItemStack getStackInSlot(int slot)
    {
        return items[slot];
    }

    public ItemStack decrStackSize(int slot, int amount)
    {
        if (items[slot] != null)
        {
            ItemStack itemstack;

            if (items[slot].stackSize == amount)
            {
                itemstack = items[slot];
                items[slot] = null;
                markDirty();
                return itemstack;
            }
            else
            {
                itemstack = items[slot].splitStack(amount);
                if (items[slot].stackSize == 0) items[slot] = null;
                markDirty();
                return itemstack;
            }
        }
        else
        {
            return null;
        }
    }

    public ItemStack getStackInSlotOnClosing(int slot)
    {
        if (items[slot] != null)
        {
            ItemStack itemstack = items[slot];
            items[slot] = null;
            return itemstack;
        }
        else
        {
            return null;
        }
    }

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

        markDirty();
    }

    public String getInventoryName()
    {
        return "container.storage";
    }

    public boolean hasCustomInventoryName()
    {
        return false;
    }

    @Override
    public void readFromNBT(NBTTagCompound nbt)
    {
        super.readFromNBT(nbt);
        NBTTagList list = nbt.getTagList("Items", Constants.NBT.TAG_COMPOUND);
        items = new ItemStack[getSizeInventory()];

        for (int i = 0; i < list.tagCount(); ++i) { NBTTagCompound comp = list.getCompoundTagAt(i); int j = comp.getByte("Slot") & 255; if (j >= 0 && j < items.length)
            {
                items[j] = ItemStack.loadItemStackFromNBT(comp);
            }
        }
    }

    @Override
    public void writeToNBT(NBTTagCompound nbt)
    {
        super.writeToNBT(nbt);
        NBTTagList list = new NBTTagList();

        for (int i = 0; i < items.length; ++i)
        {
            if (items[i] != null)
            {
                NBTTagCompound comp = new NBTTagCompound();
                comp.setByte("Slot", (byte)i);
                items[i].writeToNBT(comp);
                list.appendTag(comp);
            }
        }

        nbt.setTag("Items", list);
    }

    public int getInventoryStackLimit()
    {
        return 64;
    }

    public boolean isUseableByPlayer(EntityPlayer player)
    {
        return worldObj.getTileEntity(xCoord, yCoord, zCoord) != this ? false : player.getDistanceSq((double)xCoord + 0.5D, (double)yCoord + 0.5D, (double)zCoord + 0.5D) <= 64.0D;
    }

    public void openInventory() {}

    public void closeInventory() {}

    public boolean isItemValidForSlot(int slot, ItemStack stack)
    {
        return true;
    }
}

Most of these code are just copy and pasted from vanilla code, with some changes. I haven’t implemented custom inventory names yet, that may come in another tutorial. For now, the gui name will always be “container.storage”, so you can add that in your en_US.lang file.

Now, let’s start messing with GUI stuff. But before making the actual GUI, we need a class for our mod to handle GUIs, and forge provides an interface for that called IGuiHandler.

public class GuiHandler implements IGuiHandler
{
    public Object getServerGuiElement(int ID, EntityPlayer player, World world, int x, int y, int z)
    {
        TileEntity te = world.getTileEntity(x, y, z);

        if (te != null)
        {
            if (ID == 0) //Gui ID for storage block, will add later
            {
                return new ContainerStorage((TileEntityStorage)te, player);
            }
        }
        return null;
    }

    public Object getClientGuiElement(int ID, EntityPlayer player, World world, int x, int y, int z)
    {
        TileEntity te = world.getTileEntity(x, y, z);

        if (te != null)
        {
            if (ID == 0) //Gui ID for storage block, will add later
            {
                return new GuiStorage((TileEntityStorage)te, player);
            }
        }
        return null;
    }
}

That’s our GuiHandler class. You need to register this in your CommonProxy with this line:

NetworkRegistry.INSTANCE.registerGuiHandler(StorageMod.instance, new GuiHandler());

Also, note that the more GUIs you add, you also have to register it in your GuiHandler class, with the respective Gui IDs.

Now, let’s make the storage blocks’ GUI, finally.

public class GuiStorage extends GuiContainer
{
    private ResourceLocation texture = new ResourceLocation(StorageMod.MODID, "textures/gui/container/storage.png");

    private InventoryPlayer inventory;
    private TileEntityStorage te;

    public GuiStorage(TileEntityStorage te, EntityPlayer player)
    {
        super(new ContainerStorage(te, player));
        inventory = player.inventory;
        this.te = te;
    }

    @Override
    protected void drawGuiContainerBackgroundLayer(float par1, int par2, int par3)
    {
        Minecraft.getMinecraft().renderEngine.bindTexture(texture);

        GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F);

        int x = (width - xSize) / 2;
        int y = (height - ySize) / 2;

        drawTexturedModalRect(x, y, 0, 0, xSize, ySize);
    }

    @Override
    protected void drawGuiContainerForegroundLayer(int par1, int par2)
    {
        fontRendererObj.drawString(I18n.format(te.getInventoryName()), (xSize / 2) - (fontRendererObj.getStringWidth(I18n.format(te.getInventoryName())) / 2), 6, 4210752, false);
        fontRendererObj.drawString(I18n.format(inventory.getInventoryName()), 8, ySize - 96 + 2, 4210752);
    }
}

drawGuiContainerBackgroundLayer() draws your actual background, so only one rectangle is needed. drawGuiContainerForegroundLayer() draws any other stuff, like text. The two lines in that method is adding the gui name and “Inventory” on the player inventory.

If you want the GUI texture, it’s this:

Now, let’s make the container itself, where it registers the slots of the Gui.

public class ContainerStorage extends Container
{
    private TileEntityStorage te;

    private int slotID = 0;

    public ContainerStorage(TileEntityStorage te, EntityPlayer player)
    {
        this.te = te;

        //Storage
        for (int i = 0; i < 3; i++)
        {
            for (int j = 0; j < 5; j++)
            {
                addSlotToContainer(new Slot(te, slotID++, 44 + j * 18, 17 + i * 18));
            }
        }

        //Inventory
        for (int i = 0; i < 3; i++)
        {
            for (int j = 0; j < 9; j++)
            {
                addSlotToContainer(new Slot(player.inventory, j + i * 9 + 9, 8 + j * 18, 84 + i * 18));
            }
        }
        // Hotbar
        for (int i = 0; i < 9; i++)
        {
            addSlotToContainer(new Slot(player.inventory, i, 8 + i * 18, 142));
        }
    }

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

        if (slot != null && slot.getHasStack())
        {
            ItemStack stackInSlot = slot.getStack();
            stack = stackInSlot.copy();

            if (slotRaw < 3 * 9)
            {
                if (!mergeItemStack(stackInSlot, 3 * 9, inventorySlots.size(), true))
                {
                    return null;
                }
            }
            else if (!mergeItemStack(stackInSlot, 0, 3 * 9, false))
            {
                return null;
            }

            if (stackInSlot.stackSize == 0)
            {
                slot.putStack((ItemStack)null);
            }
            else
            {
                slot.onSlotChanged();
            }
        }
        return stack;
    }

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

Again, as usual, these are mostly copy and pasted code from vanilla, with modifications. below the //Storage comment is where I register the slots, mine happen to be 5 wide and 3 tall. If you want a different size, you can modify the numbers in the for-loop.

And we are done! This is a quite long tutorial, so if anyone notices any incorrect or incomplete info, please add a comment below!

Advertisements

20 thoughts on “Custom Storage Block

  1. jawser78 May 2, 2015 / 3:37 AM

    at the block class,
    item.setVelocity((rand.nextDouble() – 0.5) * 0.25, rand.nextDouble() * 0.5 * 0.25, (rand.nextDouble() – 0.5) * 0.25);
    I get a error at rand.
    At first I thought it was Random, but it didnt work. How do I fix this?

    Like

    • Emx2000 May 2, 2015 / 1:26 PM

      item.setVelocity() has three double parameters. If that for any reason doesn’t work for you, you can change that with any custom values 🙂 This one is copied straight from vanilla code, so I can’t see why that wouldn’t work.

      Like

  2. DjairoH May 18, 2015 / 10:49 PM

    i get this: he constructor EntityItem(World, double, double, double, Object) is undefined

    here:
    EntityItem item = new EntityItem(world, x + 0.5, y + 0.5, z + 0.5, drops.get(i));

    after importing Entityitem

    Like

    • Emx2000 May 20, 2015 / 10:48 PM

      Check my reply at MCForums 🙂

      Like

  3. Jwwood13 May 22, 2015 / 5:00 AM

    My texture for the block will not appear.
    I added this to the BlockStorage class: setBlockTextureName(StorageMod.MODID+”:”+name);
    Then i put my texture, called name.png or “storage.png”, into the assets.modid.textures.blocks folder.
    I have registered the block in my ModBlocks class.

    Like

    • Emx2000 May 25, 2015 / 5:11 PM

      please reply with logs via pastebin.com

      Like

  4. Jwwood13 May 22, 2015 / 5:02 AM

    Also could you add a tutorial on custom mobs?

    Like

  5. ItsLurq July 15, 2015 / 8:12 PM

    Hey, nice tutorial, any reason why the items dont load back into the block when leaving and joining again?
    Thanks

    Like

    • ItsLurq July 16, 2015 / 9:31 AM

      Do you think you could also do a tutorial for a block placing other blocks and such?

      Like

      • Emx2000 July 17, 2015 / 12:59 AM

        Do you mean a block that when placed creates structures? I may do that in the future.

        Like

    • Emx2000 July 17, 2015 / 1:00 AM

      Make sure you have overridden readFromNBT and writeToNBT correctly, as well as adding super() calls. On, my side, this works fine whatsoever.

      Like

      • ItsLurq July 17, 2015 / 10:52 AM

        Thank you 🙂 Will try this now.

        And I was hoping for a block when placed, and blocks placed inside, it would place blocks in a line to a certain point (placed marker)

        Like

  6. Kevin B August 4, 2015 / 1:20 AM

    @emx2000 is there any way you can make a tutorial on multiblock structures?

    Like

    • Emx2000 August 15, 2015 / 12:01 PM

      I have not yet mastered multiblock structures yet, but I might do it in the future. I can’t say when, though.

      Like

  7. HashtagShell December 11, 2015 / 2:44 AM

    This blog still active? I really like the way you explain things. This just needs more content.

    Like

      • HashtagShell January 10, 2016 / 6:35 PM

        Awesome. Any tutorial plans?

        Like

      • Emx2000 January 10, 2016 / 6:44 PM

        Just released one! Go ahead and refresh. More coming, I hope!

        Like

  8. HashtagShell January 10, 2016 / 7:47 PM

    Cpw and the rest of the forge crew are working on 1.8.9. It is getting quite popular for modders. Some tutorial pages are updating to 1.8. Would be nice if you read into it as well. If you need examples of working mods, BloodMagic, Botania, and EE3 are being updated. Just search for them on github. (It really helps to see a working example when learning)

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s