package net.minecraft.world.effect; import com.mojang.serialization.Codec; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import java.util.Map; import java.util.Optional; import java.util.Map.Entry; import java.util.function.BiConsumer; import java.util.function.Function; import net.minecraft.Util; import net.minecraft.core.Holder; import net.minecraft.core.particles.ColorParticleOption; import net.minecraft.core.particles.ParticleOptions; import net.minecraft.core.particles.ParticleTypes; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.core.registries.Registries; import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.network.chat.Component; import net.minecraft.network.codec.ByteBufCodecs; import net.minecraft.network.codec.StreamCodec; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerLevel; import net.minecraft.sounds.SoundEvent; import net.minecraft.util.ARGB; import net.minecraft.util.Mth; import net.minecraft.world.damagesource.DamageSource; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.ai.attributes.Attribute; import net.minecraft.world.entity.ai.attributes.AttributeInstance; import net.minecraft.world.entity.ai.attributes.AttributeMap; import net.minecraft.world.entity.ai.attributes.AttributeModifier; import net.minecraft.world.flag.FeatureElement; import net.minecraft.world.flag.FeatureFlag; import net.minecraft.world.flag.FeatureFlagSet; import net.minecraft.world.flag.FeatureFlags; import org.jetbrains.annotations.Nullable; public class MobEffect implements FeatureElement { public static final Codec> CODEC = BuiltInRegistries.MOB_EFFECT.holderByNameCodec(); public static final StreamCodec> STREAM_CODEC = ByteBufCodecs.holderRegistry(Registries.MOB_EFFECT); private static final int AMBIENT_ALPHA = Mth.floor(38.25F); /** * Contains a Map of the AttributeModifiers registered by potions */ private final Map, MobEffect.AttributeTemplate> attributeModifiers = new Object2ObjectOpenHashMap<>(); private final MobEffectCategory category; private final int color; private final Function particleFactory; @Nullable private String descriptionId; private int blendInDurationTicks; private int blendOutDurationTicks; private int blendOutAdvanceTicks; private Optional soundOnAdded = Optional.empty(); private FeatureFlagSet requiredFeatures = FeatureFlags.VANILLA_SET; protected MobEffect(MobEffectCategory category, int color) { this.category = category; this.color = color; this.particleFactory = mobEffectInstance -> { int j = mobEffectInstance.isAmbient() ? AMBIENT_ALPHA : 255; return ColorParticleOption.create(ParticleTypes.ENTITY_EFFECT, ARGB.color(j, color)); }; } protected MobEffect(MobEffectCategory category, int color, ParticleOptions particle) { this.category = category; this.color = color; this.particleFactory = mobEffectInstance -> particle; } public int getBlendInDurationTicks() { return this.blendInDurationTicks; } public int getBlendOutDurationTicks() { return this.blendOutDurationTicks; } public int getBlendOutAdvanceTicks() { return this.blendOutAdvanceTicks; } public boolean applyEffectTick(ServerLevel level, LivingEntity entity, int amplifier) { return true; } public void applyInstantenousEffect( ServerLevel level, @Nullable Entity source, @Nullable Entity indirectSource, LivingEntity entity, int amplifier, double health ) { this.applyEffectTick(level, entity, amplifier); } public boolean shouldApplyEffectTickThisTick(int duration, int amplifier) { return false; } public void onEffectStarted(LivingEntity entity, int amplifier) { } public void onEffectAdded(LivingEntity entity, int amplifier) { this.soundOnAdded .ifPresent(soundEvent -> entity.level().playSound(null, entity.getX(), entity.getY(), entity.getZ(), soundEvent, entity.getSoundSource(), 1.0F, 1.0F)); } public void onMobRemoved(ServerLevel level, LivingEntity entity, int amplifier, Entity.RemovalReason reason) { } public void onMobHurt(ServerLevel level, LivingEntity entity, int amplifier, DamageSource damageSource, float amount) { } /** * Returns {@code true} if the potion has an instant effect instead of a continuous one (e.g. Harming) */ public boolean isInstantenous() { return false; } protected String getOrCreateDescriptionId() { if (this.descriptionId == null) { this.descriptionId = Util.makeDescriptionId("effect", BuiltInRegistries.MOB_EFFECT.getKey(this)); } return this.descriptionId; } /** * Returns the name of the effect. */ public String getDescriptionId() { return this.getOrCreateDescriptionId(); } public Component getDisplayName() { return Component.translatable(this.getDescriptionId()); } public MobEffectCategory getCategory() { return this.category; } /** * Returns the color of the potion liquid. */ public int getColor() { return this.color; } public MobEffect addAttributeModifier(Holder attribute, ResourceLocation id, double amount, AttributeModifier.Operation operation) { this.attributeModifiers.put(attribute, new MobEffect.AttributeTemplate(id, amount, operation)); return this; } public MobEffect setBlendDuration(int blendDuration) { return this.setBlendDuration(blendDuration, blendDuration, blendDuration); } public MobEffect setBlendDuration(int blendInDurationTicks, int blendOutDurationTicks, int blendOutAdvanceTicks) { this.blendInDurationTicks = blendInDurationTicks; this.blendOutDurationTicks = blendOutDurationTicks; this.blendOutAdvanceTicks = blendOutAdvanceTicks; return this; } public void createModifiers(int amplifier, BiConsumer, AttributeModifier> output) { this.attributeModifiers.forEach((holder, attributeTemplate) -> output.accept(holder, attributeTemplate.create(amplifier))); } public void removeAttributeModifiers(AttributeMap attributeMap) { for (Entry, MobEffect.AttributeTemplate> entry : this.attributeModifiers.entrySet()) { AttributeInstance attributeInstance = attributeMap.getInstance((Holder)entry.getKey()); if (attributeInstance != null) { attributeInstance.removeModifier(((MobEffect.AttributeTemplate)entry.getValue()).id()); } } } public void addAttributeModifiers(AttributeMap attributeMap, int amplifier) { for (Entry, MobEffect.AttributeTemplate> entry : this.attributeModifiers.entrySet()) { AttributeInstance attributeInstance = attributeMap.getInstance((Holder)entry.getKey()); if (attributeInstance != null) { attributeInstance.removeModifier(((MobEffect.AttributeTemplate)entry.getValue()).id()); attributeInstance.addPermanentModifier(((MobEffect.AttributeTemplate)entry.getValue()).create(amplifier)); } } } /** * Get if the potion is beneficial to the player. Beneficial potions are shown on the first row of the HUD */ public boolean isBeneficial() { return this.category == MobEffectCategory.BENEFICIAL; } public ParticleOptions createParticleOptions(MobEffectInstance effect) { return (ParticleOptions)this.particleFactory.apply(effect); } public MobEffect withSoundOnAdded(SoundEvent sound) { this.soundOnAdded = Optional.of(sound); return this; } public MobEffect requiredFeatures(FeatureFlag... requiredFeatures) { this.requiredFeatures = FeatureFlags.REGISTRY.subset(requiredFeatures); return this; } @Override public FeatureFlagSet requiredFeatures() { return this.requiredFeatures; } record AttributeTemplate(ResourceLocation id, double amount, AttributeModifier.Operation operation) { public AttributeModifier create(int level) { return new AttributeModifier(this.id, this.amount * (level + 1), this.operation); } } }