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!
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?
LikeLike
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.
LikeLike
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
LikeLike
Check my reply at MCForums 🙂
LikeLike
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.
LikeLike
please reply with logs via pastebin.com
LikeLike
Also could you add a tutorial on custom mobs?
LikeLike
Will do.
LikeLike
Hey, nice tutorial, any reason why the items dont load back into the block when leaving and joining again?
Thanks
LikeLike
Do you think you could also do a tutorial for a block placing other blocks and such?
LikeLike
Do you mean a block that when placed creates structures? I may do that in the future.
LikeLike
Make sure you have overridden readFromNBT and writeToNBT correctly, as well as adding super() calls. On, my side, this works fine whatsoever.
LikeLike
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)
LikeLike
@emx2000 is there any way you can make a tutorial on multiblock structures?
LikeLike
I have not yet mastered multiblock structures yet, but I might do it in the future. I can’t say when, though.
LikeLike
This blog still active? I really like the way you explain things. This just needs more content.
LikeLike
It is now 🙂
LikeLike
Awesome. Any tutorial plans?
LikeLike
Just released one! Go ahead and refresh. More coming, I hope!
LikeLike
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)
LikeLiked by 1 person