diff --git a/src/main/java/fr/quatrevieux/araknemu/game/fight/ai/simulation/effect/DamageSimulator.java b/src/main/java/fr/quatrevieux/araknemu/game/fight/ai/simulation/effect/DamageSimulator.java index a0c006f45..5e042edda 100644 --- a/src/main/java/fr/quatrevieux/araknemu/game/fight/ai/simulation/effect/DamageSimulator.java +++ b/src/main/java/fr/quatrevieux/araknemu/game/fight/ai/simulation/effect/DamageSimulator.java @@ -26,6 +26,7 @@ import fr.quatrevieux.araknemu.game.fight.castable.effect.EffectValue; import fr.quatrevieux.araknemu.game.fight.castable.effect.Element; import fr.quatrevieux.araknemu.game.fight.castable.effect.handler.damage.Damage; +import fr.quatrevieux.araknemu.game.fight.fighter.ActiveFighter; import fr.quatrevieux.araknemu.game.fight.fighter.PassiveFighter; /** @@ -42,15 +43,19 @@ public DamageSimulator(Element element) { @Override public void simulate(CastSimulation simulation, CastScope.EffectScope effect) { - // @todo apply caster and target buff - final Interval value = new EffectValue(effect.effect()) - .percent(simulation.caster().characteristics().get(element.boost())) - .percent(simulation.caster().characteristics().get(Characteristic.PERCENT_DAMAGE)) - .fixed(simulation.caster().characteristics().get(Characteristic.FIXED_DAMAGE)) - .interval() - ; + final ActiveFighter caster = simulation.caster(); + final int boost = caster.characteristics().get(element.boost()); + final int percent = caster.characteristics().get(Characteristic.PERCENT_DAMAGE); + final int fixed = caster.characteristics().get(Characteristic.FIXED_DAMAGE); for (PassiveFighter target : effect.targets()) { + final Interval value = EffectValue.create(effect.effect(), simulation.caster(), target) + .percent(boost) + .percent(percent) + .fixed(fixed) + .interval() + ; + final Interval damage = value.map(base -> computeDamage(base, target)); if (effect.effect().duration() < 1) { diff --git a/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/EffectValue.java b/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/EffectValue.java index 414e890e3..227cae7cf 100644 --- a/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/EffectValue.java +++ b/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/EffectValue.java @@ -21,19 +21,23 @@ import fr.arakne.utils.value.Interval; import fr.arakne.utils.value.helper.RandomUtil; +import fr.quatrevieux.araknemu.game.fight.fighter.PassiveFighter; import fr.quatrevieux.araknemu.game.spell.effect.SpellEffect; +import java.util.function.BiConsumer; + /** * Handle effect jet value * * Computed value is : * ( [jet + boost] * percent / 100 + fixed + effectBonus ) * multiply */ -public final class EffectValue { +public final class EffectValue implements Cloneable { enum State { MINIMIZED, RANDOMIZED, MAXIMIZED, + FIXED, } /** @@ -48,8 +52,9 @@ enum State { private int percent = 100; private int fixed = 0; private int multiply = 1; + private int value = 0; - public EffectValue(SpellEffect effect) { + EffectValue(SpellEffect effect) { this.effect = effect; } @@ -80,6 +85,16 @@ public EffectValue randomize() { return this; } + /** + * Roll the dice to fix the effect value + */ + public EffectValue roll() { + value = jet(); + state = State.FIXED; + + return this; + } + /** * Boost the dice value * The boost will be added at dice value @@ -141,6 +156,9 @@ public int value() { */ public Interval interval() { switch (state) { + case FIXED: + return Interval.of(value); + case MINIMIZED: return Interval.of(applyBoost(effect.min())); @@ -153,8 +171,75 @@ public Interval interval() { } } + @Override + protected EffectValue clone() { + try { + return (EffectValue) super.clone(); + } catch (CloneNotSupportedException e) { + throw new RuntimeException(); // Should not occur + } + } + + /** + * Create and configure an effect value for a given caster and target + * + * @param effect The spell effect + * @param caster The spell caster on which {@link fr.quatrevieux.araknemu.game.fight.castable.effect.buff.Buffs#onEffectValueCast(EffectValue)} will be called + * @param target The target on which {@link fr.quatrevieux.araknemu.game.fight.castable.effect.buff.Buffs#onEffectValueTarget(EffectValue, PassiveFighter)} will be called + * + * @return The configured effect + */ + public static EffectValue create(SpellEffect effect, PassiveFighter caster, PassiveFighter target) { + final EffectValue value = new EffectValue(effect); + + caster.buffs().onEffectValueCast(value); + target.buffs().onEffectValueTarget(value, caster); + + return value; + } + + /** + * Create and configure multiple effect values for multiple targets + * + * Only one "dice" will be used for all targets, but each target will receive their own EffectValue, + * configured using {@link fr.quatrevieux.araknemu.game.fight.castable.effect.buff.Buffs#onEffectValueTarget(EffectValue, PassiveFighter)}. + * + * So {@link EffectValue#minimize()} and {@link EffectValue#maximize()} are effective, without change the effects value of others targets + * + * Usage: + *
{@code
+     * public void handle(CastScope cast, CastScope.EffectScope effect) {
+     *     EffectValue.forEachTargets(effect.effect(), cast.caster(), effect.targets(), (target, effectValue) -> {
+     *         // Apply the effect (effectValue) on target
+     *         target.life().alter(cast.caster(), effectValue.value());
+     *     });
+     * }
+     * }
+ * + * @param effect The spell effect + * @param caster The spell caster on which {@link fr.quatrevieux.araknemu.game.fight.castable.effect.buff.Buffs#onEffectValueCast(EffectValue)} will be called + * @param targets Targets used to configure the effect value using {@link fr.quatrevieux.araknemu.game.fight.castable.effect.buff.Buffs#onEffectValueTarget(EffectValue, PassiveFighter)} + * @param action Action to perform on each target, with their related effect value + */ + public static void forEachTargets(SpellEffect effect, PassiveFighter caster, Iterable targets, BiConsumer action) { + final EffectValue value = new EffectValue(effect); + + caster.buffs().onEffectValueCast(value); + value.roll(); + + for (PassiveFighter target : targets) { + final EffectValue targetValue = value.clone(); + target.buffs().onEffectValueTarget(targetValue, caster); + + action.accept(target, targetValue); + } + } + private int jet() { switch (state) { + case FIXED: + return value; + case MINIMIZED: return effect.min(); diff --git a/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/buff/BuffHook.java b/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/buff/BuffHook.java index aea1461c4..91b1ecfde 100644 --- a/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/buff/BuffHook.java +++ b/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/buff/BuffHook.java @@ -20,6 +20,7 @@ package fr.quatrevieux.araknemu.game.fight.castable.effect.buff; import fr.quatrevieux.araknemu.game.fight.castable.CastScope; +import fr.quatrevieux.araknemu.game.fight.castable.effect.EffectValue; import fr.quatrevieux.araknemu.game.fight.castable.effect.handler.damage.Damage; import fr.quatrevieux.araknemu.game.fight.castable.effect.handler.damage.ReflectedDamage; import fr.quatrevieux.araknemu.game.fight.fighter.ActiveFighter; @@ -154,4 +155,37 @@ public default void onLifeAltered(Buff buff, int value) {} * @param damage The reflected damage */ public default void onReflectedDamage(Buff buff, ReflectedDamage damage) {} + + /** + * A damage will be applied on target + * Use this hook to configure damage before apply it + * + * Unlike {@link BuffHook#onDamage(Buff, Damage)} this hook is called on the caster instead of the target + * + * @param buff The active buff + * @param damage Damage to configure + * @param target The effect target + */ + public default void onCastDamage(Buff buff, Damage damage, PassiveFighter target) {} + + /** + * A new effect value is generated by the current fighter + * + * Note: {@link BuffHook#onEffectValueTarget(Buff, EffectValue, PassiveFighter)} is always called on the target after + * + * @param buff The active buff + * @param value The new effect value which will be applied + */ + public default void onEffectValueCast(Buff buff, EffectValue value) {} + + /** + * An effect value will be applied on the current fighter + * + * Note: {@link BuffHook#onEffectValueCast(Buff, EffectValue)} is always called on the caster before + * + * @param buff The active buff + * @param value The effect value which will be applied + * @param caster The effect caster + */ + public default void onEffectValueTarget(Buff buff, EffectValue value, PassiveFighter caster) {} } diff --git a/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/buff/BuffList.java b/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/buff/BuffList.java index 890c91415..d19c028b1 100644 --- a/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/buff/BuffList.java +++ b/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/buff/BuffList.java @@ -20,6 +20,7 @@ package fr.quatrevieux.araknemu.game.fight.castable.effect.buff; import fr.quatrevieux.araknemu.game.fight.castable.CastScope; +import fr.quatrevieux.araknemu.game.fight.castable.effect.EffectValue; import fr.quatrevieux.araknemu.game.fight.castable.effect.handler.damage.Damage; import fr.quatrevieux.araknemu.game.fight.castable.effect.handler.damage.ReflectedDamage; import fr.quatrevieux.araknemu.game.fight.fighter.ActiveFighter; @@ -131,6 +132,27 @@ public void onReflectedDamage(ReflectedDamage damage) { } } + @Override + public void onCastDamage(Damage damage, PassiveFighter target) { + for (Buff buff : buffs) { + buff.hook().onCastDamage(buff, damage, target); + } + } + + @Override + public void onEffectValueCast(EffectValue value) { + for (Buff buff : buffs) { + buff.hook().onEffectValueCast(buff, value); + } + } + + @Override + public void onEffectValueTarget(EffectValue value, PassiveFighter caster) { + for (Buff buff : buffs) { + buff.hook().onEffectValueTarget(buff, value, caster); + } + } + @Override public void refresh() { removeIf(buff -> { diff --git a/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/buff/Buffs.java b/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/buff/Buffs.java index 9a0e4ee36..6490139a9 100644 --- a/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/buff/Buffs.java +++ b/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/buff/Buffs.java @@ -20,6 +20,7 @@ package fr.quatrevieux.araknemu.game.fight.castable.effect.buff; import fr.quatrevieux.araknemu.game.fight.castable.CastScope; +import fr.quatrevieux.araknemu.game.fight.castable.effect.EffectValue; import fr.quatrevieux.araknemu.game.fight.castable.effect.handler.damage.Damage; import fr.quatrevieux.araknemu.game.fight.castable.effect.handler.damage.ReflectedDamage; import fr.quatrevieux.araknemu.game.fight.fighter.ActiveFighter; @@ -76,6 +77,21 @@ public interface Buffs extends Iterable { */ public void onEndTurn(); + /** + * @see BuffHook#onCastDamage(Buff, Damage, PassiveFighter) + */ + public void onCastDamage(Damage damage, PassiveFighter target); + + /** + * @see BuffHook#onEffectValueCast(Buff, EffectValue) + */ + public void onEffectValueCast(EffectValue value); + + /** + * @see BuffHook#onEffectValueTarget(Buff, EffectValue, PassiveFighter) + */ + public void onEffectValueTarget(EffectValue value, PassiveFighter caster); + /** * Refresh the buff list after turn end */ diff --git a/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/armor/ReflectDamageHandler.java b/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/armor/ReflectDamageHandler.java index eff9dcb06..a1e9012d6 100644 --- a/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/armor/ReflectDamageHandler.java +++ b/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/armor/ReflectDamageHandler.java @@ -55,7 +55,7 @@ public void onDirectDamage(Buff buff, ActiveFighter caster, Damage value) { // Ignore self damage if (!caster.equals(buff.target())) { value.reflect( - new EffectValue(buff.effect()) + EffectValue.create(buff.effect(), buff.target(), caster) .percent(buff.target().characteristics().get(Characteristic.WISDOM)) .value() ); diff --git a/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/AbstractAlterCharacteristicHandler.java b/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/AbstractAlterCharacteristicHandler.java index 3616f783a..8adaa8141 100644 --- a/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/AbstractAlterCharacteristicHandler.java +++ b/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/AbstractAlterCharacteristicHandler.java @@ -25,7 +25,7 @@ import fr.quatrevieux.araknemu.game.fight.castable.effect.buff.BuffEffect; import fr.quatrevieux.araknemu.game.fight.castable.effect.buff.BuffHook; import fr.quatrevieux.araknemu.game.fight.castable.effect.handler.EffectHandler; -import fr.quatrevieux.araknemu.game.fight.fighter.PassiveFighter; +import fr.quatrevieux.araknemu.game.fight.fighter.ActiveFighter; import fr.quatrevieux.araknemu.game.spell.effect.SpellEffect; /** @@ -45,11 +45,21 @@ public void handle(CastScope cast, CastScope.EffectScope effect) { @Override public void buff(CastScope cast, CastScope.EffectScope effect) { - final SpellEffect buffEffect = computeBuffEffect(cast, effect.effect()); + final SpellEffect spellEffect = effect.effect(); + final ActiveFighter caster = cast.caster(); - for (PassiveFighter target : effect.targets()) { - target.buffs().add(new Buff(buffEffect, cast.action(), cast.caster(), target, this)); - } + EffectValue.forEachTargets( + spellEffect, + caster, + effect.targets(), + (target, effectValue) -> target.buffs().add(new Buff( + BuffEffect.fixed(spellEffect, effectValue.value()), + cast.action(), + caster, + target, + this + )) + ); } @Override @@ -61,13 +71,4 @@ public void onBuffStarted(Buff buff) { public void onBuffTerminated(Buff buff) { hook.onBuffTerminated(buff); } - - /** - * Compute the buff value - */ - private SpellEffect computeBuffEffect(CastScope cast, SpellEffect effect) { - final EffectValue value = new EffectValue(effect); - - return BuffEffect.fixed(effect, value.value()); - } } diff --git a/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/AddActionPointsHandler.java b/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/AddActionPointsHandler.java index 7a3de515e..b67da07bb 100644 --- a/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/AddActionPointsHandler.java +++ b/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/AddActionPointsHandler.java @@ -23,6 +23,7 @@ import fr.quatrevieux.araknemu.game.fight.castable.CastScope; import fr.quatrevieux.araknemu.game.fight.castable.effect.EffectValue; import fr.quatrevieux.araknemu.game.fight.castable.effect.handler.characteristic.point.AlterPointHook; +import fr.quatrevieux.araknemu.game.fight.fighter.ActiveFighter; import fr.quatrevieux.araknemu.network.game.out.fight.action.ActionEffect; /** @@ -41,11 +42,12 @@ public AddActionPointsHandler(Fight fight) { @Override public void handle(CastScope cast, CastScope.EffectScope effect) { fight.turnList().current().ifPresent(turn -> { - final EffectValue value = new EffectValue(effect.effect()); + final ActiveFighter fighter = turn.fighter(); + final EffectValue value = EffectValue.create(effect.effect(), fighter, fighter); final int ap = value.value(); turn.points().addActionPoints(ap); - fight.send(ActionEffect.addActionPoints(turn.fighter(), ap)); + fight.send(ActionEffect.addActionPoints(fighter, ap)); }); } } diff --git a/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/AddMovementPointsHandler.java b/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/AddMovementPointsHandler.java index 98745183e..aa08f82e3 100644 --- a/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/AddMovementPointsHandler.java +++ b/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/AddMovementPointsHandler.java @@ -23,6 +23,7 @@ import fr.quatrevieux.araknemu.game.fight.castable.CastScope; import fr.quatrevieux.araknemu.game.fight.castable.effect.EffectValue; import fr.quatrevieux.araknemu.game.fight.castable.effect.handler.characteristic.point.AlterPointHook; +import fr.quatrevieux.araknemu.game.fight.fighter.ActiveFighter; import fr.quatrevieux.araknemu.network.game.out.fight.action.ActionEffect; /** @@ -41,11 +42,12 @@ public AddMovementPointsHandler(Fight fight) { @Override public void handle(CastScope cast, CastScope.EffectScope effect) { fight.turnList().current().ifPresent(turn -> { - final EffectValue value = new EffectValue(effect.effect()); + final ActiveFighter fighter = turn.fighter(); + final EffectValue value = EffectValue.create(effect.effect(), fighter, fighter); final int mp = value.value(); turn.points().addMovementPoints(mp); - fight.send(ActionEffect.addMovementPoints(turn.fighter(), mp)); + fight.send(ActionEffect.addMovementPoints(fighter, mp)); }); } } diff --git a/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/RemoveActionPointsHandler.java b/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/RemoveActionPointsHandler.java index e221e05df..5d820a01f 100644 --- a/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/RemoveActionPointsHandler.java +++ b/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/RemoveActionPointsHandler.java @@ -23,6 +23,7 @@ import fr.quatrevieux.araknemu.game.fight.castable.CastScope; import fr.quatrevieux.araknemu.game.fight.castable.effect.EffectValue; import fr.quatrevieux.araknemu.game.fight.castable.effect.handler.characteristic.point.AlterPointHook; +import fr.quatrevieux.araknemu.game.fight.fighter.ActiveFighter; import fr.quatrevieux.araknemu.network.game.out.fight.action.ActionEffect; /** @@ -41,10 +42,11 @@ public RemoveActionPointsHandler(Fight fight) { @Override public void handle(CastScope cast, CastScope.EffectScope effect) { fight.turnList().current().ifPresent(turn -> { - final EffectValue value = new EffectValue(effect.effect()); + final ActiveFighter fighter = turn.fighter(); + final EffectValue value = EffectValue.create(effect.effect(), fighter, fighter); final int ap = turn.points().removeActionPoints(value.value()); - fight.send(ActionEffect.removeActionPoints(turn.fighter(), ap)); + fight.send(ActionEffect.removeActionPoints(fighter, ap)); }); } } diff --git a/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/RemoveMovementPointsHandler.java b/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/RemoveMovementPointsHandler.java index 67d166361..ad95436d8 100644 --- a/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/RemoveMovementPointsHandler.java +++ b/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/RemoveMovementPointsHandler.java @@ -23,6 +23,7 @@ import fr.quatrevieux.araknemu.game.fight.castable.CastScope; import fr.quatrevieux.araknemu.game.fight.castable.effect.EffectValue; import fr.quatrevieux.araknemu.game.fight.castable.effect.handler.characteristic.point.AlterPointHook; +import fr.quatrevieux.araknemu.game.fight.fighter.ActiveFighter; import fr.quatrevieux.araknemu.network.game.out.fight.action.ActionEffect; /** @@ -41,10 +42,11 @@ public RemoveMovementPointsHandler(Fight fight) { @Override public void handle(CastScope cast, CastScope.EffectScope effect) { fight.turnList().current().ifPresent(turn -> { - final EffectValue value = new EffectValue(effect.effect()); + final ActiveFighter fighter = turn.fighter(); + final EffectValue value = EffectValue.create(effect.effect(), fighter, fighter); final int mp = turn.points().removeMovementPoints(value.value()); - fight.send(ActionEffect.removeMovementPoints(turn.fighter(), mp)); + fight.send(ActionEffect.removeMovementPoints(fighter, mp)); }); } } diff --git a/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/point/AbstractPointLostApplier.java b/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/point/AbstractPointLostApplier.java index 27de4e84a..8fe7596b8 100644 --- a/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/point/AbstractPointLostApplier.java +++ b/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/point/AbstractPointLostApplier.java @@ -74,7 +74,7 @@ protected AbstractPointLostApplier(Fight fight, AlterPointHook hook, Characteris public final int apply(CastScope cast, PassiveFighter target, SpellEffect effect) { final ActiveFighter caster = cast.caster(); - final int baseValue = new EffectValue(effect).value(); + final int baseValue = EffectValue.create(effect, caster, target).value(); final int lost = computePointLost(caster, target, baseValue); final int dodge = baseValue - lost; diff --git a/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/damage/Damage.java b/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/damage/Damage.java index 8e6b94238..8dd069798 100644 --- a/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/damage/Damage.java +++ b/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/damage/Damage.java @@ -84,7 +84,7 @@ public Damage fixed(int fixed) { @Override public Damage multiply(int factor) { - this.multiply = factor; + this.multiply *= factor; return this; } diff --git a/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/damage/DamageApplier.java b/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/damage/DamageApplier.java index 589a16bea..f6bd511ae 100644 --- a/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/damage/DamageApplier.java +++ b/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/damage/DamageApplier.java @@ -84,10 +84,7 @@ public int apply(ActiveFighter caster, SpellEffect effect, PassiveFighter target * @see fr.quatrevieux.araknemu.game.fight.castable.effect.buff.Buffs#onDirectDamage(ActiveFighter, Damage) The called buff hook */ public int applyFixed(ActiveFighter caster, int value, PassiveFighter target) { - final Damage damage = new Damage(value, element) - .percent(target.characteristics().get(element.percentResistance())) - .fixed(target.characteristics().get(element.fixedResistance())) - ; + final Damage damage = createDamage(caster, target, value); return applyDirectDamage(caster, damage, target); } @@ -126,10 +123,7 @@ public int apply(Buff buff) { */ public int applyFixed(Buff buff, int value) { final PassiveFighter target = buff.target(); - final Damage damage = new Damage(value, element) - .percent(target.characteristics().get(element.percentResistance())) - .fixed(target.characteristics().get(element.fixedResistance())) - ; + final Damage damage = createDamage(buff.caster(), target, value); return applyBuffDamage(buff, damage, target); } @@ -138,16 +132,13 @@ public int applyFixed(Buff buff, int value) { * Create the damage object */ private Damage computeDamage(ActiveFighter caster, SpellEffect effect, PassiveFighter target) { - final EffectValue value = new EffectValue(effect) + final EffectValue value = EffectValue.create(effect, caster, target) .percent(caster.characteristics().get(element.boost())) .percent(caster.characteristics().get(Characteristic.PERCENT_DAMAGE)) .fixed(caster.characteristics().get(Characteristic.FIXED_DAMAGE)) ; - return new Damage(value.value(), element) - .percent(target.characteristics().get(element.percentResistance())) - .fixed(target.characteristics().get(element.fixedResistance())) - ; + return createDamage(caster, target, value.value()); } /** @@ -218,4 +209,24 @@ private void applyReflectedDamage(PassiveFighter castTarget, ActiveFighter caste returnedDamage.target().life().alter(castTarget, -returnedDamage.value()); } } + + /** + * Create the damage object, and call {@link fr.quatrevieux.araknemu.game.fight.castable.effect.buff.Buffs#onCastDamage(Damage, PassiveFighter)} + * + * @param caster The spell caster + * @param target The effect target + * @param value Raw damage value + * + * @return The damage object to apply + */ + private Damage createDamage(ActiveFighter caster, PassiveFighter target, int value) { + final Damage damage = new Damage(value, element) + .percent(target.characteristics().get(element.percentResistance())) + .fixed(target.characteristics().get(element.fixedResistance())) + ; + + caster.buffs().onCastDamage(damage, target); + + return damage; + } } diff --git a/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/damage/FixedCasterDamageHandler.java b/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/damage/FixedCasterDamageHandler.java index 95659b6d0..9abefed26 100644 --- a/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/damage/FixedCasterDamageHandler.java +++ b/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/damage/FixedCasterDamageHandler.java @@ -38,7 +38,7 @@ public void handle(CastScope cast, CastScope.EffectScope effect) { // This is a fixed effect, without any elements // So it does not call any buff hooks - caster.life().alter(caster, -new EffectValue(effect.effect()).value()); + caster.life().alter(caster, -EffectValue.create(effect.effect(), caster, caster).value()); } @Override diff --git a/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/damage/FixedDamageHandler.java b/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/damage/FixedDamageHandler.java index 1ff48696e..23d838e72 100644 --- a/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/damage/FixedDamageHandler.java +++ b/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/damage/FixedDamageHandler.java @@ -39,7 +39,7 @@ public void handle(CastScope cast, CastScope.EffectScope effect) { // This is a fixed effect, without any elements // So it does not call any buff hooks for (PassiveFighter target : effect.targets()) { - target.life().alter(caster, -new EffectValue(effect.effect()).value()); + target.life().alter(caster, -EffectValue.create(effect.effect(), caster, target).value()); } } @@ -52,7 +52,7 @@ public void buff(CastScope cast, CastScope.EffectScope effect) { @Override public boolean onStartTurn(Buff buff) { - buff.target().life().alter(buff.caster(), -new EffectValue(buff.effect()).value()); + buff.target().life().alter(buff.caster(), -EffectValue.create(buff.effect(), buff.caster(), buff.target()).value()); return !buff.target().dead(); } diff --git a/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/damage/FixedStealLifeHandler.java b/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/damage/FixedStealLifeHandler.java index 76ac6ab7d..9e8b5d648 100644 --- a/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/damage/FixedStealLifeHandler.java +++ b/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/damage/FixedStealLifeHandler.java @@ -42,7 +42,7 @@ public void handle(CastScope cast, CastScope.EffectScope effect) { // This is a fixed effect, without any elements // So it does not call any buff hooks for (PassiveFighter target : effect.targets()) { - casterLife.alter(caster, -target.life().alter(caster, -new EffectValue(effect.effect()).value())); + casterLife.alter(caster, -target.life().alter(caster, -EffectValue.create(effect.effect(), caster, target).value())); } } diff --git a/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/damage/PercentLifeDamageHandler.java b/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/damage/PercentLifeDamageHandler.java index c962f0128..ec739c8a3 100644 --- a/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/damage/PercentLifeDamageHandler.java +++ b/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/damage/PercentLifeDamageHandler.java @@ -44,11 +44,11 @@ public PercentLifeDamageHandler(Element element, Fight fight) { @Override public void handle(CastScope cast, CastScope.EffectScope effect) { final ActiveFighter caster = cast.caster(); - final int damage = caster.life().current() * (new EffectValue(effect.effect())).value() / 100; + final int currentLife = caster.life().current(); - for (PassiveFighter target : effect.targets()) { - applier.applyFixed(caster, damage, target); - } + EffectValue.forEachTargets(effect.effect(), caster, effect.targets(), (target, effectValue) -> { + applier.applyFixed(caster, currentLife * effectValue.value() / 100, target); + }); } @Override @@ -60,10 +60,12 @@ public void buff(CastScope cast, CastScope.EffectScope effect) { @Override public boolean onStartTurn(Buff buff) { - final int damage = buff.caster().life().current() * (new EffectValue(buff.effect())).value() / 100; + final ActiveFighter caster = buff.caster(); + final PassiveFighter target = buff.target(); + final int damage = caster.life().current() * (EffectValue.create(buff.effect(), caster, target)).value() / 100; applier.applyFixed(buff, damage); - return !buff.target().dead(); + return !target.dead(); } } diff --git a/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/damage/PercentLifeLostDamageHandler.java b/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/damage/PercentLifeLostDamageHandler.java index 93185899c..27087eed0 100644 --- a/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/damage/PercentLifeLostDamageHandler.java +++ b/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/damage/PercentLifeLostDamageHandler.java @@ -26,7 +26,6 @@ import fr.quatrevieux.araknemu.game.fight.castable.effect.handler.EffectHandler; import fr.quatrevieux.araknemu.game.fight.fighter.ActiveFighter; import fr.quatrevieux.araknemu.game.fight.fighter.FighterLife; -import fr.quatrevieux.araknemu.game.fight.fighter.PassiveFighter; /** * Handle damage based on the lost caster life @@ -46,12 +45,11 @@ public PercentLifeLostDamageHandler(Element element, Fight fight) { public void handle(CastScope cast, CastScope.EffectScope effect) { final ActiveFighter caster = cast.caster(); final FighterLife casterLife = caster.life(); + final int lostLife = casterLife.max() - casterLife.current(); - final int damage = (casterLife.max() - casterLife.current()) * (new EffectValue(effect.effect())).value() / 100; - - for (PassiveFighter target : effect.targets()) { - applier.applyFixed(caster, damage, target); - } + EffectValue.forEachTargets(effect.effect(), caster, effect.targets(), (target, effectValue) -> { + applier.applyFixed(caster, lostLife * effectValue.value() / 100, target); + }); } @Override diff --git a/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/damage/PunishmentHandler.java b/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/damage/PunishmentHandler.java index 1aa14c982..e2b8a9cd2 100644 --- a/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/damage/PunishmentHandler.java +++ b/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/damage/PunishmentHandler.java @@ -61,7 +61,7 @@ public void handle(CastScope cast, CastScope.EffectScope effect) { final double factor = base * base / 4 * casterLife.max(); for (PassiveFighter target : effect.targets()) { - final double percent = new EffectValue(effect.effect()).value() / 100d; + final double percent = EffectValue.create(effect.effect(), caster, target).value() / 100d; final int value = (int) (factor * percent); applier.applyFixed(caster, value, target); diff --git a/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/heal/FixedHealHandler.java b/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/heal/FixedHealHandler.java index 8205ae003..62d904742 100644 --- a/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/heal/FixedHealHandler.java +++ b/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/heal/FixedHealHandler.java @@ -54,6 +54,6 @@ public boolean onStartTurn(Buff buff) { } private void apply(ActiveFighter caster, SpellEffect effect, PassiveFighter target) { - target.life().alter(caster, new EffectValue(effect).value()); + target.life().alter(caster, EffectValue.create(effect, caster, target).value()); } } diff --git a/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/heal/GivePercentLifeHandler.java b/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/heal/GivePercentLifeHandler.java index fa64123ef..406ba0867 100644 --- a/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/heal/GivePercentLifeHandler.java +++ b/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/heal/GivePercentLifeHandler.java @@ -32,7 +32,7 @@ public final class GivePercentLifeHandler implements EffectHandler { @Override public void handle(CastScope cast, CastScope.EffectScope effect) { final ActiveFighter caster = cast.caster(); - final int heal = new EffectValue(effect.effect()).value() * caster.life().current() / 100; + final int heal = EffectValue.create(effect.effect(), caster, caster).value() * caster.life().current() / 100; caster.life().alter(caster, -heal); diff --git a/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/heal/HealHandler.java b/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/heal/HealHandler.java index 2ff5857b3..5daf3b2ae 100644 --- a/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/heal/HealHandler.java +++ b/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/heal/HealHandler.java @@ -57,7 +57,7 @@ public boolean onStartTurn(Buff buff) { } private void apply(ActiveFighter caster, SpellEffect effect, PassiveFighter target) { - final EffectValue value = new EffectValue(effect) + final EffectValue value = EffectValue.create(effect, caster, target) .percent(caster.characteristics().get(Characteristic.INTELLIGENCE)) .fixed(caster.characteristics().get(Characteristic.HEALTH_BOOST)) ; diff --git a/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/heal/HealOnDamageHandler.java b/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/heal/HealOnDamageHandler.java index 6967bbd9d..2cec22fc3 100644 --- a/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/heal/HealOnDamageHandler.java +++ b/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/heal/HealOnDamageHandler.java @@ -60,7 +60,7 @@ public void onLifeAltered(Buff buff, int value) { } private void apply(ActiveFighter caster, SpellEffect effect, PassiveFighter target) { - final EffectValue value = new EffectValue(effect) + final EffectValue value = EffectValue.create(effect, caster, target) .percent(caster.characteristics().get(Characteristic.INTELLIGENCE)) .fixed(caster.characteristics().get(Characteristic.HEALTH_BOOST)) ; diff --git a/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/modifier/MaximizeTargetEffectsHandler.java b/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/modifier/MaximizeTargetEffectsHandler.java new file mode 100644 index 000000000..f953913b4 --- /dev/null +++ b/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/modifier/MaximizeTargetEffectsHandler.java @@ -0,0 +1,51 @@ +/* + * This file is part of Araknemu. + * + * Araknemu is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Araknemu is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Araknemu. If not, see . + * + * Copyright (c) 2017-2022 Vincent Quatrevieux + */ + +package fr.quatrevieux.araknemu.game.fight.castable.effect.handler.modifier; + +import fr.quatrevieux.araknemu.game.fight.castable.CastScope; +import fr.quatrevieux.araknemu.game.fight.castable.effect.EffectValue; +import fr.quatrevieux.araknemu.game.fight.castable.effect.buff.Buff; +import fr.quatrevieux.araknemu.game.fight.castable.effect.buff.BuffHook; +import fr.quatrevieux.araknemu.game.fight.castable.effect.handler.EffectHandler; +import fr.quatrevieux.araknemu.game.fight.fighter.PassiveFighter; + +/** + * Maximize all effect values targeting the current target + * + * @see EffectValue#maximize() The called modifier + */ +public final class MaximizeTargetEffectsHandler implements EffectHandler, BuffHook { + @Override + public void handle(CastScope cast, CastScope.EffectScope effect) { + throw new UnsupportedOperationException("Maximize can only be used as buff"); + } + + @Override + public void buff(CastScope cast, CastScope.EffectScope effect) { + for (PassiveFighter target : effect.targets()) { + target.buffs().add(new Buff(effect.effect(), cast.action(), cast.caster(), target, this)); + } + } + + @Override + public void onEffectValueTarget(Buff buff, EffectValue value, PassiveFighter caster) { + value.maximize(); + } +} diff --git a/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/modifier/MinimizeCastedEffectsHandler.java b/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/modifier/MinimizeCastedEffectsHandler.java new file mode 100644 index 000000000..1952047dd --- /dev/null +++ b/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/modifier/MinimizeCastedEffectsHandler.java @@ -0,0 +1,51 @@ +/* + * This file is part of Araknemu. + * + * Araknemu is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Araknemu is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Araknemu. If not, see . + * + * Copyright (c) 2017-2022 Vincent Quatrevieux + */ + +package fr.quatrevieux.araknemu.game.fight.castable.effect.handler.modifier; + +import fr.quatrevieux.araknemu.game.fight.castable.CastScope; +import fr.quatrevieux.araknemu.game.fight.castable.effect.EffectValue; +import fr.quatrevieux.araknemu.game.fight.castable.effect.buff.Buff; +import fr.quatrevieux.araknemu.game.fight.castable.effect.buff.BuffHook; +import fr.quatrevieux.araknemu.game.fight.castable.effect.handler.EffectHandler; +import fr.quatrevieux.araknemu.game.fight.fighter.PassiveFighter; + +/** + * Maximize all casted effect value from the current target + * + * @see EffectValue#minimize() The called modifier + */ +public final class MinimizeCastedEffectsHandler implements EffectHandler, BuffHook { + @Override + public void handle(CastScope cast, CastScope.EffectScope effect) { + throw new UnsupportedOperationException("Minimize can only be used as buff"); + } + + @Override + public void buff(CastScope cast, CastScope.EffectScope effect) { + for (PassiveFighter target : effect.targets()) { + target.buffs().add(new Buff(effect.effect(), cast.action(), cast.caster(), target, this)); + } + } + + @Override + public void onEffectValueCast(Buff buff, EffectValue value) { + value.minimize(); + } +} diff --git a/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/modifier/MultiplyDamageHandler.java b/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/modifier/MultiplyDamageHandler.java new file mode 100644 index 000000000..774d8650a --- /dev/null +++ b/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/modifier/MultiplyDamageHandler.java @@ -0,0 +1,53 @@ +/* + * This file is part of Araknemu. + * + * Araknemu is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Araknemu is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Araknemu. If not, see . + * + * Copyright (c) 2017-2022 Vincent Quatrevieux + */ + +package fr.quatrevieux.araknemu.game.fight.castable.effect.handler.modifier; + +import fr.quatrevieux.araknemu.game.fight.castable.CastScope; +import fr.quatrevieux.araknemu.game.fight.castable.effect.buff.Buff; +import fr.quatrevieux.araknemu.game.fight.castable.effect.buff.BuffHook; +import fr.quatrevieux.araknemu.game.fight.castable.effect.handler.EffectHandler; +import fr.quatrevieux.araknemu.game.fight.castable.effect.handler.damage.Damage; +import fr.quatrevieux.araknemu.game.fight.fighter.PassiveFighter; + +/** + * Multiply casted damage + * + * Note: This effect is not randomized, "min" value is always used + * + * @see Damage#multiply(int) The called modifier + */ +public final class MultiplyDamageHandler implements EffectHandler, BuffHook { + @Override + public void handle(CastScope cast, CastScope.EffectScope effect) { + throw new UnsupportedOperationException("Multiply damage can only be used as buff"); + } + + @Override + public void buff(CastScope cast, CastScope.EffectScope effect) { + for (PassiveFighter target : effect.targets()) { + target.buffs().add(new Buff(effect.effect(), cast.action(), cast.caster(), target, this)); + } + } + + @Override + public void onCastDamage(Buff buff, Damage damage, PassiveFighter target) { + damage.multiply(buff.effect().min()); + } +} diff --git a/src/main/java/fr/quatrevieux/araknemu/game/fight/module/CommonEffectsModule.java b/src/main/java/fr/quatrevieux/araknemu/game/fight/module/CommonEffectsModule.java index ebd00fdfb..768f55c6a 100644 --- a/src/main/java/fr/quatrevieux/araknemu/game/fight/module/CommonEffectsModule.java +++ b/src/main/java/fr/quatrevieux/araknemu/game/fight/module/CommonEffectsModule.java @@ -54,6 +54,9 @@ import fr.quatrevieux.araknemu.game.fight.castable.effect.handler.misc.PushStateHandler; import fr.quatrevieux.araknemu.game.fight.castable.effect.handler.misc.RemoveStateHandler; import fr.quatrevieux.araknemu.game.fight.castable.effect.handler.misc.SkipTurnHandler; +import fr.quatrevieux.araknemu.game.fight.castable.effect.handler.modifier.MaximizeTargetEffectsHandler; +import fr.quatrevieux.araknemu.game.fight.castable.effect.handler.modifier.MinimizeCastedEffectsHandler; +import fr.quatrevieux.araknemu.game.fight.castable.effect.handler.modifier.MultiplyDamageHandler; import fr.quatrevieux.araknemu.game.fight.castable.effect.handler.shifting.AvoidDamageByMovingBackHandler; import fr.quatrevieux.araknemu.game.fight.castable.effect.handler.shifting.MoveBackHandler; import fr.quatrevieux.araknemu.game.fight.castable.effect.handler.shifting.MoveFrontHandler; @@ -125,6 +128,10 @@ public void effects(EffectsHandler handler) { handler.register(107, new ReflectDamageHandler()); handler.register(265, new ReduceDamageHandler()); + handler.register(114, new MultiplyDamageHandler()); + handler.register(781, new MinimizeCastedEffectsHandler()); + handler.register(782, new MaximizeTargetEffectsHandler()); + handler.register(111, new AddActionPointsHandler(fight)); handler.register(120, new AddActionPointsHandler(fight)); handler.register(168, new RemoveActionPointsHandler(fight)); diff --git a/src/test/java/fr/quatrevieux/araknemu/game/GameDataSet.java b/src/test/java/fr/quatrevieux/araknemu/game/GameDataSet.java index ff51ddc13..e6eec78d6 100644 --- a/src/test/java/fr/quatrevieux/araknemu/game/GameDataSet.java +++ b/src/test/java/fr/quatrevieux/araknemu/game/GameDataSet.java @@ -504,6 +504,9 @@ public GameDataSet pushFunctionalSpells() throws SQLException, ContainerExceptio "(1708, 'Correction Bwork', 0, '0,1,1', '279,30,,,0,0,0d0+30|279,35,,,0,0,0d0+35|5|1|1|50|100|false|true|false|false|0|0|0|2|PaPa||18;19;3;1;41|0|false', '279,30,,,0,0,0d0+30|279,35,,,0,0,0d0+35|5|1|1|50|100|false|true|false|false|0|0|0|2|PaPa||18;19;3;1;41|0|false', '279,30,,,0,0,0d0+30|279,35,,,0,0,0d0+35|5|1|1|50|100|false|true|false|false|0|0|0|2|PaPa||18;19;3;1;41|0|false', '279,30,,,0,0,0d0+30|279,35,,,0,0,0d0+35|5|1|1|50|100|false|true|false|false|0|0|0|2|PaPa||18;19;3;1;41|0|false', '279,30,,,0,0,0d0+30|279,35,,,0,0,0d0+35|5|1|1|50|100|false|true|false|false|0|0|0|2|PaPa||18;19;3;1;41|0|false', '', '')", "(446, 'Punition', 0, '0,6,1', '672,30,,,0,0,0d0+30|672,35,,,0,0,0d0+35|5|1|1|50|100|false|true|false|false|0|0|0|6|PaPa||18;19;3;1;41|60|false', '672,30,,,0,0,0d0+30|672,35,,,0,0,0d0+35|5|1|1|50|100|false|true|false|false|0|0|0|5|PaPa||18;19;3;1;41|60|false', '672,30,,,0,0,0d0+30|672,35,,,0,0,0d0+35|5|1|1|50|100|false|true|false|false|0|0|0|4|PaPa||18;19;3;1;41|60|false', '672,30,,,0,0,0d0+30|672,35,,,0,0,0d0+35|5|1|1|50|100|false|true|false|false|0|0|0|3|PaPa||18;19;3;1;41|60|false', '672,30,,,0,0,0d0+30|672,35,,,0,0,0d0+35|5|1|1|50|100|false|true|false|false|0|0|0|2|PaPa||18;19;3;1;41|60|false', '672,30,,,0,0,0d0+30|672,35,,,0,0,0d0+35|4|1|1|50|100|false|true|false|false|0|0|0|2|PaPa||18;19;3;1;41|160|false', '')", "(435, 'Transfert de Vie', 1050, '51,1,1', '90,10,,,0,0,0d0+10|90,10,,,0,0,0d0+10;108,10,,,0,0,0d0+10|2|0|0|50|100|false|false|false|false|0|1|0|0|CbCbPa||18;19;3;1;41|90|false', '90,10,,,0,0,0d0+10|90,10,,,0,0,0d0+10;108,10,,,0,0,0d0+10|2|0|0|50|100|false|false|false|false|0|2|0|0|CbCbPa||18;19;3;1;41|90|false', '90,10,,,0,0,0d0+10|90,10,,,0,0,0d0+10;108,10,,,0,0,0d0+10|2|0|0|50|100|false|false|false|false|0|3|0|0|CbCbPa||18;19;3;1;41|90|false', '90,10,,,0,0,0d0+10|90,10,,,0,0,0d0+10;108,10,,,0,0,0d0+10|2|0|0|50|100|false|false|false|false|0|4|0|0|CcCcPa||18;19;3;1;41|90|false', '90,10,,,0,0,0d0+10|90,10,,,0,0,0d0+10;108,10,,,0,0,0d0+10|2|0|0|50|100|false|false|false|false|0|5|0|0|CdCdPa||18;19;3;1;41|90|false', '90,10,,,0,0,0d0+10|90,10,,,0,0,0d0+10;108,10,,,0,0,0d0+10|2|0|0|50|100|false|false|false|false|0|6|0|0|CeCePa||18;19;3;1;41|190|false', '6')", + "(416, 'Poisse', 0, '0,1,1', '781,,,,2,0||3|1|1|0|100|false|true|false|false|4|0|0|10|Pa||18;19;3;1;41|0|false', '781,,,,2,0||3|1|1|0|100|false|true|false|false|4|0|0|9|Pa||18;19;3;1;41|0|false', '781,,,,2,0||3|1|1|0|100|false|true|false|false|4|0|0|8|Pa||18;19;3;1;41|0|false', '781,,,,2,0||3|1|1|0|100|false|true|false|false|4|0|0|7|Pa||18;19;3;1;41|0|false', '781,,,,2,0||3|1|1|0|100|false|true|false|false|4|0|0|6|Pa||18;19;3;1;41|0|false', '781,,,,2,0||3|1|1|0|100|false|true|false|false|4|0|0|5|Pa||18;19;3;1;41|100|false', '')", + "(410, 'Brokle', 810, '51,1,1', '782,,,,2,0|782,,,,3,0|3|0|1|40|100|false|false|false|false|4|0|0|10|PaPa||18;19;3;1;41|0|false', '782,,,,2,0|782,,,,3,0|3|0|1|40|100|false|false|false|false|4|0|0|9|PaPa||18;19;3;1;41|0|false', '782,,,,2,0|782,,,,3,0|3|0|1|40|100|false|false|false|false|4|0|0|8|PaPa||18;19;3;1;41|0|false', '782,,,,2,0|782,,,,3,0|3|0|1|40|100|false|false|false|false|4|0|0|7|PaPa||18;19;3;1;41|0|false', '782,,,,2,0|782,,,,3,0|3|0|1|40|100|false|false|false|false|4|0|0|6|PaPa||18;19;3;1;41|0|false', '782,,,,2,0|782,,,,3,0|3|0|1|40|100|false|false|false|false|4|0|0|5|PaPa||18;19;3;1;41|100|false', '')", + "(2115, 'Tir Puissant du Dopeul', -1, '0,0,0', '114,2,,,2,0|114,3,,,2,0|6|0|1|40|5|false|false|false|false|0|0|0|10|PaPa||18;19;3;1;41|36|false', '114,2,,,2,0|114,3,,,2,0|5|0|1|40|10|false|false|false|false|0|0|0|10|PaPa||18;19;3;1;41|36|false', '114,2,,,2,0|114,3,,,2,0|5|0|1|40|15|false|false|false|false|0|0|0|10|PaPa||18;19;3;1;41|36|false', '114,2,,,2,0|114,3,,,2,0|5|0|1|40|20|false|false|false|false|0|0|0|10|PaPa||18;19;3;1;41|36|false', '114,2,,,3,0|114,3,,,3,0|4|0|1|40|30|false|false|false|false|0|0|0|10|PaPa||18;19;3;1;41|36|false', '114,2,,,4,0|114,3,,,4,0|4|0|1|40|100|false|false|false|false|0|0|0|10|PaPa||18;19;3;1;41|136|false', '2')", }, ",") + ";" ); diff --git a/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/EffectValueTest.java b/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/EffectValueTest.java index a6d93e38f..af7558987 100644 --- a/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/EffectValueTest.java +++ b/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/EffectValueTest.java @@ -21,13 +21,26 @@ import fr.arakne.utils.value.Interval; import fr.quatrevieux.araknemu._test.TestCase; +import fr.quatrevieux.araknemu.game.fight.Fight; +import fr.quatrevieux.araknemu.game.fight.FightBaseCase; +import fr.quatrevieux.araknemu.game.fight.castable.effect.buff.Buff; +import fr.quatrevieux.araknemu.game.fight.castable.effect.buff.BuffHook; +import fr.quatrevieux.araknemu.game.fight.fighter.Fighter; +import fr.quatrevieux.araknemu.game.fight.fighter.PassiveFighter; +import fr.quatrevieux.araknemu.game.spell.Spell; import fr.quatrevieux.araknemu.game.spell.effect.SpellEffect; import org.junit.jupiter.api.Test; import org.mockito.Mockito; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + import static org.junit.jupiter.api.Assertions.*; -class EffectValueTest extends TestCase { +class EffectValueTest extends FightBaseCase { @Test void defaultsWithRandomEffect() { SpellEffect effect = Mockito.mock(SpellEffect.class); @@ -56,6 +69,21 @@ void randomize() { assertEquals(new Interval(5, 10), value.interval()); } + @Test + void roll() { + SpellEffect effect = Mockito.mock(SpellEffect.class); + + Mockito.when(effect.min()).thenReturn(5); + Mockito.when(effect.max()).thenReturn(10); + + EffectValue value = new EffectValue(effect); + value.roll(); + + assertBetween(5, 10, value.value()); + assertEquals(value.value(), value.value()); + assertEquals(Interval.of(value.value()), value.interval()); + } + @Test void defaultsWithFixedEffect() { SpellEffect effect = Mockito.mock(SpellEffect.class); @@ -224,4 +252,106 @@ void allWithEffectBoost() { assertEquals(36, value.value()); assertEquals(new Interval(36, 36), value.interval()); } -} \ No newline at end of file + + @Test + void createShouldCallBuffs() throws Exception { + Fight fight = createFight(true); + + Fighter caster = player.fighter(); + Fighter target = other.fighter(); + + BuffHook casterHook = Mockito.mock(BuffHook.class); + BuffHook targetHook = Mockito.mock(BuffHook.class); + + Buff casterBuff = new Buff(Mockito.mock(SpellEffect.class), Mockito.mock(Spell.class), caster, caster, casterHook); + Buff targetBuff = new Buff(Mockito.mock(SpellEffect.class), Mockito.mock(Spell.class), target, target, targetHook); + + caster.buffs().add(casterBuff); + target.buffs().add(targetBuff); + + SpellEffect effect = Mockito.mock(SpellEffect.class); + + Mockito.when(effect.min()).thenReturn(5); + + EffectValue ev = EffectValue.create(effect, caster, target); + + assertEquals(5, ev.value()); + + Mockito.verify(casterHook, Mockito.times(1)).onEffectValueCast(casterBuff, ev); + Mockito.verify(targetHook, Mockito.times(1)).onEffectValueTarget(targetBuff, ev, caster); + } + + @Test + void forEachTargetsShouldCallBuffs() throws Exception { + Fight fight = createFight(true); + + Fighter caster = player.fighter(); + Fighter target = other.fighter(); + + BuffHook casterHook = Mockito.mock(BuffHook.class); + BuffHook targetHook = Mockito.mock(BuffHook.class); + + Buff casterBuff = new Buff(Mockito.mock(SpellEffect.class), Mockito.mock(Spell.class), caster, caster, casterHook); + Buff targetBuff = new Buff(Mockito.mock(SpellEffect.class), Mockito.mock(Spell.class), target, target, targetHook); + + caster.buffs().add(casterBuff); + target.buffs().add(targetBuff); + + SpellEffect effect = Mockito.mock(SpellEffect.class); + + Mockito.when(effect.min()).thenReturn(5); + + List values = new ArrayList<>(); + List fighters = new ArrayList<>(); + + EffectValue.forEachTargets(effect, caster, Arrays.asList(caster, target), ((fighter, value) -> { + values.add(value); + fighters.add(fighter); + })); + + assertIterableEquals(Arrays.asList(caster, target), fighters); + assertCount(2, values); + assertNotSame(values.get(0), values.get(1)); + assertEquals(5, values.get(0).value()); + assertEquals(5, values.get(1).value()); + + Mockito.verify(casterHook, Mockito.times(1)).onEffectValueCast(Mockito.eq(casterBuff), Mockito.any()); + + Mockito.verify(casterHook, Mockito.times(1)).onEffectValueTarget(casterBuff, values.get(0), caster); + Mockito.verify(targetHook, Mockito.times(1)).onEffectValueTarget(targetBuff, values.get(1), caster); + } + + @Test + void forEachTargetsWithMaximizeOnOneTarget() throws Exception { + Fight fight = createFight(true); + + Fighter caster = player.fighter(); + Fighter target = other.fighter(); + + Buff targetBuff = new Buff(Mockito.mock(SpellEffect.class), Mockito.mock(Spell.class), target, target, new BuffHook() { + @Override + public void onEffectValueTarget(Buff buff, EffectValue value, PassiveFighter caster) { + value.maximize(); + } + }); + target.buffs().add(targetBuff); + + SpellEffect effect = Mockito.mock(SpellEffect.class); + + Mockito.when(effect.min()).thenReturn(0); + Mockito.when(effect.max()).thenReturn(10000); + + List values = new ArrayList<>(); + List fighters = new ArrayList<>(); + + EffectValue.forEachTargets(effect, caster, Arrays.asList(caster, target), ((fighter, value) -> { + values.add(value); + fighters.add(fighter); + })); + + assertIterableEquals(Arrays.asList(caster, target), fighters); + assertCount(2, values); + assertBetween(0, 9999, values.get(0).value()); // Exclude 10 000 + assertEquals(10000, values.get(1).value()); + } +} diff --git a/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/FunctionalTest.java b/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/FunctionalTest.java index a4b741e98..da99a9653 100644 --- a/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/FunctionalTest.java +++ b/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/FunctionalTest.java @@ -822,6 +822,37 @@ void givePercentLife() { requestStack.assertOne(ActionEffect.alterLifePoints(fighters.get(0), fighters.get(1), 20)); } + @Test + void maximizeTargetEffects() { + castNormal(410, fighter2.cell()); // Brokle + passTurns(1); + castNormal(109, fighter2.cell()); // Bluff + + assertEquals(45, fighter2.life().max() - fighter2.life().current()); + requestStack.assertOne(ActionEffect.alterLifePoints(fighter1, fighter2, -45)); + } + + @Test + void minimizeCastedEffects() { + castNormal(416, fighter2.cell()); // Poisse + fighter1.turn().stop(); + + castNormal(109, fighter1.cell()); // Bluff + + assertEquals(1, fighter1.life().max() - fighter1.life().current()); + requestStack.assertOne(ActionEffect.alterLifePoints(fighter2, fighter1, -1)); + } + + @Test + void multiplyDamage() { + castNormal(2115, fighter1.cell()); // Tir Puissant du Dopeul + passTurns(1); + castNormal(183, fighter2.cell()); // Ronce + + int damage = fighter2.life().max() - fighter2.life().current(); + assertBetween(20, 34, damage); + } + private List configureFight(Consumer configurator) { fight.cancel(true); diff --git a/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/buff/BuffListTest.java b/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/buff/BuffListTest.java index d196dc769..c56bf9202 100644 --- a/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/buff/BuffListTest.java +++ b/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/buff/BuffListTest.java @@ -21,6 +21,7 @@ import fr.quatrevieux.araknemu.game.fight.FightBaseCase; import fr.quatrevieux.araknemu.game.fight.castable.CastScope; +import fr.quatrevieux.araknemu.game.fight.castable.effect.EffectValue; import fr.quatrevieux.araknemu.game.fight.castable.effect.Element; import fr.quatrevieux.araknemu.game.fight.castable.effect.handler.damage.Damage; import fr.quatrevieux.araknemu.game.fight.castable.effect.handler.damage.ReflectedDamage; @@ -301,6 +302,69 @@ void onReflectedDamage() { Mockito.verify(hook3).onReflectedDamage(buff3, damage); } + @Test + void onCastDamage() { + BuffHook hook1, hook2, hook3; + + Buff buff1 = new Buff(Mockito.mock(SpellEffect.class), Mockito.mock(Spell.class), other.fighter(), player.fighter(), hook1 = Mockito.mock(BuffHook.class)); + Buff buff2 = new Buff(Mockito.mock(SpellEffect.class), Mockito.mock(Spell.class), other.fighter(), player.fighter(), hook2 = Mockito.mock(BuffHook.class)); + Buff buff3 = new Buff(Mockito.mock(SpellEffect.class), Mockito.mock(Spell.class), other.fighter(), player.fighter(), hook3 = Mockito.mock(BuffHook.class)); + + list.add(buff1); + list.add(buff2); + list.add(buff3); + + Damage damage = new Damage(10, Element.NEUTRAL); + + list.onCastDamage(damage, other.fighter()); + + Mockito.verify(hook1).onCastDamage(buff1, damage, other.fighter()); + Mockito.verify(hook2).onCastDamage(buff2, damage, other.fighter()); + Mockito.verify(hook3).onCastDamage(buff3, damage, other.fighter()); + } + + @Test + void onEffectValueCast() { + BuffHook hook1, hook2, hook3; + + Buff buff1 = new Buff(Mockito.mock(SpellEffect.class), Mockito.mock(Spell.class), other.fighter(), player.fighter(), hook1 = Mockito.mock(BuffHook.class)); + Buff buff2 = new Buff(Mockito.mock(SpellEffect.class), Mockito.mock(Spell.class), other.fighter(), player.fighter(), hook2 = Mockito.mock(BuffHook.class)); + Buff buff3 = new Buff(Mockito.mock(SpellEffect.class), Mockito.mock(Spell.class), other.fighter(), player.fighter(), hook3 = Mockito.mock(BuffHook.class)); + + list.add(buff1); + list.add(buff2); + list.add(buff3); + + EffectValue ev = EffectValue.create(Mockito.mock(SpellEffect.class), other.fighter(), other.fighter()); + + list.onEffectValueCast(ev); + + Mockito.verify(hook1).onEffectValueCast(buff1, ev); + Mockito.verify(hook2).onEffectValueCast(buff2, ev); + Mockito.verify(hook3).onEffectValueCast(buff3, ev); + } + + @Test + void onEffectValueTarget() { + BuffHook hook1, hook2, hook3; + + Buff buff1 = new Buff(Mockito.mock(SpellEffect.class), Mockito.mock(Spell.class), other.fighter(), player.fighter(), hook1 = Mockito.mock(BuffHook.class)); + Buff buff2 = new Buff(Mockito.mock(SpellEffect.class), Mockito.mock(Spell.class), other.fighter(), player.fighter(), hook2 = Mockito.mock(BuffHook.class)); + Buff buff3 = new Buff(Mockito.mock(SpellEffect.class), Mockito.mock(Spell.class), other.fighter(), player.fighter(), hook3 = Mockito.mock(BuffHook.class)); + + list.add(buff1); + list.add(buff2); + list.add(buff3); + + EffectValue ev = EffectValue.create(Mockito.mock(SpellEffect.class), other.fighter(), other.fighter()); + + list.onEffectValueTarget(ev, player.fighter()); + + Mockito.verify(hook1).onEffectValueTarget(buff1, ev, player.fighter()); + Mockito.verify(hook2).onEffectValueTarget(buff2, ev, player.fighter()); + Mockito.verify(hook3).onEffectValueTarget(buff3, ev, player.fighter()); + } + @Test void refreshWillDecrementRemaingTurnsAndRemoveExpiredBuffs() { BuffHook hook1, hook2, hook3; diff --git a/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/AddCharacteristicHandlerTest.java b/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/AddCharacteristicHandlerTest.java index 12f889f88..62fee5315 100644 --- a/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/AddCharacteristicHandlerTest.java +++ b/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/AddCharacteristicHandlerTest.java @@ -24,7 +24,10 @@ import fr.quatrevieux.araknemu.game.fight.Fight; import fr.quatrevieux.araknemu.game.fight.FightBaseCase; import fr.quatrevieux.araknemu.game.fight.castable.CastScope; +import fr.quatrevieux.araknemu.game.fight.castable.effect.EffectValue; import fr.quatrevieux.araknemu.game.fight.castable.effect.buff.Buff; +import fr.quatrevieux.araknemu.game.fight.castable.effect.buff.BuffHook; +import fr.quatrevieux.araknemu.game.fight.fighter.PassiveFighter; import fr.quatrevieux.araknemu.game.fight.fighter.player.PlayerFighter; import fr.quatrevieux.araknemu.game.spell.Spell; import fr.quatrevieux.araknemu.game.spell.SpellConstraints; @@ -91,7 +94,7 @@ void buff() { Mockito.when(effect.effect()).thenReturn(123); Mockito.when(effect.min()).thenReturn(50); - Mockito.when(effect.min()).thenReturn(60); + Mockito.when(effect.max()).thenReturn(60); Mockito.when(effect.duration()).thenReturn(5); Mockito.when(effect.area()).thenReturn(new CircleArea(new EffectArea(EffectArea.Type.CIRCLE, 10))); Mockito.when(effect.target()).thenReturn(SpellEffectTarget.DEFAULT); @@ -111,6 +114,41 @@ void buff() { assertEquals(buff1.get().effect().min(), buff2.get().effect().min()); } + @Test + void buffWithOneTargetMaximized() { + target.buffs().add(new Buff(Mockito.mock(SpellEffect.class), Mockito.mock(Spell.class), target, target, new BuffHook() { + @Override + public void onEffectValueTarget(Buff buff, EffectValue value, PassiveFighter caster) { + value.maximize(); + } + })); + + SpellEffect effect = Mockito.mock(SpellEffect.class); + Spell spell = Mockito.mock(Spell.class); + SpellConstraints constraints = Mockito.mock(SpellConstraints.class); + + Mockito.when(effect.effect()).thenReturn(123); + Mockito.when(effect.min()).thenReturn(0); + Mockito.when(effect.max()).thenReturn(10000); + Mockito.when(effect.duration()).thenReturn(5); + Mockito.when(effect.area()).thenReturn(new CircleArea(new EffectArea(EffectArea.Type.CIRCLE, 10))); + Mockito.when(effect.target()).thenReturn(SpellEffectTarget.DEFAULT); + Mockito.when(spell.constraints()).thenReturn(constraints); + Mockito.when(constraints.freeCell()).thenReturn(false); + + CastScope scope = makeCastScope(target, spell, effect, caster.cell()); + handler.buff(scope, scope.effects().get(0)); + + Optional buff1 = caster.buffs().stream().filter(buff -> buff.effect().effect() == 123).findFirst(); + Optional buff2 = target.buffs().stream().filter(buff -> buff.effect().effect() == 123).findFirst(); + + assertTrue(buff1.isPresent()); + assertTrue(buff2.isPresent()); + + assertBetween(0, 9999, buff1.get().effect().min()); + assertEquals(10000, buff2.get().effect().min()); + } + @Test void onBuffStartedAndTerminated() { SpellEffect effect = Mockito.mock(SpellEffect.class); diff --git a/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/damage/DamageApplierTest.java b/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/damage/DamageApplierTest.java index 77ce67109..86e25059a 100644 --- a/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/damage/DamageApplierTest.java +++ b/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/damage/DamageApplierTest.java @@ -587,4 +587,78 @@ void applyFixedBuffDamageWithResistance() { assertEquals(-2, value); assertEquals(-2, target.life().current() - target.life().max()); } + + @Test + void applyShouldCallOnCastDamageOnCaster() { + SpellEffect effect = Mockito.mock(SpellEffect.class); + + Mockito.when(effect.min()).thenReturn(10); + + DamageApplier applier = new DamageApplier(Element.AIR, fight); + + BuffHook hook = Mockito.mock(BuffHook.class); + Buff buff = new Buff(Mockito.mock(SpellEffect.class), Mockito.mock(Spell.class), target, target, hook); + + caster.buffs().add(buff); + + int value = applier.apply(caster, effect, target); + + assertEquals(-10, value); + + Mockito.verify(hook, Mockito.times(1)).onCastDamage(Mockito.eq(buff), Mockito.argThat(damage -> damage.value() == 10), Mockito.eq(target)); + } + + @Test + void applyFixedShouldCallOnCastDamageOnCaster() { + DamageApplier applier = new DamageApplier(Element.AIR, fight); + + BuffHook hook = Mockito.mock(BuffHook.class); + Buff buff = new Buff(Mockito.mock(SpellEffect.class), Mockito.mock(Spell.class), target, target, hook); + + caster.buffs().add(buff); + + int value = applier.applyFixed(caster, 10, target); + + assertEquals(-10, value); + + Mockito.verify(hook, Mockito.times(1)).onCastDamage(Mockito.eq(buff), Mockito.argThat(damage -> damage.value() == 10), Mockito.eq(target)); + } + + @Test + void applyBuffShouldCallOnCastDamageOnCaster() { + DamageApplier applier = new DamageApplier(Element.AIR, fight); + + BuffHook hook = Mockito.mock(BuffHook.class); + Buff buff = new Buff(Mockito.mock(SpellEffect.class), Mockito.mock(Spell.class), target, target, hook); + + caster.buffs().add(buff); + + SpellEffect effect = Mockito.mock(SpellEffect.class); + Mockito.when(effect.min()).thenReturn(10); + Buff toApply = new Buff(effect, Mockito.mock(Spell.class), caster, target, Mockito.mock(BuffHook.class)); + + int value = applier.apply(toApply); + + assertEquals(-10, value); + + Mockito.verify(hook, Mockito.times(1)).onCastDamage(Mockito.eq(buff), Mockito.argThat(damage -> damage.value() == 10), Mockito.eq(target)); + } + + @Test + void applyFixedBuffShouldCallOnCastDamageOnCaster() { + DamageApplier applier = new DamageApplier(Element.AIR, fight); + + BuffHook hook = Mockito.mock(BuffHook.class); + Buff buff = new Buff(Mockito.mock(SpellEffect.class), Mockito.mock(Spell.class), target, target, hook); + + caster.buffs().add(buff); + + Buff toApply = new Buff(Mockito.mock(SpellEffect.class), Mockito.mock(Spell.class), caster, target, Mockito.mock(BuffHook.class)); + + int value = applier.applyFixed(toApply, 10); + + assertEquals(-10, value); + + Mockito.verify(hook, Mockito.times(1)).onCastDamage(Mockito.eq(buff), Mockito.argThat(damage -> damage.value() == 10), Mockito.eq(target)); + } } diff --git a/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/modifier/MaximizeTargetEffectsHandlerTest.java b/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/modifier/MaximizeTargetEffectsHandlerTest.java new file mode 100644 index 000000000..f28f8f02e --- /dev/null +++ b/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/modifier/MaximizeTargetEffectsHandlerTest.java @@ -0,0 +1,134 @@ +/* + * This file is part of Araknemu. + * + * Araknemu is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Araknemu is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Araknemu. If not, see . + * + * Copyright (c) 2017-2022 Vincent Quatrevieux + */ + +package fr.quatrevieux.araknemu.game.fight.castable.effect.handler.modifier; + +import fr.quatrevieux.araknemu.data.value.EffectArea; +import fr.quatrevieux.araknemu.game.fight.Fight; +import fr.quatrevieux.araknemu.game.fight.FightBaseCase; +import fr.quatrevieux.araknemu.game.fight.castable.CastScope; +import fr.quatrevieux.araknemu.game.fight.castable.effect.Element; +import fr.quatrevieux.araknemu.game.fight.castable.effect.buff.Buff; +import fr.quatrevieux.araknemu.game.fight.castable.effect.handler.damage.DamageApplier; +import fr.quatrevieux.araknemu.game.fight.fighter.player.PlayerFighter; +import fr.quatrevieux.araknemu.game.spell.Spell; +import fr.quatrevieux.araknemu.game.spell.SpellConstraints; +import fr.quatrevieux.araknemu.game.spell.effect.SpellEffect; +import fr.quatrevieux.araknemu.game.spell.effect.area.CellArea; +import fr.quatrevieux.araknemu.game.spell.effect.area.CircleArea; +import fr.quatrevieux.araknemu.game.spell.effect.target.SpellEffectTarget; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class MaximizeTargetEffectsHandlerTest extends FightBaseCase { + private Fight fight; + private PlayerFighter caster; + private PlayerFighter target; + private MaximizeTargetEffectsHandler handler; + + @Override + @BeforeEach + public void setUp() throws Exception { + super.setUp(); + + fight = createFight(); + fight.nextState(); + fight.turnList().start(); + + caster = player.fighter(); + target = other.fighter(); + + target.move(fight.map().get(123)); + + handler = new MaximizeTargetEffectsHandler(); + + requestStack.clear(); + } + + @Test + void handle() { + SpellEffect effect = Mockito.mock(SpellEffect.class); + Spell spell = Mockito.mock(Spell.class); + SpellConstraints constraints = Mockito.mock(SpellConstraints.class); + + Mockito.when(effect.area()).thenReturn(new CellArea()); + Mockito.when(effect.target()).thenReturn(SpellEffectTarget.DEFAULT); + Mockito.when(spell.constraints()).thenReturn(constraints); + Mockito.when(constraints.freeCell()).thenReturn(false); + + CastScope scope = makeCastScope(caster, spell, effect, caster.cell()); + assertThrows(UnsupportedOperationException.class, () -> handler.handle(scope, scope.effects().get(0))); + } + + @Test + void buff() { + SpellEffect effect = Mockito.mock(SpellEffect.class); + Spell spell = Mockito.mock(Spell.class); + SpellConstraints constraints = Mockito.mock(SpellConstraints.class); + + Mockito.when(effect.effect()).thenReturn(123); + Mockito.when(effect.min()).thenReturn(50); + Mockito.when(effect.max()).thenReturn(60); + Mockito.when(effect.duration()).thenReturn(5); + Mockito.when(effect.area()).thenReturn(new CircleArea(new EffectArea(EffectArea.Type.CIRCLE, 10))); + Mockito.when(effect.target()).thenReturn(SpellEffectTarget.DEFAULT); + Mockito.when(spell.constraints()).thenReturn(constraints); + Mockito.when(constraints.freeCell()).thenReturn(false); + + CastScope scope = makeCastScope(caster, spell, effect, caster.cell()); + handler.buff(scope, scope.effects().get(0)); + + Optional buff1 = caster.buffs().stream().filter(buff -> buff.effect().effect() == 123).findFirst(); + Optional buff2 = target.buffs().stream().filter(buff -> buff.effect().effect() == 123).findFirst(); + + assertTrue(buff1.isPresent()); + assertTrue(buff2.isPresent()); + } + + @Test + void functional() { + SpellEffect effect = Mockito.mock(SpellEffect.class); + Spell spell = Mockito.mock(Spell.class); + SpellConstraints constraints = Mockito.mock(SpellConstraints.class); + + Mockito.when(effect.effect()).thenReturn(123); + Mockito.when(effect.duration()).thenReturn(5); + Mockito.when(effect.area()).thenReturn(new CellArea()); + Mockito.when(effect.target()).thenReturn(SpellEffectTarget.DEFAULT); + Mockito.when(spell.constraints()).thenReturn(constraints); + Mockito.when(constraints.freeCell()).thenReturn(false); + + CastScope scope = makeCastScope(caster, spell, effect, target.cell()); + handler.buff(scope, scope.effects().get(0)); + + SpellEffect damageEffect = Mockito.mock(SpellEffect.class); + Mockito.when(damageEffect.min()).thenReturn(5); + Mockito.when(damageEffect.max()).thenReturn(10); + + assertEquals(-10, new DamageApplier(Element.AIR, fight).apply(caster, damageEffect, target)); + assertEquals(10, target.life().max() - target.life().current()); + } +} diff --git a/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/modifier/MinimizeCastedEffectsHandlerTest.java b/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/modifier/MinimizeCastedEffectsHandlerTest.java new file mode 100644 index 000000000..d60db6813 --- /dev/null +++ b/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/modifier/MinimizeCastedEffectsHandlerTest.java @@ -0,0 +1,134 @@ +/* + * This file is part of Araknemu. + * + * Araknemu is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Araknemu is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Araknemu. If not, see . + * + * Copyright (c) 2017-2022 Vincent Quatrevieux + */ + +package fr.quatrevieux.araknemu.game.fight.castable.effect.handler.modifier; + +import fr.quatrevieux.araknemu.data.value.EffectArea; +import fr.quatrevieux.araknemu.game.fight.Fight; +import fr.quatrevieux.araknemu.game.fight.FightBaseCase; +import fr.quatrevieux.araknemu.game.fight.castable.CastScope; +import fr.quatrevieux.araknemu.game.fight.castable.effect.Element; +import fr.quatrevieux.araknemu.game.fight.castable.effect.buff.Buff; +import fr.quatrevieux.araknemu.game.fight.castable.effect.handler.damage.DamageApplier; +import fr.quatrevieux.araknemu.game.fight.fighter.player.PlayerFighter; +import fr.quatrevieux.araknemu.game.spell.Spell; +import fr.quatrevieux.araknemu.game.spell.SpellConstraints; +import fr.quatrevieux.araknemu.game.spell.effect.SpellEffect; +import fr.quatrevieux.araknemu.game.spell.effect.area.CellArea; +import fr.quatrevieux.araknemu.game.spell.effect.area.CircleArea; +import fr.quatrevieux.araknemu.game.spell.effect.target.SpellEffectTarget; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class MinimizeCastedEffectsHandlerTest extends FightBaseCase { + private Fight fight; + private PlayerFighter caster; + private PlayerFighter target; + private MinimizeCastedEffectsHandler handler; + + @Override + @BeforeEach + public void setUp() throws Exception { + super.setUp(); + + fight = createFight(); + fight.nextState(); + fight.turnList().start(); + + caster = player.fighter(); + target = other.fighter(); + + target.move(fight.map().get(123)); + + handler = new MinimizeCastedEffectsHandler(); + + requestStack.clear(); + } + + @Test + void handle() { + SpellEffect effect = Mockito.mock(SpellEffect.class); + Spell spell = Mockito.mock(Spell.class); + SpellConstraints constraints = Mockito.mock(SpellConstraints.class); + + Mockito.when(effect.area()).thenReturn(new CellArea()); + Mockito.when(effect.target()).thenReturn(SpellEffectTarget.DEFAULT); + Mockito.when(spell.constraints()).thenReturn(constraints); + Mockito.when(constraints.freeCell()).thenReturn(false); + + CastScope scope = makeCastScope(caster, spell, effect, caster.cell()); + assertThrows(UnsupportedOperationException.class, () -> handler.handle(scope, scope.effects().get(0))); + } + + @Test + void buff() { + SpellEffect effect = Mockito.mock(SpellEffect.class); + Spell spell = Mockito.mock(Spell.class); + SpellConstraints constraints = Mockito.mock(SpellConstraints.class); + + Mockito.when(effect.effect()).thenReturn(123); + Mockito.when(effect.min()).thenReturn(50); + Mockito.when(effect.max()).thenReturn(60); + Mockito.when(effect.duration()).thenReturn(5); + Mockito.when(effect.area()).thenReturn(new CircleArea(new EffectArea(EffectArea.Type.CIRCLE, 10))); + Mockito.when(effect.target()).thenReturn(SpellEffectTarget.DEFAULT); + Mockito.when(spell.constraints()).thenReturn(constraints); + Mockito.when(constraints.freeCell()).thenReturn(false); + + CastScope scope = makeCastScope(caster, spell, effect, caster.cell()); + handler.buff(scope, scope.effects().get(0)); + + Optional buff1 = caster.buffs().stream().filter(buff -> buff.effect().effect() == 123).findFirst(); + Optional buff2 = target.buffs().stream().filter(buff -> buff.effect().effect() == 123).findFirst(); + + assertTrue(buff1.isPresent()); + assertTrue(buff2.isPresent()); + } + + @Test + void functional() { + SpellEffect effect = Mockito.mock(SpellEffect.class); + Spell spell = Mockito.mock(Spell.class); + SpellConstraints constraints = Mockito.mock(SpellConstraints.class); + + Mockito.when(effect.effect()).thenReturn(123); + Mockito.when(effect.duration()).thenReturn(5); + Mockito.when(effect.area()).thenReturn(new CellArea()); + Mockito.when(effect.target()).thenReturn(SpellEffectTarget.DEFAULT); + Mockito.when(spell.constraints()).thenReturn(constraints); + Mockito.when(constraints.freeCell()).thenReturn(false); + + CastScope scope = makeCastScope(caster, spell, effect, target.cell()); + handler.buff(scope, scope.effects().get(0)); + + SpellEffect damageEffect = Mockito.mock(SpellEffect.class); + Mockito.when(damageEffect.min()).thenReturn(5); + Mockito.when(damageEffect.max()).thenReturn(10); + + assertEquals(-5, new DamageApplier(Element.AIR, fight).apply(target, damageEffect, caster)); + assertEquals(5, caster.life().max() - caster.life().current()); + } +} diff --git a/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/modifier/MultiplyDamageHandlerTest.java b/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/modifier/MultiplyDamageHandlerTest.java new file mode 100644 index 000000000..7798d00dd --- /dev/null +++ b/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/modifier/MultiplyDamageHandlerTest.java @@ -0,0 +1,134 @@ +/* + * This file is part of Araknemu. + * + * Araknemu is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Araknemu is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Araknemu. If not, see . + * + * Copyright (c) 2017-2022 Vincent Quatrevieux + */ + +package fr.quatrevieux.araknemu.game.fight.castable.effect.handler.modifier; + +import fr.quatrevieux.araknemu.data.value.EffectArea; +import fr.quatrevieux.araknemu.game.fight.Fight; +import fr.quatrevieux.araknemu.game.fight.FightBaseCase; +import fr.quatrevieux.araknemu.game.fight.castable.CastScope; +import fr.quatrevieux.araknemu.game.fight.castable.effect.Element; +import fr.quatrevieux.araknemu.game.fight.castable.effect.buff.Buff; +import fr.quatrevieux.araknemu.game.fight.castable.effect.handler.damage.DamageApplier; +import fr.quatrevieux.araknemu.game.fight.fighter.player.PlayerFighter; +import fr.quatrevieux.araknemu.game.spell.Spell; +import fr.quatrevieux.araknemu.game.spell.SpellConstraints; +import fr.quatrevieux.araknemu.game.spell.effect.SpellEffect; +import fr.quatrevieux.araknemu.game.spell.effect.area.CellArea; +import fr.quatrevieux.araknemu.game.spell.effect.area.CircleArea; +import fr.quatrevieux.araknemu.game.spell.effect.target.SpellEffectTarget; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; + +class MultiplyDamageHandlerTest extends FightBaseCase { + private Fight fight; + private PlayerFighter caster; + private PlayerFighter target; + private MultiplyDamageHandler handler; + + @Override + @BeforeEach + public void setUp() throws Exception { + super.setUp(); + + fight = createFight(); + fight.nextState(); + fight.turnList().start(); + + caster = player.fighter(); + target = other.fighter(); + + target.move(fight.map().get(123)); + + handler = new MultiplyDamageHandler(); + + requestStack.clear(); + } + + @Test + void handle() { + SpellEffect effect = Mockito.mock(SpellEffect.class); + Spell spell = Mockito.mock(Spell.class); + SpellConstraints constraints = Mockito.mock(SpellConstraints.class); + + Mockito.when(effect.area()).thenReturn(new CellArea()); + Mockito.when(effect.target()).thenReturn(SpellEffectTarget.DEFAULT); + Mockito.when(spell.constraints()).thenReturn(constraints); + Mockito.when(constraints.freeCell()).thenReturn(false); + + CastScope scope = makeCastScope(caster, spell, effect, caster.cell()); + assertThrows(UnsupportedOperationException.class, () -> handler.handle(scope, scope.effects().get(0))); + } + + @Test + void buff() { + SpellEffect effect = Mockito.mock(SpellEffect.class); + Spell spell = Mockito.mock(Spell.class); + SpellConstraints constraints = Mockito.mock(SpellConstraints.class); + + Mockito.when(effect.effect()).thenReturn(123); + Mockito.when(effect.min()).thenReturn(3); + Mockito.when(effect.duration()).thenReturn(5); + Mockito.when(effect.area()).thenReturn(new CircleArea(new EffectArea(EffectArea.Type.CIRCLE, 10))); + Mockito.when(effect.target()).thenReturn(SpellEffectTarget.DEFAULT); + Mockito.when(spell.constraints()).thenReturn(constraints); + Mockito.when(constraints.freeCell()).thenReturn(false); + + CastScope scope = makeCastScope(caster, spell, effect, caster.cell()); + handler.buff(scope, scope.effects().get(0)); + + Optional buff1 = caster.buffs().stream().filter(buff -> buff.effect().effect() == 123).findFirst(); + Optional buff2 = target.buffs().stream().filter(buff -> buff.effect().effect() == 123).findFirst(); + + assertTrue(buff1.isPresent()); + assertTrue(buff2.isPresent()); + + assertEquals(3, buff1.get().effect().min()); + assertEquals(3, buff2.get().effect().min()); + } + + @Test + void functional() { + SpellEffect effect = Mockito.mock(SpellEffect.class); + Spell spell = Mockito.mock(Spell.class); + SpellConstraints constraints = Mockito.mock(SpellConstraints.class); + + Mockito.when(effect.effect()).thenReturn(123); + Mockito.when(effect.min()).thenReturn(3); + Mockito.when(effect.duration()).thenReturn(5); + Mockito.when(effect.area()).thenReturn(new CellArea()); + Mockito.when(effect.target()).thenReturn(SpellEffectTarget.DEFAULT); + Mockito.when(spell.constraints()).thenReturn(constraints); + Mockito.when(constraints.freeCell()).thenReturn(false); + + CastScope scope = makeCastScope(caster, spell, effect, caster.cell()); + handler.buff(scope, scope.effects().get(0)); + + SpellEffect damageEffect = Mockito.mock(SpellEffect.class); + Mockito.when(damageEffect.min()).thenReturn(5); + + assertEquals(-21, new DamageApplier(Element.EARTH, fight).apply(caster, damageEffect, target)); + assertEquals(21, target.life().max() - target.life().current()); + } +}