User Tools

Site Tools


tutorial:inventory

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
tutorial:inventory [2022/07/19 02:44] – external edit 127.0.0.1tutorial:inventory [2026/01/24 02:29] (current) – try_with_empty_hand i believe infinitychances
Line 1: Line 1:
 ====== Storing items in a block as an Inventory ====== ====== 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.   Make sure you've [[tutorial:blockentity|made a block entity]] before reading this tutorial.  
  
-The standard way to store items in a BlockEntity is to make it an ''Inventory'' +The simplest way to store items in a BlockEntity is to make it ''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<ItemVariant>]] tutorial. 
-This allows hoppers (or other mods) to insert and extract items from your BlockEntity without any extra work.  + 
-===== Implementing Inventory ===== +This tutorial is written for 1.21.11. For older versions, some methods may change. 
-''Inventory'' is just an interface, which means the actual ''ItemStack'' state will need to be stored on your ''BlockEntity''. + 
-A ''DefaultedList<ItemStack>'' can be used as an easy way to store  these ''ItemStacks'',  +===== Implementing Container ===== 
-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 ''Inventory'' is fairly simple, but is tedious and prone to error,  +''Container'' is just an interface, which means the actual ''ItemStack'' state will need to be stored on your ''BlockEntity''. A ''NonNullList<ItemStack>'' 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<ItemStack>'' (copy this as a new file): 
-so we'll use a default implementation of it which only requires giving it a ''DefaultList<ItemStack>'' (copy this as a new file): + 
-<code java ImplementedInventory.java>+<code java ImplementedContainer.java>
 /** /**
- * A simple {@code Inventory} implementation with only default methods + an item list getter.+ * A simple {@code Container} implementation with only default methods + an item list getter.
  *  *
- Originally by Juuz+ @author Juuz
  */  */
-public interface ImplementedInventory extends Inventory {+public interface ImplementedContainer extends Container {
  
     /**     /**
-     * Retrieves the item list of this inventory.+     * Retrieves the item list of this container.
      * Must return the same instance every time it's called.      * Must return the same instance every time it's called.
      */      */
-    DefaultedList<ItemStack> getItems();+    NonNullList<ItemStack> getItems();
          
     /**     /**
-     * Creates an inventory from the item list.+     * Creates a container from the item list.
      */      */
-    static ImplementedInventory of(DefaultedList<ItemStack> items) {+    static ImplementedContainer of(NonNullList<ItemStack> items) {
         return () -> items;         return () -> items;
     }     }
          
     /**     /**
-     * Creates a new inventory with the specified size.+     * Creates a new container with the specified size.
      */      */
-    static ImplementedInventory ofSize(int size) { +    static ImplementedContainer ofSize(int size) { 
-        return of(DefaultedList.ofSize(size, ItemStack.EMPTY));+        return of(NonNullList.withSize(size, ItemStack.EMPTY));
     }     }
          
     /**     /**
-     * Returns the inventory size.+     * Returns the container size.
      */      */
     @Override     @Override
-    default int size() {+    default int getContainerSize() {
         return getItems().size();         return getItems().size();
     }     }
          
     /**     /**
-     * Checks if the inventory is empty. +     * Checks if the container is empty. 
-     * @return true if this inventory has only empty stacks, false otherwise.+     * @return true if this container has only empty stacks, false otherwise.
      */      */
     @Override     @Override
     default boolean isEmpty() {     default boolean isEmpty() {
-        for (int i = 0; i < size(); i++) { +        for (int i = 0; i < getContainerSize(); i++) { 
-            ItemStack stack = getStack(i);+            ItemStack stack = getItem(i);
             if (!stack.isEmpty()) {             if (!stack.isEmpty()) {
                 return false;                 return false;
Line 65: Line 67:
      */      */
     @Override     @Override
-    default ItemStack getStack(int slot) {+    default ItemStack getItem(int slot) {
         return getItems().get(slot);         return getItems().get(slot);
     }     }
          
     /**     /**
-     * Removes items from an inventory slot.+     * Removes items from a container slot.
      * @param slot  The slot to remove from.      * @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,      * @param count How many items to remove. If there are less items in the slot than what are requested,
Line 76: Line 78:
      */      */
     @Override     @Override
-    default ItemStack removeStack(int slot, int count) { +    default ItemStack removeItem(int slot, int count) { 
-        ItemStack result = Inventories.splitStack(getItems(), slot, count);+        ItemStack result = ContainerHelper.removeItem(getItems(), slot, count);
         if (!result.isEmpty()) {         if (!result.isEmpty()) {
-            markDirty();+            setChanged();
         }         }
         return result;         return result;
Line 85: Line 87:
          
     /**     /**
-     * Removes all items from an inventory slot.+     * Removes all items from a container slot.
      * @param slot The slot to remove from.      * @param slot The slot to remove from.
      */      */
     @Override     @Override
-    default ItemStack removeStack(int slot) { +    default ItemStack removeItemNoUpdate(int slot) { 
-        return Inventories.removeStack(getItems(), slot);+        return ContainerHelper.takeItem(getItems(), slot);
     }     }
          
     /**     /**
-     * Replaces the current stack in an inventory slot with the provided stack. +     * Replaces the current stack in a container slot with the provided stack. 
-     * @param slot  The inventory slot of which to replace the itemstack.+     * @param slot  The container slot of which to replace the itemstack.
      * @param stack The replacing itemstack. If the stack is too big for      * @param stack The replacing itemstack. If the stack is too big for
-                  this inventory ({@link Inventory#getMaxCountPerStack()}), +                  this container ({@link Container#getMaxStackSize()}), 
-                  it gets resized to this inventory's maximum amount.+                  it gets resized to this container's maximum amount.
      */      */
     @Override     @Override
-    default void setStack(int slot, ItemStack stack) {+    default void setItem(int slot, ItemStack stack) {
         getItems().set(slot, stack);         getItems().set(slot, stack);
-        if (stack.getCount() > stack.getMaxCount()) { +        if (stack.getCount() > stack.getMaxStackSize()) { 
-            stack.setCount(stack.getMaxCount());+            stack.setCount(stack.getMaxStackSize());
         }         }
     }     }
          
     /**     /**
-     * Clears the inventory.+     * Clears the container.
      */      */
     @Override     @Override
-    default void clear() {+    default void clearContent() {
         getItems().clear();         getItems().clear();
     }     }
Line 118: Line 120:
     /**     /**
      * Marks the state as dirty.      * Marks the state as dirty.
-     * Must be called after changes in the inventory, so that the game can properly save +     * Must be called after changes in the container, so that the game can properly save 
-     * the inventory contents and notify neighboring blocks of inventory changes.+     * the container contents and notify neighboring blocks of container changes.
      */       */ 
     @Override     @Override
-    default void markDirty() {+    default void setChanged() {
         // Override if you want behavior.         // Override if you want behavior.
     }     }
          
     /**     /**
-     * @return true if the player can use the inventory, false otherwise.+     * @return true if the player can use the container, false otherwise.
      */       */ 
     @Override     @Override
-    default boolean canPlayerUse(PlayerEntity player) {+    default boolean stillValid(Player player) {
         return true;         return true;
     }     }
Line 136: Line 138:
 </code> </code>
  
-Now in your ''BlockEntity'' Implement ''ImplementedInventory'',  +Now in your ''BlockEntity'', implement ''ImplementedContainer'', and provide it with an instance of ''NonNullList<ItemStack> items'' that stores the items. For this example we'll store a maximum of 2 items in the container
-and provide it with an instance of ''DefaultedList<ItemStack> items'' that stores the items. +<code java DemoBlockEntity.java> 
-For this example we'll store a maximum of 2 items in the inventory+public class DemoBlockEntity extends BlockEntity implements ImplementedContainer 
-<code java> +    private final NonNullList<ItemStack> items = NonNullList.withSize(2, ItemStack.EMPTY);
-public class DemoBlockEntity extends BlockEntity implements ImplementedInventory +
-    private final DefaultedList<ItemStack> items = DefaultedList.ofSize(2, ItemStack.EMPTY);+
  
     @Override     @Override
-    public DefaultedList<ItemStack> getItems() {+    public NonNullList<ItemStack> getItems() {
         return items;         return items;
     }     }
Line 152: Line 152:
  
 </code> </code>
-We're also gonna need to save the inventories to tag and load it from there.  +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: 
-''Inventories'' has helper methods that makes this very easy: +<code java DemoBlockEntity.java> 
-<code java> +public class DemoBlockEntity extends BlockEntity implements ImplementedContainer {
-public class DemoBlockEntity extends BlockEntity implements ImplementedInventory {+
     [...]     [...]
     @Override     @Override
-    public void readNbt(NbtCompound nbt) { +    public void loadAdditional(ValueInput valueInput) { 
-        super.readNbt(nbt); +        super.loadAdditional(valueInput); 
-        Inventories.readNbt(nbt, items);+        items.clear(); 
 +        ContainerHelper.loadAllItems(valueInput, items);
     }     }
  
     @Override     @Override
-    public NbtCompound writeNbt(NbtCompound nbt) { +    public void saveAdditional(ValueOutput valueOutput) { 
-        Inventories.writeNbt(nbt, items); +        super.saveAdditional(valueOutput); 
-        return super.writeNbt(nbt);+        ContainerHelper.saveAllItems(valueOutput, items);
     }     }
 } }
 </code> </code>
-===== Extracting and inserting from your inventory (or any inventory) ===== + 
-In our block class, we'll override the `onUsebehavior to insert and extract items from our inventory.  +===== Extracting and inserting from your container ===== 
-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 inventory. The player will insert the item he is holding if he is holding one. +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). 
-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 inventory.  +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 inventory so it doesn't get destroyed alongside the player's ''ItemStack''+ 
-<code java> +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 ExampleBlock extends Block implements BlockEntityProvider {+<code java DemoBlock.java> 
 +public class DemoBlock extends BaseEntityBlock {
     [...]     [...]
     @Override     @Override
-    public ActionResult onUse(BlockState blockStateWorld world, BlockPos blockPosPlayerEntity player, Hand hand, BlockHitResult blockHitResult) { +    protected InteractionResult useItemOn(ItemStack stack, BlockState stateLevel level, BlockPos posPlayer player, InteractionHand hand, BlockHitResult hit) { 
-        if (world.isClient) return ActionResult.SUCCESS; +        if (level.isClient) return InteractionResult.SUCCESS; 
-        Inventory blockEntity = (Inventory) world.getBlockEntity(blockPos); +         
 +        if (!(level.getBlockEntity(posinstanceof DemoBlockEntity blockEntity)) { 
 +            return InteractionResult.PASS
 +        }
  
-        if (!player.getStackInHand(hand).isEmpty()) {+        if (!player.getItemInHand(hand).isEmpty()) {
             // Check what is the first open slot and put an item from the player's hand there             // Check what is the first open slot and put an item from the player's hand there
-            if (blockEntity.getStack(0).isEmpty()) { +            if (blockEntity.getItem(0).isEmpty()) { 
-                // Put the stack the player is holding into the inventory +                // Put the stack the player is holding into the container 
-                blockEntity.setStack(0, player.getStackInHand(hand).copy());+                blockEntity.setItem(0, player.getItemInHand(hand).copy());
                 // Remove the stack from the player's hand                 // Remove the stack from the player's hand
-                player.getStackInHand(hand).setCount(0); +                player.getItemInHand(hand).setCount(0); 
-            } else if (blockEntity.getStack(1).isEmpty()) { +            } else if (blockEntity.getItem(1).isEmpty()) { 
-                blockEntity.setStack(1, player.getStackInHand(hand).copy()); +                blockEntity.setItem(1, player.getItemInHand(hand).copy()); 
-                player.getStackInHand(hand).setCount(0);+                player.getItemInHand(hand).setCount(0);
             } else {             } else {
-                // If the inventory is full we'll print it's contents +                // If the container is full we'll notify the player 
-                System.out.println("The first slot holds " +                player.displayClientMessage(Component.literal("The inventory is full! The first slot holds ") 
-                        blockEntity.getStack(0) " and the second slot holds " blockEntity.getStack(1));+                    .append(blockEntity.getItem(0).getItemName()) 
 +                    .append(" and the second slot holds "
 +                    .append(blockEntity.getItem(1).getItemName()));
             }             }
         }          } 
-        return ActionResult.SUCCESS;+        return InteractionResult.SUCCESS;
     }     }
 } }
Line 208: Line 213:
  
  
-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 of the second is empty.  +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 as well we won't do anything+If the first is empty we pass it to default behavior
-<code java> +<code java DemoBlock.java> 
-public class ExampleBlock extends Block implements BlockEntityProvider {+public class DemoBlock extends BaseEntityBlock {
     [...]     [...]
     @Override     @Override
-    public ActionResult onUse(BlockState blockStateWorld world, BlockPos blockPosPlayerEntity player, Hand hand, BlockHitResult blockHitResult) {+    protected InteractionResult useItemOn(ItemStack stack, BlockState stateLevel level, BlockPos posPlayer player, InteractionHand hand, BlockHitResult hit) {
         ...         ...
-        if (!player.getStackInHand(hand).isEmpty()) {+        if (!player.getItemInHand(hand).isEmpty()) {
             ...             ...
         } else {         } else {
             // If the player is not holding anything we'll get give him the items in the block entity one by one             // 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 +            // Find the first slot that has an item and give it to the player 
-            if (!blockEntity.getStack(1).isEmpty()) { +            if (!blockEntity.getItem(1).isEmpty()) { 
-                // Give the player the stack in the inventory +                // Give the player the stack in the container 
-                player.getInventory().offerOrDrop(blockEntity.getStack(1)); +                player.getInventory().placeItemBackInInventory(blockEntity.getItem(1)); 
-                // Remove the stack from the inventory +                // Remove the stack from the container 
-                blockEntity.removeStack(1); +                blockEntity.removeItem(1); 
-            } else if (!blockEntity.getStack(0).isEmpty()) { +            } else if (!blockEntity.getItem(0).isEmpty()) { 
-                player.getInventory().offerOrDrop(blockEntity.getStack(0)); +                player.getInventory().placeItemBackInInventory(blockEntity.getItem(0)); 
-                blockEntity.removeStack(0);+                blockEntity.removeItem(0)
 +            } else { 
 +                return InteractionResult.TRY_WITH_EMPTY_HAND;
             }             }
         }         }
                  
-        return ActionResult.SUCCESS;+        return InteractionResult.SUCCESS;
     }     }
 } }
 </code> </code>
  
-===== Implementing SidedInventory ===== +===== Implementing WorldlyContainer ===== 
-If you want to have different logic based on what side things (hopper or other mods) interact with your block  +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: 
-you need to implement ''SidedInventory'' + 
-If say you wanted to make it so you cannot insert from the upper side of the block, you would do this: +<code java DemoBlockEntity.java> 
-<code java> +public class DemoBlockEntity extends BlockEntity implements ImplementedContainerWorldlyContainer{
-public class DemoBlockEntity extends BlockEntity implements ImplementedInventorySidedInventory {+
     [...]     [...]
     @Override     @Override
-    public int[] getInvAvailableSlots(Direction var1) {+    public int[] getSlotsForFace(Direction side) {
         // Just return an array of all slots         // Just return an array of all slots
-        int[] result = new int[getItems().size()]; +        return IntStream.of(getItems().size()).toArray();
-        for (int i = 0; i < result.length; i+++
-            result[i] = i; +
-        } +
- +
-        return result;+
     }     }
  
     @Override     @Override
-    public boolean canInsert(int slot, ItemStack stack, Direction direction) {+    public boolean canPlaceItemThroughFace(int slot, ItemStack stack, Direction direction) {
         return direction != Direction.UP;         return direction != Direction.UP;
     }     }
  
     @Override     @Override
-    public boolean canExtract(int slot, ItemStack stack, Direction direction) {+    public boolean canTakeItemThroughFace(int slot, ItemStack stack, Direction direction) {
         return true;         return true;
     }     }
 } }
- 
 </code> </code>
  
tutorial/inventory.1658198667.txt.gz · Last modified: 2022/07/19 02:44 by 127.0.0.1