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 [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 ====== | ||
| - | The standard | + | :!: This page has been updated for mojmap. Because of this change, the class known as " |
| - | This allows hoppers (or other mods) to insert and extract items from your BlockEntity without any extra work. | + | |
| - | ===== Implementing | + | Make sure you've [[tutorial: |
| - | '' | + | |
| - | A '' | + | The simplest |
| - | as it can be set to default to '' | + | |
| - | Implementing '' | + | This tutorial is written for 1.21.11. For older versions, some methods may change. |
| - | so we'll use a default implementation of it which only requires giving it a '' | + | |
| - | <code java ImplementedInventory.java> | + | ===== Implementing |
| + | |||
| + | '' | ||
| + | |||
| + | <code java ImplementedContainer.java> | ||
| /** | /** | ||
| - | * A simple {@code | + | * A simple {@code |
| * | * | ||
| - | | + | |
| */ | */ | ||
| - | public interface | + | public interface |
| /** | /** | ||
| - | | + | |
| * Must return the same instance every time it's called. | * Must return the same instance every time it's called. | ||
| */ | */ | ||
| - | | + | |
| - | | + | |
| /** | /** | ||
| - | * Creates | + | * Creates |
| */ | */ | ||
| - | static | + | static |
| return () -> items; | return () -> items; | ||
| } | } | ||
| + | | ||
| /** | /** | ||
| - | * Creates a new inventory | + | * Creates a new container |
| */ | */ | ||
| - | static | + | static |
| - | return of(DefaultedList.ofSize(size, ItemStack.EMPTY)); | + | return of(NonNullList.withSize(size, ItemStack.EMPTY)); |
| } | } | ||
| - | | + | |
| /** | /** | ||
| - | * Returns the inventory | + | * Returns the container |
| */ | */ | ||
| @Override | @Override | ||
| - | default int getInvSize() { | + | default int getContainerSize() { |
| return getItems().size(); | return getItems().size(); | ||
| } | } | ||
| + | | ||
| /** | /** | ||
| - | * @return true if this inventory | + | * Checks if the container is empty. |
| + | * @return true if this container | ||
| */ | */ | ||
| @Override | @Override | ||
| - | default boolean | + | default boolean |
| - | for (int i = 0; i < getInvSize(); i++) { | + | for (int i = 0; i < getContainerSize(); i++) { |
| - | ItemStack stack = getInvStack(i); | + | ItemStack stack = getItem(i); |
| if (!stack.isEmpty()) { | if (!stack.isEmpty()) { | ||
| return false; | return false; | ||
| Line 54: | Line 62: | ||
| 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 = ContainerHelper.removeItem(getItems(), |
| if (!result.isEmpty()) { | if (!result.isEmpty()) { | ||
| - | | + | |
| } | } | ||
| return result; | return result; | ||
| } | } | ||
| + | | ||
| /** | /** | ||
| - | * Removes | + | * Removes |
| + | | ||
| */ | */ | ||
| @Override | @Override | ||
| - | default ItemStack | + | default ItemStack |
| - | return | + | return |
| } | } | ||
| + | | ||
| /** | /** | ||
| - | * Replaces the current stack in the {@code | + | * Replaces the current stack in a container |
| - | | + | |
| - | * it gets resized to this inventory's maximum amount. | + | * @param stack The replacing itemstack. |
| + | | ||
| + | | ||
| */ | */ | ||
| @Override | @Override | ||
| - | default void setInvStack(int slot, ItemStack stack) { | + | default void setItem(int slot, ItemStack stack) { |
| getItems().set(slot, | getItems().set(slot, | ||
| - | if (stack.getCount() > getInvMaxStackAmount()) { | + | if (stack.getCount() > stack.getMaxStackSize()) { |
| - | stack.setCount(getInvMaxStackAmount()); | + | stack.setCount(stack.getMaxStackSize()); |
| } | } | ||
| } | } | ||
| + | | ||
| /** | /** | ||
| - | * Clears | + | * Clears the container. |
| */ | */ | ||
| @Override | @Override | ||
| - | default void clear() { | + | default void clearContent() { |
| getItems().clear(); | 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 | @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 container, 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 |
| - | public class DemoBlockEntity extends BlockEntity implements | + | private final NonNullList< |
| - | private final DefaultedList< | + | |
| @Override | @Override | ||
| - | public | + | public |
| 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. | + | < |
| - | '' | + | public class DemoBlockEntity extends BlockEntity implements |
| - | <code java> | + | |
| - | public class DemoBlockEntity extends BlockEntity implements | + | |
| - | ... | + | |
| @Override | @Override | ||
| - | public void fromTag(CompoundTag tag) { | + | public void loadAdditional(ValueInput valueInput) { |
| - | super.fromTag(tag); | + | super.loadAdditional(valueInput); |
| - | | + | |
| + | ContainerHelper.loadAllItems(valueInput, items); | ||
| } | } | ||
| @Override | @Override | ||
| - | public | + | public |
| - | | + | |
| - | | + | |
| } | } | ||
| } | } | ||
| </ | </ | ||
| - | ===== Extracting and inserting from your inventory | + | |
| - | Remember that the same thing can be done with any inventory... | + | ===== Extracting and inserting from your container |
| + | |||
| + | In our block class, we'll override | ||
| + | |||
| + | 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 '' | ||
| + | <code java DemoBlock.java> | ||
| + | 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' | ||
| + | if (blockEntity.getItem(0).isEmpty()) { | ||
| + | // Put the stack the player is holding into the container | ||
| + | blockEntity.setItem(0, | ||
| + | // Remove the stack from the player' | ||
| + | player.getItemInHand(hand).setCount(0); | ||
| + | } else if (blockEntity.getItem(1).isEmpty()) { | ||
| + | blockEntity.setItem(1, | ||
| + | player.getItemInHand(hand).setCount(0); | ||
| + | } else { | ||
| + | // If the container is full we'll notify the player | ||
| + | player.displayClientMessage(Component.literal(" | ||
| + | | ||
| + | .append(" | ||
| + | .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. | ||
| + | <code java DemoBlock.java> | ||
| + | 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 '' | ||
| + | |||
| + | <code java DemoBlockEntity.java> | ||
| + | public class DemoBlockEntity extends BlockEntity implements ImplementedContainer, | ||
| + | [...] | ||
| + | @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; | ||
| + | } | ||
| + | } | ||
| + | </ | ||
tutorial/inventory.1565105491.txt.gz · Last modified: 2019/08/06 15:31 by fudge