User Tools

Site Tools


tutorial:containers

This is an old revision of the document!


Creating a Container Block (DRAFT)

We are going to make a bigger chest in this tutorial as an example.

Block and BlockItem

First create and register our block and BlockItem

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

Then we need to register our Block and BlockItem.

  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. }

BlockEntity

We then need to add an onUse method in our Block class in order to open our chest.

 

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. public ItemStack transferSlot(PlayerEntity player, int invSlot) {
  43. ItemStack newStack = ItemStack.EMPTY;
  44. Slot slot = this.slots.get(invSlot);
  45. if (slot != null && slot.hasStack()) {
  46. ItemStack originalStack = slot.getStack();
  47. newStack = originalStack.copy();
  48. if (invSlot < this.inventory.getInvSize()) {
  49. if (!this.insertItem(originalStack, this.inventory.getInvSize(), this.slots.size(), true)) {
  50. return ItemStack.EMPTY;
  51. }
  52. } else if (!this.insertItem(originalStack, 0, this.inventory.getInvSize(), false)) {
  53. return ItemStack.EMPTY;
  54. }
  55.  
  56. if (originalStack.isEmpty()) {
  57. slot.setStack(ItemStack.EMPTY);
  58. } else {
  59. slot.markDirty();
  60. }
  61. }
  62.  
  63. return newStack;
  64. }
  65. }
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. public void onInitialize() {
  5. [...]
  6. ContainerProviderRegistry.INSTANCE.registerFactory(BIGGER_CHEST, (syncId, identifier, player, buf) -> {
  7. final BlockEntity blockEntity = player.world.getBlockEntity(buf.readBlockPos());
  8. return((BiggerChestBlockEntity) blockEntity).createContainer(syncId, player.inventory);
  9. });
  10. }
  1. public void onInitializeClient() {
  2. [...]
  3. ScreenProviderRegistry.INSTANCE.<BiggerChestContainer>registerFactory(ExampleMod.BIGGER_CHEST, (container) -> new BiggerChestScreen(container, MinecraftClient.getInstance().player.inventory, new TranslatableText(ExampleMod.BIGGER_CHEST_TRANSLATION_KEY)));
  4. }

Orgnizing

After following

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.1582810298.txt.gz · Last modified: 2020/02/27 13:31 by mkpoli