====== Storing items in a block as an Inventory ====== :!: This page has been updated for mojmap. Because of this change, the class known as "Inventory" in yarn mappings is now called "Container." See [[https://docs.fabricmc.net/develop/migrating-mappings/#whats-going-on-with-mappings|What's Going On With Mappings]] for more detail. Make sure you've [[tutorial:blockentity|made a block entity]] before reading this tutorial. The simplest way to store items in a BlockEntity is to make it a ''Container''. This allows hoppers (or other mods) to insert and extract items from your BlockEntity without any extra work. For more flexible and complex ways to store items, refer to the [[tutorial:transfer-api_item_storage|Item transfer with Storage]] tutorial. This tutorial is written for 1.21.11. For older versions, some methods may change. ===== Implementing Container ===== ''Container'' is just an interface, which means the actual ''ItemStack'' state will need to be stored on your ''BlockEntity''. A ''NonNullList'' can be used as an easy way to store these ''ItemStacks'', as it can be set to default to ''ItemStack.Empty'', which is the proper way of saying that there is no item in a slot. Implementing ''Container'' is fairly simple, but is tedious and prone to error, so we'll use a default implementation of it which only requires giving it a ''NonNullList'' (copy this as a new file): /** * A simple {@code Container} implementation with only default methods + an item list getter. * * @author Juuz */ public interface ImplementedContainer extends Container { /** * Retrieves the item list of this container. * Must return the same instance every time it's called. */ NonNullList getItems(); /** * Creates a container from the item list. */ static ImplementedContainer of(NonNullList items) { return () -> items; } /** * Creates a new container with the specified size. */ static ImplementedContainer ofSize(int size) { return of(NonNullList.withSize(size, ItemStack.EMPTY)); } /** * Returns the container size. */ @Override default int getContainerSize() { return getItems().size(); } /** * Checks if the container is empty. * @return true if this container has only empty stacks, false otherwise. */ @Override default boolean isEmpty() { for (int i = 0; i < getContainerSize(); i++) { ItemStack stack = getItem(i); if (!stack.isEmpty()) { return false; } } return true; } /** * Retrieves the item in the slot. */ @Override default ItemStack getItem(int slot) { return getItems().get(slot); } /** * Removes items from a container slot. * @param slot The slot to remove from. * @param count How many items to remove. If there are less items in the slot than what are requested, * takes all items in that slot. */ @Override default ItemStack removeItem(int slot, int count) { ItemStack result = ContainerHelper.removeItem(getItems(), slot, count); if (!result.isEmpty()) { setChanged(); } return result; } /** * Removes all items from a container slot. * @param slot The slot to remove from. */ @Override default ItemStack removeItemNoUpdate(int slot) { return ContainerHelper.takeItem(getItems(), slot); } /** * Replaces the current stack in a container slot with the provided stack. * @param slot The container slot of which to replace the itemstack. * @param stack The replacing itemstack. If the stack is too big for * this container ({@link Container#getMaxStackSize()}), * it gets resized to this container's maximum amount. */ @Override default void setItem(int slot, ItemStack stack) { getItems().set(slot, stack); if (stack.getCount() > stack.getMaxStackSize()) { stack.setCount(stack.getMaxStackSize()); } } /** * Clears the container. */ @Override default void clearContent() { getItems().clear(); } /** * Marks the state as dirty. * Must be called after changes in the container, so that the game can properly save * the container contents and notify neighboring blocks of container changes. */ @Override default void setChanged() { // Override if you want behavior. } /** * @return true if the player can use the container, false otherwise. */ @Override default boolean stillValid(Player player) { return true; } } Now in your ''BlockEntity'', implement ''ImplementedContainer'', and provide it with an instance of ''NonNullList items'' that stores the items. For this example we'll store a maximum of 2 items in the container: public class DemoBlockEntity extends BlockEntity implements ImplementedContainer { private final NonNullList items = NonNullList.withSize(2, ItemStack.EMPTY); @Override public NonNullList getItems() { return items; } [...] } We're also gonna need to save the inventories to NBT and load it from there. ''Container'' has helper methods that make this very easy: public class DemoBlockEntity extends BlockEntity implements ImplementedContainer { [...] @Override public void loadAdditional(ValueInput valueInput) { super.loadAdditional(valueInput); items.clear(); ContainerHelper.loadAllItems(valueInput, items); } @Override public void saveAdditional(ValueOutput valueOutput) { super.saveAdditional(valueOutput); ContainerHelper.saveAllItems(valueOutput, items); } } ===== Extracting and inserting from your container ===== In our block class, we'll override the ''onUse'' behavior to insert and extract items from our inventory. Note that this can be done to any ''Inventory'' instance, not just our own (so you could do the same thing to a chest block, for example). First we'll handle inserting into the container. The player will insert the item he is holding if he is holding one. It'll go into the first slot if it is empty, or to the second slot if the first one is empty, or if the second is empty too we'll print some information about the container. Note that we call ''copy()'' when inserting the ''ItemStack'' into the container so it doesn't get destroyed alongside the player's ''ItemStack''. public class DemoBlock extends BaseEntityBlock { [...] @Override protected InteractionResult useItemOn(ItemStack stack, BlockState state, Level level, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) { if (level.isClient) return InteractionResult.SUCCESS; if (!(level.getBlockEntity(pos) instanceof DemoBlockEntity blockEntity)) { return InteractionResult.PASS; } if (!player.getItemInHand(hand).isEmpty()) { // Check what is the first open slot and put an item from the player's hand there if (blockEntity.getItem(0).isEmpty()) { // Put the stack the player is holding into the container blockEntity.setItem(0, player.getItemInHand(hand).copy()); // Remove the stack from the player's hand player.getItemInHand(hand).setCount(0); } else if (blockEntity.getItem(1).isEmpty()) { blockEntity.setItem(1, player.getItemInHand(hand).copy()); player.getItemInHand(hand).setCount(0); } else { // If the container is full we'll notify the player player.displayClientMessage(Component.literal("The inventory is full! The first slot holds ") .append(blockEntity.getItem(0).getItemName()) .append(" and the second slot holds ") .append(blockEntity.getItem(1).getItemName())); } } return InteractionResult.SUCCESS; } } We'll have the opposite behavior when the player is not holding an item. We'll take the item from the second slot, and then the first one if the second is empty. If the first is empty we pass it to default behavior. public class DemoBlock extends BaseEntityBlock { [...] @Override protected InteractionResult useItemOn(ItemStack stack, BlockState state, Level level, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) { ... if (!player.getItemInHand(hand).isEmpty()) { ... } else { // If the player is not holding anything we'll get give him the items in the block entity one by one // Find the first slot that has an item and give it to the player if (!blockEntity.getItem(1).isEmpty()) { // Give the player the stack in the container player.getInventory().placeItemBackInInventory(blockEntity.getItem(1)); // Remove the stack from the container blockEntity.removeItem(1); } else if (!blockEntity.getItem(0).isEmpty()) { player.getInventory().placeItemBackInInventory(blockEntity.getItem(0)); blockEntity.removeItem(0); } else { return InteractionResult.TRY_WITH_EMPTY_HAND; } } return InteractionResult.SUCCESS; } } ===== Implementing WorldlyContainer ===== If you want to have different logic based on what side things (hopper or other mods) interact with your block you need to implement ''WorldlyContainer''. If say you wanted to make it so you cannot insert from the upper side of the block, you would do this: public class DemoBlockEntity extends BlockEntity implements ImplementedContainer, WorldlyContainer{ [...] @Override public int[] getSlotsForFace(Direction side) { // Just return an array of all slots return IntStream.of(getItems().size()).toArray(); } @Override public boolean canPlaceItemThroughFace(int slot, ItemStack stack, Direction direction) { return direction != Direction.UP; } @Override public boolean canTakeItemThroughFace(int slot, ItemStack stack, Direction direction) { return true; } }