This is an old revision of the document!
Table of Contents
Creating a Container Block (DRAFT)
We are going to make a bigger chest in this tutorial as an example.
Block and BlockItem
First we need to create Block and register it as well as its BlockItem.
- BiggerChestBlock.java
- public class BiggerChestBlock extends BlockWithEntity {
- public BiggerChestBlock(Settings settings) {
- super(settings);
- }
- // We need to explicitly return a MODEL render type because BlockWithEntity has a side-effect of not showing the model.
- // You can also implement BlockEntityProvider Type to avoid this side-effect.
- public BlockRenderType getRenderType(BlockState state) {
- return BlockRenderType.MODEL;
- }
- // We will create the BlockEntity later.
- return new BiggerChestBlockEntity();
- }
- public void onPlaced(World world, BlockPos pos, BlockState state, LivingEntity placer, ItemStack itemStack) {
- if (itemStack.hasCustomName()) {
- BlockEntity blockEntity = world.getBlockEntity(pos);
- if (blockEntity instanceof BiggerChestBlockEntity) {
- ((BiggerChestBlockEntity)blockEntity).setCustomName(itemStack.getName());
- }
- }
- }
- public ActionResult onUse(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hit) {
- if (!world.isClient) {
- BlockEntity blockEntity = world.getBlockEntity(pos);
- if (blockEntity instanceof BiggerChestBlockEntity) {
- ContainerProviderRegistry.INSTANCE.openContainer(ExampleMod.BIGGER_CHEST, player, buf -> buf.writeBlockPos(pos));
- }
- }
- return ActionResult.SUCCESS;
- }
- // Scatter the items in the chest when it is removed.
- public void onBlockRemoved(BlockState state, World world, BlockPos pos, BlockState newState, boolean moved) {
- if (state.getBlock() != newState.getBlock()) {
- BlockEntity blockEntity = world.getBlockEntity(pos);
- if (blockEntity instanceof BiggerChestBlockEntity) {
- ItemScatterer.spawn(world, (BlockPos)pos, (Inventory)((BiggerChestBlockEntity)blockEntity));
- world.updateHorizontalAdjacent(pos, this);
- }
- super.onBlockRemoved(state, world, pos, newState, moved);
- }
- }
- public boolean hasComparatorOutput(BlockState state) {
- return true;
- }
- public int getComparatorOutput(BlockState state, World world, BlockPos pos) {
- }
- }
Then we need to register our Block and BlockItem.
- ExampleMod.java
- public class ExampleMod implements ModInitializer
- {
- // a public identifier for multiple parts of our bigger chest
- public static final Identifier BIGGER_CHEST = new Identifier(MOD_ID, "bigger_chest_block");
- public static final Block BIGGER_CHEST_BLOCK = new BiggerChestBlock(FabricBlockSettings.of(Material.METAL).build());
- @Override
- public void onInitialize()
- {
- }
- }
BlockEntity
BlockEntities are used for managing container inventories. Actually, it implements Inventory interface.
- BiggerChestBlockEntity
- public class BiggerChestBlockEntity extends LootableContainerBlockEntity {
- private DefaultedList<ItemStack> inventory;
- private static final int INVENTORY_SIZE = 54; // 9 * 6 = 54
- public BiggerChestBlockEntity() {
- super(ExampleMod.BIGGER_CHEST_ENTITY_TYPE);
- this.inventory = DefaultedList.ofSize(INVENTORY_SIZE, ItemStack.EMPTY);
- }
- @Override
- protected Text getContainerName() {
- return new TranslatableText("container.chest");
- }
- @Override
- return new BiggerChestContainer(syncId, playerInventory, (Inventory) this);
- }
- @Override
- protected DefaultedList<ItemStack> getInvStackList() {
- return this.inventory;
- }
- @Override
- protected void setInvStackList(DefaultedList<ItemStack> list) {
- this.inventory = list;
- }
- @Override
- public int getInvSize() {
- return INVENTORY_SIZE;
- }
- @Override
- public void fromTag(CompoundTag tag) {
- super.fromTag(tag);
- this.inventory = DefaultedList.ofSize(this.getInvSize(), ItemStack.EMPTY);
- if (!this.deserializeLootTable(tag)) {
- Inventories.fromTag(tag, this.inventory);
- }
- }
- @Override
- public CompoundTag toTag(CompoundTag tag) {
- super.toTag(tag);
- if (!this.serializeLootTable(tag)) {
- Inventories.toTag(tag, this.inventory);
- }
- return tag;
- }
- }
Container GUI and Screen
We need a Container Class and a ContainerScreen Class to display and sync the GUI. Container Classes are used to synchronize GUI state between the server and the client. ContainerScreen Classes are fully client-sided and are responsible for drawing GUI elements.
- BiggerChestContainer.java
- private final Inventory inventory; // Chest inventory
- private static final int INVENTORY_SIZE = 54; // 6 rows * 9 cols
- protected BiggerChestContainer(int syncId, PlayerInventory playerInventory, Inventory inventory) {
- super(null, syncId); // Since we didn't create a ContainerType, we will place null here.
- this.inventory = inventory;
- checkContainerSize(inventory, INVENTORY_SIZE);
- inventory.onInvOpen(playerInventory.player);
- // Creating Slots for GUI. A Slot is essentially a correspoding from inventory itemstacks to the GUI position.
- int i;
- int j;
- // Chest Inventory
- for (i = 0; i < 6; i++) {
- for (j = 0; j < 9; j++) {
- this.addSlot(new Slot(inventory, i * 9 + j, 8 + j * 18, 18 + i * 18));
- }
- }
- // Player Inventory (27 storage + 9 hotbar)
- for (i = 0; i < 3; i++) {
- for (j = 0; j < 9; j++) {
- this.addSlot(new Slot(playerInventory, i * 9 + j + 9, 8 + j * 18, 18 + i * 18 + 103 + 18));
- }
- }
- for (j = 0; j < 9; j++) {
- this.addSlot(new Slot(playerInventory, j, 8 + j * 18, 18 + 161 + 18));
- }
- }
- @Override
- public boolean canUse(PlayerEntity player) {
- return this.inventory.canPlayerUseInv(player);
- }
- // Shift + Player Inv Slot
- public ItemStack transferSlot(PlayerEntity player, int invSlot) {
- ItemStack newStack = ItemStack.EMPTY;
- Slot slot = this.slots.get(invSlot);
- if (slot != null && slot.hasStack()) {
- ItemStack originalStack = slot.getStack();
- newStack = originalStack.copy();
- if (invSlot < this.inventory.getInvSize()) {
- if (!this.insertItem(originalStack, this.inventory.getInvSize(), this.slots.size(), true)) {
- return ItemStack.EMPTY;
- }
- } else if (!this.insertItem(originalStack, 0, this.inventory.getInvSize(), false)) {
- return ItemStack.EMPTY;
- }
- if (originalStack.isEmpty()) {
- slot.setStack(ItemStack.EMPTY);
- } else {
- slot.markDirty();
- }
- }
- return newStack;
- }
- }
- BiggerChestScreen.java
- public class BiggerChestScreen extends ContainerScreen<BiggerChestContainer> {
- // a path to gui texture, you may replace it with new Identifier(YourMod.MOD_ID, "textures/gui/container/your_container.png");
- private static final Identifier TEXTURE = new Identifier("textures/gui/container/generic_54.png");
- public BiggerChestScreen(BiggerChestContainer container, PlayerInventory playerInventory, Text title) {
- super(container, playerInventory, title);
- this.containerHeight = 114 + 6 * 18;
- }
- @Override
- protected void drawForeground(int mouseX, int mouseY) {
- this.font.draw(this.title.asFormattedString(), 8.0F, 6.0F, 4210752);
- this.font.draw(this.playerInventory.getDisplayName().asFormattedString(), 8.0F, (float)(this.containerHeight - 96 + 2), 4210752);
- }
- @Override
- protected void drawBackground(float delta, int mouseX, int mouseY) {
- RenderSystem.color4f(1.0F, 1.0F, 1.0F, 1.0F);
- this.minecraft.getTextureManager().bindTexture(TEXTURE);
- int i = (this.width - this.containerWidth) / 2;
- int j = (this.height - this.containerHeight) / 2;
- this.blit(i, j, 0, 0, this.containerWidth, 6 * 18 + 17);
- this.blit(i, j + 6 * 18 + 17, 0, 126, this.containerWidth, 96);
- }
- }
Then you need to register them respectively on main initializers and client initializers.
[...] public void onInitialize() { [...] ContainerProviderRegistry.INSTANCE.registerFactory(BIGGER_CHEST, (syncId, identifier, player, buf) -> { final BlockEntity blockEntity = player.world.getBlockEntity(buf.readBlockPos()); return((BiggerChestBlockEntity) blockEntity).createContainer(syncId, player.inventory); }); }
public void onInitializeClient() { [...] ScreenProviderRegistry.INSTANCE.<BiggerChestContainer>registerFactory(ExampleMod.BIGGER_CHEST, (container) -> new BiggerChestScreen(container, MinecraftClient.getInstance().player.inventory, new TranslatableText(ExampleMod.BIGGER_CHEST_TRANSLATION_KEY))); }
Orgnizing
After all the steps, you should have your ExampleMod Class and ExampleClientMod Class as such:
- ExampleMod.java
- public static final Identifier BIGGER_CHEST = new Identifier(MOD_ID, "bigger_chest");
- public static final Block BIGGER_CHEST_BLOCK = new BiggerChestBlock(FabricBlockSettings.of(Material.WOOD).build());
- public static BlockEntityType<BiggerChestBlockEntity> BIGGER_CHEST_ENTITY_TYPE;
- @Override
- public void onInitialize() {
- ContainerProviderRegistry.INSTANCE.registerFactory(BIGGER_CHEST, (syncId, identifier, player, buf) -> {
- final BlockEntity blockEntity = player.world.getBlockEntity(buf.readBlockPos());
- return((BiggerChestBlockEntity) blockEntity).createContainer(syncId, player.inventory);
- });
- }
- ExampleClientMod.java
- @Override
- public void onInitializeClient() {
- ScreenProviderRegistry.INSTANCE.<BiggerChestContainer>registerFactory(ExampleMod.BIGGER_CHEST, (container) -> new BiggerChestScreen(container, MinecraftClient.getInstance().player.inventory, new TranslatableText(ExampleMod.BIGGER_CHEST_TRANSLATION_KEY)));
- }
It's over and enjoy your custom container!