Skip to content

Common Usage Examples: A Teeny Tiny Chest

AlexIIL edited this page Jun 14, 2021 · 5 revisions

Making a Teeny Tiny Chest

(NOTE: 2019/06/05 | Look over the above and ensure it still makes sense!)

Let's say you want to make a mod, that includes a single block: A teeny-tiny chest. It's like a chest, but only has 15 slots rather than 27.

So, the basics would probably look something like this:

public class TeenyTinyChest implements ModInitializer {

  public static final Identifer CHEST_ID = new Identifier("teeny_tiny_chest:chest");
  public static final TeenyTinyChestBlock BLOCK;
  public static final BlockEntityType<TeenyTinyChestBlockEntity> BLOCK_ENTITY;
  public static final BlockItem ITEM;

  static {
    BLOCK = new TeenyTinyChestBlock(
      FabricBlockSettings.copy(Blocks.CHEST).build()
    );
    BLOCK_ENTITY = new BlockEntityType<>(
      TeenyTinyChestBlockEntity::new,
      Collections.singleton(BLOCK),
      null
    );
    ITEM = new BlockItem(BLOCK, new Item.Settings());
  }

  @Override
  public void onInitialize() {
    Registry.register(Regsitry.BLOCK, CHEST_ID, BLOCK);
    Registry.register(Regsitry.BLOCK_ENTITY, CHEST_ID, BLOCK_ENTITY);
    Registry.register(Regsitry.ITEM, CHEST_ID, ITEM);
  }
}

public class TeenyTinyChestBlock extends Block implements BlockEntityProvider {
  public TeenyTinyChestBlock(Block.Settings settings) {
    super(settings);
  }

  @Override
  public BlockEntity createBlockEntity(BlockView view) {
    return new TeenyTinyChestBlockEntity();
  }
}

public class TeenyTinyChestBlockEntity extends BlockEntity {
  public TeenyTinyChestBlockEntity() {
    this(TeenyTinyChest.BLOCK_ENTITY);
  }

  public TeenyTinyChestBlockEntity(BlockEntityType<?> type) {
    super(type);
  }
}

Which is a good start, but we don't actually store anything yet - so let's get LibBlockAttributes to manage everything. So we'll need to add this at the start of TeenyTinyChestBlockEntity:

  public final FullFixedItemInv inventory = new FullFixedItemInv(15);

So now it's a chest that can store items!

Unfortunately there's still a lot we need to do - like ensure that the inventory saves and loads properly! Otherwise it would be a slower trashcan.

  // Also added to TeenyTinyChestBlockEntity

  @Override
  public CompoundTag toTag(CompoundTag tag) {
    // We need this bit otherwise the chest won't read properly
    tag = super.toTag(tag);
    tag.put("inventory", inventory.toTag());
    return tag;
  }

  @Override
  public void fromTag(CompoundTag tag) {
    super.fromTag(tag);
    inventory.fromTag(tag.getCompound("inventory"));
  }

So now it will at least work properly, but it's missing one important aspect: automation! And a GUI. But we'll do the gui a bit later.

Adding this to the block and block entity classes will let pipes, hoppers, and other machines insert and extract items with the inventory:

public class TeenyTinyChestBlock extends Block implements BlockEntityProvider, InventoryProvider {
  @Override
  public SidedInventory getInventory(BlockState state, World world BlockPos pos) {
    BlockEntity be = world.getBlockEntity(pos);
    if (be instanceof TeenyTinyChestBlockEntity) {
      FixedItemInv inv = ((TeenyTinyChestBlockEntity) be).inventory;
      return new SidedInventoryFixedWrapper(inv) {
        @Override
        public boolean canPlayerUseInv(PlayerEntity player) {
          return world.getBlockEntity(pos) == be && player.squaredDistanceTo(new Vec3d(pos)) < 8 * 8;
        }
      };
    }
    return null;
  }
public class TeenyTinyChestBlockEntity extends BlockEntity implements AttributeProviderBlockEntity {
    @Override
    public void addAllAttributes(AttributeList<?> to) {
        to.offer(inventory);
    }
}

And the final bit: a GUI. Most of this is standard fabric gui behaviour, but there is one slight difference: instead of using minecraft's Slot class for the slots, we need to use SlotFixedItemInv.

public class TeenyTinyChest implements ModInitializer {

  public static final Identifer CHEST_ID = new Identifier("teeny_tiny_chest:chest");
  public static final TeenyTinyChestBlock BLOCK;
  public static final BlockEntityType<TeenyTinyChestBlockEntity> BLOCK_ENTITY;
  public static final BlockItem ITEM;

  static {
    BLOCK = new TeenyTinyChestBlock(
      FabricBlockSettings.copy(Blocks.CHEST).build()
    );
    BLOCK_ENTITY = new BlockEntityType<>(
      TeenyTinyChestBlockEntity::new,
      Collections.singleton(BLOCK),
      null
    );
    ITEM = new BlockItem(BLOCK, new Item.Settings());
  }

  @Override
  public void onInitialize() {
    Registry.register(Regsitry.BLOCK, CHEST_ID, BLOCK);
    Registry.register(Regsitry.BLOCK_ENTITY, CHEST_ID, BLOCK_ENTITY);
    Registry.register(Regsitry.ITEM, CHEST_ID, ITEM);
    ContainerProviderRegistry.INSTANCE.registerFactory(
      CHEST_ID,
      TeenyTinyChestContainer.FACTORY
    );
  }
}

public class TeenyTinyChestClient implements ClientModInitializer {
  @Override
  public void onInitializeClient() {
    ScreenProviderRegistry.INSTANCE.registerFactory(
      TeenyTinyChest.CHEST_ID,
      TeenyTinyChestScreen.FACTORY
    );
  }
}

public class TeenyTinyChestContainer extends Container {
  public static final ContainerFactory<Container> FACTORY =
    (syncId, id, player, buffer) -> {
      BlockPos pos = buffer.readBlockPos();
      BlockEntity be = player.world.getBlockEntity(pos);
      if (be instanceof TeenyTinyChestBlockEntity) {
        return new TeenyTinyChestContainer(
          syncId,
          player,
          (TeenyTinyChestBlockEntity) be
        );
      }
      return null;
    };

  public final TeenyTinyChestBlockEntity blockEntity;
  public final PlayerEntity player;

  public TeenyTinyChestContainer(
    int syncId,
    PlayerEntity player,
    TeenyTinyChestBlockEntity blockEntity
  ) {
    super(
      // Custom containers don't use the vanilla
      // ContainerType system, so pass null instead.
      null, syncId
    );

    this.blockEntity = blockEntity;
    this.player = player;

    addPlayerInventory(71);

    // Our teeny-tiny chest has
    // 3 rows (y) and 5 colums (x)
    for (int y = 0; y < 3; y++) {
      for (int x = 0; x < 5; x++) {      
        addSlot(new SlotFixedItemInv(
          this,
          blockEntity.inventory,
          x + 5 * y,
          8 + 18 * x,
          18 + 18 * y
        ));
      }
    }
  }

  /** 
   * Simple method to quickly add a player inventory to any GUI.
   *
   * @param startY The Y co-ordinate to start the inventory at.
   */
  // Perhaps this might be useful for you to copy+paste?
  protected void addPlayerInventory(int startY) {
    for (int y = 0; y < 3; y++) {
      for (int x = 0; x < 9; ++x) {
        addSlot(new Slot(player.inventory, x + y * 9 + 9, 8 + x * 18, startY + y * 18));
      }
    }
    for (int x = 0; x < 9; x++) {
      addSlot(new Slot(player.inventory, x, 8 + x * 18, startY + 58));
    }
  }

  @Override 
  public ItemStack transferSlot(PlayerEntity player, int slotIndex) {
    // Shift-click logic can get quite long and complicated.
    // Instead we'll just abort.
	  return ItemStack.EMPTY;
	}
}

public class TeenyTinyChestScreen extends ContainerScreen<TeenyTinyChestContainer> {
  public static final ContainerScreenFactory<TeenyTinyChestContainer> FACTORY
    = TeenyTinyChestScreen::new;

  public TeenyTinyChestScreen(TeenyTinyChestContainer container) {
    super(container, container.player.inventory, TeenyTinyChest.BLOCK.getTextComponent());
    containerHeight = 168;
  }

  // Background rendering and title TODO
}

Whew, that's a lot of code just to display a GUI! Now the only thing left to do is display it in TeenyTinyChestBlock:

  // Add this to TeenyTinyChestBlock
  @Override
  public boolean activate(
    BlockState state,
    World world,
    BlockPos pos,
    PlayerEntity player,
    Hand hand,
    BlockHitResult hitResult
  ) {
    BlockEntity be = world.getBlockEntity(pos);

    if (be instanceof TeenyTinyChestBlockEntity) {
      if (world.isClient) {
        return true;
      }
      ContainerProviderRegistry.INSTANCE.openContainer(
        TeenyTinyChest.CHEST_ID,
        player,
        (buffer) -> buffer.writeBlockPos(pos)
      );
      return true;
    }
    return super.activate(state, world, pos, player, hand, hitResult);
  }
Clone this wiki locally