diff --git a/src/main/java/org/skriptlang/skript/bukkit/potion/elements/expressions/ExprPotionDuration.java b/src/main/java/org/skriptlang/skript/bukkit/potion/elements/expressions/ExprPotionDuration.java index 46351627606..b7b930ab6a4 100644 --- a/src/main/java/org/skriptlang/skript/bukkit/potion/elements/expressions/ExprPotionDuration.java +++ b/src/main/java/org/skriptlang/skript/bukkit/potion/elements/expressions/ExprPotionDuration.java @@ -11,6 +11,7 @@ import ch.njol.util.Math2; import ch.njol.util.coll.CollectionUtils; import org.bukkit.event.Event; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; import org.skriptlang.skript.bukkit.potion.util.PotionUtils; import org.skriptlang.skript.bukkit.potion.util.SkriptPotionEffect; @@ -63,7 +64,8 @@ public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { * @param change The timespan delta. * @param mode The mode of change to perform. */ - static void changeSafe(SkriptPotionEffect potionEffect, Timespan change, ChangeMode mode) { + @ApiStatus.Internal + public static void changeSafe(SkriptPotionEffect potionEffect, Timespan change, ChangeMode mode) { Timespan duration; if (mode == ChangeMode.SET || mode == ChangeMode.RESET) { duration = change; diff --git a/src/main/java/org/skriptlang/skript/bukkit/potion/elements/expressions/ExprPotionEffect.java b/src/main/java/org/skriptlang/skript/bukkit/potion/elements/expressions/ExprPotionEffect.java index 3f119c04745..1e34fa50ded 100644 --- a/src/main/java/org/skriptlang/skript/bukkit/potion/elements/expressions/ExprPotionEffect.java +++ b/src/main/java/org/skriptlang/skript/bukkit/potion/elements/expressions/ExprPotionEffect.java @@ -1,7 +1,6 @@ package org.skriptlang.skript.bukkit.potion.elements.expressions; import ch.njol.skript.Skript; -import ch.njol.skript.aliases.ItemType; import ch.njol.skript.classes.Changer.ChangeMode; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Example; @@ -15,20 +14,17 @@ import ch.njol.skript.util.Timespan; import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; +import org.bukkit.entity.Entity; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; -import org.skriptlang.skript.bukkit.potion.elements.expressions.ExprPotionEffects.State; -import org.skriptlang.skript.bukkit.potion.util.PotionUtils; +import org.skriptlang.skript.bukkit.potion.providers.PotionEffectProvider; +import org.skriptlang.skript.bukkit.potion.providers.PotionEffectProvider.RetrievalState; import org.skriptlang.skript.bukkit.potion.util.SkriptPotionEffect; -import org.bukkit.entity.LivingEntity; import org.bukkit.event.Event; -import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffectType; import org.skriptlang.skript.registration.SyntaxRegistry; -import java.util.ArrayDeque; import java.util.ArrayList; -import java.util.Deque; import java.util.List; @Name("Potion Effect of Entity/Item") @@ -50,23 +46,23 @@ public class ExprPotionEffect extends PropertyExpression types; - private State state; + private RetrievalState state; @Override public boolean init(Expression[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { //noinspection unchecked types = (Expression) expressions[matchedPattern % 2]; setExpr(expressions[(matchedPattern + 1) % 2]); - state = State.fromParseTag(parseResult.tags.isEmpty() ? "" : parseResult.tags.getFirst()); - if (state.includesHidden() && ItemType.class.isAssignableFrom(getExpr().getReturnType())) { - Skript.error("Items (such as potions or stews) do not have hidden effects"); + state = RetrievalState.fromParseTag(parseResult.tags.isEmpty() ? "" : parseResult.tags.getFirst()); + if (state.includesHidden() && !getExpr().canReturn(Entity.class)) { + Skript.error("Only living entities have hidden effects"); return false; } return true; @@ -77,34 +73,8 @@ protected SkriptPotionEffect[] get(Event event, Object[] source) { List potionEffects = new ArrayList<>(); PotionEffectType[] types = this.types.getArray(event); for (Object object : source) { - if (object instanceof LivingEntity livingEntity) { - for (PotionEffectType type : types) { - PotionEffect potionEffect = livingEntity.getPotionEffect(type); - if (potionEffect == null) { - continue; - } - if (state.includesActive()) { - potionEffects.add(SkriptPotionEffect.fromBukkitEffect(potionEffect, livingEntity)); - } - if (state.includesHidden()) { - PotionEffect hiddenEffect = potionEffect.getHiddenPotionEffect(); - while (hiddenEffect != null) { - // do not set source for hidden effects - potionEffects.add(SkriptPotionEffect.fromBukkitEffect(hiddenEffect)); - hiddenEffect = hiddenEffect.getHiddenPotionEffect(); - } - } - } - } else if (object instanceof ItemType itemType) { - for (PotionEffect effect : PotionUtils.getPotionEffects(itemType)) { - for (PotionEffectType type : types) { - if (type.equals(effect.getType())) { - potionEffects.add(SkriptPotionEffect.fromBukkitEffect(effect, itemType)); - break; - } - } - } - } + potionEffects.addAll(PotionEffectProvider.of(object, this::error) + .get(types, state)); } return potionEffects.toArray(new SkriptPotionEffect[0]); } @@ -130,23 +100,15 @@ public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { switch (mode) { case DELETE, RESET -> { for (Object holder : holders) { - if (holder instanceof LivingEntity entity) { - reset(entity, types); - } else if (holder instanceof ItemType itemType) { - PotionUtils.removePotionEffects(itemType, types); - } + PotionEffectProvider.of(holder, this::error) + .clear(types, state); } } case ADD, REMOVE -> { assert delta != null; for (Object holder : holders) { - if (holder instanceof LivingEntity entity) { - modify(entity, types, delta, mode); - } else if (holder instanceof ItemType itemType) { - if (delta[0] instanceof Timespan change) { - modify(itemType, types, change, mode); - } - } + PotionEffectProvider.of(holder, this::error) + .modify(types, state, delta, mode); } } default -> { @@ -155,98 +117,6 @@ public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { } } - private void reset(LivingEntity entity, PotionEffectType[] types) { - if (state == State.ACTIVE) { // preserve hidden effects - for (PotionEffectType type : types) { - PotionEffect potionEffect = entity.getPotionEffect(type); - if (potionEffect == null) { - continue; - } - Deque hiddenEffects = PotionUtils.getHiddenEffects(potionEffect); - entity.removePotionEffect(type); - entity.addPotionEffects(hiddenEffects); - } - } else if (state == State.HIDDEN) { // preserve active effect - for (PotionEffectType type : types) { - PotionEffect original = entity.getPotionEffect(type); - entity.removePotionEffect(type); - if (original != null) { - // applying a potion effect ignores the hidden effect value - entity.addPotionEffect(original); - } - } - } else { - for (PotionEffectType type : types) { - entity.removePotionEffect(type); - } - } - } - - private void modify(LivingEntity entity, PotionEffectType[] types, Object[] delta, ChangeMode mode) { - for (PotionEffectType type : types) { - PotionEffect potionEffect = entity.getPotionEffect(type); - if (potionEffect == null) { - continue; - } - - Deque finalEffects; // effects to be applied - Deque effects; // effects to be filtered - boolean madeChanges = false; - - if (state.includesHidden()) { // modify hidden effects - finalEffects = new ArrayDeque<>(); - effects = PotionUtils.getHiddenEffects(potionEffect); - } else { // otherwise, simply preserve the hidden effects - finalEffects = PotionUtils.getHiddenEffects(potionEffect); - effects = new ArrayDeque<>(); - } - - if (state.includesActive()) { // need to modify the active effect too - effects.addLast(potionEffect); - } - - // filter effects - effectLoop: for (PotionEffect effect : effects) { - SkriptPotionEffect skriptEffect = SkriptPotionEffect.fromBukkitEffect(effect); - for (Object object : delta) { - if (object instanceof Timespan timespan) { - ExprPotionDuration.changeSafe(skriptEffect, timespan, mode); - madeChanges = true; - } else if (object instanceof SkriptPotionEffect base) { - if (base.matchesQualities(effect)) { // remove this effect - madeChanges = true; - continue effectLoop; - } - } - } - // since we iterate most to least hidden, we need to preserve that order - finalEffects.addLast(skriptEffect.asBukkitPotionEffect()); - } - if (!madeChanges) { // no potion effects were modified, don't reapply effects - return; - } - - if (!state.includesActive()) { // if we didn't modify the active effect, we need to push it now - effects.addLast(potionEffect); - } - - entity.removePotionEffect(type); - entity.addPotionEffects(finalEffects); - } - } - - private void modify(ItemType itemType, PotionEffectType[] types, Timespan change, ChangeMode mode) { - for (PotionEffect effect : PotionUtils.getPotionEffects(itemType)) { - for (PotionEffectType type : types) { - if (type.equals(effect.getType())) { - // use SkriptPotionEffect source system to handle removal and application - ExprPotionDuration.changeSafe(SkriptPotionEffect.fromBukkitEffect(effect, itemType), change, mode); - break; - } - } - } - } - @Override public boolean isSingle() { return types.isSingle() && !state.includesHidden(); @@ -272,7 +142,7 @@ public String toString(@Nullable Event event, boolean debug) { } @ApiStatus.Internal - public State getState() { + public RetrievalState getState() { return state; } diff --git a/src/main/java/org/skriptlang/skript/bukkit/potion/elements/expressions/ExprPotionEffects.java b/src/main/java/org/skriptlang/skript/bukkit/potion/elements/expressions/ExprPotionEffects.java index fb177f07d44..f7729791c72 100644 --- a/src/main/java/org/skriptlang/skript/bukkit/potion/elements/expressions/ExprPotionEffects.java +++ b/src/main/java/org/skriptlang/skript/bukkit/potion/elements/expressions/ExprPotionEffects.java @@ -1,7 +1,6 @@ package org.skriptlang.skript.bukkit.potion.elements.expressions; import ch.njol.skript.Skript; -import ch.njol.skript.aliases.ItemType; import ch.njol.skript.classes.Changer.ChangeMode; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Example; @@ -13,19 +12,18 @@ import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; +import org.bukkit.entity.Entity; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; -import org.skriptlang.skript.bukkit.potion.util.PotionUtils; +import org.skriptlang.skript.bukkit.potion.providers.PotionEffectProvider; +import org.skriptlang.skript.bukkit.potion.providers.PotionEffectProvider.RetrievalState; import org.skriptlang.skript.bukkit.potion.util.SkriptPotionEffect; -import org.bukkit.entity.LivingEntity; import org.bukkit.event.Event; import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffectType; import org.skriptlang.skript.registration.SyntaxRegistry; import java.util.ArrayList; -import java.util.Collection; -import java.util.Deque; import java.util.List; @Name("Potion Effects of Entity/Item") @@ -48,52 +46,20 @@ public class ExprPotionEffects extends PropertyExpression ACTIVE; // explicitly active - case "hidden" -> HIDDEN; // explicitly hidden - case "both" -> BOTH; // explicitly active and hidden - default -> UNSET; // implicitly active for get, implicitly active and hidden for delete/reset - }; - } - - boolean includesActive() { - return this != State.HIDDEN; - } - - public boolean includesHidden() { - return this == State.HIDDEN || this == State.BOTH; - } - - public String displayName() { - return switch (this) { - case UNSET -> ""; - case ACTIVE -> "active"; - case HIDDEN -> "hidden"; - case BOTH -> "active and hidden"; - }; - } - } - - private State state; + private RetrievalState state; @Override public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { setExpr(exprs[0]); - state = State.fromParseTag(parseResult.tags.isEmpty() ? "" : parseResult.tags.getFirst()); - if (state.includesHidden() && ItemType.class.isAssignableFrom(getExpr().getReturnType())) { - Skript.error("Items (such as potions or stews) do not have hidden effects"); + state = RetrievalState.fromParseTag(parseResult.tags.isEmpty() ? "" : parseResult.tags.getFirst()); + if (state.includesHidden() && !getExpr().canReturn(Entity.class)) { + Skript.error("Only living entities have hidden effects"); return false; } return true; @@ -103,25 +69,8 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye protected SkriptPotionEffect[] get(Event event, Object[] source) { List potionEffects = new ArrayList<>(); for (Object object : source) { - if (object instanceof LivingEntity livingEntity) { - for (PotionEffect potionEffect : livingEntity.getActivePotionEffects()) { - if (state.includesActive()) { - potionEffects.add(SkriptPotionEffect.fromBukkitEffect(potionEffect, livingEntity)); - } - if (state.includesHidden()) { - PotionEffect hiddenEffect = potionEffect.getHiddenPotionEffect(); - while (hiddenEffect != null) { - // do not set source for hidden effects - potionEffects.add(SkriptPotionEffect.fromBukkitEffect(hiddenEffect)); - hiddenEffect = hiddenEffect.getHiddenPotionEffect(); - } - } - } - } else if (object instanceof ItemType itemType) { - for (PotionEffect potionEffect : PotionUtils.getPotionEffects(itemType)) { - potionEffects.add(SkriptPotionEffect.fromBukkitEffect(potionEffect, itemType)); - } - } + potionEffects.addAll(PotionEffectProvider.of(object, this::error) + .getAll(state)); } return potionEffects.toArray(new SkriptPotionEffect[0]); } @@ -131,7 +80,7 @@ protected SkriptPotionEffect[] get(Event event, Object[] source) { return switch (mode) { case ADD, SET -> { if (state.includesHidden()) { - Skript.error("The hidden potion effects of an entity cannot be set or added to."); + Skript.error("Hidden potion effects cannot be directly set or added to."); yield null; } yield CollectionUtils.array(PotionEffect[].class); @@ -145,128 +94,50 @@ protected SkriptPotionEffect[] get(Event event, Object[] source) { public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { Object[] holders = getExpr().getArray(event); switch (mode) { - case SET, DELETE, RESET: + case ADD -> { + assert delta != null; for (Object holder : holders) { - if (holder instanceof LivingEntity entity) { - reset(entity); - } else if (holder instanceof ItemType itemType) { - PotionUtils.clearPotionEffects(itemType); + PotionEffectProvider provider = PotionEffectProvider.of(holder, this::error); + for (Object object : delta) { + provider.add((PotionEffect) object); } } - if (mode != ChangeMode.SET) { // Fall through for SET to add effects - break; - } - //$FALL-THROUGH$ - case ADD: + } + case SET -> { assert delta != null; for (Object holder : holders) { - if (holder instanceof LivingEntity entity) { - for (Object object : delta) { - entity.addPotionEffect((PotionEffect) object); - } - } else if (holder instanceof ItemType itemType) { - for (Object object : delta) { - PotionUtils.addPotionEffects(itemType, (PotionEffect) object); - } + PotionEffectProvider provider = PotionEffectProvider.of(holder, this::error); + provider.clearAll(state); + for (Object object : delta) { + provider.add((PotionEffect) object); } } - break; - case REMOVE: + } + case REMOVE -> { assert delta != null; for (Object holder : holders) { - if (holder instanceof LivingEntity entity) { - for (Object object : delta) { - remove(entity, (SkriptPotionEffect) object); - } - } else if (holder instanceof ItemType itemType) { - for (Object object : delta) { - remove(itemType, (SkriptPotionEffect) object); - } + PotionEffectProvider provider = PotionEffectProvider.of(holder, this::error); + for (Object object : delta) { + provider.remove((SkriptPotionEffect) object, state); } } - break; - case REMOVE_ALL: + } + case REMOVE_ALL -> { assert delta != null; for (Object holder : holders) { - if (holder instanceof LivingEntity entity) { - for (Object object : delta) { - entity.removePotionEffect((PotionEffectType) object); - } - } else if (holder instanceof ItemType itemType) { - for (Object object : delta) { - PotionUtils.removePotionEffects(itemType, (PotionEffectType) object); - } + PotionEffectProvider provider = PotionEffectProvider.of(holder, this::error); + for (Object object : delta) { + provider.removeAll((PotionEffectType) object, state); } } - break; - default: - assert false; - } - } - - private void reset(LivingEntity entity) { - Collection potionEffects = entity.getActivePotionEffects(); - if (state == State.ACTIVE) { // preserve hidden effects - for (PotionEffect potionEffect : potionEffects) { - Deque hiddenEffects = PotionUtils.getHiddenEffects(potionEffect); - entity.removePotionEffect(potionEffect.getType()); - entity.addPotionEffects(hiddenEffects); - } - } else if (state == State.HIDDEN) { // preserve active effect - for (PotionEffect potionEffect : potionEffects) { - entity.removePotionEffect(potionEffect.getType()); - // applying a potion effect ignores the hidden effect value - entity.addPotionEffect(potionEffect); - } - } else { - for (PotionEffect potionEffect : potionEffects) { - entity.removePotionEffect(potionEffect.getType()); } - } - } - - private void remove(LivingEntity entity, SkriptPotionEffect potionEffect) { - PotionEffect entityEffect = entity.getPotionEffect(potionEffect.potionEffectType()); - if (entityEffect == null) { - return; - } - - Deque effects = PotionUtils.getHiddenEffects(entityEffect); - boolean madeChanges = false; - - // retain (some or all) hidden effects - // we only remove hidden effects if the user explicitly included them - if (state.includesHidden()) { - var effectsIterator = effects.iterator(); - while (effectsIterator.hasNext()) { - if (potionEffect.matchesQualities(effectsIterator.next())) { - effectsIterator.remove(); - madeChanges = true; + case DELETE, RESET -> { + for (Object holder : holders) { + PotionEffectProvider.of(holder, this::error) + .clearAll(state); } } } - - // retain the active effect - // unless the user is only removing hidden effects, we attempt to filter the active effect - if (state == State.HIDDEN || !potionEffect.matchesQualities(entityEffect)) { // preserve the effect - effects.addLast(entityEffect); - } else { - madeChanges = true; - } - - if (madeChanges) { // only remove and apply if changes were made - entity.removePotionEffect(entityEffect.getType()); - entity.addPotionEffects(effects); - } - } - - private void remove(ItemType itemType, SkriptPotionEffect potionEffect) { - for (PotionEffect itemEffect : PotionUtils.getPotionEffects(itemType)) { - if (potionEffect.matchesQualities(itemEffect)) { - PotionUtils.removePotionEffects(itemType, potionEffect.potionEffectType()); - break; // API doesn't support multiple effects of the same type - } - } } @Override @@ -285,7 +156,7 @@ public String toString(@Nullable Event event, boolean debug) { } @ApiStatus.Internal - public State getState() { + public RetrievalState getState() { return state; } diff --git a/src/main/java/org/skriptlang/skript/bukkit/potion/providers/EntityProvider.java b/src/main/java/org/skriptlang/skript/bukkit/potion/providers/EntityProvider.java new file mode 100644 index 00000000000..7fa73a8d84d --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/potion/providers/EntityProvider.java @@ -0,0 +1,200 @@ +package org.skriptlang.skript.bukkit.potion.providers; + +import org.bukkit.entity.AreaEffectCloud; +import org.bukkit.entity.Arrow; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.bukkit.potion.PotionType; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.potion.providers.EntityProvider.PotionAccessor; +import org.skriptlang.skript.bukkit.potion.util.SkriptPotionEffect; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +class EntityProvider extends PotionEffectProvider { + + public EntityProvider(AreaEffectCloud areaEffectCloud) { + super(new AreaEffectCloudAccessor(areaEffectCloud)); + } + + public EntityProvider(Arrow arrow) { + super(new ArrowAccessor(arrow)); + } + + @Override + public Collection get(PotionEffectType[] potionEffectTypes, RetrievalState state) { + if (!state.includesActive()) { + return List.of(); + } + List potionEffects = new ArrayList<>(); + PotionType potionType = source.getBasePotionType(); + if (potionType != null) { + potionEffects.addAll(potionType.getPotionEffects()); + } + if (source.hasCustomEffects()) { + potionEffects.addAll(source.getCustomEffects()); + } + return potionEffects.stream() + .filter(effect -> { + for (PotionEffectType type : potionEffectTypes) { + if (type.equals(effect.getType())) { + return true; + } + } + return false; + }) + .map(effect -> SkriptPotionEffect.fromBukkitEffect(effect, this)) + .toList(); + } + + @Override + public Collection getAll(RetrievalState state) { + if (!state.includesActive()) { + return List.of(); + } + List potionEffects = new ArrayList<>(); + PotionType potionType = source.getBasePotionType(); + if (potionType != null) { + for (PotionEffect effect : potionType.getPotionEffects()) { + potionEffects.add(SkriptPotionEffect.fromBukkitEffect(effect, this)); + } + } + if (source.hasCustomEffects()) { + for (PotionEffect effect : source.getCustomEffects()) { + potionEffects.add(SkriptPotionEffect.fromBukkitEffect(effect, this)); + } + } + return potionEffects; + } + + @Override + public void add(PotionEffect potionEffect) { + source.addCustomEffect(potionEffect, true); + } + + @Override + public void remove(SkriptPotionEffect potionEffect, RetrievalState state) { + if (!state.includesActive() || !source.hasCustomEffects()) { + return; + } + for (PotionEffect itemEffect : source.getCustomEffects()) { + if (potionEffect.matchesQualities(itemEffect)) { + source.removeCustomEffect(potionEffect.potionEffectType()); + break; // API doesn't support multiple effects of the same type + } + } + } + + @Override + public void removeAll(PotionEffectType potionEffectType, RetrievalState state) { + if (!state.includesActive()) { + return; + } + source.removeCustomEffect(potionEffectType); + } + + @Override + public void clear(PotionEffectType[] potionEffectTypes, RetrievalState state) { + if (!state.includesActive()) { + return; + } + for (PotionEffectType potionEffectType : potionEffectTypes) { + source.removeCustomEffect(potionEffectType); + } + } + + @Override + public void clearAll(RetrievalState state) { + if (!state.includesActive()) { + return; + } + source.clearCustomEffects(); + } + + @Override + public void mirrorEffectChanges(SkriptPotionEffect potionEffect, Runnable runnable) { + source.removeCustomEffect(potionEffect.potionEffectType()); + runnable.run(); + source.addCustomEffect(potionEffect.asBukkitPotionEffect(), true); + } + + public interface PotionAccessor { + @Nullable PotionType getBasePotionType(); + boolean hasCustomEffects(); + List getCustomEffects(); + void addCustomEffect(PotionEffect effect, boolean overwrite); + void removeCustomEffect(PotionEffectType potionEffectType); + void clearCustomEffects(); + } + + private record AreaEffectCloudAccessor(AreaEffectCloud areaEffectCloud) implements PotionAccessor { + + @Override + public @Nullable PotionType getBasePotionType() { + return areaEffectCloud.getBasePotionType(); + } + + @Override + public boolean hasCustomEffects() { + return areaEffectCloud.hasCustomEffects(); + } + + @Override + public List getCustomEffects() { + return areaEffectCloud.getCustomEffects(); + } + + @Override + public void addCustomEffect(PotionEffect effect, boolean overwrite) { + areaEffectCloud.addCustomEffect(effect, overwrite); + } + + @Override + public void removeCustomEffect(PotionEffectType potionEffectType) { + areaEffectCloud.removeCustomEffect(potionEffectType); + } + + @Override + public void clearCustomEffects() { + areaEffectCloud.clearCustomEffects(); + } + + } + + private record ArrowAccessor(Arrow arrow) implements PotionAccessor { + + @Override + public @Nullable PotionType getBasePotionType() { + return arrow.getBasePotionType(); + } + + @Override + public boolean hasCustomEffects() { + return arrow.hasCustomEffects(); + } + + @Override + public List getCustomEffects() { + return arrow.getCustomEffects(); + } + + @Override + public void addCustomEffect(PotionEffect effect, boolean overwrite) { + arrow.addCustomEffect(effect, overwrite); + } + + @Override + public void removeCustomEffect(PotionEffectType potionEffectType) { + arrow.removeCustomEffect(potionEffectType); + } + + @Override + public void clearCustomEffects() { + arrow.clearCustomEffects(); + } + + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/potion/providers/ItemTypeProvider.java b/src/main/java/org/skriptlang/skript/bukkit/potion/providers/ItemTypeProvider.java new file mode 100644 index 00000000000..3b5f3c01f64 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/potion/providers/ItemTypeProvider.java @@ -0,0 +1,173 @@ +package org.skriptlang.skript.bukkit.potion.providers; + +import ch.njol.skript.aliases.ItemType; +import io.papermc.paper.potion.SuspiciousEffectEntry; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.inventory.meta.PotionMeta; +import org.bukkit.inventory.meta.SuspiciousStewMeta; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.skriptlang.skript.bukkit.potion.util.SkriptPotionEffect; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +class ItemTypeProvider extends PotionEffectProvider { + + /** + * Attempts to retrieve a list of potion effects from an ItemType. + * @param itemType The ItemType to get potion effects from. + * @return A list of potion effects from an ItemType, if any were found. + */ + public static List getPotionEffects(ItemType itemType) { + List effects = new ArrayList<>(); + ItemMeta meta = itemType.getItemMeta(); + if (meta instanceof PotionMeta potionMeta) { + if (potionMeta.hasCustomEffects()) { + effects.addAll(potionMeta.getCustomEffects()); + } + if (potionMeta.hasBasePotionType()) { + //noinspection ConstantConditions - checked via hasBasePotionType + effects.addAll(potionMeta.getBasePotionType().getPotionEffects()); + } + } else if (meta instanceof SuspiciousStewMeta stewMeta) { + effects.addAll(stewMeta.getCustomEffects()); + } + return effects; + } + + /** + * Adds potions effects to an ItemType. + * @param itemType The ItemType to modify. + * @param potionEffects The potion effects to add. + */ + public static void addPotionEffects(ItemType itemType, PotionEffect... potionEffects) { + ItemMeta meta = itemType.getItemMeta(); + if (meta instanceof PotionMeta potionMeta) { + for (PotionEffect potionEffect : potionEffects) { + potionMeta.addCustomEffect(potionEffect, true); + } + } else if (meta instanceof SuspiciousStewMeta stewMeta) { + for (PotionEffect potionEffect : potionEffects) { + stewMeta.addCustomEffect( + SuspiciousEffectEntry.create(potionEffect.getType(), potionEffect.getDuration()), true); + } + } + itemType.setItemMeta(meta); + } + + /** + * Removes potions effects from an ItemType. + * @param itemType The ItemType to modify. + * @param potionEffectTypes The potion effects to remove. + */ + public static void removePotionEffects(ItemType itemType, PotionEffectType... potionEffectTypes) { + ItemMeta meta = itemType.getItemMeta(); + if (meta instanceof PotionMeta potionMeta) { + for (PotionEffectType potionEffectType : potionEffectTypes) { + potionMeta.removeCustomEffect(potionEffectType); + } + } else if (meta instanceof SuspiciousStewMeta stewMeta) { + for (PotionEffectType potionEffectType : potionEffectTypes) { + stewMeta.removeCustomEffect(potionEffectType); + } + } + itemType.setItemMeta(meta); + } + + /** + * Removes all potion effects from the ItemType's meta. + * @param itemType The ItemType to modify. + */ + public static void clearPotionEffects(ItemType itemType) { + ItemMeta meta = itemType.getItemMeta(); + if (meta instanceof PotionMeta potionMeta) { + potionMeta.clearCustomEffects(); + } else if (meta instanceof SuspiciousStewMeta stewMeta) { + stewMeta.clearCustomEffects(); + } + itemType.setItemMeta(meta); + } + + public ItemTypeProvider(ItemType source) { + super(source); + } + + @Override + public Collection get(PotionEffectType[] potionEffectTypes, RetrievalState state) { + if (!state.includesActive()) { + return List.of(); + } + List potionEffects = new ArrayList<>(); + for (PotionEffect effect : getPotionEffects(source)) { + for (PotionEffectType type : potionEffectTypes) { + if (type.equals(effect.getType())) { + potionEffects.add(SkriptPotionEffect.fromBukkitEffect(effect, this)); + break; + } + } + } + return potionEffects; + } + + @Override + public Collection getAll(RetrievalState state) { + if (!state.includesActive()) { + return List.of(); + } + return getPotionEffects(source).stream() + .map(potionEffect -> SkriptPotionEffect.fromBukkitEffect(potionEffect, this)) + .toList(); + } + + @Override + public void add(PotionEffect potionEffect) { + addPotionEffects(source, potionEffect); + } + + @Override + public void remove(SkriptPotionEffect potionEffect, RetrievalState state) { + if (!state.includesActive()) { + return; + } + for (PotionEffect itemEffect : getPotionEffects(source)) { + if (potionEffect.matchesQualities(itemEffect)) { + removePotionEffects(source, potionEffect.potionEffectType()); + break; // API doesn't support multiple effects of the same type + } + } + } + + @Override + public void removeAll(PotionEffectType potionEffectType, RetrievalState state) { + if (!state.includesActive()) { + return; + } + removePotionEffects(source, potionEffectType); + } + + @Override + public void clear(PotionEffectType[] potionEffectTypes, RetrievalState state) { + if (!state.includesActive()) { + return; + } + removePotionEffects(source, potionEffectTypes); + } + + @Override + public void clearAll(RetrievalState state) { + if (!state.includesActive()) { + return; + } + clearPotionEffects(source); + } + + @Override + public void mirrorEffectChanges(SkriptPotionEffect potionEffect, Runnable runnable) { + removePotionEffects(source, potionEffect.potionEffectType()); + runnable.run(); + addPotionEffects(source, potionEffect.asBukkitPotionEffect()); + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/potion/providers/LivingEntityProvider.java b/src/main/java/org/skriptlang/skript/bukkit/potion/providers/LivingEntityProvider.java new file mode 100644 index 00000000000..39065f7e428 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/potion/providers/LivingEntityProvider.java @@ -0,0 +1,238 @@ +package org.skriptlang.skript.bukkit.potion.providers; + +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.util.Timespan; +import org.bukkit.entity.LivingEntity; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.skriptlang.skript.bukkit.potion.elements.expressions.ExprPotionDuration; +import org.skriptlang.skript.bukkit.potion.util.PotionUtils; +import org.skriptlang.skript.bukkit.potion.util.SkriptPotionEffect; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Deque; +import java.util.List; + +class LivingEntityProvider extends PotionEffectProvider { + + public LivingEntityProvider(LivingEntity source) { + super(source); + } + + @Override + public Collection get(PotionEffectType[] potionEffectTypes, RetrievalState state) { + List potionEffects = new ArrayList<>(); + for (PotionEffectType type : potionEffectTypes) { + PotionEffect potionEffect = source.getPotionEffect(type); + if (potionEffect != null) { + getEffects(potionEffect, state, potionEffects); + } + } + return potionEffects; + } + + @Override + public Collection getAll(RetrievalState state) { + List potionEffects = new ArrayList<>(); + for (PotionEffect potionEffect : source.getActivePotionEffects()) { + getEffects(potionEffect, state, potionEffects); + } + return potionEffects; + } + + private void getEffects(PotionEffect potionEffect, RetrievalState state, Collection destination) { + if (state.includesActive()) { + destination.add(SkriptPotionEffect.fromBukkitEffect(potionEffect, this)); + } + if (state.includesHidden()) { + PotionEffect hiddenEffect = potionEffect.getHiddenPotionEffect(); + while (hiddenEffect != null) { + // do not set source for hidden effects + destination.add(SkriptPotionEffect.fromBukkitEffect(hiddenEffect)); + hiddenEffect = hiddenEffect.getHiddenPotionEffect(); + } + } + } + + @Override + public void add(PotionEffect potionEffect) { + source.addPotionEffect(potionEffect); + } + + @Override + public void remove(SkriptPotionEffect potionEffect, RetrievalState state) { + PotionEffect entityEffect = source.getPotionEffect(potionEffect.potionEffectType()); + if (entityEffect == null) { + return; + } + + Deque effects = PotionUtils.getHiddenEffects(entityEffect); + boolean madeChanges = false; + + // retain (some or all) hidden effects + // we only remove hidden effects if the user explicitly included them + if (state.includesHidden()) { + var effectsIterator = effects.iterator(); + while (effectsIterator.hasNext()) { + if (potionEffect.matchesQualities(effectsIterator.next())) { + effectsIterator.remove(); + madeChanges = true; + } + } + } + + // retain the active effect + // unless the user is only removing hidden effects, we attempt to filter the active effect + if (state == RetrievalState.HIDDEN || !potionEffect.matchesQualities(entityEffect)) { // preserve the effect + effects.addLast(entityEffect); + } else { + madeChanges = true; + } + + if (madeChanges) { // only remove and apply if changes were made + source.removePotionEffect(entityEffect.getType()); + source.addPotionEffects(effects); + } + } + + @Override + public void removeAll(PotionEffectType potionEffectType, RetrievalState state) { + if (state == RetrievalState.ACTIVE || state == RetrievalState.HIDDEN) { + clear(new PotionEffectType[]{potionEffectType}, state); + } else { + source.removePotionEffect(potionEffectType); + } + } + + @Override + public void clear(PotionEffectType[] potionEffectTypes, RetrievalState state) { + if (state == RetrievalState.ACTIVE) { // preserve hidden effects + for (PotionEffectType type : potionEffectTypes) { + PotionEffect potionEffect = source.getPotionEffect(type); + if (potionEffect == null) { + continue; + } + Deque hiddenEffects = PotionUtils.getHiddenEffects(potionEffect); + source.removePotionEffect(type); + source.addPotionEffects(hiddenEffects); + } + } else if (state == RetrievalState.HIDDEN) { // preserve active effect + for (PotionEffectType type : potionEffectTypes) { + PotionEffect original = source.getPotionEffect(type); + source.removePotionEffect(type); + if (original != null) { + // applying a potion effect ignores the hidden effect value + source.addPotionEffect(original); + } + } + } else { + for (PotionEffectType type : potionEffectTypes) { + source.removePotionEffect(type); + } + } + } + + @Override + public void clearAll(RetrievalState state) { + if (state == RetrievalState.ACTIVE) { // preserve hidden effects + for (PotionEffect potionEffect : source.getActivePotionEffects()) { + Deque hiddenEffects = PotionUtils.getHiddenEffects(potionEffect); + source.removePotionEffect(potionEffect.getType()); + source.addPotionEffects(hiddenEffects); + } + } else if (state == RetrievalState.HIDDEN) { // preserve active effect + for (PotionEffect potionEffect : source.getActivePotionEffects()) { + source.removePotionEffect(potionEffect.getType()); + // applying a potion effect ignores the hidden effect value + source.addPotionEffect(potionEffect); + } + } else { + source.clearActivePotionEffects(); + } + } + + @Override + public void modify(PotionEffectType[] types, RetrievalState state, Object[] delta, ChangeMode mode) { + for (PotionEffectType type : types) { + PotionEffect potionEffect = source.getPotionEffect(type); + if (potionEffect == null) { + continue; + } + + Deque finalEffects; // effects to be applied + Deque effects; // effects to be filtered + boolean madeChanges = false; + + if (state.includesHidden()) { // modify hidden effects + finalEffects = new ArrayDeque<>(); + effects = PotionUtils.getHiddenEffects(potionEffect); + } else { // otherwise, simply preserve the hidden effects + finalEffects = PotionUtils.getHiddenEffects(potionEffect); + effects = new ArrayDeque<>(); + } + + if (state.includesActive()) { // need to modify the active effect too + effects.addLast(potionEffect); + } + + // filter effects + effectLoop: for (PotionEffect effect : effects) { + SkriptPotionEffect skriptEffect = SkriptPotionEffect.fromBukkitEffect(effect); + for (Object object : delta) { + if (object instanceof Timespan timespan) { + ExprPotionDuration.changeSafe(skriptEffect, timespan, mode); + madeChanges = true; + } else if (object instanceof SkriptPotionEffect base) { + if (base.matchesQualities(effect)) { // remove this effect + madeChanges = true; + continue effectLoop; + } + } + } + // since we iterate most to least hidden, we need to preserve that order + finalEffects.addLast(skriptEffect.asBukkitPotionEffect()); + } + if (!madeChanges) { // no potion effects were modified, don't reapply effects + return; + } + + if (!state.includesActive()) { // if we didn't modify the active effect, we need to push it now + effects.addLast(potionEffect); + } + + source.removePotionEffect(type); + source.addPotionEffects(finalEffects); + } + } + + @Override + public void mirrorEffectChanges(SkriptPotionEffect potionEffect, Runnable runnable) { + Deque hiddenEffects = null; + PotionEffectType potionEffectType = potionEffect.potionEffectType(); + if (source.hasPotionEffect(potionEffectType)) { + //noinspection DataFlowIssue - NotNull by hasPotionEffect check + hiddenEffects = PotionUtils.getHiddenEffects(source.getPotionEffect(potionEffectType)); + source.removePotionEffect(potionEffectType); + } + runnable.run(); + PotionEffect updatedPotionEffect = potionEffect.asBukkitPotionEffect(); + if (hiddenEffects != null) { // reapply hidden effects + for (PotionEffect hiddenEffect : hiddenEffects) { + // we need to add this potion effect in the right order + // it might end up not being applied at all, but we'll let the game determine that + if (updatedPotionEffect != null && + (hiddenEffect.isShorterThan(updatedPotionEffect) || hiddenEffect.getAmplifier() > updatedPotionEffect.getAmplifier())) { + source.addPotionEffect(updatedPotionEffect); + updatedPotionEffect = null; + } + source.addPotionEffect(hiddenEffect); + } + } + if (updatedPotionEffect != null) { + source.addPotionEffect(updatedPotionEffect); + } + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/potion/providers/NullProvider.java b/src/main/java/org/skriptlang/skript/bukkit/potion/providers/NullProvider.java new file mode 100644 index 00000000000..b9ae38ce5db --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/potion/providers/NullProvider.java @@ -0,0 +1,56 @@ +package org.skriptlang.skript.bukkit.potion.providers; + +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.skriptlang.skript.bukkit.potion.util.SkriptPotionEffect; + +import java.util.Collection; +import java.util.List; + +public class NullProvider extends PotionEffectProvider { + + public NullProvider() { + super(null); + } + + @Override + public Collection get(PotionEffectType[] potionEffectTypes, RetrievalState state) { + return List.of(); + } + + @Override + public Collection getAll(RetrievalState state) { + return List.of(); + } + + @Override + public void add(PotionEffect potionEffect) { + + } + + @Override + public void remove(SkriptPotionEffect potionEffect, RetrievalState state) { + + } + + @Override + public void removeAll(PotionEffectType potionEffectType, RetrievalState state) { + + } + + @Override + public void clear(PotionEffectType[] potionEffectTypes, RetrievalState state) { + + } + + @Override + public void clearAll(RetrievalState state) { + + } + + @Override + public void mirrorEffectChanges(SkriptPotionEffect potionEffect, Runnable runnable) { + + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/potion/providers/PotionEffectProvider.java b/src/main/java/org/skriptlang/skript/bukkit/potion/providers/PotionEffectProvider.java new file mode 100644 index 00000000000..633c80451ec --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/potion/providers/PotionEffectProvider.java @@ -0,0 +1,200 @@ +package org.skriptlang.skript.bukkit.potion.providers; + +import ch.njol.skript.aliases.ItemType; +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.registrations.Classes; +import ch.njol.skript.util.Timespan; +import ch.njol.skript.util.Utils; +import org.bukkit.entity.AreaEffectCloud; +import org.bukkit.entity.Arrow; +import org.bukkit.entity.LivingEntity; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.jetbrains.annotations.ApiStatus; +import org.skriptlang.skript.bukkit.potion.elements.expressions.ExprPotionDuration; +import org.skriptlang.skript.bukkit.potion.util.SkriptPotionEffect; + +import java.util.Collection; +import java.util.function.Consumer; + +/** + * A wrapper for working with objects that provide potion effects. + * @param The type providing potion effects. + * @see #of(Object, Consumer) + */ +@ApiStatus.Internal +public abstract class PotionEffectProvider { + + /** + * @param object The object to obtain a provider from. + * @param errorProducer A consumer to use for printing errors. + * @return A provider wrapping {@code object}. + * If {@code object} is not a known potion provider, {@code errorProducer} is invoked + * and a dummy provider is returned. + */ + public static PotionEffectProvider of(Object object, Consumer errorProducer) { + return switch (object) { + case AreaEffectCloud areaEffectCloud -> new EntityProvider(areaEffectCloud); + case Arrow arrow -> new EntityProvider(arrow); + case ItemType itemType -> new ItemTypeProvider(itemType); + case LivingEntity livingEntity -> new LivingEntityProvider(livingEntity); + default -> { + errorProducer.accept(Utils.A(Classes.toString(object)) + " does not have potion effects"); + yield new NullProvider(); + } + }; + } + + /** + * Describes the type of potion effects being retrieved in a retrieval operation. + * This is used for distinguishing between active and hidden potion effects, which only apply to {@link LivingEntity}. + */ + public enum RetrievalState { + + /** + * Unspecified retrieval state. + * Behavior will differ depending on retrieval context. + */ + UNSET, + + /** + * Obtaining only active effects + */ + ACTIVE, + + /** + * Obtaining only hidden effects + */ + HIDDEN, + + /** + * Obtaining both active and hidden effects. + * A combination of {@link #ACTIVE} and {@link #HIDDEN}. + */ + BOTH; + + /** + * Utility method for standardized mapping of a parse tag to a retrieval state. + * @param tag Parse tag representing a retrieval state. + * @return A retrieval state. + */ + public static RetrievalState fromParseTag(String tag) { + return switch (tag) { + case "active" -> ACTIVE; // explicitly active + case "hidden" -> HIDDEN; // explicitly hidden + case "both" -> BOTH; // explicitly active and hidden + default -> UNSET; // implicitly active for get, implicitly active and hidden for delete/reset + }; + } + + /** + * @return Whether this state includes active potion effects. + */ + public boolean includesActive() { + return this != RetrievalState.HIDDEN; + } + + /** + * @return Whether this state includes hidden potion effects. + */ + public boolean includesHidden() { + return this == RetrievalState.HIDDEN || this == RetrievalState.BOTH; + } + + /** + * @return A user-friendly string describing this retrieval state. + */ + public String displayName() { + return switch (this) { + case UNSET -> ""; + case ACTIVE -> "active"; + case HIDDEN -> "hidden"; + case BOTH -> "active and hidden"; + }; + } + } + + /** + * Source object providing potion effects. + */ + protected final T source; + + /** + * @param source The source object providing potion effects. + */ + public PotionEffectProvider(T source) { + this.source = source; + } + + /** + * Obtains specific types of potion effects from this provider. + * @param potionEffectTypes The type of potion effects to obtain. + * @param state The type of retrieval to perform. + * @return All potion effects of {@code potionEffectTypes} present on this provider. + */ + public abstract Collection get(PotionEffectType[] potionEffectTypes, RetrievalState state); + + /** + * Obtains all potion effects from this provider. + * @param state The type of retrieval to perform. + * @return All potion effects present on this provider. + */ + public abstract Collection getAll(RetrievalState state); + + /** + * Adds a potion effect to this provider. + * @param potionEffect The potion effect to add. + */ + public abstract void add(PotionEffect potionEffect); + + /** + * Removes a potion effect from this provider. + * A potion effect only needs to match the qualities of {@code potionEffect} to be removed. + * Thus, an effect will be removed even if it has other, distinguishing qualities. + * @param potionEffect The potion effect to remove. + * @param state State determining whether certain types of potion effects should be ignored. + */ + public abstract void remove(SkriptPotionEffect potionEffect, RetrievalState state); + + /** + * Removes all potion effects of a specific type from this provider. + * @param potionEffectType The type of potion effect to remove. + * @param state State determining whether certain types of potion effects should be preserved. + */ + public abstract void removeAll(PotionEffectType potionEffectType, RetrievalState state); + + /** + * Clears all potion effects of {@code potionEffectTypes} from this provider. + * @param potionEffectTypes The types of potion effects to clear. + * @param state State determining whether certain types of potion effects should be ignored. + */ + public abstract void clear(PotionEffectType[] potionEffectTypes, RetrievalState state); + + /** + * Clears all potion effects from this provider. + * @param state State determining whether certain types of potion effects should be preserved. + */ + public abstract void clearAll(RetrievalState state); + + /** + * Modifies properties of existing potion effects on this provider. + * @param types The types of potion effects to modify. + * @param state State determining whether certain types of potion effects should be ignored. + * @param delta The change values. + * @param mode The type of modification to perform. + */ + public void modify(PotionEffectType[] types, RetrievalState state, Object[] delta, ChangeMode mode) { + if (delta[0] instanceof Timespan timespan) { + get(types, state).forEach(potionEffect -> + ExprPotionDuration.changeSafe(potionEffect, timespan, mode)); + } + } + + /** + * Used for mirroring modifications of a potion effect actively applied to this provider. + * @param potionEffect The potion effect being modified. + * @param runnable A runnable that applies the modification(s) to {@code potionEffect} when invoked. + */ + public abstract void mirrorEffectChanges(SkriptPotionEffect potionEffect, Runnable runnable); + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/potion/util/PotionUtils.java b/src/main/java/org/skriptlang/skript/bukkit/potion/util/PotionUtils.java index e6fc18b5915..0a4b43015a4 100644 --- a/src/main/java/org/skriptlang/skript/bukkit/potion/util/PotionUtils.java +++ b/src/main/java/org/skriptlang/skript/bukkit/potion/util/PotionUtils.java @@ -1,21 +1,11 @@ package org.skriptlang.skript.bukkit.potion.util; -import ch.njol.skript.Skript; -import ch.njol.skript.aliases.ItemType; import ch.njol.skript.util.Timespan; import ch.njol.skript.util.Timespan.TimePeriod; -import io.papermc.paper.potion.SuspiciousEffectEntry; -import org.bukkit.inventory.meta.ItemMeta; -import org.bukkit.inventory.meta.PotionMeta; -import org.bukkit.inventory.meta.SuspiciousStewMeta; import org.bukkit.potion.PotionEffect; -import org.bukkit.potion.PotionEffectType; -import org.bukkit.potion.PotionType; import java.util.ArrayDeque; -import java.util.ArrayList; import java.util.Deque; -import java.util.List; public final class PotionUtils { @@ -29,81 +19,6 @@ public final class PotionUtils { */ public static final String DEFAULT_DURATION_STRING = new Timespan(TimePeriod.TICK, DEFAULT_DURATION_TICKS).toString(); - /** - * Attempts to retrieve a list of potion effects from an ItemType. - * @param itemType The ItemType to get potion effects from. - * @return A list of potion effects from an ItemType, if any were found. - */ - public static List getPotionEffects(ItemType itemType) { - List effects = new ArrayList<>(); - ItemMeta meta = itemType.getItemMeta(); - if (meta instanceof PotionMeta potionMeta) { - if (potionMeta.hasCustomEffects()) { - effects.addAll(potionMeta.getCustomEffects()); - } - if (potionMeta.hasBasePotionType()) { - //noinspection ConstantConditions - checked via hasBasePotionType - effects.addAll(potionMeta.getBasePotionType().getPotionEffects()); - } - } else if (meta instanceof SuspiciousStewMeta stewMeta) { - effects.addAll(stewMeta.getCustomEffects()); - } - return effects; - } - - /** - * Adds potions effects to an ItemType. - * @param itemType The ItemType to modify. - * @param potionEffects The potion effects to add. - */ - public static void addPotionEffects(ItemType itemType, PotionEffect... potionEffects) { - ItemMeta meta = itemType.getItemMeta(); - if (meta instanceof PotionMeta potionMeta) { - for (PotionEffect potionEffect : potionEffects) { - potionMeta.addCustomEffect(potionEffect, true); - } - } else if (meta instanceof SuspiciousStewMeta stewMeta) { - for (PotionEffect potionEffect : potionEffects) { - stewMeta.addCustomEffect( - SuspiciousEffectEntry.create(potionEffect.getType(), potionEffect.getDuration()), true); - } - } - itemType.setItemMeta(meta); - } - - /** - * Removes potions effects from an ItemType. - * @param itemType The ItemType to modify. - * @param potionEffectTypes The potion effects to remove. - */ - public static void removePotionEffects(ItemType itemType, PotionEffectType... potionEffectTypes) { - ItemMeta meta = itemType.getItemMeta(); - if (meta instanceof PotionMeta potionMeta) { - for (PotionEffectType potionEffectType : potionEffectTypes) { - potionMeta.removeCustomEffect(potionEffectType); - } - } else if (meta instanceof SuspiciousStewMeta stewMeta) { - for (PotionEffectType potionEffectType : potionEffectTypes) { - stewMeta.removeCustomEffect(potionEffectType); - } - } - itemType.setItemMeta(meta); - } - - /** - * Removes all potion effects from the ItemType's meta. - * @param itemType The ItemType to modify. - */ - public static void clearPotionEffects(ItemType itemType) { - ItemMeta meta = itemType.getItemMeta(); - if (meta instanceof PotionMeta potionMeta) { - potionMeta.clearCustomEffects(); - } else if (meta instanceof SuspiciousStewMeta stewMeta) { - stewMeta.clearCustomEffects(); - } - itemType.setItemMeta(meta); - } - /** * A utility method to obtain the hidden effects of a potion effect. * @param effect The effect to obtain hidden effects from. diff --git a/src/main/java/org/skriptlang/skript/bukkit/potion/util/SkriptPotionEffect.java b/src/main/java/org/skriptlang/skript/bukkit/potion/util/SkriptPotionEffect.java index b884e212173..93ef84eec7e 100644 --- a/src/main/java/org/skriptlang/skript/bukkit/potion/util/SkriptPotionEffect.java +++ b/src/main/java/org/skriptlang/skript/bukkit/potion/util/SkriptPotionEffect.java @@ -1,14 +1,12 @@ package org.skriptlang.skript.bukkit.potion.util; import ch.njol.skript.Skript; -import ch.njol.skript.aliases.ItemType; import ch.njol.skript.lang.Expression; import ch.njol.skript.registrations.Classes; import ch.njol.skript.util.Timespan; import ch.njol.skript.util.Timespan.TimePeriod; import ch.njol.yggdrasil.Fields; import ch.njol.yggdrasil.YggdrasilSerializable.YggdrasilExtendedSerializable; -import org.bukkit.entity.LivingEntity; import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffectType; import org.jetbrains.annotations.ApiStatus; @@ -17,6 +15,7 @@ import org.jetbrains.annotations.Nullable; import org.skriptlang.skript.bukkit.potion.elements.expressions.ExprPotionEffect; import org.skriptlang.skript.bukkit.potion.elements.expressions.ExprPotionEffects; +import org.skriptlang.skript.bukkit.potion.providers.PotionEffectProvider; import java.io.StreamCorruptedException; import java.util.Deque; @@ -40,11 +39,10 @@ public class SkriptPotionEffect implements Cloneable, YggdrasilExtendedSerializa private @Nullable PotionEffect lastEffect; /** - * Sources for where this effect was created from. + * A source for where this effect was created from. * Modifying this effect will update the effect on any sources. */ - private @Nullable LivingEntity entitySource; - private @Nullable ItemType itemSource; + private @Nullable PotionEffectProvider sourceProvider; /** * Internal usage only for serialization. @@ -84,28 +82,13 @@ public static SkriptPotionEffect fromBukkitEffect(PotionEffect potionEffect) { * source is expected to currently be affected by potionEffect. * When changes are made to this potion effect, they will be reflected on source. * @param potionEffect The potion effect to obtain properties from. - * @param source An entity that should mirror the changes to this potion effect. + * @param source A potion effect provider that should mirror the changes to this potion effect. * @return A potion effect whose properties are set from potionEffect. * @see #fromBukkitEffect(PotionEffect) */ - public static SkriptPotionEffect fromBukkitEffect(PotionEffect potionEffect, LivingEntity source) { + public static SkriptPotionEffect fromBukkitEffect(PotionEffect potionEffect, PotionEffectProvider source) { SkriptPotionEffect skriptPotionEffect = fromBukkitEffect(potionEffect); - skriptPotionEffect.entitySource = source; - return skriptPotionEffect; - } - - /** - * Constructs a SkriptPotionEffect from a Bukkit PotionEffect and source item. - * source is expected to be an item (potion, stew, etc.) whose meta contains potionEffect. - * When changes are made to this potion effect, they will be reflected on source. - * @param potionEffect The potion effect to obtain properties from. - * @param source An item that should mirror the changes to this potion effect. - * @return A potion effect whose properties are set from potionEffect. - * @see #fromBukkitEffect(PotionEffect) - */ - public static SkriptPotionEffect fromBukkitEffect(PotionEffect potionEffect, ItemType source) { - SkriptPotionEffect skriptPotionEffect = fromBukkitEffect(potionEffect); - skriptPotionEffect.itemSource = source; + skriptPotionEffect.sourceProvider = source; return skriptPotionEffect; } @@ -348,34 +331,10 @@ public boolean matchesQualities(PotionEffect potionEffect) { */ private void withSource(Runnable runnable) { - Deque hiddenEffects = null; - if (entitySource != null && entitySource.hasPotionEffect(potionEffectType)) { - //noinspection DataFlowIssue - NotNull by hasPotionEffect check - hiddenEffects = PotionUtils.getHiddenEffects(entitySource.getPotionEffect(potionEffectType)); - entitySource.removePotionEffect(potionEffectType); - } else if (itemSource != null) { - PotionUtils.removePotionEffects(itemSource, potionEffectType); - } - runnable.run(); - if (entitySource != null) { - PotionEffect thisPotionEffect = asBukkitPotionEffect(); - if (hiddenEffects != null) { // reapply hidden effects - for (PotionEffect hiddenEffect : hiddenEffects) { - // we need to add this potion effect in the right order - // it might end up not being applied at all, but we'll let the game determine that - if (thisPotionEffect != null && - (hiddenEffect.isShorterThan(thisPotionEffect) || hiddenEffect.getAmplifier() > thisPotionEffect.getAmplifier())) { - entitySource.addPotionEffect(thisPotionEffect); - thisPotionEffect = null; - } - entitySource.addPotionEffect(hiddenEffect); - } - } - if (thisPotionEffect != null) { - entitySource.addPotionEffect(asBukkitPotionEffect()); - } - } else if (itemSource != null) { - PotionUtils.addPotionEffects(itemSource, asBukkitPotionEffect()); + if (sourceProvider == null) { + runnable.run(); + } else { + sourceProvider.mirrorEffectChanges(this, runnable); } } @@ -411,8 +370,7 @@ public SkriptPotionEffect clone() { SkriptPotionEffect skriptPotionEffect = (SkriptPotionEffect) super.clone(); // we do not copy over sources on clones // for example, copying a potion effect into a variable should not continue tracking the source - skriptPotionEffect.entitySource = null; - skriptPotionEffect.itemSource = null; + skriptPotionEffect.sourceProvider = null; return skriptPotionEffect; } catch (CloneNotSupportedException e) { throw new AssertionError(); diff --git a/src/test/skript/tests/bukkit/potion module.sk b/src/test/skript/tests/bukkit/potion module.sk index c0f726ed514..66b505a5d59 100644 --- a/src/test/skript/tests/bukkit/potion module.sk +++ b/src/test/skript/tests/bukkit/potion module.sk @@ -151,7 +151,7 @@ test "potion effects of entities/items": # test invalid usage parse: clear the hidden potion effects of {_holders::1}'s tool - assert last parse logs is "Items (such as potions or stews) do not have hidden effects" with "hidden potion effects of item did not error" + assert last parse logs is "Only living entities have hidden effects" with "hidden potion effects of item did not error" # cleanup delete the entity within {_holders::1} @@ -227,7 +227,7 @@ test "specific potion effect of entities/items": # test invalid usage parse: clear the hidden speed effects of {_entity}'s tool - assert last parse logs is "Items (such as potions or stews) do not have hidden effects" with "hidden speed effects of item did not error" + assert last parse logs is "Only living entities have hidden effects" with "hidden speed effects of item did not error" # cleanup delete the entity within {_entity}