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());
+ }
+}