diff --git a/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/buff/BuffEffect.java b/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/buff/BuffEffect.java
index e647c66d6..e0fbd44f1 100644
--- a/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/buff/BuffEffect.java
+++ b/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/buff/BuffEffect.java
@@ -29,49 +29,33 @@
public final class BuffEffect implements SpellEffect {
private final SpellEffect baseEffect;
- private final int min;
- private final int max;
- private final int special;
- private final String text;
+ private final int effect;
+ private final int value;
- public BuffEffect(SpellEffect baseEffect, int min) {
- this(baseEffect, min, 0, 0);
- }
-
- public BuffEffect(SpellEffect baseEffect, int min, int max) {
- this(baseEffect, min, max, 0);
- }
-
- public BuffEffect(SpellEffect baseEffect, int min, int max, int special) {
- this(baseEffect, min, max, special, null);
- }
-
- public BuffEffect(SpellEffect baseEffect, int min, int max, int special, String text) {
+ private BuffEffect(SpellEffect baseEffect, int effect, int value) {
this.baseEffect = baseEffect;
- this.min = min;
- this.max = max;
- this.special = special;
- this.text = text;
+ this.effect = effect;
+ this.value = value;
}
@Override
public int effect() {
- return baseEffect.effect();
+ return effect;
}
@Override
public int min() {
- return min;
+ return value;
}
@Override
public int max() {
- return max;
+ return 0;
}
@Override
public int special() {
- return special;
+ return 0;
}
@Override
@@ -86,7 +70,7 @@ public int probability() {
@Override
public String text() {
- return text;
+ return null;
}
@Override
@@ -98,4 +82,25 @@ public SpellEffectArea area() {
public EffectTarget target() {
return baseEffect.target();
}
+
+ /**
+ * Set a fixed effect value
+ *
+ * @param baseEffect The spell effect
+ * @param value The applied value
+ */
+ public static BuffEffect fixed(SpellEffect baseEffect, int value) {
+ return new BuffEffect(baseEffect, baseEffect.effect(), value);
+ }
+
+ /**
+ * Define an effect with custom effect id and a fixed value
+ *
+ * @param baseEffect The spell effect
+ * @param effect The overridden effect id
+ * @param value The applied value
+ */
+ public static BuffEffect withCustomEffect(SpellEffect baseEffect, int effect, int value) {
+ return new BuffEffect(baseEffect, effect, 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 44be62723..3616f783a 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
@@ -68,6 +68,6 @@ public void onBuffTerminated(Buff buff) {
private SpellEffect computeBuffEffect(CastScope cast, SpellEffect effect) {
final EffectValue value = new EffectValue(effect);
- return new BuffEffect(effect, value.value());
+ return BuffEffect.fixed(effect, value.value());
}
}
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 bf513f32d..27de4e84a 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
@@ -41,18 +41,22 @@
* See: https://forums.jeuxonline.info/sujet/801243/les-formules-de-calcul-dans-dofus#titre_7
*/
public abstract class AbstractPointLostApplier {
+ public static final int USE_SPELL_EFFECT = 0;
+
private final Fight fight;
private final AlterPointHook hook;
private final Characteristic characteristic;
private final Characteristic resistance;
+ private final int removalPointEffect;
private final RandomUtil random = new RandomUtil();
- protected AbstractPointLostApplier(Fight fight, AlterPointHook hook, Characteristic characteristic, Characteristic resistance) {
+ protected AbstractPointLostApplier(Fight fight, AlterPointHook hook, Characteristic characteristic, Characteristic resistance, int removalPointEffect) {
this.fight = fight;
this.hook = hook;
this.characteristic = characteristic;
this.resistance = resistance;
+ this.removalPointEffect = removalPointEffect;
}
/**
@@ -79,7 +83,7 @@ public final int apply(CastScope cast, PassiveFighter target, SpellEffect effect
}
if (lost > 0) {
- target.buffs().add(new Buff(new BuffEffect(effect, lost), cast.action(), caster, target, hook));
+ target.buffs().add(new Buff(buffEffect(effect, lost), cast.action(), caster, target, hook));
}
return lost;
@@ -90,6 +94,16 @@ public final int apply(CastScope cast, PassiveFighter target, SpellEffect effect
*/
protected abstract ActionEffect dodgeMessage(PassiveFighter caster, PassiveFighter target, int value);
+ /**
+ * Create the buff effect for the given point lost
+ */
+ private SpellEffect buffEffect(SpellEffect baseEffect, int pointLost) {
+ return removalPointEffect == USE_SPELL_EFFECT
+ ? BuffEffect.fixed(baseEffect, pointLost)
+ : BuffEffect.withCustomEffect(baseEffect, removalPointEffect, pointLost)
+ ;
+ }
+
/**
* Compute how many points will be loose
*
diff --git a/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/point/AbstractStealPointHandler.java b/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/point/AbstractStealPointHandler.java
new file mode 100644
index 000000000..38b590728
--- /dev/null
+++ b/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/point/AbstractStealPointHandler.java
@@ -0,0 +1,118 @@
+/*
+ * 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-2021 Vincent Quatrevieux
+ */
+
+package fr.quatrevieux.araknemu.game.fight.castable.effect.handler.characteristic.point;
+
+import fr.quatrevieux.araknemu.game.fight.Fight;
+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.BuffEffect;
+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.PassiveFighter;
+import fr.quatrevieux.araknemu.game.fight.turn.Turn;
+import fr.quatrevieux.araknemu.game.spell.effect.SpellEffect;
+
+/**
+ * Base effect for steal turn points
+ *
+ * @see StealActionPointHandler
+ * @see StealMovementPointHandler
+ */
+public abstract class AbstractStealPointHandler implements EffectHandler {
+ private final Fight fight;
+ private final AbstractPointLostApplier removePointApplier;
+ private final AlterPointHook addPointHook;
+ private final int addPointEffect;
+
+ /**
+ * @param fight Fight where the effect will be applied
+ * @param removePointApplier Applied use to remove points from targets
+ * @param addPointHook Applied use to add points to caster
+ * @param addPointEffect Effect id used by the "add point" buff
+ */
+ public AbstractStealPointHandler(Fight fight, AbstractPointLostApplier removePointApplier, AlterPointHook addPointHook, int addPointEffect) {
+ this.fight = fight;
+ this.removePointApplier = removePointApplier;
+ this.addPointHook = addPointHook;
+ this.addPointEffect = addPointEffect;
+ }
+
+ @Override
+ public void handle(CastScope cast, CastScope.EffectScope effect) {
+ final ActiveFighter caster = cast.caster();
+ final int stolen = apply(cast, effect);
+
+ if (stolen > 0) {
+ fight.turnList().current()
+ .filter(turn -> turn.fighter().equals(caster))
+ .ifPresent(turn -> applyOnCurrentTurn(fight, turn, caster, stolen))
+ ;
+ }
+ }
+
+ @Override
+ public void buff(CastScope cast, CastScope.EffectScope effect) {
+ final ActiveFighter caster = cast.caster();
+ final int stolen = apply(cast, effect);
+
+ if (stolen > 0) {
+ caster.buffs().add(
+ new Buff(
+ BuffEffect.withCustomEffect(effect.effect(), addPointEffect, stolen),
+ cast.action(),
+ caster,
+ caster,
+ addPointHook
+ )
+ );
+ }
+ }
+
+ /**
+ * Apply the add point effect to the current turn
+ * This method is called only if the effect is used as direct effect (i.e. without duration)
+ * This method should update turn points and send to client the current turn point modification
+ *
+ * @param fight The active fight
+ * @param turn The active turn
+ * @param toAdd Number of points to add. This value is always >= 1
+ */
+ protected abstract void applyOnCurrentTurn(Fight fight, Turn turn, ActiveFighter caster, int toAdd);
+
+ /**
+ * Apply to all targets and compute the stolen points
+ *
+ * @return Stolen action points. 0 if target has dodged
+ */
+ private int apply(CastScope cast, CastScope.EffectScope effect) {
+ final ActiveFighter caster = cast.caster();
+ final SpellEffect baseEffect = effect.effect();
+
+ int stolen = 0;
+
+ for (PassiveFighter target : effect.targets()) {
+ if (!target.equals(caster)) {
+ stolen += removePointApplier.apply(cast, target, baseEffect);
+ }
+ }
+
+ return stolen;
+ }
+}
diff --git a/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/point/ActionPointLostApplier.java b/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/point/ActionPointLostApplier.java
index ff0647f52..b68875082 100644
--- a/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/point/ActionPointLostApplier.java
+++ b/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/point/ActionPointLostApplier.java
@@ -29,7 +29,21 @@
*/
public final class ActionPointLostApplier extends AbstractPointLostApplier {
public ActionPointLostApplier(Fight fight) {
- super(fight, AlterPointHook.removeActionPoint(fight), Characteristic.ACTION_POINT, Characteristic.RESISTANCE_ACTION_POINT);
+ this(fight, USE_SPELL_EFFECT);
+ }
+
+ /**
+ * @param fight The fight where the effect should be applied
+ * @param removalPointEffect Overrides the spell effect used by the buff
+ */
+ public ActionPointLostApplier(Fight fight, int removalPointEffect) {
+ super(
+ fight,
+ AlterPointHook.removeActionPoint(fight),
+ Characteristic.ACTION_POINT,
+ Characteristic.RESISTANCE_ACTION_POINT,
+ removalPointEffect
+ );
}
@Override
diff --git a/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/point/MovementPointLostApplier.java b/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/point/MovementPointLostApplier.java
index a42c31457..8a07eeac7 100644
--- a/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/point/MovementPointLostApplier.java
+++ b/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/point/MovementPointLostApplier.java
@@ -29,7 +29,21 @@
*/
public final class MovementPointLostApplier extends AbstractPointLostApplier {
public MovementPointLostApplier(Fight fight) {
- super(fight, AlterPointHook.removeMovementPoint(fight), Characteristic.MOVEMENT_POINT, Characteristic.RESISTANCE_MOVEMENT_POINT);
+ this(fight, USE_SPELL_EFFECT);
+ }
+
+ /**
+ * @param fight The fight where the effect should be applied
+ * @param removalPointEffect Overrides the spell effect used by the buff
+ */
+ public MovementPointLostApplier(Fight fight, int removalPointEffect) {
+ super(
+ fight,
+ AlterPointHook.removeMovementPoint(fight),
+ Characteristic.MOVEMENT_POINT,
+ Characteristic.RESISTANCE_MOVEMENT_POINT,
+ removalPointEffect
+ );
}
@Override
diff --git a/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/point/StealActionPointHandler.java b/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/point/StealActionPointHandler.java
new file mode 100644
index 000000000..755d64104
--- /dev/null
+++ b/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/point/StealActionPointHandler.java
@@ -0,0 +1,55 @@
+/*
+ * 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-2021 Vincent Quatrevieux
+ */
+
+package fr.quatrevieux.araknemu.game.fight.castable.effect.handler.characteristic.point;
+
+import fr.quatrevieux.araknemu.game.fight.Fight;
+import fr.quatrevieux.araknemu.game.fight.fighter.ActiveFighter;
+import fr.quatrevieux.araknemu.game.fight.turn.Turn;
+import fr.quatrevieux.araknemu.network.game.out.fight.action.ActionEffect;
+
+/**
+ * Effect for steal action points
+ *
+ * Like {@link ActionPointLostHandler} the removal can be dodged
+ * This effect is equivalent to apply {@link ActionPointLostHandler} chained with {@link fr.quatrevieux.araknemu.game.fight.castable.effect.handler.characteristic.AddActionPointsHandler}
+ *
+ * In case of direct effect (i.e. without duration), action points will simply be added to the current turn
+ */
+public final class StealActionPointHandler extends AbstractStealPointHandler {
+ /**
+ * @param fight Fight where the effect will be applied
+ * @param removeActionPointEffect Effect id used by the "remove action point" buff
+ * @param addActionPointEffect Effect id used by the "add action point" buff
+ */
+ public StealActionPointHandler(Fight fight, int removeActionPointEffect, int addActionPointEffect) {
+ super(
+ fight,
+ new ActionPointLostApplier(fight, removeActionPointEffect),
+ AlterPointHook.addActionPoint(fight),
+ addActionPointEffect
+ );
+ }
+
+ @Override
+ protected void applyOnCurrentTurn(Fight fight, Turn turn, ActiveFighter caster, int toAdd) {
+ turn.points().addActionPoints(toAdd);
+ fight.send(ActionEffect.addActionPoints(caster, toAdd));
+ }
+}
diff --git a/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/point/StealMovementPointHandler.java b/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/point/StealMovementPointHandler.java
new file mode 100644
index 000000000..97332ec8e
--- /dev/null
+++ b/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/point/StealMovementPointHandler.java
@@ -0,0 +1,55 @@
+/*
+ * 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-2021 Vincent Quatrevieux
+ */
+
+package fr.quatrevieux.araknemu.game.fight.castable.effect.handler.characteristic.point;
+
+import fr.quatrevieux.araknemu.game.fight.Fight;
+import fr.quatrevieux.araknemu.game.fight.fighter.ActiveFighter;
+import fr.quatrevieux.araknemu.game.fight.turn.Turn;
+import fr.quatrevieux.araknemu.network.game.out.fight.action.ActionEffect;
+
+/**
+ * Effect for steal movement points
+ *
+ * Like {@link MovementPointLostHandler} the removal can be dodged
+ * This effect is equivalent to apply {@link MovementPointLostHandler} chained with {@link fr.quatrevieux.araknemu.game.fight.castable.effect.handler.characteristic.AddMovementPointsHandler}
+ *
+ * In case of direct effect (i.e. without duration), movement points will simply be added to the current turn
+ */
+public final class StealMovementPointHandler extends AbstractStealPointHandler {
+ /**
+ * @param fight Fight where the effect will be applied
+ * @param removeMovementPointEffect Effect id used by the "remove movement point" buff
+ * @param addMovementPointEffect Effect id used by the "add movement point" buff
+ */
+ public StealMovementPointHandler(Fight fight, int removeMovementPointEffect, int addMovementPointEffect) {
+ super(
+ fight,
+ new MovementPointLostApplier(fight, removeMovementPointEffect),
+ AlterPointHook.addMovementPoint(fight),
+ addMovementPointEffect
+ );
+ }
+
+ @Override
+ protected void applyOnCurrentTurn(Fight fight, Turn turn, ActiveFighter caster, int toAdd) {
+ turn.points().addMovementPoints(toAdd);
+ fight.send(ActionEffect.addMovementPoints(caster, toAdd));
+ }
+}
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 bd11eafd8..c778d58a2 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
@@ -36,6 +36,8 @@
import fr.quatrevieux.araknemu.game.fight.castable.effect.handler.characteristic.RemoveCharacteristicHandler;
import fr.quatrevieux.araknemu.game.fight.castable.effect.handler.characteristic.RemoveMovementPointsHandler;
import fr.quatrevieux.araknemu.game.fight.castable.effect.handler.characteristic.point.MovementPointLostHandler;
+import fr.quatrevieux.araknemu.game.fight.castable.effect.handler.characteristic.point.StealActionPointHandler;
+import fr.quatrevieux.araknemu.game.fight.castable.effect.handler.characteristic.point.StealMovementPointHandler;
import fr.quatrevieux.araknemu.game.fight.castable.effect.handler.damage.DamageHandler;
import fr.quatrevieux.araknemu.game.fight.castable.effect.handler.damage.StealLifeHandler;
import fr.quatrevieux.araknemu.game.fight.castable.effect.handler.heal.FixedHealHandler;
@@ -109,6 +111,8 @@ public void effects(EffectsHandler handler) {
handler.register(128, new AddMovementPointsHandler(fight));
handler.register(169, new RemoveMovementPointsHandler(fight));
+ handler.register(77, new StealMovementPointHandler(fight, 127, 128));
+ handler.register(84, new StealActionPointHandler(fight, 101, 111));
handler.register(101, new ActionPointLostHandler(fight));
handler.register(127, new MovementPointLostHandler(fight));
diff --git a/src/test/java/fr/quatrevieux/araknemu/game/GameDataSet.java b/src/test/java/fr/quatrevieux/araknemu/game/GameDataSet.java
index 3928b9427..c04b6bb5d 100644
--- a/src/test/java/fr/quatrevieux/araknemu/game/GameDataSet.java
+++ b/src/test/java/fr/quatrevieux/araknemu/game/GameDataSet.java
@@ -495,6 +495,8 @@ public GameDataSet pushFunctionalSpells() throws SQLException, ContainerExceptio
"(82, 'Contre', 504, '10,1,1', '107,3,,,3,0,0d0+3;666,,,,0,90;120,1,,,0,10,0d0+1|107,4,,,3,0,0d0+4;666,,,,0,90;120,1,,,0,10,0d0+1|2|0|1|50|100|false|true|false|false|0|1|0|6|PaPaPaPaPaPa||18;19;3;1;41|1|false', '107,4,,,3,0,0d0+4;666,,,,0,90;120,1,,,0,10,0d0+1|107,5,,,3,0,0d0+5;666,,,,0,90;120,1,,,0,10,0d0+1|2|0|1|50|100|false|true|false|false|0|1|0|6|PaPaPaPaPaPa||18;19;3;1;41|1|false', '107,5,,,3,0,0d0+5;666,,,,0,90;120,1,,,0,10,0d0+1|107,6,,,3,0,0d0+6;666,,,,0,90;120,1,,,0,10,0d0+1|2|0|1|50|100|false|true|false|false|0|1|0|6|PaPaPaPaPaPa||18;19;3;1;41|1|false', '107,6,,,3,0,0d0+6;666,,,,0,90;120,1,,,0,10,0d0+1|107,7,,,3,0,0d0+7;666,,,,0,90;120,1,,,0,10,0d0+1|2|0|1|50|100|false|true|false|false|0|1|0|6|PaPaPaPaPaPa||18;19;3;1;41|1|false', '107,7,,,3,0,0d0+7;666,,,,0,90;120,1,,,0,10,0d0+1|107,8,,,3,0,0d0+8;666,,,,0,90;120,1,,,0,10,0d0+1|2|0|1|50|100|false|true|false|false|0|1|0|6|PaPaPaPaPaPa||18;19;3;1;41|1|false', '107,8,,,3,0,0d0+8;666,,,,0,90;120,1,,,0,10,0d0+1|107,9,,,3,0,0d0+9;666,,,,0,90;120,1,,,0,10,0d0+1|2|0|1|50|100|false|true|false|false|0|1|0|6|PaPaPaPaPaPa||18;19;3;1;41|101|false', '')",
"(81, 'Ralentissement', 503, '11,1,1', '101,1,,,1,0,0d0+1;666,,,,0,90;120,1,,,0,10,0d0+1||1|3|4|0|100|false|true|false|true|0|4|1|0|PaPaPa||18;19;3;1;41|1|false', '101,1,2,,1,0,1d2+0;666,,,,0,90;120,1,,,0,10,0d0+1||1|3|5|0|100|false|true|false|true|0|4|1|0|PaPaPa||18;19;3;1;41|1|false', '101,1,2,,1,0,1d2+0;666,,,,0,90;120,1,,,0,10,0d0+1||1|3|6|0|100|false|true|false|true|0|4|1|0|PaPaPa||18;19;3;1;41|1|false', '101,1,2,,1,0,1d2+0;666,,,,0,90;120,1,,,0,10,0d0+1||1|3|7|0|100|false|true|false|true|0|4|1|0|PaPaPa||18;19;3;1;41|1|false', '101,2,,,1,0,0d0+2;666,,,,0,90;120,1,,,0,10,0d0+1||1|3|8|0|100|false|true|false|true|0|4|1|0|PaPaPa||18;19;3;1;41|1|false', '101,3,,,1,0,0d0+3;666,,,,0,90;120,1,,,0,10,0d0+1||1|3|9|0|100|false|true|false|true|0|4|1|0|PaPaPa||18;19;3;1;41|101|false', '')",
"(50, 'Maladresse', 303, '11,1,1', '127,1,,,1,0,0d0+1|127,2,,,1,0,0d0+2|1|1|4|50|100|false|true|false|true|0|0|1|0|PaPa||18;19;3;1;41|42|false', '127,1,2,,1,0,1d2+0|127,2,3,,1,0,1d2+1|1|1|5|50|100|false|true|false|true|0|0|1|0|PaPa||18;19;3;1;41|42|false', '127,1,2,,1,0,1d2+0|127,2,3,,1,0,1d2+1|1|1|8|50|100|false|true|false|true|0|0|1|0|PaPa||18;19;3;1;41|42|false', '127,1,2,,1,0,1d2+0|127,2,3,,1,0,1d2+1|1|1|11|50|100|false|true|false|true|0|0|1|0|PaPa||18;19;3;1;41|42|false', '127,2,,,1,0,0d0+2|127,3,,,1,0,0d0+3|1|1|12|50|100|false|true|false|true|0|0|1|0|PaPa||18;19;3;1;41|42|false', '127,3,,,1,0,0d0+3|127,4,,,1,0,0d0+4|1|1|13|50|100|false|true|false|true|0|0|1|0|PaPa||18;19;3;1;41|142|false', '')",
+ "(98, 'Vol du Temps', 507, '11,1,1', '84,2,,,1,0,0d0+2;666,,,,0,90;120,1,,,0,10,0d0+1||4|3|3|0|20|false|true|false|false|0|0|2|0|PaPaPa||18;19;3;1;41|31|false', '84,2,,,1,0,0d0+2;666,,,,0,90;120,1,,,0,10,0d0+1||4|3|3|0|30|false|true|false|false|0|0|2|0|PaPaPa||18;19;3;1;41|31|false', '84,2,,,1,0,0d0+2;666,,,,0,90;120,1,,,0,10,0d0+1||4|3|4|0|40|false|true|false|false|0|0|2|0|PaPaPa||18;19;3;1;41|31|false', '84,2,,,1,0,0d0+2;666,,,,0,90;120,1,,,0,10,0d0+1||4|3|4|0|50|false|true|false|false|0|0|2|0|PaPaPa||18;19;3;1;41|31|false', '84,2,,,1,0,0d0+2;666,,,,0,90;120,1,,,0,10,0d0+1||4|3|5|0|60|false|true|false|false|0|0|2|0|PaPaPa||18;19;3;1;41|31|false', '84,2,,,1,0,0d0+2;666,,,,0,90;120,1,,,0,10,0d0+1||4|3|6|0|100|false|true|false|false|0|0|2|0|PaPaPa||18;19;3;1;41|131|false', '')",
+ "(170, 'Flèche d Immobilisation', 901, '31,2,1', '96,3,4,,0,0,1d2+2;77,1,,,0,0,0d0+1|96,4,5,,0,0,1d2+3;77,1,,,0,0,0d0+1|2|1|5|40|100|false|true|false|true|0|0|2|0|PaPaPaPa||18;19;3;1;41|26|false', '96,4,5,,0,0,1d2+3;77,1,,,0,0,0d0+1|96,5,6,,0,0,1d2+4;77,1,,,0,0,0d0+1|2|1|6|40|100|false|true|false|true|0|0|2|0|PaPaPaPa||18;19;3;1;41|26|false', '96,5,6,,0,0,1d2+4;77,1,,,0,0,0d0+1|96,6,7,,0,0,1d2+5;77,1,,,0,0,0d0+1|2|1|7|40|100|false|true|false|true|0|0|2|0|PaPaPaPa||18;19;3;1;41|26|false', '96,6,7,,0,0,1d2+5;77,1,,,0,0,0d0+1|96,9,10,,0,0,1d2+8;77,1,,,0,0,0d0+1|2|1|8|40|100|false|true|false|true|0|0|2|0|PaPaPaPa||18;19;3;1;41|26|false', '96,7,8,,0,0,1d2+6;77,1,,,0,0,0d0+1|96,10,11,,0,0,1d2+9;77,1,,,0,0,0d0+1|2|1|9|40|100|false|true|false|true|0|0|2|0|PaPaPaPa||18;19;3;1;41|26|false', '96,9,10,,0,0,1d2+8;77,1,,,0,0,0d0+1|96,12,13,,0,0,1d2+11;77,1,,,0,0,0d0+1|2|1|10|40|100|false|true|false|true|0|0|2|0|PaPaPaPa||18;19;3;1;41|126|false', '')",
}, ",") + ";"
);
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 a7e16dfc2..8823d6633 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
@@ -685,6 +685,57 @@ void movementPointLost() {
assertEquals(3, fighter2.characteristics().get(Characteristic.MOVEMENT_POINT));
}
+ @Test
+ void stealActionPoints() {
+ fighter2.move(fight.map().get(241));
+ fighter1.characteristics().alter(Characteristic.WISDOM, 100);
+
+ castNormal(98, fighter2.cell()); // Vol du Temps
+
+ Buff buffT = fighter2.buffs().stream().filter(b -> b.effect().effect() == 101).findFirst().get();
+ Buff buffC = fighter1.buffs().stream().filter(b -> b.effect().effect() == 111).findFirst().get();
+
+ assertEquals(8, fighter1.characteristics().get(Characteristic.ACTION_POINT));
+ assertEquals(4, fighter1.turn().points().actionPoints());
+ assertEquals(4, fighter2.characteristics().get(Characteristic.ACTION_POINT));
+
+ requestStack.assertOne(ActionEffect.buff(buffT, -2));
+ requestStack.assertOne(ActionEffect.buff(buffC, 2));
+
+ fighter1.turn().stop();
+ assertEquals(4, fighter2.turn().points().actionPoints());
+
+ fighter2.turn().stop();
+ assertEquals(6, fighter2.characteristics().get(Characteristic.ACTION_POINT));
+ assertEquals(8, fighter1.characteristics().get(Characteristic.ACTION_POINT));
+
+ fighter1.turn().stop();
+ assertEquals(6, fighter1.characteristics().get(Characteristic.ACTION_POINT));
+ assertEquals(6, fighter2.characteristics().get(Characteristic.ACTION_POINT));
+ }
+
+ @Test
+ void stealMovementPoints() {
+ fighter2.move(fight.map().get(241));
+ fighter1.characteristics().alter(Characteristic.WISDOM, 100);
+
+ castNormal(170, fighter2.cell()); // Flèche Immobilisation
+
+ Buff buffT = fighter2.buffs().stream().filter(b -> b.effect().effect() == 127).findFirst().get();
+
+ assertEquals(4, fighter1.turn().points().movementPoints());
+ assertEquals(2, fighter2.characteristics().get(Characteristic.MOVEMENT_POINT));
+
+ requestStack.assertOne(ActionEffect.buff(buffT, -1));
+ requestStack.assertOne(ActionEffect.addMovementPoints(fighter1, 1));
+
+ fighter1.turn().stop();
+ assertEquals(2, fighter2.turn().points().movementPoints());
+
+ fighter2.turn().stop();
+ assertEquals(3, fighter2.characteristics().get(Characteristic.MOVEMENT_POINT));
+ }
+
private void passTurns(int number) {
for (; number > 0; --number) {
fighter1.turn().stop();
diff --git a/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/AlterCharacteristicHookTest.java b/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/AlterCharacteristicHookTest.java
index d2ff4db74..ab1a8497b 100644
--- a/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/AlterCharacteristicHookTest.java
+++ b/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/AlterCharacteristicHookTest.java
@@ -67,7 +67,7 @@ void add() {
Mockito.when(effect.min()).thenReturn(5);
Mockito.when(effect.duration()).thenReturn(1);
- Buff buff = new Buff(new BuffEffect(effect, 50), spell, caster, target, hook);
+ Buff buff = new Buff(BuffEffect.fixed(effect, 50), spell, caster, target, hook);
target.buffs().add(buff);
assertEquals(50, target.characteristics().get(Characteristic.STRENGTH));
@@ -89,7 +89,7 @@ void remove() {
Mockito.when(effect.min()).thenReturn(5);
Mockito.when(effect.duration()).thenReturn(1);
- Buff buff = new Buff(new BuffEffect(effect, 50), spell, caster, target, hook);
+ Buff buff = new Buff(BuffEffect.fixed(effect, 50), spell, caster, target, hook);
target.buffs().add(buff);
assertEquals(-50, target.characteristics().get(Characteristic.STRENGTH));
diff --git a/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/point/AlterPointHookTest.java b/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/point/AlterPointHookTest.java
index bf218e8ce..a542b4a3e 100644
--- a/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/point/AlterPointHookTest.java
+++ b/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/point/AlterPointHookTest.java
@@ -67,7 +67,7 @@ void addActionPoint() {
Mockito.when(effect.min()).thenReturn(5);
Mockito.when(effect.duration()).thenReturn(1);
- Buff buff = new Buff(new BuffEffect(effect, 2), spell, caster, caster, hook);
+ Buff buff = new Buff(BuffEffect.fixed(effect, 2), spell, caster, caster, hook);
caster.buffs().add(buff);
assertEquals(8, caster.characteristics().get(Characteristic.ACTION_POINT));
@@ -91,7 +91,7 @@ void removeActionPoint() {
Mockito.when(effect.min()).thenReturn(5);
Mockito.when(effect.duration()).thenReturn(1);
- Buff buff = new Buff(new BuffEffect(effect, 2), spell, caster, caster, hook);
+ Buff buff = new Buff(BuffEffect.fixed(effect, 2), spell, caster, caster, hook);
caster.buffs().add(buff);
assertEquals(4, caster.characteristics().get(Characteristic.ACTION_POINT));
@@ -115,7 +115,7 @@ void addMovementPoint() {
Mockito.when(effect.min()).thenReturn(5);
Mockito.when(effect.duration()).thenReturn(1);
- Buff buff = new Buff(new BuffEffect(effect, 2), spell, caster, caster, hook);
+ Buff buff = new Buff(BuffEffect.fixed(effect, 2), spell, caster, caster, hook);
caster.buffs().add(buff);
assertEquals(5, caster.characteristics().get(Characteristic.MOVEMENT_POINT));
@@ -139,7 +139,7 @@ void removeMovementPoint() {
Mockito.when(effect.min()).thenReturn(5);
Mockito.when(effect.duration()).thenReturn(1);
- Buff buff = new Buff(new BuffEffect(effect, 2), spell, caster, caster, hook);
+ Buff buff = new Buff(BuffEffect.fixed(effect, 2), spell, caster, caster, hook);
caster.buffs().add(buff);
assertEquals(1, caster.characteristics().get(Characteristic.MOVEMENT_POINT));
diff --git a/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/point/StealActionPointHandlerTest.java b/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/point/StealActionPointHandlerTest.java
new file mode 100644
index 000000000..240584abf
--- /dev/null
+++ b/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/point/StealActionPointHandlerTest.java
@@ -0,0 +1,331 @@
+/*
+ * 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-2021 Vincent Quatrevieux
+ */
+
+package fr.quatrevieux.araknemu.game.fight.castable.effect.handler.characteristic.point;
+
+import fr.quatrevieux.araknemu.data.constant.Characteristic;
+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.buff.Buff;
+import fr.quatrevieux.araknemu.game.fight.castable.effect.handler.shifting.SwitchPositionHandler;
+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 fr.quatrevieux.araknemu.network.game.out.account.Stats;
+import fr.quatrevieux.araknemu.network.game.out.fight.AddBuff;
+import fr.quatrevieux.araknemu.network.game.out.fight.action.ActionEffect;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+import java.util.Optional;
+import java.util.function.Consumer;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class StealActionPointHandlerTest extends FightBaseCase {
+ private Fight fight;
+ private PlayerFighter caster;
+ private PlayerFighter target;
+ private StealActionPointHandler 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 StealActionPointHandler(fight, 101, 111);
+
+ requestStack.clear();
+ }
+
+ @Test
+ void buffFixed() {
+ caster.characteristics().alter(Characteristic.WISDOM, 10000);
+ requestStack.clear();
+
+ SpellEffect effect = Mockito.mock(SpellEffect.class);
+ Spell spell = Mockito.mock(Spell.class);
+ SpellConstraints constraints = Mockito.mock(SpellConstraints.class);
+
+ Mockito.when(effect.effect()).thenReturn(84);
+ Mockito.when(effect.min()).thenReturn(2);
+ 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));
+
+ Optional buffT = target.buffs().stream().filter(b -> b.effect().effect() == 101).findFirst();
+ Optional buffC = caster.buffs().stream().filter(b -> b.effect().effect() == 111).findFirst();
+
+ assertTrue(buffT.isPresent());
+ assertTrue(buffC.isPresent());
+ assertEquals(2, buffT.get().effect().min());
+ assertEquals(2, buffC.get().effect().min());
+ assertEquals(4, target.characteristics().get(Characteristic.ACTION_POINT));
+ assertEquals(8, caster.characteristics().get(Characteristic.ACTION_POINT));
+ assertEquals(8, caster.turn().points().actionPoints());
+
+ requestStack.assertAll(
+ ActionEffect.buff(buffT.get(), -2),
+ new AddBuff(buffT.get()),
+ new Stats(caster.properties()),
+ ActionEffect.buff(buffC.get(), 2),
+ "GIE111;1;2;;0;null;5;0"
+ );
+ }
+
+ @Test
+ void directEffectShouldBeTransformedToSingleTurnBuffForTargetAndAddTurnPointToCaster() {
+ caster.characteristics().alter(Characteristic.WISDOM, 10000);
+ requestStack.clear();
+
+ SpellEffect effect = Mockito.mock(SpellEffect.class);
+ Spell spell = Mockito.mock(Spell.class);
+ SpellConstraints constraints = Mockito.mock(SpellConstraints.class);
+
+ Mockito.when(effect.effect()).thenReturn(101);
+ Mockito.when(effect.min()).thenReturn(2);
+ 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.handle(scope, scope.effects().get(0));
+
+
+ Optional buffT = target.buffs().stream().filter(b -> b.effect().effect() == 101).findFirst();
+ Optional buffC = caster.buffs().stream().filter(b -> b.effect().effect() == 111).findFirst();
+
+ assertTrue(buffT.isPresent());
+ assertFalse(buffC.isPresent());
+ assertEquals(2, buffT.get().effect().min());
+ assertEquals(1, buffT.get().remainingTurns());
+ assertEquals(4, target.characteristics().get(Characteristic.ACTION_POINT));
+ assertEquals(6, caster.characteristics().get(Characteristic.ACTION_POINT));
+ assertEquals(8, caster.turn().points().actionPoints());
+
+ requestStack.assertAll(
+ ActionEffect.buff(buffT.get(), -2),
+ new AddBuff(buffT.get()),
+ ActionEffect.addActionPoints(caster, 2)
+ );
+ }
+
+ @Test
+ void applyOnSelfShouldBeIgnored() {
+ requestStack.clear();
+
+ SpellEffect effect = Mockito.mock(SpellEffect.class);
+ Spell spell = Mockito.mock(Spell.class);
+ SpellConstraints constraints = Mockito.mock(SpellConstraints.class);
+
+ Mockito.when(effect.effect()).thenReturn(101);
+ Mockito.when(effect.min()).thenReturn(2);
+ Mockito.when(effect.duration()).thenReturn(2);
+ 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));
+
+ Optional buff = caster.buffs().stream().filter(b -> b.effect().effect() == 101).findFirst();
+
+ assertFalse(buff.isPresent());
+ requestStack.assertEmpty();
+ }
+
+ @Test
+ void dodgedAllShouldNotAddBuff() {
+ target.characteristics().alter(Characteristic.RESISTANCE_ACTION_POINT, 1000);
+ requestStack.clear();
+
+ SpellEffect effect = Mockito.mock(SpellEffect.class);
+ Spell spell = Mockito.mock(Spell.class);
+ SpellConstraints constraints = Mockito.mock(SpellConstraints.class);
+
+ Mockito.when(effect.effect()).thenReturn(84);
+ Mockito.when(effect.min()).thenReturn(2);
+ 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));
+
+ assertFalse(target.buffs().stream().anyMatch(b -> b.effect().effect() == 101));
+ assertFalse(caster.buffs().stream().anyMatch(b -> b.effect().effect() == 111));
+
+ assertEquals(6, target.characteristics().get(Characteristic.ACTION_POINT));
+ assertEquals(6, caster.characteristics().get(Characteristic.ACTION_POINT));
+ assertEquals(6, caster.turn().points().actionPoints());
+
+ requestStack.assertAll(new ActionEffect(308, caster, target.id(), 2));
+ requestStack.assertNotContains(AddBuff.class);
+ }
+
+ @Test
+ void partialDodge() {
+ requestStack.clear();
+
+ SpellEffect effect = Mockito.mock(SpellEffect.class);
+ Spell spell = Mockito.mock(Spell.class);
+ SpellConstraints constraints = Mockito.mock(SpellConstraints.class);
+
+ Mockito.when(effect.effect()).thenReturn(84);
+ 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, target.cell());
+ handler.buff(scope, scope.effects().get(0));
+
+ Optional buffT = target.buffs().stream().filter(b -> b.effect().effect() == 101).findFirst();
+ Optional buffC = caster.buffs().stream().filter(b -> b.effect().effect() == 111).findFirst();
+
+ assertTrue(buffT.isPresent());
+ assertTrue(buffC.isPresent());
+ assertBetween(1, 2, buffT.get().effect().min());
+ assertBetween(1, 2, buffC.get().effect().min());
+ assertEquals(6 - buffT.get().effect().min(), target.characteristics().get(Characteristic.ACTION_POINT));
+ assertEquals(6 + buffC.get().effect().min(), caster.characteristics().get(Characteristic.ACTION_POINT));
+ assertEquals(6 + buffC.get().effect().min(), caster.turn().points().actionPoints());
+
+ requestStack.assertAll(
+ new ActionEffect(308, caster, target.id(), 3 - buffT.get().effect().min()),
+ ActionEffect.buff(buffT.get(), -buffT.get().effect().min()),
+ new AddBuff(buffT.get()),
+ new Stats(caster.properties()),
+ ActionEffect.buff(buffC.get(), buffC.get().effect().min()),
+ "GIE111;1;2;;0;null;5;0"
+ );
+ }
+
+ @Test
+ void withArea() {
+ fight.cancel(true);
+
+ configureFight(fb -> fb
+ .addSelf(b -> b.cell(165))
+ .addEnemy(b -> b.cell(150))
+ .addEnemy(b -> b.cell(151))
+ );
+
+ requestStack.clear();
+
+ SpellEffect effect = Mockito.mock(SpellEffect.class);
+ Spell spell = Mockito.mock(Spell.class);
+ SpellConstraints constraints = Mockito.mock(SpellConstraints.class);
+
+ Mockito.when(effect.effect()).thenReturn(84);
+ Mockito.when(effect.min()).thenReturn(1000);
+ 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(fight.fighters().get(0), spell, effect, fight.map().get(150));
+ handler.buff(scope, scope.effects().get(0));
+
+ Optional buff0 = fight.fighters().get(0).buffs().stream().filter(b -> b.effect().effect() == 111).findFirst();
+ Optional buff1 = fight.fighters().get(1).buffs().stream().filter(b -> b.effect().effect() == 101).findFirst();
+ Optional buff2 = fight.fighters().get(2).buffs().stream().filter(b -> b.effect().effect() == 101).findFirst();
+
+ assertTrue(buff0.isPresent());
+ assertTrue(buff1.isPresent());
+ assertTrue(buff2.isPresent());
+
+ assertEquals(18, fight.fighters().get(0).characteristics().get(Characteristic.ACTION_POINT));
+ assertEquals(0, fight.fighters().get(1).characteristics().get(Characteristic.ACTION_POINT));
+ assertEquals(0, fight.fighters().get(2).characteristics().get(Characteristic.ACTION_POINT));
+ }
+
+ @Test
+ void buffStartAndTerminated() {
+ caster.characteristics().alter(Characteristic.WISDOM, 10000);
+ requestStack.clear();
+
+ SpellEffect effect = Mockito.mock(SpellEffect.class);
+ Spell spell = Mockito.mock(Spell.class);
+ SpellConstraints constraints = Mockito.mock(SpellConstraints.class);
+
+ Mockito.when(effect.effect()).thenReturn(101);
+ Mockito.when(effect.min()).thenReturn(2);
+ Mockito.when(effect.duration()).thenReturn(1);
+ 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));
+
+ assertEquals(4, target.characteristics().get(Characteristic.ACTION_POINT));
+ assertEquals(8, caster.characteristics().get(Characteristic.ACTION_POINT));
+
+ requestStack.clear();
+
+ target.buffs().refresh();
+ caster.buffs().refresh();
+ caster.buffs().refresh();
+
+ assertEquals(6, target.characteristics().get(Characteristic.ACTION_POINT));
+ assertEquals(6, caster.characteristics().get(Characteristic.ACTION_POINT));
+ }
+
+ private void configureFight(Consumer configurator) {
+ FightBuilder builder = fightBuilder();
+ configurator.accept(builder);
+
+ fight = builder.build(true);
+ handler = new StealActionPointHandler(fight, 101, 111);
+
+ fight.nextState();
+
+ requestStack.clear();
+ }
+}
diff --git a/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/point/StealMovementPointHandlerTest.java b/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/point/StealMovementPointHandlerTest.java
new file mode 100644
index 000000000..72f8747e6
--- /dev/null
+++ b/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/point/StealMovementPointHandlerTest.java
@@ -0,0 +1,332 @@
+/*
+ * 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-2021 Vincent Quatrevieux
+ */
+
+package fr.quatrevieux.araknemu.game.fight.castable.effect.handler.characteristic.point;
+
+import fr.quatrevieux.araknemu.data.constant.Characteristic;
+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.buff.Buff;
+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 fr.quatrevieux.araknemu.network.game.out.account.Stats;
+import fr.quatrevieux.araknemu.network.game.out.fight.AddBuff;
+import fr.quatrevieux.araknemu.network.game.out.fight.action.ActionEffect;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+import java.util.Optional;
+import java.util.function.Consumer;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class StealMovementPointHandlerTest extends FightBaseCase {
+ private Fight fight;
+ private PlayerFighter caster;
+ private PlayerFighter target;
+ private StealMovementPointHandler 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 StealMovementPointHandler(fight, 127, 128);
+
+ requestStack.clear();
+ }
+
+ @Test
+ void buffFixed() {
+ caster.characteristics().alter(Characteristic.WISDOM, 10000);
+ requestStack.clear();
+
+ SpellEffect effect = Mockito.mock(SpellEffect.class);
+ Spell spell = Mockito.mock(Spell.class);
+ SpellConstraints constraints = Mockito.mock(SpellConstraints.class);
+
+ Mockito.when(effect.effect()).thenReturn(77);
+ Mockito.when(effect.min()).thenReturn(2);
+ 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));
+
+ Optional buffT = target.buffs().stream().filter(b -> b.effect().effect() == 127).findFirst();
+ Optional buffC = caster.buffs().stream().filter(b -> b.effect().effect() == 128).findFirst();
+
+ assertTrue(buffT.isPresent());
+ assertTrue(buffC.isPresent());
+ assertEquals(2, buffT.get().effect().min());
+ assertEquals(2, buffC.get().effect().min());
+ assertEquals(1, target.characteristics().get(Characteristic.MOVEMENT_POINT));
+ assertEquals(5, caster.characteristics().get(Characteristic.MOVEMENT_POINT));
+ assertEquals(5, caster.turn().points().movementPoints());
+
+ requestStack.assertAll(
+ ActionEffect.buff(buffT.get(), -2),
+ new AddBuff(buffT.get()),
+ new Stats(caster.properties()),
+ ActionEffect.buff(buffC.get(), 2),
+ "GIE128;1;2;;0;null;5;0"
+ );
+ }
+
+ @Test
+ void directEffectShouldBeTransformedToSingleTurnBuffForTargetAndAddTurnPointToCaster() {
+ caster.characteristics().alter(Characteristic.WISDOM, 10000);
+ requestStack.clear();
+
+ SpellEffect effect = Mockito.mock(SpellEffect.class);
+ Spell spell = Mockito.mock(Spell.class);
+ SpellConstraints constraints = Mockito.mock(SpellConstraints.class);
+
+ Mockito.when(effect.effect()).thenReturn(127);
+ Mockito.when(effect.min()).thenReturn(2);
+ 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.handle(scope, scope.effects().get(0));
+
+
+ Optional buffT = target.buffs().stream().filter(b -> b.effect().effect() == 127).findFirst();
+ Optional buffC = caster.buffs().stream().filter(b -> b.effect().effect() == 128).findFirst();
+
+ assertTrue(buffT.isPresent());
+ assertFalse(buffC.isPresent());
+ assertEquals(2, buffT.get().effect().min());
+ assertEquals(1, buffT.get().remainingTurns());
+ assertEquals(1, target.characteristics().get(Characteristic.MOVEMENT_POINT));
+ assertEquals(3, caster.characteristics().get(Characteristic.MOVEMENT_POINT));
+ assertEquals(5, caster.turn().points().movementPoints());
+
+ requestStack.assertAll(
+ ActionEffect.buff(buffT.get(), -2),
+ new AddBuff(buffT.get()),
+ ActionEffect.addMovementPoints(caster, 2)
+ );
+ }
+
+ @Test
+ void applyOnSelfShouldBeIgnored() {
+ requestStack.clear();
+
+ SpellEffect effect = Mockito.mock(SpellEffect.class);
+ Spell spell = Mockito.mock(Spell.class);
+ SpellConstraints constraints = Mockito.mock(SpellConstraints.class);
+
+ Mockito.when(effect.effect()).thenReturn(127);
+ Mockito.when(effect.min()).thenReturn(2);
+ Mockito.when(effect.duration()).thenReturn(2);
+ 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));
+
+ Optional buff = caster.buffs().stream().filter(b -> b.effect().effect() == 127).findFirst();
+
+ assertFalse(buff.isPresent());
+ requestStack.assertEmpty();
+ }
+
+ @Test
+ void dodgedAllShouldNotAddBuff() {
+ target.characteristics().alter(Characteristic.RESISTANCE_MOVEMENT_POINT, 1000);
+ requestStack.clear();
+
+ SpellEffect effect = Mockito.mock(SpellEffect.class);
+ Spell spell = Mockito.mock(Spell.class);
+ SpellConstraints constraints = Mockito.mock(SpellConstraints.class);
+
+ Mockito.when(effect.effect()).thenReturn(77);
+ Mockito.when(effect.min()).thenReturn(2);
+ 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));
+
+ assertFalse(target.buffs().stream().anyMatch(b -> b.effect().effect() == 127));
+ assertFalse(caster.buffs().stream().anyMatch(b -> b.effect().effect() == 128));
+
+ assertEquals(3, target.characteristics().get(Characteristic.MOVEMENT_POINT));
+ assertEquals(3, caster.characteristics().get(Characteristic.MOVEMENT_POINT));
+ assertEquals(3, caster.turn().points().movementPoints());
+
+ requestStack.assertAll(new ActionEffect(309, caster, target.id(), 2));
+ requestStack.assertNotContains(AddBuff.class);
+ }
+
+ @Test
+ void partialDodge() {
+ requestStack.clear();
+
+ SpellEffect effect = Mockito.mock(SpellEffect.class);
+ Spell spell = Mockito.mock(Spell.class);
+ SpellConstraints constraints = Mockito.mock(SpellConstraints.class);
+
+ Mockito.when(effect.effect()).thenReturn(77);
+ 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, target.cell());
+ handler.buff(scope, scope.effects().get(0));
+
+ Optional buffT = target.buffs().stream().filter(b -> b.effect().effect() == 127).findFirst();
+ Optional buffC = caster.buffs().stream().filter(b -> b.effect().effect() == 128).findFirst();
+
+ assertTrue(buffT.isPresent());
+ assertTrue(buffC.isPresent());
+ assertBetween(1, 2, buffT.get().effect().min());
+ assertBetween(1, 2, buffC.get().effect().min());
+ assertEquals(3 - buffT.get().effect().min(), target.characteristics().get(Characteristic.MOVEMENT_POINT));
+ assertEquals(3 + buffC.get().effect().min(), caster.characteristics().get(Characteristic.MOVEMENT_POINT));
+ assertEquals(3 + buffC.get().effect().min(), caster.turn().points().movementPoints());
+
+ requestStack.assertAll(
+ new ActionEffect(309, caster, target.id(), 3 - buffT.get().effect().min()),
+ ActionEffect.buff(buffT.get(), -buffT.get().effect().min()),
+ new AddBuff(buffT.get()),
+ new Stats(caster.properties()),
+ ActionEffect.buff(buffC.get(), buffC.get().effect().min()),
+ "GIE128;1;2;;0;null;5;0"
+ );
+ }
+
+ @Test
+ void withArea() {
+ fight.cancel(true);
+
+ configureFight(fb -> fb
+ .addSelf(b -> b.cell(165))
+ .addEnemy(b -> b.cell(150))
+ .addEnemy(b -> b.cell(151))
+ );
+
+ requestStack.clear();
+
+ SpellEffect effect = Mockito.mock(SpellEffect.class);
+ Spell spell = Mockito.mock(Spell.class);
+ SpellConstraints constraints = Mockito.mock(SpellConstraints.class);
+
+ Mockito.when(effect.effect()).thenReturn(77);
+ Mockito.when(effect.min()).thenReturn(1000);
+ 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(fight.fighters().get(0), spell, effect, fight.map().get(150));
+ handler.buff(scope, scope.effects().get(0));
+
+ Optional buff0 = fight.fighters().get(0).buffs().stream().filter(b -> b.effect().effect() == 128).findFirst();
+ Optional buff1 = fight.fighters().get(1).buffs().stream().filter(b -> b.effect().effect() == 127).findFirst();
+ Optional buff2 = fight.fighters().get(2).buffs().stream().filter(b -> b.effect().effect() == 127).findFirst();
+
+ assertTrue(buff0.isPresent());
+ assertTrue(buff1.isPresent());
+ assertTrue(buff2.isPresent());
+
+ assertEquals(9, fight.fighters().get(0).characteristics().get(Characteristic.MOVEMENT_POINT));
+ assertEquals(0, fight.fighters().get(1).characteristics().get(Characteristic.MOVEMENT_POINT));
+ assertEquals(0, fight.fighters().get(2).characteristics().get(Characteristic.MOVEMENT_POINT));
+ }
+
+ @Test
+ void buffStartAndTerminated() {
+ caster.characteristics().alter(Characteristic.WISDOM, 10000);
+ requestStack.clear();
+
+ SpellEffect effect = Mockito.mock(SpellEffect.class);
+ Spell spell = Mockito.mock(Spell.class);
+ SpellConstraints constraints = Mockito.mock(SpellConstraints.class);
+
+ Mockito.when(effect.effect()).thenReturn(127);
+ Mockito.when(effect.min()).thenReturn(2);
+ Mockito.when(effect.duration()).thenReturn(1);
+ 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));
+
+ assertEquals(1, target.characteristics().get(Characteristic.MOVEMENT_POINT));
+ assertEquals(5, caster.characteristics().get(Characteristic.MOVEMENT_POINT));
+
+ requestStack.clear();
+
+ target.buffs().refresh();
+ caster.buffs().refresh();
+ caster.buffs().refresh();
+
+ assertEquals(3, target.characteristics().get(Characteristic.MOVEMENT_POINT));
+ assertEquals(3, caster.characteristics().get(Characteristic.MOVEMENT_POINT));
+ }
+
+ private void configureFight(Consumer configurator) {
+ FightBuilder builder = fightBuilder();
+ configurator.accept(builder);
+
+ fight = builder.build(true);
+ handler = new StealMovementPointHandler(fight, 127, 128);
+
+ fight.nextState();
+
+ requestStack.clear();
+ }
+}