package net.minecraft.world.entity.player; import com.google.common.collect.ImmutableList; import java.util.List; import java.util.function.Predicate; import net.minecraft.CrashReport; import net.minecraft.CrashReportCategory; import net.minecraft.CrashReportDetail; import net.minecraft.ReportedException; import net.minecraft.core.Holder; import net.minecraft.core.NonNullList; import net.minecraft.core.component.DataComponents; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; import net.minecraft.network.chat.Component; import net.minecraft.network.protocol.game.ClientboundSetPlayerInventoryPacket; import net.minecraft.server.level.ServerPlayer; import net.minecraft.tags.TagKey; import net.minecraft.world.Container; import net.minecraft.world.ContainerHelper; import net.minecraft.world.Nameable; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.block.state.BlockState; public class Inventory implements Container, Nameable { public static final int POP_TIME_DURATION = 5; public static final int INVENTORY_SIZE = 36; public static final int SELECTION_SIZE = 9; public static final int SLOT_OFFHAND = 40; public static final int NOT_FOUND_INDEX = -1; public final NonNullList items = NonNullList.withSize(36, ItemStack.EMPTY); public final NonNullList armor = NonNullList.withSize(4, ItemStack.EMPTY); public final NonNullList offhand = NonNullList.withSize(1, ItemStack.EMPTY); private final List> compartments = ImmutableList.of(this.items, this.armor, this.offhand); public int selected; public final Player player; private int timesChanged; public Inventory(Player player) { this.player = player; } /** * Returns the item stack currently held by the player. */ public ItemStack getSelected() { return isHotbarSlot(this.selected) ? this.items.get(this.selected) : ItemStack.EMPTY; } /** * Get the size of the player hotbar inventory */ public static int getSelectionSize() { return 9; } private boolean hasRemainingSpaceForItem(ItemStack destination, ItemStack origin) { return !destination.isEmpty() && ItemStack.isSameItemSameComponents(destination, origin) && destination.isStackable() && destination.getCount() < this.getMaxStackSize(destination); } /** * Returns the first item stack that is empty. */ public int getFreeSlot() { for (int i = 0; i < this.items.size(); i++) { if (this.items.get(i).isEmpty()) { return i; } } return -1; } public void addAndPickItem(ItemStack stack) { this.selected = this.getSuitableHotbarSlot(); if (!this.items.get(this.selected).isEmpty()) { int i = this.getFreeSlot(); if (i != -1) { this.items.set(i, this.items.get(this.selected)); } } this.items.set(this.selected, stack); } public void pickSlot(int index) { this.selected = this.getSuitableHotbarSlot(); ItemStack itemStack = this.items.get(this.selected); this.items.set(this.selected, this.items.get(index)); this.items.set(index, itemStack); } public static boolean isHotbarSlot(int index) { return index >= 0 && index < 9; } /** * Finds the stack or an equivalent one in the main inventory */ public int findSlotMatchingItem(ItemStack stack) { for (int i = 0; i < this.items.size(); i++) { if (!this.items.get(i).isEmpty() && ItemStack.isSameItemSameComponents(stack, this.items.get(i))) { return i; } } return -1; } public static boolean isUsableForCrafting(ItemStack stack) { return !stack.isDamaged() && !stack.isEnchanted() && !stack.has(DataComponents.CUSTOM_NAME); } public int findSlotMatchingCraftingIngredient(Holder item, ItemStack stack) { for (int i = 0; i < this.items.size(); i++) { ItemStack itemStack = this.items.get(i); if (!itemStack.isEmpty() && itemStack.is(item) && isUsableForCrafting(itemStack) && (stack.isEmpty() || ItemStack.isSameItemSameComponents(stack, itemStack))) { return i; } } return -1; } public int getSuitableHotbarSlot() { for (int i = 0; i < 9; i++) { int j = (this.selected + i) % 9; if (this.items.get(j).isEmpty()) { return j; } } for (int ix = 0; ix < 9; ix++) { int j = (this.selected + ix) % 9; if (!this.items.get(j).isEnchanted()) { return j; } } return this.selected; } public void setSelectedHotbarSlot(int selectedHotbarSlot) { this.selected = selectedHotbarSlot; } public int clearOrCountMatchingItems(Predicate stackPredicate, int maxCount, Container inventory) { int i = 0; boolean bl = maxCount == 0; i += ContainerHelper.clearOrCountMatchingItems(this, stackPredicate, maxCount - i, bl); i += ContainerHelper.clearOrCountMatchingItems(inventory, stackPredicate, maxCount - i, bl); ItemStack itemStack = this.player.containerMenu.getCarried(); i += ContainerHelper.clearOrCountMatchingItems(itemStack, stackPredicate, maxCount - i, bl); if (itemStack.isEmpty()) { this.player.containerMenu.setCarried(ItemStack.EMPTY); } return i; } /** * This function stores as many items of an ItemStack as possible in a matching slot and returns the quantity of left over items. */ private int addResource(ItemStack stack) { int i = this.getSlotWithRemainingSpace(stack); if (i == -1) { i = this.getFreeSlot(); } return i == -1 ? stack.getCount() : this.addResource(i, stack); } private int addResource(int slot, ItemStack stack) { int i = stack.getCount(); ItemStack itemStack = this.getItem(slot); if (itemStack.isEmpty()) { itemStack = stack.copyWithCount(0); this.setItem(slot, itemStack); } int j = this.getMaxStackSize(itemStack) - itemStack.getCount(); int k = Math.min(i, j); if (k == 0) { return i; } else { i -= k; itemStack.grow(k); itemStack.setPopTime(5); return i; } } /** * Stores a stack in the player's inventory. It first tries to place it in the selected slot in the player's hotbar, then the offhand slot, then any available/empty slot in the player's inventory. */ public int getSlotWithRemainingSpace(ItemStack stack) { if (this.hasRemainingSpaceForItem(this.getItem(this.selected), stack)) { return this.selected; } else if (this.hasRemainingSpaceForItem(this.getItem(40), stack)) { return 40; } else { for (int i = 0; i < this.items.size(); i++) { if (this.hasRemainingSpaceForItem(this.items.get(i), stack)) { return i; } } return -1; } } /** * Ticks every item in inventory. Used for animations. Is called on client and server. */ public void tick() { for (NonNullList nonNullList : this.compartments) { for (int i = 0; i < nonNullList.size(); i++) { if (!nonNullList.get(i).isEmpty()) { nonNullList.get(i).inventoryTick(this.player.level(), this.player, i, this.selected == i); } } } } /** * Adds the stack to the first empty slot in the player's inventory. Returns {@code false} if it's not possible to place the entire stack in the inventory. */ public boolean add(ItemStack stack) { return this.add(-1, stack); } /** * Adds the stack to the specified slot in the player's inventory. Returns {@code false} if it's not possible to place the entire stack in the inventory. */ public boolean add(int slot, ItemStack stack) { if (stack.isEmpty()) { return false; } else { try { if (stack.isDamaged()) { if (slot == -1) { slot = this.getFreeSlot(); } if (slot >= 0) { this.items.set(slot, stack.copyAndClear()); this.items.get(slot).setPopTime(5); return true; } else if (this.player.hasInfiniteMaterials()) { stack.setCount(0); return true; } else { return false; } } else { int i; do { i = stack.getCount(); if (slot == -1) { stack.setCount(this.addResource(stack)); } else { stack.setCount(this.addResource(slot, stack)); } } while (!stack.isEmpty() && stack.getCount() < i); if (stack.getCount() == i && this.player.hasInfiniteMaterials()) { stack.setCount(0); return true; } else { return stack.getCount() < i; } } } catch (Throwable var6) { CrashReport crashReport = CrashReport.forThrowable(var6, "Adding item to inventory"); CrashReportCategory crashReportCategory = crashReport.addCategory("Item being added"); crashReportCategory.setDetail("Item ID", Item.getId(stack.getItem())); crashReportCategory.setDetail("Item data", stack.getDamageValue()); crashReportCategory.setDetail("Item name", (CrashReportDetail)(() -> stack.getHoverName().getString())); throw new ReportedException(crashReport); } } } public void placeItemBackInInventory(ItemStack stack) { this.placeItemBackInInventory(stack, true); } public void placeItemBackInInventory(ItemStack stack, boolean sendPacket) { while (!stack.isEmpty()) { int i = this.getSlotWithRemainingSpace(stack); if (i == -1) { i = this.getFreeSlot(); } if (i == -1) { this.player.drop(stack, false); break; } int j = stack.getMaxStackSize() - this.getItem(i).getCount(); if (this.add(i, stack.split(j)) && sendPacket && this.player instanceof ServerPlayer serverPlayer) { serverPlayer.connection.send(this.createInventoryUpdatePacket(i)); } } } public ClientboundSetPlayerInventoryPacket createInventoryUpdatePacket(int slot) { return new ClientboundSetPlayerInventoryPacket(slot, this.getItem(slot).copy()); } @Override public ItemStack removeItem(int slot, int amount) { List list = null; for (NonNullList nonNullList : this.compartments) { if (slot < nonNullList.size()) { list = nonNullList; break; } slot -= nonNullList.size(); } return list != null && !((ItemStack)list.get(slot)).isEmpty() ? ContainerHelper.removeItem(list, slot, amount) : ItemStack.EMPTY; } public void removeItem(ItemStack stack) { for (NonNullList nonNullList : this.compartments) { for (int i = 0; i < nonNullList.size(); i++) { if (nonNullList.get(i) == stack) { nonNullList.set(i, ItemStack.EMPTY); break; } } } } @Override public ItemStack removeItemNoUpdate(int slot) { NonNullList nonNullList = null; for (NonNullList nonNullList2 : this.compartments) { if (slot < nonNullList2.size()) { nonNullList = nonNullList2; break; } slot -= nonNullList2.size(); } if (nonNullList != null && !nonNullList.get(slot).isEmpty()) { ItemStack itemStack = nonNullList.get(slot); nonNullList.set(slot, ItemStack.EMPTY); return itemStack; } else { return ItemStack.EMPTY; } } @Override public void setItem(int slot, ItemStack stack) { NonNullList nonNullList = null; for (NonNullList nonNullList2 : this.compartments) { if (slot < nonNullList2.size()) { nonNullList = nonNullList2; break; } slot -= nonNullList2.size(); } if (nonNullList != null) { nonNullList.set(slot, stack); } } public float getDestroySpeed(BlockState state) { return this.items.get(this.selected).getDestroySpeed(state); } /** * Writes the inventory out as a list of compound tags. This is where the slot indices are used (+100 for armor, +80 for crafting). */ public ListTag save(ListTag listTag) { for (int i = 0; i < this.items.size(); i++) { if (!this.items.get(i).isEmpty()) { CompoundTag compoundTag = new CompoundTag(); compoundTag.putByte("Slot", (byte)i); listTag.add(this.items.get(i).save(this.player.registryAccess(), compoundTag)); } } for (int ix = 0; ix < this.armor.size(); ix++) { if (!this.armor.get(ix).isEmpty()) { CompoundTag compoundTag = new CompoundTag(); compoundTag.putByte("Slot", (byte)(ix + 100)); listTag.add(this.armor.get(ix).save(this.player.registryAccess(), compoundTag)); } } for (int ixx = 0; ixx < this.offhand.size(); ixx++) { if (!this.offhand.get(ixx).isEmpty()) { CompoundTag compoundTag = new CompoundTag(); compoundTag.putByte("Slot", (byte)(ixx + 150)); listTag.add(this.offhand.get(ixx).save(this.player.registryAccess(), compoundTag)); } } return listTag; } /** * Reads from the given tag list and fills the slots in the inventory with the correct items. */ public void load(ListTag listTag) { this.items.clear(); this.armor.clear(); this.offhand.clear(); for (int i = 0; i < listTag.size(); i++) { CompoundTag compoundTag = listTag.getCompound(i); int j = compoundTag.getByte("Slot") & 255; ItemStack itemStack = (ItemStack)ItemStack.parse(this.player.registryAccess(), compoundTag).orElse(ItemStack.EMPTY); if (j >= 0 && j < this.items.size()) { this.items.set(j, itemStack); } else if (j >= 100 && j < this.armor.size() + 100) { this.armor.set(j - 100, itemStack); } else if (j >= 150 && j < this.offhand.size() + 150) { this.offhand.set(j - 150, itemStack); } } } @Override public int getContainerSize() { return this.items.size() + this.armor.size() + this.offhand.size(); } @Override public boolean isEmpty() { for (ItemStack itemStack : this.items) { if (!itemStack.isEmpty()) { return false; } } for (ItemStack itemStackx : this.armor) { if (!itemStackx.isEmpty()) { return false; } } for (ItemStack itemStackxx : this.offhand) { if (!itemStackxx.isEmpty()) { return false; } } return true; } @Override public ItemStack getItem(int slot) { List list = null; for (NonNullList nonNullList : this.compartments) { if (slot < nonNullList.size()) { list = nonNullList; break; } slot -= nonNullList.size(); } return list == null ? ItemStack.EMPTY : (ItemStack)list.get(slot); } @Override public Component getName() { return Component.translatable("container.inventory"); } /** * @return a player armor item (as an {@code ItemStack}) contained in specified armor slot */ public ItemStack getArmor(int slot) { return this.armor.get(slot); } /** * Drop all armor and main inventory items. */ public void dropAll() { for (List list : this.compartments) { for (int i = 0; i < list.size(); i++) { ItemStack itemStack = (ItemStack)list.get(i); if (!itemStack.isEmpty()) { this.player.drop(itemStack, true, false); list.set(i, ItemStack.EMPTY); } } } } @Override public void setChanged() { this.timesChanged++; } public int getTimesChanged() { return this.timesChanged; } @Override public boolean stillValid(Player player) { return player.canInteractWithEntity(this.player, 4.0); } /** * Returns {@code true} if the specified {@link net.minecraft.world.item.ItemStack} exists in the inventory. */ public boolean contains(ItemStack stack) { for (List list : this.compartments) { for (ItemStack itemStack : list) { if (!itemStack.isEmpty() && ItemStack.isSameItemSameComponents(itemStack, stack)) { return true; } } } return false; } public boolean contains(TagKey tag) { for (List list : this.compartments) { for (ItemStack itemStack : list) { if (!itemStack.isEmpty() && itemStack.is(tag)) { return true; } } } return false; } public boolean contains(Predicate predicate) { for (List list : this.compartments) { for (ItemStack itemStack : list) { if (predicate.test(itemStack)) { return true; } } } return false; } /** * Copy the ItemStack contents from another InventoryPlayer instance */ public void replaceWith(Inventory playerInventory) { for (int i = 0; i < this.getContainerSize(); i++) { this.setItem(i, playerInventory.getItem(i)); } this.selected = playerInventory.selected; } @Override public void clearContent() { for (List list : this.compartments) { list.clear(); } } public void fillStackedContents(StackedItemContents contents) { for (ItemStack itemStack : this.items) { contents.accountSimpleStack(itemStack); } } /** * @param removeStack Whether to remove the entire stack of items. If {@code false}, removes a single item. */ public ItemStack removeFromSelected(boolean removeStack) { ItemStack itemStack = this.getSelected(); return itemStack.isEmpty() ? ItemStack.EMPTY : this.removeItem(this.selected, removeStack ? itemStack.getCount() : 1); } }