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