1208 lines
44 KiB
Java
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);
|
|
}
|
|
}
|