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 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 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 CODEC = Codec.lazyInitialized(MAP_CODEC::codec); public static final Codec 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 STRICT_CODEC = CODEC.validate(ItemStack::validateStrict); public static final Codec STRICT_SINGLE_ITEM_CODEC = SINGLE_ITEM_CODEC.validate(ItemStack::validateStrict); public static final Codec OPTIONAL_CODEC = ExtraCodecs.optionalEmptyMap(CODEC) .xmap(optional -> (ItemStack)optional.orElse(ItemStack.EMPTY), itemStack -> itemStack.isEmpty() ? Optional.empty() : Optional.of(itemStack)); public static final Codec SIMPLE_ITEM_CODEC = Item.CODEC.xmap(ItemStack::new, ItemStack::getItemHolder); public static final StreamCodec OPTIONAL_STREAM_CODEC = createOptionalStreamCodec(DataComponentPatch.STREAM_CODEC); public static final StreamCodec OPTIONAL_UNTRUSTED_STREAM_CODEC = createOptionalStreamCodec( DataComponentPatch.DELIMITED_STREAM_CODEC ); public static final StreamCodec STREAM_CODEC = new StreamCodec() { 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> 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 validateStrict(ItemStack stack) { DataResult 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 createOptionalStreamCodec(StreamCodec codec) { return new StreamCodec() { public ItemStack decode(RegistryFriendlyByteBuf registryFriendlyByteBuf) { int i = registryFriendlyByteBuf.readVarInt(); if (i <= 0) { return ItemStack.EMPTY; } else { Holder 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 validatedStreamCodec(StreamCodec codec) { return new StreamCodec() { public ItemStack decode(RegistryFriendlyByteBuf registryFriendlyByteBuf) { ItemStack itemStack = codec.decode(registryFriendlyByteBuf); if (!itemStack.isEmpty()) { RegistryOps 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 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 tag) { this(tag.value(), 1); } public ItemStack(Holder tag, int count, DataComponentPatch components) { this(tag.value(), count, PatchedDataComponentMap.fromPatch(tag.value().components(), components)); } public ItemStack(Holder 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 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 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 getItemHolder() { return this.getItem().builtInRegistryHolder(); } public boolean is(TagKey tag) { return this.getItem().builtInRegistryHolder().is(tag); } public boolean is(Item item) { return this.getItem() == item; } public boolean is(Predicate> item) { return item.test(this.getItem().builtInRegistryHolder()); } public boolean is(Holder item) { return this.getItem().builtInRegistryHolder() == item; } public boolean is(HolderSet item) { return item.contains(this.getItemHolder()); } public Stream> 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 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 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 list, List 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 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 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 set(DataComponentType component, @Nullable T value) { return this.components.set(component, value); } public void copyFrom(DataComponentType componentType, DataComponentGetter componentGetter) { this.set(componentType, componentGetter.get(componentType)); } @Nullable public T update(DataComponentType component, T defaultValue, U updateValue, BiFunction updater) { return this.set(component, (T)updater.apply(this.getOrDefault(component, defaultValue), updateValue)); } @Nullable public T update(DataComponentType component, T defaultValue, UnaryOperator updater) { T object = this.getOrDefault(component, defaultValue); return this.set(component, (T)updater.apply(object)); } @Nullable public T remove(DataComponentType component) { return this.components.remove(component); } public void applyComponentsAndValidate(DataComponentPatch components) { DataComponentPatch dataComponentPatch = this.components.asPatch(); this.components.applyPatch(components); Optional> 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 void addToTooltip( DataComponentType component, Item.TooltipContext context, TooltipDisplay tooltipDisplay, Consumer tooltipAdder, TooltipFlag tooltipFlag ) { T tooltipProvider = (T)this.get(component); if (tooltipProvider != null && tooltipDisplay.shows(component)) { tooltipProvider.addToTooltip(context, tooltipAdder, tooltipFlag, this.components); } } public List 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 list = Lists.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 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 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 tooltipAdder, @Nullable Player player, Holder 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, 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, 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, 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); } }