minecraft-src/net/minecraft/world/item/ItemStack.java
2025-07-04 03:45:38 +03:00

1208 lines
44 KiB
Java

package net.minecraft.world.item;
import com.google.common.collect.Lists;
import com.mojang.logging.LogUtils;
import com.mojang.serialization.Codec;
import com.mojang.serialization.DataResult;
import com.mojang.serialization.MapCodec;
import com.mojang.serialization.DataResult.Error;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import io.netty.handler.codec.DecoderException;
import io.netty.handler.codec.EncoderException;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.function.UnaryOperator;
import java.util.stream.Stream;
import net.minecraft.ChatFormatting;
import net.minecraft.advancements.CriteriaTriggers;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
import net.minecraft.core.HolderLookup;
import net.minecraft.core.HolderSet;
import net.minecraft.core.NonNullList;
import net.minecraft.core.component.DataComponentGetter;
import net.minecraft.core.component.DataComponentHolder;
import net.minecraft.core.component.DataComponentMap;
import net.minecraft.core.component.DataComponentPatch;
import net.minecraft.core.component.DataComponentType;
import net.minecraft.core.component.DataComponents;
import net.minecraft.core.component.PatchedDataComponentMap;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.nbt.NbtOps;
import net.minecraft.nbt.Tag;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.chat.CommonComponents;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.ComponentUtils;
import net.minecraft.network.chat.HoverEvent;
import net.minecraft.network.chat.MutableComponent;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.resources.RegistryOps;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.stats.Stats;
import net.minecraft.tags.TagKey;
import net.minecraft.util.ExtraCodecs;
import net.minecraft.util.Mth;
import net.minecraft.util.NullOps;
import net.minecraft.util.StringUtil;
import net.minecraft.util.Unit;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.InteractionResult.Success;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.EquipmentSlotGroup;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.SlotAccess;
import net.minecraft.world.entity.ai.attributes.Attribute;
import net.minecraft.world.entity.ai.attributes.AttributeModifier;
import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.entity.decoration.ItemFrame;
import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.flag.FeatureFlagSet;
import net.minecraft.world.inventory.ClickAction;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.inventory.tooltip.TooltipComponent;
import net.minecraft.world.item.component.Consumable;
import net.minecraft.world.item.component.CustomData;
import net.minecraft.world.item.component.DamageResistant;
import net.minecraft.world.item.component.ItemAttributeModifiers;
import net.minecraft.world.item.component.ItemContainerContents;
import net.minecraft.world.item.component.TooltipDisplay;
import net.minecraft.world.item.component.TooltipProvider;
import net.minecraft.world.item.component.UseCooldown;
import net.minecraft.world.item.component.UseRemainder;
import net.minecraft.world.item.component.Weapon;
import net.minecraft.world.item.component.WrittenBookContent;
import net.minecraft.world.item.context.UseOnContext;
import net.minecraft.world.item.enchantment.Enchantment;
import net.minecraft.world.item.enchantment.EnchantmentHelper;
import net.minecraft.world.item.enchantment.ItemEnchantments;
import net.minecraft.world.item.enchantment.Repairable;
import net.minecraft.world.item.equipment.Equippable;
import net.minecraft.world.level.ItemLike;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.Spawner;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.pattern.BlockInWorld;
import org.apache.commons.lang3.mutable.MutableBoolean;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
public final class ItemStack implements DataComponentHolder {
private static final List<Component> OP_NBT_WARNING = List.of(
Component.translatable("item.op_warning.line1").withStyle(ChatFormatting.RED, ChatFormatting.BOLD),
Component.translatable("item.op_warning.line2").withStyle(ChatFormatting.RED),
Component.translatable("item.op_warning.line3").withStyle(ChatFormatting.RED)
);
private static final Component UNBREAKABLE_TOOLTIP = Component.translatable("item.unbreakable").withStyle(ChatFormatting.BLUE);
public static final MapCodec<ItemStack> MAP_CODEC = MapCodec.recursive(
"ItemStack",
codec -> RecordCodecBuilder.mapCodec(
instance -> instance.group(
Item.CODEC.fieldOf("id").forGetter(ItemStack::getItemHolder),
ExtraCodecs.intRange(1, 99).fieldOf("count").orElse(1).forGetter(ItemStack::getCount),
DataComponentPatch.CODEC.optionalFieldOf("components", DataComponentPatch.EMPTY).forGetter(itemStack -> itemStack.components.asPatch())
)
.apply(instance, ItemStack::new)
)
);
public static final Codec<ItemStack> CODEC = Codec.lazyInitialized(MAP_CODEC::codec);
public static final Codec<ItemStack> SINGLE_ITEM_CODEC = Codec.lazyInitialized(
() -> RecordCodecBuilder.create(
instance -> instance.group(
Item.CODEC.fieldOf("id").forGetter(ItemStack::getItemHolder),
DataComponentPatch.CODEC.optionalFieldOf("components", DataComponentPatch.EMPTY).forGetter(itemStack -> itemStack.components.asPatch())
)
.apply(instance, (holder, dataComponentPatch) -> new ItemStack(holder, 1, dataComponentPatch))
)
);
public static final Codec<ItemStack> STRICT_CODEC = CODEC.validate(ItemStack::validateStrict);
public static final Codec<ItemStack> STRICT_SINGLE_ITEM_CODEC = SINGLE_ITEM_CODEC.validate(ItemStack::validateStrict);
public static final Codec<ItemStack> OPTIONAL_CODEC = ExtraCodecs.optionalEmptyMap(CODEC)
.xmap(optional -> (ItemStack)optional.orElse(ItemStack.EMPTY), itemStack -> itemStack.isEmpty() ? Optional.empty() : Optional.of(itemStack));
public static final Codec<ItemStack> SIMPLE_ITEM_CODEC = Item.CODEC.xmap(ItemStack::new, ItemStack::getItemHolder);
public static final StreamCodec<RegistryFriendlyByteBuf, ItemStack> OPTIONAL_STREAM_CODEC = createOptionalStreamCodec(DataComponentPatch.STREAM_CODEC);
public static final StreamCodec<RegistryFriendlyByteBuf, ItemStack> OPTIONAL_UNTRUSTED_STREAM_CODEC = createOptionalStreamCodec(
DataComponentPatch.DELIMITED_STREAM_CODEC
);
public static final StreamCodec<RegistryFriendlyByteBuf, ItemStack> STREAM_CODEC = new StreamCodec<RegistryFriendlyByteBuf, ItemStack>() {
public ItemStack decode(RegistryFriendlyByteBuf registryFriendlyByteBuf) {
ItemStack itemStack = ItemStack.OPTIONAL_STREAM_CODEC.decode(registryFriendlyByteBuf);
if (itemStack.isEmpty()) {
throw new DecoderException("Empty ItemStack not allowed");
} else {
return itemStack;
}
}
public void encode(RegistryFriendlyByteBuf registryFriendlyByteBuf, ItemStack itemStack) {
if (itemStack.isEmpty()) {
throw new EncoderException("Empty ItemStack not allowed");
} else {
ItemStack.OPTIONAL_STREAM_CODEC.encode(registryFriendlyByteBuf, itemStack);
}
}
};
public static final StreamCodec<RegistryFriendlyByteBuf, List<ItemStack>> OPTIONAL_LIST_STREAM_CODEC = OPTIONAL_STREAM_CODEC.apply(
ByteBufCodecs.collection(NonNullList::createWithCapacity)
);
private static final Logger LOGGER = LogUtils.getLogger();
public static final ItemStack EMPTY = new ItemStack((Void)null);
private static final Component DISABLED_ITEM_TOOLTIP = Component.translatable("item.disabled").withStyle(ChatFormatting.RED);
private int count;
private int popTime;
@Deprecated
@Nullable
private final Item item;
final PatchedDataComponentMap components;
/**
* The entity the item is attached to, like an Item Frame.
*/
@Nullable
private Entity entityRepresentation;
public static DataResult<ItemStack> validateStrict(ItemStack stack) {
DataResult<Unit> dataResult = validateComponents(stack.getComponents());
if (dataResult.isError()) {
return dataResult.map(unit -> stack);
} else {
return stack.getCount() > stack.getMaxStackSize()
? DataResult.error(() -> "Item stack with stack size of " + stack.getCount() + " was larger than maximum: " + stack.getMaxStackSize())
: DataResult.success(stack);
}
}
private static StreamCodec<RegistryFriendlyByteBuf, ItemStack> createOptionalStreamCodec(StreamCodec<RegistryFriendlyByteBuf, DataComponentPatch> codec) {
return new StreamCodec<RegistryFriendlyByteBuf, ItemStack>() {
public ItemStack decode(RegistryFriendlyByteBuf registryFriendlyByteBuf) {
int i = registryFriendlyByteBuf.readVarInt();
if (i <= 0) {
return ItemStack.EMPTY;
} else {
Holder<Item> holder = Item.STREAM_CODEC.decode(registryFriendlyByteBuf);
DataComponentPatch dataComponentPatch = codec.decode(registryFriendlyByteBuf);
return new ItemStack(holder, i, dataComponentPatch);
}
}
public void encode(RegistryFriendlyByteBuf registryFriendlyByteBuf, ItemStack itemStack) {
if (itemStack.isEmpty()) {
registryFriendlyByteBuf.writeVarInt(0);
} else {
registryFriendlyByteBuf.writeVarInt(itemStack.getCount());
Item.STREAM_CODEC.encode(registryFriendlyByteBuf, itemStack.getItemHolder());
codec.encode(registryFriendlyByteBuf, itemStack.components.asPatch());
}
}
};
}
public static StreamCodec<RegistryFriendlyByteBuf, ItemStack> validatedStreamCodec(StreamCodec<RegistryFriendlyByteBuf, ItemStack> codec) {
return new StreamCodec<RegistryFriendlyByteBuf, ItemStack>() {
public ItemStack decode(RegistryFriendlyByteBuf registryFriendlyByteBuf) {
ItemStack itemStack = codec.decode(registryFriendlyByteBuf);
if (!itemStack.isEmpty()) {
RegistryOps<Unit> registryOps = registryFriendlyByteBuf.registryAccess().createSerializationContext(NullOps.INSTANCE);
ItemStack.CODEC.encodeStart(registryOps, itemStack).getOrThrow(DecoderException::new);
}
return itemStack;
}
public void encode(RegistryFriendlyByteBuf registryFriendlyByteBuf, ItemStack itemStack) {
codec.encode(registryFriendlyByteBuf, itemStack);
}
};
}
public Optional<TooltipComponent> getTooltipImage() {
return this.getItem().getTooltipImage(this);
}
@Override
public DataComponentMap getComponents() {
return (DataComponentMap)(!this.isEmpty() ? this.components : DataComponentMap.EMPTY);
}
public DataComponentMap getPrototype() {
return !this.isEmpty() ? this.getItem().components() : DataComponentMap.EMPTY;
}
public DataComponentPatch getComponentsPatch() {
return !this.isEmpty() ? this.components.asPatch() : DataComponentPatch.EMPTY;
}
public DataComponentMap immutableComponents() {
return !this.isEmpty() ? this.components.toImmutableMap() : DataComponentMap.EMPTY;
}
public boolean hasNonDefault(DataComponentType<?> component) {
return !this.isEmpty() && this.components.hasNonDefault(component);
}
public ItemStack(ItemLike item) {
this(item, 1);
}
public ItemStack(Holder<Item> tag) {
this(tag.value(), 1);
}
public ItemStack(Holder<Item> tag, int count, DataComponentPatch components) {
this(tag.value(), count, PatchedDataComponentMap.fromPatch(tag.value().components(), components));
}
public ItemStack(Holder<Item> item, int count) {
this(item.value(), count);
}
public ItemStack(ItemLike item, int count) {
this(item, count, new PatchedDataComponentMap(item.asItem().components()));
}
private ItemStack(ItemLike item, int count, PatchedDataComponentMap components) {
this.item = item.asItem();
this.count = count;
this.components = components;
this.getItem().verifyComponentsAfterLoad(this);
}
private ItemStack(@Nullable Void unused) {
this.item = null;
this.components = new PatchedDataComponentMap(DataComponentMap.EMPTY);
}
public static DataResult<Unit> validateComponents(DataComponentMap components) {
if (components.has(DataComponents.MAX_DAMAGE) && components.getOrDefault(DataComponents.MAX_STACK_SIZE, 1) > 1) {
return DataResult.error(() -> "Item cannot be both damageable and stackable");
} else {
ItemContainerContents itemContainerContents = components.getOrDefault(DataComponents.CONTAINER, ItemContainerContents.EMPTY);
for (ItemStack itemStack : itemContainerContents.nonEmptyItems()) {
int i = itemStack.getCount();
int j = itemStack.getMaxStackSize();
if (i > j) {
return DataResult.error(() -> "Item stack with count of " + i + " was larger than maximum: " + j);
}
}
return DataResult.success(Unit.INSTANCE);
}
}
public static Optional<ItemStack> parse(HolderLookup.Provider lookupProvider, Tag tag) {
return CODEC.parse(lookupProvider.createSerializationContext(NbtOps.INSTANCE), tag)
.resultOrPartial(string -> LOGGER.error("Tried to load invalid item: '{}'", string));
}
public boolean isEmpty() {
return this == EMPTY || this.item == Items.AIR || this.count <= 0;
}
public boolean isItemEnabled(FeatureFlagSet enabledFlags) {
return this.isEmpty() || this.getItem().isEnabled(enabledFlags);
}
/**
* Splits off a stack of the given amount of this stack and reduces this stack by the amount.
*/
public ItemStack split(int amount) {
int i = Math.min(amount, this.getCount());
ItemStack itemStack = this.copyWithCount(i);
this.shrink(i);
return itemStack;
}
public ItemStack copyAndClear() {
if (this.isEmpty()) {
return EMPTY;
} else {
ItemStack itemStack = this.copy();
this.setCount(0);
return itemStack;
}
}
/**
* Returns the object corresponding to the stack.
*/
public Item getItem() {
return this.isEmpty() ? Items.AIR : this.item;
}
public Holder<Item> getItemHolder() {
return this.getItem().builtInRegistryHolder();
}
public boolean is(TagKey<Item> tag) {
return this.getItem().builtInRegistryHolder().is(tag);
}
public boolean is(Item item) {
return this.getItem() == item;
}
public boolean is(Predicate<Holder<Item>> item) {
return item.test(this.getItem().builtInRegistryHolder());
}
public boolean is(Holder<Item> item) {
return this.getItem().builtInRegistryHolder() == item;
}
public boolean is(HolderSet<Item> item) {
return item.contains(this.getItemHolder());
}
public Stream<TagKey<Item>> getTags() {
return this.getItem().builtInRegistryHolder().tags();
}
public InteractionResult useOn(UseOnContext context) {
Player player = context.getPlayer();
BlockPos blockPos = context.getClickedPos();
if (player != null && !player.getAbilities().mayBuild && !this.canPlaceOnBlockInAdventureMode(new BlockInWorld(context.getLevel(), blockPos, false))) {
return InteractionResult.PASS;
} else {
Item item = this.getItem();
InteractionResult interactionResult = item.useOn(context);
if (player != null && interactionResult instanceof Success success && success.wasItemInteraction()) {
player.awardStat(Stats.ITEM_USED.get(item));
}
return interactionResult;
}
}
public float getDestroySpeed(BlockState state) {
return this.getItem().getDestroySpeed(this, state);
}
public InteractionResult use(Level level, Player player, InteractionHand hand) {
ItemStack itemStack = this.copy();
boolean bl = this.getUseDuration(player) <= 0;
InteractionResult interactionResult = this.getItem().use(level, player, hand);
return (InteractionResult)(bl && interactionResult instanceof Success success
? success.heldItemTransformedTo(
success.heldItemTransformedTo() == null
? this.applyAfterUseComponentSideEffects(player, itemStack)
: success.heldItemTransformedTo().applyAfterUseComponentSideEffects(player, itemStack)
)
: interactionResult);
}
/**
* Called when the item in use count reach 0, e.g. item food eaten. Return the new ItemStack. Args : world, entity
*/
public ItemStack finishUsingItem(Level level, LivingEntity livingEntity) {
ItemStack itemStack = this.copy();
ItemStack itemStack2 = this.getItem().finishUsingItem(this, level, livingEntity);
return itemStack2.applyAfterUseComponentSideEffects(livingEntity, itemStack);
}
private ItemStack applyAfterUseComponentSideEffects(LivingEntity entity, ItemStack stack) {
UseRemainder useRemainder = stack.get(DataComponents.USE_REMAINDER);
UseCooldown useCooldown = stack.get(DataComponents.USE_COOLDOWN);
int i = stack.getCount();
ItemStack itemStack = this;
if (useRemainder != null) {
itemStack = useRemainder.convertIntoRemainder(this, i, entity.hasInfiniteMaterials(), entity::handleExtraItemsCreatedOnUse);
}
if (useCooldown != null) {
useCooldown.apply(stack, entity);
}
return itemStack;
}
public Tag save(HolderLookup.Provider levelRegistryAccess, Tag outputTag) {
if (this.isEmpty()) {
throw new IllegalStateException("Cannot encode empty ItemStack");
} else {
return CODEC.encode(this, levelRegistryAccess.createSerializationContext(NbtOps.INSTANCE), outputTag).getOrThrow();
}
}
public Tag save(HolderLookup.Provider levelRegistryAccess) {
if (this.isEmpty()) {
throw new IllegalStateException("Cannot encode empty ItemStack");
} else {
return CODEC.encodeStart(levelRegistryAccess.createSerializationContext(NbtOps.INSTANCE), this).getOrThrow();
}
}
/**
* Returns maximum size of the stack.
*/
public int getMaxStackSize() {
return this.getOrDefault(DataComponents.MAX_STACK_SIZE, 1);
}
/**
* Returns {@code true} if the {@code ItemStack} can hold 2 or more units of the item.
*/
public boolean isStackable() {
return this.getMaxStackSize() > 1 && (!this.isDamageableItem() || !this.isDamaged());
}
/**
* Returns {@code true} if this {@code ItemStack} is damageable.
*/
public boolean isDamageableItem() {
return this.has(DataComponents.MAX_DAMAGE) && !this.has(DataComponents.UNBREAKABLE) && this.has(DataComponents.DAMAGE);
}
/**
* Returns {@code true} when a damageable item is damaged.
*/
public boolean isDamaged() {
return this.isDamageableItem() && this.getDamageValue() > 0;
}
public int getDamageValue() {
return Mth.clamp(this.getOrDefault(DataComponents.DAMAGE, 0), 0, this.getMaxDamage());
}
public void setDamageValue(int damage) {
this.set(DataComponents.DAMAGE, Mth.clamp(damage, 0, this.getMaxDamage()));
}
/**
* Returns the max damage an item in the stack can take.
*/
public int getMaxDamage() {
return this.getOrDefault(DataComponents.MAX_DAMAGE, 0);
}
public boolean isBroken() {
return this.isDamageableItem() && this.getDamageValue() >= this.getMaxDamage();
}
public boolean nextDamageWillBreak() {
return this.isDamageableItem() && this.getDamageValue() >= this.getMaxDamage() - 1;
}
public void hurtAndBreak(int damage, ServerLevel level, @Nullable ServerPlayer player, Consumer<Item> onBreak) {
int i = this.processDurabilityChange(damage, level, player);
if (i != 0) {
this.applyDamage(this.getDamageValue() + i, player, onBreak);
}
}
private int processDurabilityChange(int damage, ServerLevel level, @Nullable ServerPlayer player) {
if (!this.isDamageableItem()) {
return 0;
} else if (player != null && player.hasInfiniteMaterials()) {
return 0;
} else {
return damage > 0 ? EnchantmentHelper.processDurabilityChange(level, this, damage) : damage;
}
}
private void applyDamage(int damage, @Nullable ServerPlayer player, Consumer<Item> onBreak) {
if (player != null) {
CriteriaTriggers.ITEM_DURABILITY_CHANGED.trigger(player, this, damage);
}
this.setDamageValue(damage);
if (this.isBroken()) {
Item item = this.getItem();
this.shrink(1);
onBreak.accept(item);
}
}
public void hurtWithoutBreaking(int damage, Player player) {
if (player instanceof ServerPlayer serverPlayer) {
int i = this.processDurabilityChange(damage, serverPlayer.serverLevel(), serverPlayer);
if (i == 0) {
return;
}
int j = Math.min(this.getDamageValue() + i, this.getMaxDamage() - 1);
this.applyDamage(j, serverPlayer, item -> {});
}
}
public void hurtAndBreak(int amount, LivingEntity entity, EquipmentSlot slot) {
if (entity.level() instanceof ServerLevel serverLevel) {
this.hurtAndBreak(amount, serverLevel, entity instanceof ServerPlayer serverPlayer ? serverPlayer : null, item -> entity.onEquippedItemBroken(item, slot));
}
}
public ItemStack hurtAndConvertOnBreak(int amount, ItemLike item, LivingEntity entity, EquipmentSlot slot) {
this.hurtAndBreak(amount, entity, slot);
if (this.isEmpty()) {
ItemStack itemStack = this.transmuteCopyIgnoreEmpty(item, 1);
if (itemStack.isDamageableItem()) {
itemStack.setDamageValue(0);
}
return itemStack;
} else {
return this;
}
}
public boolean isBarVisible() {
return this.getItem().isBarVisible(this);
}
public int getBarWidth() {
return this.getItem().getBarWidth(this);
}
public int getBarColor() {
return this.getItem().getBarColor(this);
}
public boolean overrideStackedOnOther(Slot slot, ClickAction action, Player player) {
return this.getItem().overrideStackedOnOther(this, slot, action, player);
}
public boolean overrideOtherStackedOnMe(ItemStack stack, Slot slot, ClickAction action, Player player, SlotAccess access) {
return this.getItem().overrideOtherStackedOnMe(this, stack, slot, action, player, access);
}
public boolean hurtEnemy(LivingEntity enemy, LivingEntity attacker) {
Item item = this.getItem();
item.hurtEnemy(this, enemy, attacker);
if (this.has(DataComponents.WEAPON)) {
if (attacker instanceof Player player) {
player.awardStat(Stats.ITEM_USED.get(item));
}
return true;
} else {
return false;
}
}
public void postHurtEnemy(LivingEntity enemy, LivingEntity attacker) {
this.getItem().postHurtEnemy(this, enemy, attacker);
Weapon weapon = this.get(DataComponents.WEAPON);
if (weapon != null) {
this.hurtAndBreak(weapon.itemDamagePerAttack(), attacker, EquipmentSlot.MAINHAND);
}
}
/**
* Called when a Block is destroyed using this ItemStack
*/
public void mineBlock(Level level, BlockState state, BlockPos pos, Player player) {
Item item = this.getItem();
if (item.mineBlock(this, level, state, pos, player)) {
player.awardStat(Stats.ITEM_USED.get(item));
}
}
/**
* Check whether the given Block can be harvested using this ItemStack.
*/
public boolean isCorrectToolForDrops(BlockState state) {
return this.getItem().isCorrectToolForDrops(this, state);
}
public InteractionResult interactLivingEntity(Player player, LivingEntity entity, InteractionHand usedHand) {
Equippable equippable = this.get(DataComponents.EQUIPPABLE);
if (equippable != null && equippable.equipOnInteract()) {
InteractionResult interactionResult = equippable.equipOnTarget(player, entity, this);
if (interactionResult != InteractionResult.PASS) {
return interactionResult;
}
}
return this.getItem().interactLivingEntity(this, player, entity, usedHand);
}
/**
* Returns a new stack with the same properties.
*/
public ItemStack copy() {
if (this.isEmpty()) {
return EMPTY;
} else {
ItemStack itemStack = new ItemStack(this.getItem(), this.count, this.components.copy());
itemStack.setPopTime(this.getPopTime());
return itemStack;
}
}
public ItemStack copyWithCount(int count) {
if (this.isEmpty()) {
return EMPTY;
} else {
ItemStack itemStack = this.copy();
itemStack.setCount(count);
return itemStack;
}
}
public ItemStack transmuteCopy(ItemLike item) {
return this.transmuteCopy(item, this.getCount());
}
public ItemStack transmuteCopy(ItemLike item, int count) {
return this.isEmpty() ? EMPTY : this.transmuteCopyIgnoreEmpty(item, count);
}
private ItemStack transmuteCopyIgnoreEmpty(ItemLike item, int count) {
return new ItemStack(item.asItem().builtInRegistryHolder(), count, this.components.asPatch());
}
/**
* Compares both {@code ItemStacks}, returns {@code true} if both {@code ItemStacks} are equal.
*/
public static boolean matches(ItemStack stack, ItemStack other) {
if (stack == other) {
return true;
} else {
return stack.getCount() != other.getCount() ? false : isSameItemSameComponents(stack, other);
}
}
@Deprecated
public static boolean listMatches(List<ItemStack> list, List<ItemStack> other) {
if (list.size() != other.size()) {
return false;
} else {
for (int i = 0; i < list.size(); i++) {
if (!matches((ItemStack)list.get(i), (ItemStack)other.get(i))) {
return false;
}
}
return true;
}
}
public static boolean isSameItem(ItemStack stack, ItemStack other) {
return stack.is(other.getItem());
}
public static boolean isSameItemSameComponents(ItemStack stack, ItemStack other) {
if (!stack.is(other.getItem())) {
return false;
} else {
return stack.isEmpty() && other.isEmpty() ? true : Objects.equals(stack.components, other.components);
}
}
public static MapCodec<ItemStack> lenientOptionalFieldOf(String fieldName) {
return CODEC.lenientOptionalFieldOf(fieldName)
.xmap(optional -> (ItemStack)optional.orElse(EMPTY), itemStack -> itemStack.isEmpty() ? Optional.empty() : Optional.of(itemStack));
}
public static int hashItemAndComponents(@Nullable ItemStack stack) {
if (stack != null) {
int i = 31 + stack.getItem().hashCode();
return 31 * i + stack.getComponents().hashCode();
} else {
return 0;
}
}
@Deprecated
public static int hashStackList(List<ItemStack> list) {
int i = 0;
for (ItemStack itemStack : list) {
i = i * 31 + hashItemAndComponents(itemStack);
}
return i;
}
public String toString() {
return this.getCount() + " " + this.getItem();
}
public void inventoryTick(Level level, Entity entity, @Nullable EquipmentSlot slot) {
if (this.popTime > 0) {
this.popTime--;
}
if (level instanceof ServerLevel serverLevel) {
this.getItem().inventoryTick(this, serverLevel, entity, slot);
}
}
public void onCraftedBy(Player player, int amount) {
player.awardStat(Stats.ITEM_CRAFTED.get(this.getItem()), amount);
this.getItem().onCraftedBy(this, player);
}
public void onCraftedBySystem(Level level) {
this.getItem().onCraftedPostProcess(this, level);
}
public int getUseDuration(LivingEntity entity) {
return this.getItem().getUseDuration(this, entity);
}
public ItemUseAnimation getUseAnimation() {
return this.getItem().getUseAnimation(this);
}
/**
* Called when the player releases the use item button.
*/
public void releaseUsing(Level level, LivingEntity livingEntity, int timeLeft) {
ItemStack itemStack = this.copy();
if (this.getItem().releaseUsing(this, level, livingEntity, timeLeft)) {
ItemStack itemStack2 = this.applyAfterUseComponentSideEffects(livingEntity, itemStack);
if (itemStack2 != this) {
livingEntity.setItemInHand(livingEntity.getUsedItemHand(), itemStack2);
}
}
}
public boolean useOnRelease() {
return this.getItem().useOnRelease(this);
}
@Nullable
public <T> T set(DataComponentType<T> component, @Nullable T value) {
return this.components.set(component, value);
}
public <T> void copyFrom(DataComponentType<T> componentType, DataComponentGetter componentGetter) {
this.set(componentType, componentGetter.get(componentType));
}
@Nullable
public <T, U> T update(DataComponentType<T> component, T defaultValue, U updateValue, BiFunction<T, U, T> updater) {
return this.set(component, (T)updater.apply(this.getOrDefault(component, defaultValue), updateValue));
}
@Nullable
public <T> T update(DataComponentType<T> component, T defaultValue, UnaryOperator<T> updater) {
T object = this.getOrDefault(component, defaultValue);
return this.set(component, (T)updater.apply(object));
}
@Nullable
public <T> T remove(DataComponentType<? extends T> component) {
return this.components.remove(component);
}
public void applyComponentsAndValidate(DataComponentPatch components) {
DataComponentPatch dataComponentPatch = this.components.asPatch();
this.components.applyPatch(components);
Optional<Error<ItemStack>> optional = validateStrict(this).error();
if (optional.isPresent()) {
LOGGER.error("Failed to apply component patch '{}' to item: '{}'", components, ((Error)optional.get()).message());
this.components.restorePatch(dataComponentPatch);
} else {
this.getItem().verifyComponentsAfterLoad(this);
}
}
public void applyComponents(DataComponentPatch components) {
this.components.applyPatch(components);
this.getItem().verifyComponentsAfterLoad(this);
}
public void applyComponents(DataComponentMap components) {
this.components.setAll(components);
this.getItem().verifyComponentsAfterLoad(this);
}
public Component getHoverName() {
Component component = this.getCustomName();
return component != null ? component : this.getItemName();
}
@Nullable
public Component getCustomName() {
Component component = this.get(DataComponents.CUSTOM_NAME);
if (component != null) {
return component;
} else {
WrittenBookContent writtenBookContent = this.get(DataComponents.WRITTEN_BOOK_CONTENT);
if (writtenBookContent != null) {
String string = writtenBookContent.title().raw();
if (!StringUtil.isBlank(string)) {
return Component.literal(string);
}
}
return null;
}
}
public Component getItemName() {
return this.getItem().getName(this);
}
public Component getStyledHoverName() {
MutableComponent mutableComponent = Component.empty().append(this.getHoverName()).withStyle(this.getRarity().color());
if (this.has(DataComponents.CUSTOM_NAME)) {
mutableComponent.withStyle(ChatFormatting.ITALIC);
}
return mutableComponent;
}
public <T extends TooltipProvider> void addToTooltip(
DataComponentType<T> component, Item.TooltipContext context, TooltipDisplay tooltipDisplay, Consumer<Component> tooltipAdder, TooltipFlag tooltipFlag
) {
T tooltipProvider = (T)this.get(component);
if (tooltipProvider != null && tooltipDisplay.shows(component)) {
tooltipProvider.addToTooltip(context, tooltipAdder, tooltipFlag, this.components);
}
}
public List<Component> getTooltipLines(Item.TooltipContext tooltipContext, @Nullable Player player, TooltipFlag tooltipFlag) {
TooltipDisplay tooltipDisplay = this.getOrDefault(DataComponents.TOOLTIP_DISPLAY, TooltipDisplay.DEFAULT);
if (!tooltipFlag.isCreative() && tooltipDisplay.hideTooltip()) {
boolean bl = this.getItem().shouldPrintOpWarning(this, player);
return bl ? OP_NBT_WARNING : List.of();
} else {
List<Component> list = Lists.<Component>newArrayList();
list.add(this.getStyledHoverName());
this.addDetailsToTooltip(tooltipContext, tooltipDisplay, player, tooltipFlag, list::add);
return list;
}
}
public void addDetailsToTooltip(
Item.TooltipContext context, TooltipDisplay tooltipDisplay, @Nullable Player playef, TooltipFlag tooltipFlag, Consumer<Component> tooltipAdder
) {
this.getItem().appendHoverText(this, context, tooltipDisplay, tooltipAdder, tooltipFlag);
this.addToTooltip(DataComponents.TROPICAL_FISH_PATTERN, context, tooltipDisplay, tooltipAdder, tooltipFlag);
this.addToTooltip(DataComponents.INSTRUMENT, context, tooltipDisplay, tooltipAdder, tooltipFlag);
this.addToTooltip(DataComponents.MAP_ID, context, tooltipDisplay, tooltipAdder, tooltipFlag);
this.addToTooltip(DataComponents.BEES, context, tooltipDisplay, tooltipAdder, tooltipFlag);
this.addToTooltip(DataComponents.CONTAINER_LOOT, context, tooltipDisplay, tooltipAdder, tooltipFlag);
this.addToTooltip(DataComponents.CONTAINER, context, tooltipDisplay, tooltipAdder, tooltipFlag);
this.addToTooltip(DataComponents.BANNER_PATTERNS, context, tooltipDisplay, tooltipAdder, tooltipFlag);
this.addToTooltip(DataComponents.POT_DECORATIONS, context, tooltipDisplay, tooltipAdder, tooltipFlag);
this.addToTooltip(DataComponents.WRITTEN_BOOK_CONTENT, context, tooltipDisplay, tooltipAdder, tooltipFlag);
this.addToTooltip(DataComponents.CHARGED_PROJECTILES, context, tooltipDisplay, tooltipAdder, tooltipFlag);
this.addToTooltip(DataComponents.FIREWORKS, context, tooltipDisplay, tooltipAdder, tooltipFlag);
this.addToTooltip(DataComponents.FIREWORK_EXPLOSION, context, tooltipDisplay, tooltipAdder, tooltipFlag);
this.addToTooltip(DataComponents.POTION_CONTENTS, context, tooltipDisplay, tooltipAdder, tooltipFlag);
this.addToTooltip(DataComponents.JUKEBOX_PLAYABLE, context, tooltipDisplay, tooltipAdder, tooltipFlag);
this.addToTooltip(DataComponents.TRIM, context, tooltipDisplay, tooltipAdder, tooltipFlag);
this.addToTooltip(DataComponents.STORED_ENCHANTMENTS, context, tooltipDisplay, tooltipAdder, tooltipFlag);
this.addToTooltip(DataComponents.ENCHANTMENTS, context, tooltipDisplay, tooltipAdder, tooltipFlag);
this.addToTooltip(DataComponents.DYED_COLOR, context, tooltipDisplay, tooltipAdder, tooltipFlag);
this.addToTooltip(DataComponents.LORE, context, tooltipDisplay, tooltipAdder, tooltipFlag);
this.addAttributeTooltips(tooltipAdder, tooltipDisplay, playef);
if (this.has(DataComponents.UNBREAKABLE) && tooltipDisplay.shows(DataComponents.UNBREAKABLE)) {
tooltipAdder.accept(UNBREAKABLE_TOOLTIP);
}
this.addToTooltip(DataComponents.OMINOUS_BOTTLE_AMPLIFIER, context, tooltipDisplay, tooltipAdder, tooltipFlag);
this.addToTooltip(DataComponents.SUSPICIOUS_STEW_EFFECTS, context, tooltipDisplay, tooltipAdder, tooltipFlag);
this.addToTooltip(DataComponents.BLOCK_STATE, context, tooltipDisplay, tooltipAdder, tooltipFlag);
if ((this.is(Items.SPAWNER) || this.is(Items.TRIAL_SPAWNER)) && tooltipDisplay.shows(DataComponents.BLOCK_ENTITY_DATA)) {
CustomData customData = this.getOrDefault(DataComponents.BLOCK_ENTITY_DATA, CustomData.EMPTY);
Spawner.appendHoverText(customData, tooltipAdder, "SpawnData");
}
AdventureModePredicate adventureModePredicate = this.get(DataComponents.CAN_BREAK);
if (adventureModePredicate != null && tooltipDisplay.shows(DataComponents.CAN_BREAK)) {
tooltipAdder.accept(CommonComponents.EMPTY);
tooltipAdder.accept(AdventureModePredicate.CAN_BREAK_HEADER);
adventureModePredicate.addToTooltip(tooltipAdder);
}
AdventureModePredicate adventureModePredicate2 = this.get(DataComponents.CAN_PLACE_ON);
if (adventureModePredicate2 != null && tooltipDisplay.shows(DataComponents.CAN_PLACE_ON)) {
tooltipAdder.accept(CommonComponents.EMPTY);
tooltipAdder.accept(AdventureModePredicate.CAN_PLACE_HEADER);
adventureModePredicate2.addToTooltip(tooltipAdder);
}
if (tooltipFlag.isAdvanced()) {
if (this.isDamaged() && tooltipDisplay.shows(DataComponents.DAMAGE)) {
tooltipAdder.accept(Component.translatable("item.durability", this.getMaxDamage() - this.getDamageValue(), this.getMaxDamage()));
}
tooltipAdder.accept(Component.literal(BuiltInRegistries.ITEM.getKey(this.getItem()).toString()).withStyle(ChatFormatting.DARK_GRAY));
int i = this.components.size();
if (i > 0) {
tooltipAdder.accept(Component.translatable("item.components", i).withStyle(ChatFormatting.DARK_GRAY));
}
}
if (playef != null && !this.getItem().isEnabled(playef.level().enabledFeatures())) {
tooltipAdder.accept(DISABLED_ITEM_TOOLTIP);
}
boolean bl = this.getItem().shouldPrintOpWarning(this, playef);
if (bl) {
OP_NBT_WARNING.forEach(tooltipAdder);
}
}
private void addAttributeTooltips(Consumer<Component> tooltipAdder, TooltipDisplay tooltipDisplay, @Nullable Player player) {
if (tooltipDisplay.shows(DataComponents.ATTRIBUTE_MODIFIERS)) {
for (EquipmentSlotGroup equipmentSlotGroup : EquipmentSlotGroup.values()) {
MutableBoolean mutableBoolean = new MutableBoolean(true);
this.forEachModifier(equipmentSlotGroup, (holder, attributeModifier) -> {
if (mutableBoolean.isTrue()) {
tooltipAdder.accept(CommonComponents.EMPTY);
tooltipAdder.accept(Component.translatable("item.modifiers." + equipmentSlotGroup.getSerializedName()).withStyle(ChatFormatting.GRAY));
mutableBoolean.setFalse();
}
this.addModifierTooltip(tooltipAdder, player, holder, attributeModifier);
});
}
}
}
private void addModifierTooltip(Consumer<Component> tooltipAdder, @Nullable Player player, Holder<Attribute> attribute, AttributeModifier modifier) {
double d = modifier.amount();
boolean bl = false;
if (player != null) {
if (modifier.is(Item.BASE_ATTACK_DAMAGE_ID)) {
d += player.getAttributeBaseValue(Attributes.ATTACK_DAMAGE);
bl = true;
} else if (modifier.is(Item.BASE_ATTACK_SPEED_ID)) {
d += player.getAttributeBaseValue(Attributes.ATTACK_SPEED);
bl = true;
}
}
double e;
if (modifier.operation() == AttributeModifier.Operation.ADD_MULTIPLIED_BASE || modifier.operation() == AttributeModifier.Operation.ADD_MULTIPLIED_TOTAL) {
e = d * 100.0;
} else if (attribute.is(Attributes.KNOCKBACK_RESISTANCE)) {
e = d * 10.0;
} else {
e = d;
}
if (bl) {
tooltipAdder.accept(
CommonComponents.space()
.append(
Component.translatable(
"attribute.modifier.equals." + modifier.operation().id(),
ItemAttributeModifiers.ATTRIBUTE_MODIFIER_FORMAT.format(e),
Component.translatable(attribute.value().getDescriptionId())
)
)
.withStyle(ChatFormatting.DARK_GREEN)
);
} else if (d > 0.0) {
tooltipAdder.accept(
Component.translatable(
"attribute.modifier.plus." + modifier.operation().id(),
ItemAttributeModifiers.ATTRIBUTE_MODIFIER_FORMAT.format(e),
Component.translatable(attribute.value().getDescriptionId())
)
.withStyle(attribute.value().getStyle(true))
);
} else if (d < 0.0) {
tooltipAdder.accept(
Component.translatable(
"attribute.modifier.take." + modifier.operation().id(),
ItemAttributeModifiers.ATTRIBUTE_MODIFIER_FORMAT.format(-e),
Component.translatable(attribute.value().getDescriptionId())
)
.withStyle(attribute.value().getStyle(false))
);
}
}
public boolean hasFoil() {
Boolean boolean_ = this.get(DataComponents.ENCHANTMENT_GLINT_OVERRIDE);
return boolean_ != null ? boolean_ : this.getItem().isFoil(this);
}
public Rarity getRarity() {
Rarity rarity = this.getOrDefault(DataComponents.RARITY, Rarity.COMMON);
if (!this.isEnchanted()) {
return rarity;
} else {
return switch (rarity) {
case COMMON, UNCOMMON -> Rarity.RARE;
case RARE -> Rarity.EPIC;
default -> rarity;
};
}
}
/**
* True if it is a tool and has no enchantments to begin with
*/
public boolean isEnchantable() {
if (!this.has(DataComponents.ENCHANTABLE)) {
return false;
} else {
ItemEnchantments itemEnchantments = this.get(DataComponents.ENCHANTMENTS);
return itemEnchantments != null && itemEnchantments.isEmpty();
}
}
public void enchant(Holder<Enchantment> enchantment, int level) {
EnchantmentHelper.updateEnchantments(this, mutable -> mutable.upgrade(enchantment, level));
}
/**
* True if the item has enchantment data
*/
public boolean isEnchanted() {
return !this.getOrDefault(DataComponents.ENCHANTMENTS, ItemEnchantments.EMPTY).isEmpty();
}
public ItemEnchantments getEnchantments() {
return this.getOrDefault(DataComponents.ENCHANTMENTS, ItemEnchantments.EMPTY);
}
/**
* Return whether this stack is on an item frame.
*/
public boolean isFramed() {
return this.entityRepresentation instanceof ItemFrame;
}
public void setEntityRepresentation(@Nullable Entity entity) {
if (!this.isEmpty()) {
this.entityRepresentation = entity;
}
}
/**
* Return the item frame this stack is on. Returns null if not on an item frame.
*/
@Nullable
public ItemFrame getFrame() {
return this.entityRepresentation instanceof ItemFrame ? (ItemFrame)this.getEntityRepresentation() : null;
}
/**
* For example, it'll return an {@code ItemFrameEntity} if it is in an itemframe.
*/
@Nullable
public Entity getEntityRepresentation() {
return !this.isEmpty() ? this.entityRepresentation : null;
}
public void forEachModifier(EquipmentSlotGroup slotGroup, BiConsumer<Holder<Attribute>, AttributeModifier> action) {
ItemAttributeModifiers itemAttributeModifiers = this.getOrDefault(DataComponents.ATTRIBUTE_MODIFIERS, ItemAttributeModifiers.EMPTY);
itemAttributeModifiers.forEach(slotGroup, action);
EnchantmentHelper.forEachModifier(this, slotGroup, action);
}
public void forEachModifier(EquipmentSlot equipmentSLot, BiConsumer<Holder<Attribute>, AttributeModifier> action) {
ItemAttributeModifiers itemAttributeModifiers = this.getOrDefault(DataComponents.ATTRIBUTE_MODIFIERS, ItemAttributeModifiers.EMPTY);
itemAttributeModifiers.forEach(equipmentSLot, action);
EnchantmentHelper.forEachModifier(this, equipmentSLot, action);
}
/**
* Get a ChatComponent for this Item's display name that shows this Item on hover
*/
public Component getDisplayName() {
MutableComponent mutableComponent = Component.empty().append(this.getHoverName());
if (this.has(DataComponents.CUSTOM_NAME)) {
mutableComponent.withStyle(ChatFormatting.ITALIC);
}
MutableComponent mutableComponent2 = ComponentUtils.wrapInSquareBrackets(mutableComponent);
if (!this.isEmpty()) {
mutableComponent2.withStyle(this.getRarity().color()).withStyle(style -> style.withHoverEvent(new HoverEvent.ShowItem(this)));
}
return mutableComponent2;
}
public boolean canPlaceOnBlockInAdventureMode(BlockInWorld block) {
AdventureModePredicate adventureModePredicate = this.get(DataComponents.CAN_PLACE_ON);
return adventureModePredicate != null && adventureModePredicate.test(block);
}
public boolean canBreakBlockInAdventureMode(BlockInWorld block) {
AdventureModePredicate adventureModePredicate = this.get(DataComponents.CAN_BREAK);
return adventureModePredicate != null && adventureModePredicate.test(block);
}
public int getPopTime() {
return this.popTime;
}
public void setPopTime(int popTime) {
this.popTime = popTime;
}
public int getCount() {
return this.isEmpty() ? 0 : this.count;
}
public void setCount(int count) {
this.count = count;
}
public void limitSize(int maxSize) {
if (!this.isEmpty() && this.getCount() > maxSize) {
this.setCount(maxSize);
}
}
public void grow(int increment) {
this.setCount(this.getCount() + increment);
}
public void shrink(int decrement) {
this.grow(-decrement);
}
public void consume(int amount, @Nullable LivingEntity entity) {
if (entity == null || !entity.hasInfiniteMaterials()) {
this.shrink(amount);
}
}
public ItemStack consumeAndReturn(int amount, @Nullable LivingEntity entity) {
ItemStack itemStack = this.copyWithCount(amount);
this.consume(amount, entity);
return itemStack;
}
/**
* Called as the stack is being used by an entity.
*/
public void onUseTick(Level level, LivingEntity livingEntity, int remainingUseDuration) {
Consumable consumable = this.get(DataComponents.CONSUMABLE);
if (consumable != null && consumable.shouldEmitParticlesAndSounds(remainingUseDuration)) {
consumable.emitParticlesAndSounds(livingEntity.getRandom(), livingEntity, this, 5);
}
this.getItem().onUseTick(level, livingEntity, this, remainingUseDuration);
}
public void onDestroyed(ItemEntity itemEntity) {
this.getItem().onDestroyed(itemEntity);
}
public boolean canBeHurtBy(DamageSource damageSource) {
DamageResistant damageResistant = this.get(DataComponents.DAMAGE_RESISTANT);
return damageResistant == null || !damageResistant.isResistantTo(damageSource);
}
public boolean isValidRepairItem(ItemStack item) {
Repairable repairable = this.get(DataComponents.REPAIRABLE);
return repairable != null && repairable.isValidRepairItem(item);
}
public boolean canDestroyBlock(BlockState state, Level level, BlockPos pos, Player player) {
return this.getItem().canDestroyBlock(this, state, level, pos, player);
}
}