tutorial:inventory
Differences
This shows you the differences between two versions of the page.
| Both sides previous revisionPrevious revisionNext revision | Previous revision | ||
| tutorial:inventory [2019/08/06 15:31] – fudge | tutorial:inventory [2025/03/30 22:06] (current) – [Storing items in a block as an Inventory] technici4n | ||
|---|---|---|---|
| Line 1: | Line 1: | ||
| ====== Storing items in a block as an Inventory ====== | ====== Storing items in a block as an Inventory ====== | ||
| - | The standard | + | Make sure you've [[tutorial: |
| - | This allows hoppers (or other mods) to insert and extract items from your BlockEntity without any extra work. | + | |
| + | The simplest | ||
| + | |||
| + | This tutorial is written for 1.21. For older versions, some methods may change. | ||
| ===== Implementing Inventory ===== | ===== Implementing Inventory ===== | ||
| - | '' | + | |
| - | A '' | + | '' |
| - | as it can be set to default to '' | + | |
| - | Implementing '' | + | |
| - | so we'll use a default implementation of it which only requires giving it a '' | + | |
| <code java ImplementedInventory.java> | <code java ImplementedInventory.java> | ||
| /** | /** | ||
| * A simple {@code Inventory} implementation with only default methods + an item list getter. | * A simple {@code Inventory} implementation with only default methods + an item list getter. | ||
| * | * | ||
| - | | + | |
| */ | */ | ||
| public interface ImplementedInventory extends Inventory { | public interface ImplementedInventory extends Inventory { | ||
| + | |||
| /** | /** | ||
| - | | + | |
| * Must return the same instance every time it's called. | * Must return the same instance every time it's called. | ||
| */ | */ | ||
| DefaultedList< | DefaultedList< | ||
| - | | + | |
| /** | /** | ||
| * Creates an inventory from the item list. | * Creates an inventory from the item list. | ||
| Line 27: | Line 30: | ||
| return () -> items; | return () -> items; | ||
| } | } | ||
| + | | ||
| /** | /** | ||
| - | * Creates a new inventory with the size. | + | * Creates a new inventory with the specified |
| */ | */ | ||
| static ImplementedInventory ofSize(int size) { | static ImplementedInventory ofSize(int size) { | ||
| return of(DefaultedList.ofSize(size, | return of(DefaultedList.ofSize(size, | ||
| } | } | ||
| - | | + | |
| /** | /** | ||
| * Returns the inventory size. | * Returns the inventory size. | ||
| */ | */ | ||
| @Override | @Override | ||
| - | default int getInvSize() { | + | default int size() { |
| return getItems().size(); | return getItems().size(); | ||
| } | } | ||
| + | | ||
| /** | /** | ||
| - | * @return true if this inventory has only empty stacks, false otherwise | + | * Checks if the inventory is empty. |
| + | * @return true if this inventory has only empty stacks, false otherwise. | ||
| */ | */ | ||
| @Override | @Override | ||
| - | default boolean | + | default boolean |
| - | for (int i = 0; i < getInvSize(); i++) { | + | for (int i = 0; i < size(); i++) { |
| - | ItemStack stack = getInvStack(i); | + | ItemStack stack = getStack(i); |
| if (!stack.isEmpty()) { | if (!stack.isEmpty()) { | ||
| return false; | return false; | ||
| Line 54: | Line 60: | ||
| return true; | return true; | ||
| } | } | ||
| + | | ||
| /** | /** | ||
| - | | + | |
| */ | */ | ||
| @Override | @Override | ||
| - | default ItemStack | + | default ItemStack |
| return getItems().get(slot); | return getItems().get(slot); | ||
| } | } | ||
| + | | ||
| /** | /** | ||
| - | | + | |
| - | | + | |
| - | * takes all items in that slot. | + | * @param count How many items to remove. |
| + | | ||
| */ | */ | ||
| @Override | @Override | ||
| - | default ItemStack | + | default ItemStack |
| ItemStack result = Inventories.splitStack(getItems(), | ItemStack result = Inventories.splitStack(getItems(), | ||
| if (!result.isEmpty()) { | if (!result.isEmpty()) { | ||
| Line 74: | Line 83: | ||
| return result; | return result; | ||
| } | } | ||
| + | | ||
| /** | /** | ||
| - | * Removes | + | * Removes |
| + | | ||
| */ | */ | ||
| @Override | @Override | ||
| - | default ItemStack | + | default ItemStack |
| return Inventories.removeStack(getItems(), | return Inventories.removeStack(getItems(), | ||
| } | } | ||
| + | | ||
| /** | /** | ||
| - | * Replaces the current stack in the {@code | + | * Replaces the current stack in an inventory |
| - | | + | |
| - | * it gets resized to this inventory' | + | * @param stack The replacing itemstack. |
| + | | ||
| + | | ||
| */ | */ | ||
| @Override | @Override | ||
| - | default void setInvStack(int slot, ItemStack stack) { | + | default void setStack(int slot, ItemStack stack) { |
| getItems().set(slot, | getItems().set(slot, | ||
| - | if (stack.getCount() > getInvMaxStackAmount()) { | + | if (stack.getCount() > stack.getMaxCount()) { |
| - | stack.setCount(getInvMaxStackAmount()); | + | stack.setCount(stack.getMaxCount()); |
| } | } | ||
| } | } | ||
| + | | ||
| /** | /** | ||
| - | * Clears | + | * Clears the inventory. |
| */ | */ | ||
| @Override | @Override | ||
| Line 100: | Line 115: | ||
| getItems().clear(); | getItems().clear(); | ||
| } | } | ||
| + | | ||
| + | /** | ||
| + | * Marks the state as dirty. | ||
| + | * Must be called after changes in the inventory, so that the game can properly save | ||
| + | * the inventory contents and notify neighboring blocks of inventory changes. | ||
| + | | ||
| @Override | @Override | ||
| default void markDirty() { | default void markDirty() { | ||
| // Override if you want behavior. | // Override if you want behavior. | ||
| } | } | ||
| + | | ||
| + | /** | ||
| + | * @return true if the player can use the inventory, false otherwise. | ||
| + | | ||
| @Override | @Override | ||
| - | default boolean | + | default boolean |
| return true; | return true; | ||
| } | } | ||
| } | } | ||
| </ | </ | ||
| - | Now in your '' | + | |
| - | and provide it with an instance of '' | + | Now in your '' |
| - | For this example we'll store a maximum of 2 items in the inventory: | + | < |
| - | <code java> | + | |
| public class DemoBlockEntity extends BlockEntity implements ImplementedInventory { | public class DemoBlockEntity extends BlockEntity implements ImplementedInventory { | ||
| private final DefaultedList< | private final DefaultedList< | ||
| Line 121: | Line 145: | ||
| return items; | return items; | ||
| } | } | ||
| - | ... | + | |
| } | } | ||
| </ | </ | ||
| - | -->>>>>>> | + | We're also gonna need to save the inventories to NBT and load it from there. '' |
| - | We're also gonna need to save the inventories to tag and load it from there. | + | < |
| - | '' | + | |
| - | <code java> | + | |
| public class DemoBlockEntity extends BlockEntity implements ImplementedInventory { | public class DemoBlockEntity extends BlockEntity implements ImplementedInventory { | ||
| - | ... | + | |
| @Override | @Override | ||
| - | public void fromTag(CompoundTag tag) { | + | public void readNbt(NbtCompound nbt, RegistryWrapper.WrapperLookup registryLookup) { |
| - | super.fromTag(tag); | + | super.readNbt(nbt, registryLookup); |
| - | Inventories.fromTag(tag,items); | + | Inventories.readNbt(nbt, items, registryLookup); |
| } | } | ||
| @Override | @Override | ||
| - | public | + | public |
| - | Inventories.toTag(tag,items); | + | Inventories.writeNbt(nbt, items, registryLookup); |
| - | return super.toTag(tag); | + | return super.writeNbt(nbt, registryLookup); |
| } | } | ||
| } | } | ||
| </ | </ | ||
| + | |||
| ===== Extracting and inserting from your inventory ===== | ===== Extracting and inserting from your inventory ===== | ||
| - | Remember that the same thing can be done with any inventory... | + | |
| + | In our block class, we'll override | ||
| + | |||
| + | First we'll handle inserting into the inventory. | ||
| + | |||
| + | Note that we call '' | ||
| + | <code java DemoBlock.java> | ||
| + | public class DemoBlock extends BlockWithEntity { | ||
| + | [...] | ||
| + | @Override | ||
| + | protected ItemActionResult onUseWithItem(ItemStack stack, BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hit) { | ||
| + | if (world.isClient) return ActionResult.SUCCESS; | ||
| + | |||
| + | if (!(world.getBlockEntity(pos) instanceof DemoBlockEntity blockEntity)) { | ||
| + | return ItemActionResult.PASS_TO_DEFAULT_BLOCK_INTERACTION; | ||
| + | } | ||
| + | |||
| + | if (!player.getStackInHand(hand).isEmpty()) { | ||
| + | // Check what is the first open slot and put an item from the player' | ||
| + | if (blockEntity.getStack(0).isEmpty()) { | ||
| + | // Put the stack the player is holding into the inventory | ||
| + | blockEntity.setStack(0, | ||
| + | // Remove the stack from the player' | ||
| + | player.getStackInHand(hand).setCount(0); | ||
| + | } else if (blockEntity.getStack(1).isEmpty()) { | ||
| + | blockEntity.setStack(1, | ||
| + | player.getStackInHand(hand).setCount(0); | ||
| + | } else { | ||
| + | // If the inventory is full we'll notify the player | ||
| + | player.sendMessage(Text.literal(" | ||
| + | .append(blockEntity.getStack(0).getName()) | ||
| + | .append(" | ||
| + | .append(blockEntity.getStack(1).getName())); | ||
| + | } | ||
| + | } | ||
| + | return ItemActionResult.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. | ||
| + | <code java DemoBlock.java> | ||
| + | public class DemoBlock extends BlockWithEntity { | ||
| + | [...] | ||
| + | @Override | ||
| + | protected ItemActionResult onUseWithItem(ItemStack stack, BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hit) { | ||
| + | ... | ||
| + | if (!player.getStackInHand(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.getStack(1).isEmpty()) { | ||
| + | // Give the player the stack in the inventory | ||
| + | player.getInventory().offerOrDrop(blockEntity.getStack(1)); | ||
| + | // Remove the stack from the inventory | ||
| + | blockEntity.removeStack(1); | ||
| + | } else if (!blockEntity.getStack(0).isEmpty()) { | ||
| + | player.getInventory().offerOrDrop(blockEntity.getStack(0)); | ||
| + | blockEntity.removeStack(0); | ||
| + | } else { | ||
| + | return ItemActionResult.PASS_TO_DEFAULT_BLOCK_INTERACTION; | ||
| + | } | ||
| + | } | ||
| + | |||
| + | return ItemActionResult.SUCCESS; | ||
| + | } | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | ===== Implementing SidedInventory ===== | ||
| + | If you want to have different logic based on what side things (hopper or other mods) interact with your block you need to implement '' | ||
| + | |||
| + | <code java DemoBlockEntity.java> | ||
| + | public class DemoBlockEntity extends BlockEntity implements ImplementedInventory, | ||
| + | [...] | ||
| + | @Override | ||
| + | public int[] getInvAvailableSlots(Direction side) { | ||
| + | // Just return an array of all slots | ||
| + | return IntStream.of(getItems().size()).toArray(); | ||
| + | } | ||
| + | |||
| + | @Override | ||
| + | public boolean canInsert(int slot, ItemStack stack, Direction direction) { | ||
| + | return direction != Direction.UP; | ||
| + | } | ||
| + | |||
| + | @Override | ||
| + | public boolean canExtract(int slot, ItemStack stack, Direction direction) { | ||
| + | return true; | ||
| + | } | ||
| + | } | ||
| + | </ | ||
tutorial/inventory.1565105491.txt.gz · Last modified: 2019/08/06 15:31 by fudge