package org.bukkit.potion; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableMap; import java.util.Map; import java.util.NoSuchElementException; import org.bukkit.Bukkit; import org.bukkit.Color; import org.bukkit.NamespacedKey; import org.bukkit.Registry; import org.bukkit.configuration.serialization.ConfigurationSerializable; import org.bukkit.configuration.serialization.SerializableAs; import org.bukkit.entity.LivingEntity; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; /** * Represents a potion effect, that can be added to a {@link LivingEntity}. A * potion effect has a duration that it will last for, an amplifier that will * enhance its effects, and a {@link PotionEffectType}, that represents its * effect on an entity. */ @SerializableAs("PotionEffect") public class PotionEffect implements ConfigurationSerializable { /** * A constant denoting infinite potion duration. */ public static final int INFINITE_DURATION = -1; private static final String HIDDEN_EFFECT = "hidden_effect"; // Paper private static final String AMPLIFIER = "amplifier"; private static final String DURATION = "duration"; private static final String TYPE = "effect"; private static final String AMBIENT = "ambient"; private static final String PARTICLES = "has-particles"; private static final String ICON = "has-icon"; private final int amplifier; private final int duration; private final PotionEffectType type; private final boolean ambient; private final boolean particles; private final boolean icon; private final PotionEffect hiddenEffect; // Paper /** * Creates a potion effect. * @param type effect type * @param duration measured in ticks, see {@link * PotionEffect#getDuration()} * @param amplifier the amplifier, see {@link PotionEffect#getAmplifier()} * @param ambient the ambient status, see {@link PotionEffect#isAmbient()} * @param particles the particle status, see {@link PotionEffect#hasParticles()} * @param icon the icon status, see {@link PotionEffect#hasIcon()} * @param hiddenEffect the hidden PotionEffect * @hidden Internal-- hidden effects are only shown internally */ @org.jetbrains.annotations.ApiStatus.Internal // Paper public PotionEffect(@NotNull PotionEffectType type, int duration, int amplifier, boolean ambient, boolean particles, boolean icon, @Nullable PotionEffect hiddenEffect) { // Paper Preconditions.checkArgument(type != null, "effect type cannot be null"); this.type = type; this.duration = duration; this.amplifier = amplifier; this.ambient = ambient; this.particles = particles; this.icon = icon; // Paper start this.hiddenEffect = hiddenEffect; } /** * Creates a potion effect. * @param type effect type * @param duration measured in ticks, see {@link * PotionEffect#getDuration()} * @param amplifier the amplifier, see {@link PotionEffect#getAmplifier()} * @param ambient the ambient status, see {@link PotionEffect#isAmbient()} * @param particles the particle status, see {@link PotionEffect#hasParticles()} * @param icon the icon status, see {@link PotionEffect#hasIcon()} */ public PotionEffect(@NotNull PotionEffectType type, int duration, int amplifier, boolean ambient, boolean particles, boolean icon) { this(type, duration, amplifier, ambient, particles, icon, null); // Paper end } /** * Creates a potion effect with no defined color. * * @param type effect type * @param duration measured in ticks, see {@link * PotionEffect#getDuration()} * @param amplifier the amplifier, see {@link PotionEffect#getAmplifier()} * @param ambient the ambient status, see {@link PotionEffect#isAmbient()} * @param particles the particle status, see {@link PotionEffect#hasParticles()} */ public PotionEffect(@NotNull PotionEffectType type, int duration, int amplifier, boolean ambient, boolean particles) { this(type, duration, amplifier, ambient, particles, particles); } /** * Creates a potion effect. Assumes that particles are visible * * @param type effect type * @param duration measured in ticks, see {@link * PotionEffect#getDuration()} * @param amplifier the amplifier, see {@link PotionEffect#getAmplifier()} * @param ambient the ambient status, see {@link PotionEffect#isAmbient()} */ public PotionEffect(@NotNull PotionEffectType type, int duration, int amplifier, boolean ambient) { this(type, duration, amplifier, ambient, true); } /** * Creates a potion effect. Assumes ambient is true. * * @param type Effect type * @param duration measured in ticks * @param amplifier the amplifier for the effect * @see PotionEffect#PotionEffect(PotionEffectType, int, int, boolean) */ public PotionEffect(@NotNull PotionEffectType type, int duration, int amplifier) { this(type, duration, amplifier, true); } /** * Constructor for deserialization. * * @param map the map to deserialize from */ public PotionEffect(@NotNull Map map) { this(getEffectType(map), getInt(map, DURATION), getInt(map, AMPLIFIER), getBool(map, AMBIENT, false), getBool(map, PARTICLES, true), getBool(map, ICON, getBool(map, PARTICLES, true)), (PotionEffect) map.get(HIDDEN_EFFECT)); // Paper } // Paper start @NotNull public PotionEffect withType(@NotNull PotionEffectType type) { return new PotionEffect(type, duration, amplifier, ambient, particles, icon); } @NotNull public PotionEffect withDuration(int duration) { return new PotionEffect(this.type, duration, amplifier, ambient, particles, icon); } @NotNull public PotionEffect withAmplifier(int amplifier) { return new PotionEffect(this.type, duration, amplifier, ambient, particles, icon); } @NotNull public PotionEffect withAmbient(boolean ambient) { return new PotionEffect(this.type, duration, amplifier, ambient, particles, icon); } @NotNull public PotionEffect withParticles(boolean particles) { return new PotionEffect(this.type, duration, amplifier, ambient, particles, icon); } @NotNull public PotionEffect withIcon(boolean icon) { return new PotionEffect(this.type, duration, amplifier, ambient, particles, icon); } /** * Returns the PotionEffect that will become active * after the current PotionEffect has run out. *

* Note: This value is only applicable to type applied to living entities. * * @return The hidden PotionEffect. */ @Nullable public PotionEffect getHiddenPotionEffect() { return hiddenEffect; } // Paper end @NotNull private static PotionEffectType getEffectType(@NotNull Map map) { PotionEffectType effect; if (map.get(TYPE) instanceof String value) { effect = Bukkit.getUnsafe().get(Registry.EFFECT, NamespacedKey.fromString(value)); } else { int type = getInt(map, TYPE); effect = PotionEffectType.getById(type); } if (effect != null) { return effect; } throw new NoSuchElementException(map + " does not contain " + TYPE); } private static int getInt(@NotNull Map map, @NotNull Object key) { Object num = map.get(key); if (num instanceof Integer) { return (Integer) num; } throw new NoSuchElementException(map + " does not contain " + key); } private static boolean getBool(@NotNull Map map, @NotNull Object key, boolean def) { Object bool = map.get(key); if (bool instanceof Boolean) { return (Boolean) bool; } return def; } @Override @NotNull public Map serialize() { ImmutableMap.Builder builder = ImmutableMap.builder() // Paper .put(TYPE, type.getKey().toString()) .put(DURATION, duration) .put(AMPLIFIER, amplifier) .put(AMBIENT, ambient) .put(PARTICLES, particles) .put(ICON, icon); // Paper start if (this.hiddenEffect != null) { builder.put(HIDDEN_EFFECT, this.hiddenEffect); } return builder.build(); // Paper end } /** * Attempts to add the effect represented by this object to the given * {@link LivingEntity}. *

* Note: {@link PotionEffect#getHiddenPotionEffect()} is ignored when * adding the effect to the entity. * * @param entity The entity to add this effect to * @return Whether the effect could be added * @see LivingEntity#addPotionEffect(PotionEffect) */ public boolean apply(@NotNull LivingEntity entity) { return entity.addPotionEffect(this); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof PotionEffect)) { return false; } PotionEffect that = (PotionEffect) obj; return this.type.equals(that.type) && this.ambient == that.ambient && this.amplifier == that.amplifier && this.duration == that.duration && this.particles == that.particles && this.icon == that.icon && java.util.Objects.equals(this.hiddenEffect, that.hiddenEffect); // Paper } /** * Returns the amplifier of this effect. A higher amplifier means the * potion effect happens more often over its duration and in some cases * has more effect on its target. * * @return The effect amplifier */ public int getAmplifier() { return amplifier; } /** * Returns the duration (in ticks) that this effect will run for when * applied to a {@link LivingEntity}. * * @return The duration of the effect, or {@value #INFINITE_DURATION} if * this effect is infinite * @see #isInfinite() */ public int getDuration() { return duration; } /** * Returns whether or not this potion effect has an infinite duration. Potion * effects with infinite durations will display an infinite symbol and never * expire unless manually removed. * * @return whether this duration is infinite or not */ public boolean isInfinite() { return duration == INFINITE_DURATION; } /** * Returns whether or not this potion effect has a shorter duration than the * provided potion effect. *

* An infinite duration is considered longer than non-infinite durations. If * both potion effects have infinite durations, then neither is shorter than * the other and this method will return false. * * @param other the other effect * @return true if this effect is shorter than the other, false if longer or equal */ public boolean isShorterThan(@NotNull PotionEffect other) { return !isInfinite() && (duration < other.duration || other.isInfinite()); } /** * Returns the {@link PotionEffectType} of this effect. * * @return The potion type of this effect */ @NotNull public PotionEffectType getType() { return type; } /** * Makes potion effect produce more, translucent, particles. * * @return if this effect is ambient */ public boolean isAmbient() { return ambient; } /** * @return whether this effect has particles or not */ public boolean hasParticles() { return particles; } /** * @return color of this potion's particles. May be null if the potion has no particles or defined color. * @deprecated color is not part of potion effects */ @Deprecated(since = "1.13") @Nullable @Contract("-> null") public Color getColor() { return null; } /** * @return whether this effect has an icon or not */ public boolean hasIcon() { return icon; } @Override public int hashCode() { int hash = 1; hash = hash * 31 + type.hashCode(); hash = hash * 31 + amplifier; hash = hash * 31 + duration; hash ^= 0x22222222 >> (ambient ? 1 : -1); hash ^= 0x22222222 >> (particles ? 1 : -1); hash ^= 0x22222222 >> (icon ? 1 : -1); if (hiddenEffect != null) hash = hash * 31 + hiddenEffect.hashCode(); // Paper return hash; } @Override public String toString() { return "PotionEffect{" + "amplifier=" + amplifier + ", duration=" + duration + ", type=" + type + ", ambient=" + ambient + ", particles=" + particles + ", icon=" + icon + ", hiddenEffect=" + hiddenEffect + '}'; // Paper } }