minecraft-src/net/minecraft/world/entity/player/Inventory.java
2025-07-04 02:00:41 +03:00

603 lines
16 KiB
Java

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<ItemStack> items = NonNullList.withSize(36, ItemStack.EMPTY);
public final NonNullList<ItemStack> armor = NonNullList.withSize(4, ItemStack.EMPTY);
public final NonNullList<ItemStack> offhand = NonNullList.withSize(1, ItemStack.EMPTY);
private final List<NonNullList<ItemStack>> 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 setPickedItem(ItemStack stack) {
int i = this.findSlotMatchingItem(stack);
if (isHotbarSlot(i)) {
this.selected = i;
} else {
if (i == -1) {
this.selected = this.getSuitableHotbarSlot();
if (!this.items.get(this.selected).isEmpty()) {
int j = this.getFreeSlot();
if (j != -1) {
this.items.set(j, this.items.get(this.selected));
}
}
this.items.set(this.selected, stack);
} else {
this.pickSlot(i);
}
}
}
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 itemStack) {
return !itemStack.isDamaged() && !itemStack.isEnchanted() && !itemStack.has(DataComponents.CUSTOM_NAME);
}
public int findSlotMatchingCraftingIngredient(Holder<Item> holder) {
for (int i = 0; i < this.items.size(); i++) {
ItemStack itemStack = this.items.get(i);
if (!itemStack.isEmpty() && itemStack.is(holder) && isUsableForCrafting(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 i) {
this.selected = i;
}
public int clearOrCountMatchingItems(Predicate<ItemStack> 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<ItemStack> 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<String>)(() -> 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 i) {
return new ClientboundSetPlayerInventoryPacket(i, this.getItem(i).copy());
}
@Override
public ItemStack removeItem(int slot, int amount) {
List<ItemStack> list = null;
for (NonNullList<ItemStack> 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<ItemStack> 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<ItemStack> nonNullList = null;
for (NonNullList<ItemStack> 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<ItemStack> nonNullList = null;
for (NonNullList<ItemStack> 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<ItemStack> list = null;
for (NonNullList<ItemStack> 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<ItemStack> 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<ItemStack> list : this.compartments) {
for (ItemStack itemStack : list) {
if (!itemStack.isEmpty() && ItemStack.isSameItemSameComponents(itemStack, stack)) {
return true;
}
}
}
return false;
}
public boolean contains(TagKey<Item> tag) {
for (List<ItemStack> list : this.compartments) {
for (ItemStack itemStack : list) {
if (!itemStack.isEmpty() && itemStack.is(tag)) {
return true;
}
}
}
return false;
}
public boolean contains(Predicate<ItemStack> predicate) {
for (List<ItemStack> 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<ItemStack> list : this.compartments) {
list.clear();
}
}
public void fillStackedContents(StackedItemContents stackedItemContents) {
for (ItemStack itemStack : this.items) {
stackedItemContents.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);
}
}