User Tools

Site Tools


Sidebar

Setup

Basics

Items

Blocks and Block Entities

Fluids

Entities

World Generation

Miscellaneous

Events

Mixins

Advanced

Tutorials for Minecraft 1.14

Documentation

tutorial:containers

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 the Block and register it as well as its BlockItem.

BiggerChestBlock.java
  1. public class BiggerChestBlock extends BlockWithEntity {
  2. public BiggerChestBlock(Settings settings) {
  3. super(settings);
  4. }
  5.  
  6. // A side effect of extending BlockWithEntity is it changes the render type to INVISIBLE, so we have to revert this
  7. @Override
  8. public BlockRenderType getRenderType(BlockState state) {
  9. return BlockRenderType.MODEL;
  10. }
  11.  
  12. // We will create the BlockEntity later.
  13. @Override
  14. public BlockEntity createBlockEntity(BlockView view) {
  15. return new BiggerChestBlockEntity();
  16. }
  17.  
  18. @Override
  19. public void onPlaced(World world, BlockPos pos, BlockState state, LivingEntity placer, ItemStack itemStack) {
  20. if (itemStack.hasCustomName()) {
  21. BlockEntity blockEntity = world.getBlockEntity(pos);
  22. if (blockEntity instanceof BiggerChestBlockEntity) {
  23. ((BiggerChestBlockEntity)blockEntity).setCustomName(itemStack.getName());
  24. }
  25. }
  26. }
  27.  
  28. @Override
  29. public ActionResult onUse(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hit) {
  30. if (!world.isClient) {
  31. BlockEntity blockEntity = world.getBlockEntity(pos);
  32. if (blockEntity instanceof BiggerChestBlockEntity) {
  33. ContainerProviderRegistry.INSTANCE.openContainer(ExampleMod.BIGGER_CHEST, player, buf -> buf.writeBlockPos(pos));
  34. }
  35. }
  36. return ActionResult.SUCCESS;
  37. }
  38.  
  39. // Scatter the items in the chest when it is removed.
  40. @Override
  41. public void onBlockRemoved(BlockState state, World world, BlockPos pos, BlockState newState, boolean moved) {
  42. if (state.getBlock() != newState.getBlock()) {
  43. BlockEntity blockEntity = world.getBlockEntity(pos);
  44. if (blockEntity instanceof BiggerChestBlockEntity) {
  45. ItemScatterer.spawn(world, (BlockPos)pos, (Inventory)((BiggerChestBlockEntity)blockEntity));
  46. // update comparators
  47. world.updateHorizontalAdjacent(pos, this);
  48. }
  49. super.onBlockRemoved(state, world, pos, newState, moved);
  50. }
  51. }
  52.  
  53. @Override
  54. public boolean hasComparatorOutput(BlockState state) {
  55. return true;
  56. }
  57.  
  58. @Override
  59. public int getComparatorOutput(BlockState state, World world, BlockPos pos) {
  60. return Container.calculateComparatorOutput(world.getBlockEntity(pos));
  61. }
  62. }

Then we need to register our Block and BlockItem.

ExampleMod.java
  1. public class ExampleMod implements ModInitializer
  2. {
  3.  
  4. public static final String MOD_ID = "tutorial";
  5.  
  6. // a public identifier for multiple parts of our bigger chest
  7. public static final Identifier BIGGER_CHEST = new Identifier(MOD_ID, "bigger_chest_block");
  8.  
  9. public static final Block BIGGER_CHEST_BLOCK = new BiggerChestBlock(FabricBlockSettings.of(Material.METAL).build());
  10.  
  11. @Override
  12. public void onInitialize()
  13. {
  14. Registry.register(Registry.BLOCK, BIGGER_CHEST, BIGGER_CHEST_BLOCK);
  15. Registry.register(Registry.BLOCK, BIGGER_CHEST, new BlockItem(BIGGER_CHEST_BLOCK, new Item.Settings().group(ItemGroup.REDSTONE)));
  16. }
  17. }

You may refer to other tutorials to modify the appearance and other properties of the Block by adding models or adjusting rendering later.

BlockEntity

BlockEntity is used for managing container inventories. Actually, it implements Inventory interface. It is required to save and load the inventory.

BiggerChestBlockEntity.java
  1. public class BiggerChestBlockEntity extends LootableContainerBlockEntity {
  2. private DefaultedList<ItemStack> inventory;
  3. private static final int INVENTORY_SIZE = 54; // 9 * 6 = 54
  4.  
  5. public BiggerChestBlockEntity() {
  6. super(ExampleMod.BIGGER_CHEST_ENTITY_TYPE);
  7. this.inventory = DefaultedList.ofSize(INVENTORY_SIZE, ItemStack.EMPTY);
  8. }
  9.  
  10. @Override
  11. protected Text getContainerName() {
  12. return new TranslatableText("container.chest");
  13. }
  14.  
  15. @Override
  16. protected Container createContainer(int syncId, PlayerInventory playerInventory) {
  17. return new BiggerChestContainer(syncId, playerInventory, (Inventory) this);
  18. }
  19.  
  20. @Override
  21. protected DefaultedList<ItemStack> getInvStackList() {
  22. return this.inventory;
  23. }
  24.  
  25. @Override
  26. protected void setInvStackList(DefaultedList<ItemStack> list) {
  27. this.inventory = list;
  28. }
  29.  
  30. @Override
  31. public int getInvSize() {
  32. return INVENTORY_SIZE;
  33. }
  34.  
  35. @Override
  36. public void fromTag(CompoundTag tag) {
  37. super.fromTag(tag);
  38. this.inventory = DefaultedList.ofSize(this.getInvSize(), ItemStack.EMPTY);
  39. if (!this.deserializeLootTable(tag)) {
  40. Inventories.fromTag(tag, this.inventory);
  41. }
  42. }
  43.  
  44. @Override
  45. public CompoundTag toTag(CompoundTag tag) {
  46. super.toTag(tag);
  47. if (!this.serializeLootTable(tag)) {
  48. Inventories.toTag(tag, this.inventory);
  49. }
  50. return tag;
  51. }
  52. }

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
  1. public class BiggerChestContainer extends Container {
  2. private final Inventory inventory; // Chest inventory
  3. private static final int INVENTORY_SIZE = 54; // 6 rows * 9 cols
  4.  
  5. protected BiggerChestContainer(int syncId, PlayerInventory playerInventory, Inventory inventory) {
  6. super(null, syncId); // Since we didn't create a ContainerType, we will place null here.
  7. this.inventory = inventory;
  8. checkContainerSize(inventory, INVENTORY_SIZE);
  9. inventory.onInvOpen(playerInventory.player);
  10.  
  11. // Creating Slots for GUI. A Slot is essentially a correspoding from inventory itemstacks to the GUI position.
  12. int i;
  13. int j;
  14.  
  15. // Chest Inventory
  16. for (i = 0; i < 6; i++) {
  17. for (j = 0; j < 9; j++) {
  18. this.addSlot(new Slot(inventory, i * 9 + j, 8 + j * 18, 18 + i * 18));
  19. }
  20. }
  21.  
  22.  
  23. // Player Inventory (27 storage + 9 hotbar)
  24. for (i = 0; i < 3; i++) {
  25. for (j = 0; j < 9; j++) {
  26. this.addSlot(new Slot(playerInventory, i * 9 + j + 9, 8 + j * 18, 18 + i * 18 + 103 + 18));
  27. }
  28. }
  29.  
  30.  
  31. for (j = 0; j < 9; j++) {
  32. this.addSlot(new Slot(playerInventory, j, 8 + j * 18, 18 + 161 + 18));
  33. }
  34. }
  35.  
  36. @Override
  37. public boolean canUse(PlayerEntity player) {
  38. return this.inventory.canPlayerUseInv(player);
  39. }
  40.  
  41. // Shift + Player Inv Slot
  42. @Override
  43. public ItemStack transferSlot(PlayerEntity player, int invSlot) {
  44. ItemStack newStack = ItemStack.EMPTY;
  45. Slot slot = this.slots.get(invSlot);
  46. if (slot != null && slot.hasStack()) {
  47. ItemStack originalStack = slot.getStack();
  48. newStack = originalStack.copy();
  49. if (invSlot < this.inventory.getInvSize()) {
  50. if (!this.insertItem(originalStack, this.inventory.getInvSize(), this.slots.size(), true)) {
  51. return ItemStack.EMPTY;
  52. }
  53. } else if (!this.insertItem(originalStack, 0, this.inventory.getInvSize(), false)) {
  54. return ItemStack.EMPTY;
  55. }
  56.  
  57. if (originalStack.isEmpty()) {
  58. slot.setStack(ItemStack.EMPTY);
  59. } else {
  60. slot.markDirty();
  61. }
  62. }
  63.  
  64. return newStack;
  65. }
  66. }
BiggerChestScreen.java
  1. public class BiggerChestScreen extends ContainerScreen<BiggerChestContainer> {
  2.  
  3. // a path to gui texture, you may replace it with new Identifier(YourMod.MOD_ID, "textures/gui/container/your_container.png");
  4. private static final Identifier TEXTURE = new Identifier("textures/gui/container/generic_54.png");
  5.  
  6. public BiggerChestScreen(BiggerChestContainer container, PlayerInventory playerInventory, Text title) {
  7. super(container, playerInventory, title);
  8. this.containerHeight = 114 + 6 * 18;
  9. }
  10.  
  11. @Override
  12. protected void drawForeground(int mouseX, int mouseY) {
  13. this.font.draw(this.title.asFormattedString(), 8.0F, 6.0F, 4210752);
  14. this.font.draw(this.playerInventory.getDisplayName().asFormattedString(), 8.0F, (float)(this.containerHeight - 96 + 2), 4210752);
  15. }
  16.  
  17. @Override
  18. protected void drawBackground(float delta, int mouseX, int mouseY) {
  19. RenderSystem.color4f(1.0F, 1.0F, 1.0F, 1.0F);
  20. this.minecraft.getTextureManager().bindTexture(TEXTURE);
  21. int i = (this.width - this.containerWidth) / 2;
  22. int j = (this.height - this.containerHeight) / 2;
  23. this.blit(i, j, 0, 0, this.containerWidth, 6 * 18 + 17);
  24. this.blit(i, j + 6 * 18 + 17, 0, 126, this.containerWidth, 96);
  25. }
  26. }

Then you need to register them respectively on main initializers and client initializers.

  1. [...]
  2. public static final String BIGGER_CHEST_TRANSLATION_KEY = Util.createTranslationKey("container", BIGGER_CHEST);
  3.  
  4. @Override
  5. public void onInitialize() {
  6. [...]
  7. ContainerProviderRegistry.INSTANCE.registerFactory(BIGGER_CHEST, (syncId, identifier, player, buf) -> {
  8. final World world = player.world;
  9. final BlockPos pos = buf.readBlockPos();
  10. return world.getBlockState(pos).createContainerFactory(player.world, pos).createMenu(syncId, player.inventory, player);
  11. });
  12. }
  1. @Override
  2. public void onInitializeClient() {
  3. [...]
  4. ScreenProviderRegistry.INSTANCE.<BiggerChestContainer>registerFactory(ExampleMod.BIGGER_CHEST, (container) -> new BiggerChestScreen(container, MinecraftClient.getInstance().player.inventory, new TranslatableText(ExampleMod.BIGGER_CHEST_TRANSLATION_KEY)));
  5. }

Organizing

After all the steps, you should have your ExampleMod Class and ExampleClientMod Class as such:

ExampleMod.java
  1. public static final String MOD_ID = "tutorial";
  2.  
  3. public static final Identifier BIGGER_CHEST = new Identifier(MOD_ID, "bigger_chest");
  4. public static final Block BIGGER_CHEST_BLOCK = new BiggerChestBlock(FabricBlockSettings.of(Material.WOOD).build());
  5. public static final String BIGGER_CHEST_TRANSLATION_KEY = Util.createTranslationKey("container", BIGGER_CHEST);
  6.  
  7. public static BlockEntityType<BiggerChestBlockEntity> BIGGER_CHEST_ENTITY_TYPE;
  8.  
  9. @Override
  10. public void onInitialize() {
  11. Registry.register(Registry.BLOCK, BIGGER_CHEST, BIGGER_CHEST_BLOCK);
  12. Registry.register(Registry.ITEM, BIGGER_CHEST, new BlockItem(BIGGER_CHEST_BLOCK, new Item.Settings().group(ItemGroup.REDSTONE)));
  13. BIGGER_CHEST_ENTITY_TYPE = Registry.register(Registry.BLOCK_ENTITY_TYPE, BIGGER_CHEST, BlockEntityType.Builder.create(BiggerChestBlockEntity::new, BIGGER_CHEST_BLOCK).build(null));
  14. ContainerProviderRegistry.INSTANCE.registerFactory(BIGGER_CHEST, (syncId, identifier, player, buf) -> {
  15. final BlockEntity blockEntity = player.world.getBlockEntity(buf.readBlockPos());
  16. return((BiggerChestBlockEntity) blockEntity).createContainer(syncId, player.inventory);
  17. });
  18. }
ExampleClientMod.java
  1. @Override
  2. public void onInitializeClient() {
  3. ScreenProviderRegistry.INSTANCE.<BiggerChestContainer>registerFactory(ExampleMod.BIGGER_CHEST, (container) -> new BiggerChestScreen(container, MinecraftClient.getInstance().player.inventory, new TranslatableText(ExampleMod.BIGGER_CHEST_TRANSLATION_KEY)));
  4. }

It's over and enjoy your custom container!

tutorial/containers.txt · Last modified: 2020/04/02 12:57 by earthcomputer