package net.minecraft.world.item.alchemy; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.mojang.datafixers.util.Pair; import com.mojang.serialization.Codec; import com.mojang.serialization.codecs.RecordCodecBuilder; import java.util.List; import java.util.Optional; import java.util.OptionalInt; import java.util.function.Consumer; import net.minecraft.ChatFormatting; import net.minecraft.Util; import net.minecraft.core.Holder; import net.minecraft.core.component.DataComponentGetter; import net.minecraft.core.component.DataComponents; import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.network.chat.CommonComponents; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.MutableComponent; import net.minecraft.network.codec.ByteBufCodecs; import net.minecraft.network.codec.StreamCodec; import net.minecraft.server.level.ServerLevel; import net.minecraft.util.ARGB; import net.minecraft.world.effect.MobEffect; import net.minecraft.world.effect.MobEffectInstance; import net.minecraft.world.effect.MobEffectUtil; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.ai.attributes.Attribute; import net.minecraft.world.entity.ai.attributes.AttributeModifier; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.TooltipFlag; import net.minecraft.world.item.component.Consumable; import net.minecraft.world.item.component.ConsumableListener; import net.minecraft.world.item.component.ItemAttributeModifiers; import net.minecraft.world.item.component.TooltipProvider; import net.minecraft.world.level.Level; public record PotionContents(Optional> potion, Optional customColor, List customEffects, Optional customName) implements ConsumableListener, TooltipProvider { public static final PotionContents EMPTY = new PotionContents(Optional.empty(), Optional.empty(), List.of(), Optional.empty()); private static final Component NO_EFFECT = Component.translatable("effect.none").withStyle(ChatFormatting.GRAY); public static final int BASE_POTION_COLOR = -13083194; private static final Codec FULL_CODEC = RecordCodecBuilder.create( instance -> instance.group( Potion.CODEC.optionalFieldOf("potion").forGetter(PotionContents::potion), Codec.INT.optionalFieldOf("custom_color").forGetter(PotionContents::customColor), MobEffectInstance.CODEC.listOf().optionalFieldOf("custom_effects", List.of()).forGetter(PotionContents::customEffects), Codec.STRING.optionalFieldOf("custom_name").forGetter(PotionContents::customName) ) .apply(instance, PotionContents::new) ); public static final Codec CODEC = Codec.withAlternative(FULL_CODEC, Potion.CODEC, PotionContents::new); public static final StreamCodec STREAM_CODEC = StreamCodec.composite( Potion.STREAM_CODEC.apply(ByteBufCodecs::optional), PotionContents::potion, ByteBufCodecs.INT.apply(ByteBufCodecs::optional), PotionContents::customColor, MobEffectInstance.STREAM_CODEC.apply(ByteBufCodecs.list()), PotionContents::customEffects, ByteBufCodecs.STRING_UTF8.apply(ByteBufCodecs::optional), PotionContents::customName, PotionContents::new ); public PotionContents(Holder potion) { this(Optional.of(potion), Optional.empty(), List.of(), Optional.empty()); } public static ItemStack createItemStack(Item item, Holder potion) { ItemStack itemStack = new ItemStack(item); itemStack.set(DataComponents.POTION_CONTENTS, new PotionContents(potion)); return itemStack; } public boolean is(Holder potion) { return this.potion.isPresent() && ((Holder)this.potion.get()).is(potion) && this.customEffects.isEmpty(); } public Iterable getAllEffects() { if (this.potion.isEmpty()) { return this.customEffects; } else { return (Iterable)(this.customEffects.isEmpty() ? ((Potion)((Holder)this.potion.get()).value()).getEffects() : Iterables.concat(((Potion)((Holder)this.potion.get()).value()).getEffects(), this.customEffects)); } } public void forEachEffect(Consumer action, float durationScale) { if (this.potion.isPresent()) { for (MobEffectInstance mobEffectInstance : ((Potion)((Holder)this.potion.get()).value()).getEffects()) { action.accept(mobEffectInstance.withScaledDuration(durationScale)); } } for (MobEffectInstance mobEffectInstance : this.customEffects) { action.accept(mobEffectInstance.withScaledDuration(durationScale)); } } public PotionContents withPotion(Holder potion) { return new PotionContents(Optional.of(potion), this.customColor, this.customEffects, this.customName); } public PotionContents withEffectAdded(MobEffectInstance effect) { return new PotionContents(this.potion, this.customColor, Util.copyAndAdd(this.customEffects, effect), this.customName); } public int getColor() { return this.getColorOr(-13083194); } public int getColorOr(int defaultValue) { return this.customColor.isPresent() ? (Integer)this.customColor.get() : getColorOptional(this.getAllEffects()).orElse(defaultValue); } public Component getName(String name) { String string = (String)this.customName.or(() -> this.potion.map(holder -> ((Potion)holder.value()).name())).orElse("empty"); return Component.translatable(name + string); } public static OptionalInt getColorOptional(Iterable effects) { int i = 0; int j = 0; int k = 0; int l = 0; for (MobEffectInstance mobEffectInstance : effects) { if (mobEffectInstance.isVisible()) { int m = mobEffectInstance.getEffect().value().getColor(); int n = mobEffectInstance.getAmplifier() + 1; i += n * ARGB.red(m); j += n * ARGB.green(m); k += n * ARGB.blue(m); l += n; } } return l == 0 ? OptionalInt.empty() : OptionalInt.of(ARGB.color(i / l, j / l, k / l)); } public boolean hasEffects() { return !this.customEffects.isEmpty() ? true : this.potion.isPresent() && !((Potion)((Holder)this.potion.get()).value()).getEffects().isEmpty(); } public List customEffects() { return Lists.transform(this.customEffects, MobEffectInstance::new); } public void applyToLivingEntity(LivingEntity entity, float durationScale) { if (entity.level() instanceof ServerLevel serverLevel) { Player player2 = entity instanceof Player player ? player : null; this.forEachEffect(mobEffectInstance -> { if (mobEffectInstance.getEffect().value().isInstantenous()) { mobEffectInstance.getEffect().value().applyInstantenousEffect(serverLevel, player2, player2, entity, mobEffectInstance.getAmplifier(), 1.0); } else { entity.addEffect(mobEffectInstance); } }, durationScale); } } public static void addPotionTooltip(Iterable effects, Consumer tooltipAdder, float durationFactor, float ticksPerSecond) { List, AttributeModifier>> list = Lists., AttributeModifier>>newArrayList(); boolean bl = true; for (MobEffectInstance mobEffectInstance : effects) { bl = false; Holder holder = mobEffectInstance.getEffect(); int i = mobEffectInstance.getAmplifier(); holder.value().createModifiers(i, (holderx, attributeModifierx) -> list.add(new Pair<>(holderx, attributeModifierx))); MutableComponent mutableComponent = getPotionDescription(holder, i); if (!mobEffectInstance.endsWithin(20)) { mutableComponent = Component.translatable( "potion.withDuration", mutableComponent, MobEffectUtil.formatDuration(mobEffectInstance, durationFactor, ticksPerSecond) ); } tooltipAdder.accept(mutableComponent.withStyle(holder.value().getCategory().getTooltipFormatting())); } if (bl) { tooltipAdder.accept(NO_EFFECT); } if (!list.isEmpty()) { tooltipAdder.accept(CommonComponents.EMPTY); tooltipAdder.accept(Component.translatable("potion.whenDrank").withStyle(ChatFormatting.DARK_PURPLE)); for (Pair, AttributeModifier> pair : list) { AttributeModifier attributeModifier = pair.getSecond(); double d = attributeModifier.amount(); double e; if (attributeModifier.operation() != AttributeModifier.Operation.ADD_MULTIPLIED_BASE && attributeModifier.operation() != AttributeModifier.Operation.ADD_MULTIPLIED_TOTAL) { e = attributeModifier.amount(); } else { e = attributeModifier.amount() * 100.0; } if (d > 0.0) { tooltipAdder.accept( Component.translatable( "attribute.modifier.plus." + attributeModifier.operation().id(), ItemAttributeModifiers.ATTRIBUTE_MODIFIER_FORMAT.format(e), Component.translatable(pair.getFirst().value().getDescriptionId()) ) .withStyle(ChatFormatting.BLUE) ); } else if (d < 0.0) { e *= -1.0; tooltipAdder.accept( Component.translatable( "attribute.modifier.take." + attributeModifier.operation().id(), ItemAttributeModifiers.ATTRIBUTE_MODIFIER_FORMAT.format(e), Component.translatable(pair.getFirst().value().getDescriptionId()) ) .withStyle(ChatFormatting.RED) ); } } } } public static MutableComponent getPotionDescription(Holder effect, int level) { MutableComponent mutableComponent = Component.translatable(effect.value().getDescriptionId()); return level > 0 ? Component.translatable("potion.withAmplifier", mutableComponent, Component.translatable("potion.potency." + level)) : mutableComponent; } @Override public void onConsume(Level level, LivingEntity entity, ItemStack stack, Consumable consumable) { this.applyToLivingEntity(entity, stack.getOrDefault(DataComponents.POTION_DURATION_SCALE, 1.0F)); } @Override public void addToTooltip(Item.TooltipContext context, Consumer tooltipAdder, TooltipFlag flag, DataComponentGetter componentGetter) { addPotionTooltip(this.getAllEffects(), tooltipAdder, componentGetter.getOrDefault(DataComponents.POTION_DURATION_SCALE, 1.0F), context.tickRate()); } }