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

563 lines
15 KiB
Java

package net.minecraft.world.entity.player;
import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import java.util.Map;
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.entity.EntityEquipment;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
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 static final Int2ObjectMap<EquipmentSlot> EQUIPMENT_SLOT_MAPPING = new Int2ObjectArrayMap<>(
Map.of(
EquipmentSlot.FEET.getIndex(36),
EquipmentSlot.FEET,
EquipmentSlot.LEGS.getIndex(36),
EquipmentSlot.LEGS,
EquipmentSlot.CHEST.getIndex(36),
EquipmentSlot.CHEST,
EquipmentSlot.HEAD.getIndex(36),
EquipmentSlot.HEAD,
40,
EquipmentSlot.OFFHAND
)
);
private final NonNullList<ItemStack> items = NonNullList.withSize(36, ItemStack.EMPTY);
private int selected;
public final Player player;
private final EntityEquipment equipment;
private int timesChanged;
public Inventory(Player player, EntityEquipment equipment) {
this.player = player;
this.equipment = equipment;
}
public int getSelectedSlot() {
return this.selected;
}
public void setSelectedSlot(int slot) {
if (!isHotbarSlot(slot)) {
throw new IllegalArgumentException("Invalid selected slot");
} else {
this.selected = slot;
}
}
public ItemStack getSelectedItem() {
return this.items.get(this.selected);
}
public ItemStack setSelectedItem(ItemStack stack) {
return this.items.set(this.selected, stack);
}
/**
* Get the size of the player hotbar inventory
*/
public static int getSelectionSize() {
return 9;
}
public NonNullList<ItemStack> getNonEquipmentItems() {
return this.items;
}
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.setSelectedSlot(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.setSelectedSlot(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> 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 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 (int i = 0; i < this.items.size(); i++) {
ItemStack itemStack = this.getItem(i);
if (!itemStack.isEmpty()) {
itemStack.inventoryTick(this.player.level(), this.player, i == this.selected ? EquipmentSlot.MAINHAND : null);
}
}
}
/**
* 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 slot) {
return new ClientboundSetPlayerInventoryPacket(slot, this.getItem(slot).copy());
}
@Override
public ItemStack removeItem(int slot, int amount) {
if (slot < this.items.size()) {
return ContainerHelper.removeItem(this.items, slot, amount);
} else {
EquipmentSlot equipmentSlot = EQUIPMENT_SLOT_MAPPING.get(slot);
if (equipmentSlot != null) {
ItemStack itemStack = this.equipment.get(equipmentSlot);
if (!itemStack.isEmpty()) {
return itemStack.split(amount);
}
}
return ItemStack.EMPTY;
}
}
public void removeItem(ItemStack stack) {
for (int i = 0; i < this.items.size(); i++) {
if (this.items.get(i) == stack) {
this.items.set(i, ItemStack.EMPTY);
return;
}
}
for (EquipmentSlot equipmentSlot : EQUIPMENT_SLOT_MAPPING.values()) {
ItemStack itemStack = this.equipment.get(equipmentSlot);
if (itemStack == stack) {
this.equipment.set(equipmentSlot, ItemStack.EMPTY);
return;
}
}
}
@Override
public ItemStack removeItemNoUpdate(int slot) {
if (slot < this.items.size()) {
ItemStack itemStack = this.items.get(slot);
this.items.set(slot, ItemStack.EMPTY);
return itemStack;
} else {
EquipmentSlot equipmentSlot = EQUIPMENT_SLOT_MAPPING.get(slot);
return equipmentSlot != null ? this.equipment.set(equipmentSlot, ItemStack.EMPTY) : ItemStack.EMPTY;
}
}
@Override
public void setItem(int slot, ItemStack stack) {
if (slot < this.items.size()) {
this.items.set(slot, stack);
}
EquipmentSlot equipmentSlot = EQUIPMENT_SLOT_MAPPING.get(slot);
if (equipmentSlot != null) {
this.equipment.set(equipmentSlot, stack);
}
}
/**
* 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));
}
}
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();
for (int i = 0; i < listTag.size(); i++) {
CompoundTag compoundTag = listTag.getCompoundOrEmpty(i);
int j = compoundTag.getByteOr("Slot", (byte)0) & 255;
ItemStack itemStack = (ItemStack)ItemStack.parse(this.player.registryAccess(), compoundTag).orElse(ItemStack.EMPTY);
if (j < this.items.size()) {
this.setItem(j, itemStack);
}
}
}
@Override
public int getContainerSize() {
return this.items.size() + EQUIPMENT_SLOT_MAPPING.size();
}
@Override
public boolean isEmpty() {
for (ItemStack itemStack : this.items) {
if (!itemStack.isEmpty()) {
return false;
}
}
for (EquipmentSlot equipmentSlot : EQUIPMENT_SLOT_MAPPING.values()) {
if (!this.equipment.get(equipmentSlot).isEmpty()) {
return false;
}
}
return true;
}
@Override
public ItemStack getItem(int slot) {
if (slot < this.items.size()) {
return this.items.get(slot);
} else {
EquipmentSlot equipmentSlot = EQUIPMENT_SLOT_MAPPING.get(slot);
return equipmentSlot != null ? this.equipment.get(equipmentSlot) : ItemStack.EMPTY;
}
}
@Override
public Component getName() {
return Component.translatable("container.inventory");
}
/**
* Drop all armor and main inventory items.
*/
public void dropAll() {
for (int i = 0; i < this.items.size(); i++) {
ItemStack itemStack = this.items.get(i);
if (!itemStack.isEmpty()) {
this.player.drop(itemStack, true, false);
this.items.set(i, ItemStack.EMPTY);
}
}
this.equipment.dropAll(this.player);
}
@Override
public void setChanged() {
this.timesChanged++;
}
public int getTimesChanged() {
return this.timesChanged;
}
@Override
public boolean stillValid(Player player) {
return true;
}
/**
* Returns {@code true} if the specified {@link net.minecraft.world.item.ItemStack} exists in the inventory.
*/
public boolean contains(ItemStack stack) {
for (ItemStack itemStack : this) {
if (!itemStack.isEmpty() && ItemStack.isSameItemSameComponents(itemStack, stack)) {
return true;
}
}
return false;
}
public boolean contains(TagKey<Item> tag) {
for (ItemStack itemStack : this) {
if (!itemStack.isEmpty() && itemStack.is(tag)) {
return true;
}
}
return false;
}
public boolean contains(Predicate<ItemStack> predicate) {
for (ItemStack itemStack : this) {
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.setSelectedSlot(playerInventory.getSelectedSlot());
}
@Override
public void clearContent() {
this.items.clear();
this.equipment.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.getSelectedItem();
return itemStack.isEmpty() ? ItemStack.EMPTY : this.removeItem(this.selected, removeStack ? itemStack.getCount() : 1);
}
}