From cb25345e381760d2d2dfaf5c2f767a8c9ce2f41b Mon Sep 17 00:00:00 2001 From: Vincent Quatrevieux Date: Sun, 18 Jun 2023 15:35:57 +0200 Subject: [PATCH] feat(fight): Handle double effect (#27) --- .../quatrevieux/araknemu/game/GameModule.java | 11 +- .../araknemu/game/fight/FighterSprite.java | 38 ++ .../fight/ai/action/BlockNearestEnemy.java | 73 ++++ .../game/fight/ai/action/MoveNearEnemy.java | 58 +-- .../game/fight/ai/action/MoveNearFighter.java | 98 +++++ .../ai/action/builder/GeneratorBuilder.java | 12 + .../fight/ai/factory/DoubleAiFactory.java | 53 +++ .../game/fight/ai/factory/type/Blocking.java | 35 ++ .../invocations/CreateDoubleHandler.java | 68 +++ .../araknemu/game/fight/fighter/Fighter.java | 4 + .../fighter/invocation/DoubleFighter.java | 117 ++++++ .../DoubleFighterCharacteristics.java | 75 ++++ .../fighter/invocation/InvocationFighter.java | 4 +- .../invocation/StaticInvocationFighter.java | 4 +- .../fight/fighter/monster/MonsterFighter.java | 4 +- .../fighter/monster/MonsterFighterSprite.java | 9 +- .../fighter/operation/FighterOperation.java | 8 + .../fight/fighter/player/PlayerFighter.java | 4 +- .../fighter/player/PlayerFighterSprite.java | 18 +- .../fight/module/MonsterInvocationModule.java | 4 +- .../game/out/fight/action/ActionEffect.java | 10 + .../araknemu/game/GameDataSet.java | 1 + .../araknemu/game/GameModuleTest.java | 2 + .../araknemu/game/fight/ai/AiBaseCase.java | 22 + .../ai/action/BlockNearestEnemyTest.java | 94 +++++ .../fight/ai/action/MoveNearFighterTest.java | 134 ++++++ .../action/builder/GeneratorBuilderTest.java | 6 + .../fight/ai/factory/DoubleAiFactoryTest.java | 58 +++ .../fight/ai/factory/type/BlockingTest.java | 103 +++++ .../fight/castable/effect/FunctionalTest.java | 32 +- .../invocations/CreateDoubleHandlerTest.java | 135 ++++++ .../DoubleFighterCharacteristicsTest.java | 94 +++++ .../fighter/invocation/DoubleFighterTest.java | 392 ++++++++++++++++++ .../monster/MonsterFighterSpriteTest.java | 25 +- .../player/PlayerFighterSpriteTest.java | 27 +- .../order/AlternateTeamFighterOrderTest.java | 3 +- .../out/fight/action/ActionEffectTest.java | 21 +- 37 files changed, 1780 insertions(+), 76 deletions(-) create mode 100644 src/main/java/fr/quatrevieux/araknemu/game/fight/FighterSprite.java create mode 100644 src/main/java/fr/quatrevieux/araknemu/game/fight/ai/action/BlockNearestEnemy.java create mode 100644 src/main/java/fr/quatrevieux/araknemu/game/fight/ai/action/MoveNearFighter.java create mode 100644 src/main/java/fr/quatrevieux/araknemu/game/fight/ai/factory/DoubleAiFactory.java create mode 100644 src/main/java/fr/quatrevieux/araknemu/game/fight/ai/factory/type/Blocking.java create mode 100644 src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/invocations/CreateDoubleHandler.java create mode 100644 src/main/java/fr/quatrevieux/araknemu/game/fight/fighter/invocation/DoubleFighter.java create mode 100644 src/main/java/fr/quatrevieux/araknemu/game/fight/fighter/invocation/DoubleFighterCharacteristics.java create mode 100644 src/test/java/fr/quatrevieux/araknemu/game/fight/ai/action/BlockNearestEnemyTest.java create mode 100644 src/test/java/fr/quatrevieux/araknemu/game/fight/ai/action/MoveNearFighterTest.java create mode 100644 src/test/java/fr/quatrevieux/araknemu/game/fight/ai/factory/DoubleAiFactoryTest.java create mode 100644 src/test/java/fr/quatrevieux/araknemu/game/fight/ai/factory/type/BlockingTest.java create mode 100644 src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/invocations/CreateDoubleHandlerTest.java create mode 100644 src/test/java/fr/quatrevieux/araknemu/game/fight/fighter/invocation/DoubleFighterCharacteristicsTest.java create mode 100644 src/test/java/fr/quatrevieux/araknemu/game/fight/fighter/invocation/DoubleFighterTest.java diff --git a/src/main/java/fr/quatrevieux/araknemu/game/GameModule.java b/src/main/java/fr/quatrevieux/araknemu/game/GameModule.java index 0c5690c9c..dbb3da69f 100644 --- a/src/main/java/fr/quatrevieux/araknemu/game/GameModule.java +++ b/src/main/java/fr/quatrevieux/araknemu/game/GameModule.java @@ -133,8 +133,10 @@ import fr.quatrevieux.araknemu.game.fight.FightService; import fr.quatrevieux.araknemu.game.fight.ai.factory.AiFactory; import fr.quatrevieux.araknemu.game.fight.ai.factory.ChainAiFactory; +import fr.quatrevieux.araknemu.game.fight.ai.factory.DoubleAiFactory; import fr.quatrevieux.araknemu.game.fight.ai.factory.MonsterAiFactory; import fr.quatrevieux.araknemu.game.fight.ai.factory.type.Aggressive; +import fr.quatrevieux.araknemu.game.fight.ai.factory.type.Blocking; import fr.quatrevieux.araknemu.game.fight.ai.factory.type.Fixed; import fr.quatrevieux.araknemu.game.fight.ai.factory.type.Runaway; import fr.quatrevieux.araknemu.game.fight.ai.factory.type.Support; @@ -895,7 +897,8 @@ private void configureServices(ContainerConfigurator configurator) { configurator.persist( AiFactory.class, container -> new ChainAiFactory( - container.get(MonsterAiFactory.class) + container.get(MonsterAiFactory.class), + container.get(DoubleAiFactory.class) ) ); @@ -984,11 +987,17 @@ private void configureServices(ContainerConfigurator configurator) { factory.register("SUPPORT", new Support(simulator)); factory.register("TACTICAL", new Tactical(simulator)); factory.register("FIXED", new Fixed(simulator)); + factory.register("BLOCKING", new Blocking()); return factory; } ); + configurator.persist( + DoubleAiFactory.class, + container -> new DoubleAiFactory(new Blocking()) + ); + configurator.persist(ExchangeFactory.class, container -> new DefaultExchangeFactory( new PlayerExchangeFactories(), new NpcExchangeFactories() diff --git a/src/main/java/fr/quatrevieux/araknemu/game/fight/FighterSprite.java b/src/main/java/fr/quatrevieux/araknemu/game/fight/FighterSprite.java new file mode 100644 index 000000000..a26aefe10 --- /dev/null +++ b/src/main/java/fr/quatrevieux/araknemu/game/fight/FighterSprite.java @@ -0,0 +1,38 @@ +/* + * 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-2023 Vincent Quatrevieux + */ + +package fr.quatrevieux.araknemu.game.fight; + +import fr.quatrevieux.araknemu.game.fight.fighter.Fighter; +import fr.quatrevieux.araknemu.game.world.creature.Sprite; + +/** + * Extends the base sprite for fighters, allowing temporary alterations + */ +public interface FighterSprite extends Sprite { + /** + * Duplicate the sprite with the given fighter + * The returned sprite should be same as current one, but with new fighter cell, orientation, and characteristics. + * + * @param fighter The new fighter + * + * @return The new sprite instance + */ + public FighterSprite withFighter(Fighter fighter); +} diff --git a/src/main/java/fr/quatrevieux/araknemu/game/fight/ai/action/BlockNearestEnemy.java b/src/main/java/fr/quatrevieux/araknemu/game/fight/ai/action/BlockNearestEnemy.java new file mode 100644 index 000000000..33deeff2c --- /dev/null +++ b/src/main/java/fr/quatrevieux/araknemu/game/fight/ai/action/BlockNearestEnemy.java @@ -0,0 +1,73 @@ +/* + * 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-2023 Vincent Quatrevieux + */ + +package fr.quatrevieux.araknemu.game.fight.ai.action; + +import fr.arakne.utils.maps.CoordinateCell; +import fr.quatrevieux.araknemu.game.fight.ai.AI; +import fr.quatrevieux.araknemu.game.fight.fighter.ActiveFighter; +import fr.quatrevieux.araknemu.game.fight.fighter.FighterData; +import fr.quatrevieux.araknemu.game.fight.map.BattlefieldCell; +import fr.quatrevieux.araknemu.game.fight.turn.action.Action; + +import java.util.Comparator; +import java.util.Optional; + +/** + * Try to block the nearest enemy of the invoker + * If the invoker is hidden, this generator will act like {@link MoveNearEnemy} + * + * @param the fighter type + */ +public final class BlockNearestEnemy implements ActionGenerator { + private final ActionGenerator moveGenerator; + + @SuppressWarnings("methodref.receiver.bound") + public BlockNearestEnemy() { + moveGenerator = new MoveNearFighter<>(this::resolve); + } + + @Override + public void initialize(AI ai) { + moveGenerator.initialize(ai); + } + + @Override + public Optional generate(AI ai, AiActionFactory actions) { + return moveGenerator.generate(ai, actions); + } + + private Optional resolve(AI ai) { + final FighterData invoker = ai.fighter().invoker(); + + if (invoker == null || invoker.hidden()) { + return ai.enemy(); + } + + final CoordinateCell currentCell = invoker.cell().coordinate(); + + return ai.helper().enemies().stream() + .filter(fighter -> !fighter.hidden()) + .min(Comparator + .comparingInt(f -> currentCell.distance(f.cell())) + .thenComparingInt(f -> f.life().current()) + ) + ; + } +} diff --git a/src/main/java/fr/quatrevieux/araknemu/game/fight/ai/action/MoveNearEnemy.java b/src/main/java/fr/quatrevieux/araknemu/game/fight/ai/action/MoveNearEnemy.java index af035bb86..f352df1e0 100644 --- a/src/main/java/fr/quatrevieux/araknemu/game/fight/ai/action/MoveNearEnemy.java +++ b/src/main/java/fr/quatrevieux/araknemu/game/fight/ai/action/MoveNearEnemy.java @@ -19,16 +19,9 @@ package fr.quatrevieux.araknemu.game.fight.ai.action; -import fr.arakne.utils.maps.path.Pathfinder; import fr.quatrevieux.araknemu.game.fight.ai.AI; -import fr.quatrevieux.araknemu.game.fight.ai.util.AIHelper; import fr.quatrevieux.araknemu.game.fight.fighter.ActiveFighter; -import fr.quatrevieux.araknemu.game.fight.map.BattlefieldCell; import fr.quatrevieux.araknemu.game.fight.turn.action.Action; -import fr.quatrevieux.araknemu.util.Asserter; -import org.checkerframework.checker.index.qual.Positive; -import org.checkerframework.checker.nullness.qual.MonotonicNonNull; -import org.checkerframework.checker.nullness.util.NullnessUtil; import java.util.Optional; @@ -36,56 +29,19 @@ * Try to move near the selected enemy */ public final class MoveNearEnemy implements ActionGenerator { - private @MonotonicNonNull Pathfinder pathfinder; - private @MonotonicNonNull AIHelper helper; + private final ActionGenerator moveGenerator; + + public MoveNearEnemy() { + this.moveGenerator = new MoveNearFighter<>(AI::enemy); + } @Override public void initialize(AI ai) { - this.helper = ai.helper(); - this.pathfinder = helper.cells().pathfinder() - .targetDistance(1) - .walkablePredicate(cell -> true) // Fix #94 Ignore inaccessible cell (handled by cell cost) - .cellWeightFunction(this::cellCost) - ; + moveGenerator.initialize(ai); } @Override public Optional generate(AI ai, AiActionFactory actions) { - if (helper == null || !helper.canMove()) { - return Optional.empty(); - } - - final int movementPoints = helper.movementPoints(); - final BattlefieldCell currentCell = ai.fighter().cell(); - - return ai.enemy() - .map(enemy -> NullnessUtil.castNonNull(pathfinder).findPath(currentCell, enemy.cell()).truncate(movementPoints + 1)) - .map(path -> path.keepWhile(step -> step.cell().equals(currentCell) || step.cell().walkable())) // Truncate path to first unwalkable cell (may occur if the enemy cell is inaccessible or if other fighters block the path) - .filter(path -> path.size() > 1) - .map(path -> actions.move(path)) - ; - } - - /** - * Compute the cell cost for optimize the path finding - */ - private @Positive int cellCost(BattlefieldCell cell) { - // Fix #94 : Some cells are not accessible, but walkable/targetable using teleport. - // In this case the pathfinder will fail, so instead of ignoring unwalkable cells, simply set a very high cost, - // which allows the AI to generate a path to an inaccessible cell without throws a PathException - if (!cell.walkableIgnoreFighter()) { - return 1000; - } - - // A fighter is on the cell : the cell is not walkable - // But the fighter may leave the place at the next turn - // The cost is higher than a simple detour, but permit to resolve a path blocked by a fighter - if (cell.hasFighter()) { - return 15; - } - - // Add a cost of 3 for each enemy around the cell - // This cost corresponds to the detour cost + 1 - return 1 + Asserter.castNonNegative(3 * (int) NullnessUtil.castNonNull(helper).enemies().adjacent(cell).count()); + return moveGenerator.generate(ai, actions); } } diff --git a/src/main/java/fr/quatrevieux/araknemu/game/fight/ai/action/MoveNearFighter.java b/src/main/java/fr/quatrevieux/araknemu/game/fight/ai/action/MoveNearFighter.java new file mode 100644 index 000000000..b279b04a9 --- /dev/null +++ b/src/main/java/fr/quatrevieux/araknemu/game/fight/ai/action/MoveNearFighter.java @@ -0,0 +1,98 @@ +/* + * 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-2023 Vincent Quatrevieux + */ + +package fr.quatrevieux.araknemu.game.fight.ai.action; + +import fr.arakne.utils.maps.path.Pathfinder; +import fr.quatrevieux.araknemu.game.fight.ai.AI; +import fr.quatrevieux.araknemu.game.fight.ai.util.AIHelper; +import fr.quatrevieux.araknemu.game.fight.fighter.ActiveFighter; +import fr.quatrevieux.araknemu.game.fight.fighter.FighterData; +import fr.quatrevieux.araknemu.game.fight.map.BattlefieldCell; +import fr.quatrevieux.araknemu.game.fight.turn.action.Action; +import fr.quatrevieux.araknemu.util.Asserter; +import org.checkerframework.checker.index.qual.Positive; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; +import org.checkerframework.checker.nullness.util.NullnessUtil; + +import java.util.Optional; +import java.util.function.Function; + +/** + * Try to move near the selected fighter + */ +public final class MoveNearFighter implements ActionGenerator { + private @MonotonicNonNull Pathfinder pathfinder; + private @MonotonicNonNull AIHelper helper; + private final Function, Optional> fighterResolver; + + public MoveNearFighter(Function, Optional> fighterResolver) { + this.fighterResolver = fighterResolver; + } + + @Override + public void initialize(AI ai) { + this.helper = ai.helper(); + this.pathfinder = helper.cells().pathfinder() + .targetDistance(1) + .walkablePredicate(cell -> true) // Fix #94 Ignore inaccessible cell (handled by cell cost) + .cellWeightFunction(this::cellCost) + ; + } + + @Override + public Optional generate(AI ai, AiActionFactory actions) { + if (helper == null || !helper.canMove()) { + return Optional.empty(); + } + + final int movementPoints = helper.movementPoints(); + final BattlefieldCell currentCell = ai.fighter().cell(); + + return fighterResolver.apply(ai) + .map(enemy -> NullnessUtil.castNonNull(pathfinder).findPath(currentCell, enemy.cell()).truncate(movementPoints + 1)) + .map(path -> path.keepWhile(step -> step.cell().equals(currentCell) || step.cell().walkable())) // Truncate path to first unwalkable cell (may occur if the enemy cell is inaccessible or if other fighters block the path) + .filter(path -> path.size() > 1) + .map(path -> actions.move(path)) + ; + } + + /** + * Compute the cell cost for optimize the path finding + */ + private @Positive int cellCost(BattlefieldCell cell) { + // Fix #94 : Some cells are not accessible, but walkable/targetable using teleport. + // In this case the pathfinder will fail, so instead of ignoring unwalkable cells, simply set a very high cost, + // which allows the AI to generate a path to an inaccessible cell without throws a PathException + if (!cell.walkableIgnoreFighter()) { + return 1000; + } + + // A fighter is on the cell : the cell is not walkable + // But the fighter may leave the place at the next turn + // The cost is higher than a simple detour, but permit to resolve a path blocked by a fighter + if (cell.hasFighter()) { + return 15; + } + + // Add a cost of 3 for each enemy around the cell + // This cost corresponds to the detour cost + 1 + return 1 + Asserter.castNonNegative(3 * (int) NullnessUtil.castNonNull(helper).enemies().adjacent(cell).count()); + } +} diff --git a/src/main/java/fr/quatrevieux/araknemu/game/fight/ai/action/builder/GeneratorBuilder.java b/src/main/java/fr/quatrevieux/araknemu/game/fight/ai/action/builder/GeneratorBuilder.java index a16c1b287..015e41fc0 100644 --- a/src/main/java/fr/quatrevieux/araknemu/game/fight/ai/action/builder/GeneratorBuilder.java +++ b/src/main/java/fr/quatrevieux/araknemu/game/fight/ai/action/builder/GeneratorBuilder.java @@ -22,6 +22,7 @@ import fr.quatrevieux.araknemu.game.fight.ai.AI; import fr.quatrevieux.araknemu.game.fight.ai.action.ActionGenerator; import fr.quatrevieux.araknemu.game.fight.ai.action.Attack; +import fr.quatrevieux.araknemu.game.fight.ai.action.BlockNearestEnemy; import fr.quatrevieux.araknemu.game.fight.ai.action.Boost; import fr.quatrevieux.araknemu.game.fight.ai.action.Debuff; import fr.quatrevieux.araknemu.game.fight.ai.action.Heal; @@ -278,6 +279,17 @@ public final GeneratorBuilder moveNearEnemy() { return add(new MoveNearEnemy<>()); } + /** + * Try to move to the nearest enemy of the invoker + * + * @return The builder instance + * + * @see BlockNearestEnemy The used action generator + */ + public final GeneratorBuilder blockNearestEnemy() { + return add(new BlockNearestEnemy<>()); + } + /** * Try to teleport near the selected enemy * diff --git a/src/main/java/fr/quatrevieux/araknemu/game/fight/ai/factory/DoubleAiFactory.java b/src/main/java/fr/quatrevieux/araknemu/game/fight/ai/factory/DoubleAiFactory.java new file mode 100644 index 000000000..b55782097 --- /dev/null +++ b/src/main/java/fr/quatrevieux/araknemu/game/fight/ai/factory/DoubleAiFactory.java @@ -0,0 +1,53 @@ +/* + * This file is part of Araknemu. + * + * Araknemu is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Araknemu is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Araknemu. If not, see . + * + * Copyright (c) 2017-2023 Vincent Quatrevieux + */ + +package fr.quatrevieux.araknemu.game.fight.ai.factory; + +import fr.quatrevieux.araknemu.game.fight.ai.AI; +import fr.quatrevieux.araknemu.game.fight.fighter.PlayableFighter; +import fr.quatrevieux.araknemu.game.fight.fighter.invocation.DoubleFighter; +import fr.quatrevieux.araknemu.game.fight.fighter.operation.FighterOperation; +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.util.Optional; + +/** + * AI factory for {@link DoubleFighter} + */ +public final class DoubleAiFactory implements AiFactory { + private final AiFactory factory; + + public DoubleAiFactory(AiFactory factory) { + this.factory = factory; + } + + @Override + public Optional> create(PlayableFighter fighter) { + return Optional.ofNullable(fighter.apply(new Resolver()).ai); + } + + private class Resolver implements FighterOperation { + private @Nullable AI ai; + + @Override + public void onDouble(DoubleFighter fighter) { + factory.create(fighter).ifPresent(ai -> this.ai = ai); + } + } +} diff --git a/src/main/java/fr/quatrevieux/araknemu/game/fight/ai/factory/type/Blocking.java b/src/main/java/fr/quatrevieux/araknemu/game/fight/ai/factory/type/Blocking.java new file mode 100644 index 000000000..b8e1f2864 --- /dev/null +++ b/src/main/java/fr/quatrevieux/araknemu/game/fight/ai/factory/type/Blocking.java @@ -0,0 +1,35 @@ +/* + * 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-2019 Vincent Quatrevieux + */ + +package fr.quatrevieux.araknemu.game.fight.ai.factory.type; + +import fr.quatrevieux.araknemu.game.fight.ai.action.builder.GeneratorBuilder; +import fr.quatrevieux.araknemu.game.fight.ai.factory.AbstractAiBuilderFactory; +import fr.quatrevieux.araknemu.game.fight.fighter.PlayableFighter; + +/** + * AI for blocking invocation + * This AI will move to the enemy nearest of the invoker to block it + */ +public final class Blocking extends AbstractAiBuilderFactory { + @Override + public void configure(GeneratorBuilder builder, PlayableFighter fighter) { + builder.blockNearestEnemy(); + } +} diff --git a/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/invocations/CreateDoubleHandler.java b/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/invocations/CreateDoubleHandler.java new file mode 100644 index 000000000..99a766ab2 --- /dev/null +++ b/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/invocations/CreateDoubleHandler.java @@ -0,0 +1,68 @@ +/* + * 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-2023 Vincent Quatrevieux + */ + +package fr.quatrevieux.araknemu.game.fight.castable.effect.handler.invocations; + +import fr.quatrevieux.araknemu.game.fight.Fight; +import fr.quatrevieux.araknemu.game.fight.castable.FightCastScope; +import fr.quatrevieux.araknemu.game.fight.castable.effect.handler.EffectHandler; +import fr.quatrevieux.araknemu.game.fight.fighter.Fighter; +import fr.quatrevieux.araknemu.game.fight.fighter.FighterFactory; +import fr.quatrevieux.araknemu.game.fight.fighter.PlayableFighter; +import fr.quatrevieux.araknemu.game.fight.fighter.invocation.DoubleFighter; +import fr.quatrevieux.araknemu.network.game.out.fight.action.ActionEffect; +import fr.quatrevieux.araknemu.network.game.out.fight.turn.FighterTurnOrder; + +/** + * Create a double of the caster + * This spell effect does not take any parameters + * + * @see DoubleFighter + */ +public final class CreateDoubleHandler implements EffectHandler { + private final FighterFactory fighterFactory; + private final Fight fight; + + public CreateDoubleHandler(FighterFactory fighterFactory, Fight fight) { + this.fighterFactory = fighterFactory; + this.fight = fight; + } + + @Override + public void buff(FightCastScope cast, FightCastScope.EffectScope effect) { + throw new UnsupportedOperationException("CreateDoubleHandler cannot be used as buff"); + } + + @Override + public void handle(FightCastScope cast, FightCastScope.EffectScope effect) { + final Fighter caster = cast.caster(); + + final PlayableFighter invocation = fighterFactory.generate(id -> new DoubleFighter( + id, + cast.caster() + )); + + fight.fighters().joinTurnList(invocation, cast.target()); + + invocation.init(); + + fight.send(ActionEffect.addDouble(caster, invocation)); + fight.send(ActionEffect.packet(cast.caster(), new FighterTurnOrder(fight.turnList()))); + } +} diff --git a/src/main/java/fr/quatrevieux/araknemu/game/fight/fighter/Fighter.java b/src/main/java/fr/quatrevieux/araknemu/game/fight/fighter/Fighter.java index d5238ec5c..a017ea8b1 100644 --- a/src/main/java/fr/quatrevieux/araknemu/game/fight/fighter/Fighter.java +++ b/src/main/java/fr/quatrevieux/araknemu/game/fight/fighter/Fighter.java @@ -22,6 +22,7 @@ import fr.arakne.utils.maps.constant.Direction; import fr.quatrevieux.araknemu.core.event.Dispatcher; import fr.quatrevieux.araknemu.game.fight.Fight; +import fr.quatrevieux.araknemu.game.fight.FighterSprite; import fr.quatrevieux.araknemu.game.fight.castable.effect.buff.BuffList; import fr.quatrevieux.araknemu.game.fight.castable.weapon.CastableWeapon; import fr.quatrevieux.araknemu.game.fight.fighter.operation.FighterOperation; @@ -147,6 +148,9 @@ public default void attach(Object value) { */ public O apply(O operation); + @Override + public FighterSprite sprite(); + /** * Check if the fighter is the team leader */ diff --git a/src/main/java/fr/quatrevieux/araknemu/game/fight/fighter/invocation/DoubleFighter.java b/src/main/java/fr/quatrevieux/araknemu/game/fight/fighter/invocation/DoubleFighter.java new file mode 100644 index 000000000..132d3871e --- /dev/null +++ b/src/main/java/fr/quatrevieux/araknemu/game/fight/fighter/invocation/DoubleFighter.java @@ -0,0 +1,117 @@ +/* + * 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-2023 Vincent Quatrevieux + */ + +package fr.quatrevieux.araknemu.game.fight.fighter.invocation; + +import fr.quatrevieux.araknemu.game.fight.FighterSprite; +import fr.quatrevieux.araknemu.game.fight.castable.weapon.CastableWeapon; +import fr.quatrevieux.araknemu.game.fight.exception.FightException; +import fr.quatrevieux.araknemu.game.fight.fighter.AbstractPlayableFighter; +import fr.quatrevieux.araknemu.game.fight.fighter.BaseFighterLife; +import fr.quatrevieux.araknemu.game.fight.fighter.EmptySpellList; +import fr.quatrevieux.araknemu.game.fight.fighter.Fighter; +import fr.quatrevieux.araknemu.game.fight.fighter.FighterCharacteristics; +import fr.quatrevieux.araknemu.game.fight.fighter.FighterData; +import fr.quatrevieux.araknemu.game.fight.fighter.FighterLife; +import fr.quatrevieux.araknemu.game.fight.fighter.FighterSpellList; +import fr.quatrevieux.araknemu.game.fight.fighter.operation.FighterOperation; +import fr.quatrevieux.araknemu.game.fight.team.FightTeam; +import org.checkerframework.checker.index.qual.Positive; +import org.checkerframework.checker.nullness.qual.NonNull; + +/** + * Create a double of a fighter as invocation + * The double will inherit the characteristics of the invoker, but not the spells + */ +public final class DoubleFighter extends AbstractPlayableFighter { + private final int id; + private final Fighter invoker; + private final FightTeam team; + private final BaseFighterLife life; + private final FighterCharacteristics characteristics; + private final FighterSprite sprite; + + @SuppressWarnings({"assignment", "argument"}) + public DoubleFighter(int id, Fighter invoker) { + this.id = id; + this.invoker = invoker; + this.team = invoker.team(); + + this.life = new BaseFighterLife(this, invoker.life().current(), invoker.life().max()); + this.characteristics = new DoubleFighterCharacteristics(this, invoker); + this.sprite = invoker.sprite().withFighter(this); + } + + @Override + public O apply(O operation) { + operation.onDouble(this); + + return operation; + } + + @Override + public @Positive int level() { + return invoker.level(); + } + + @Override + public boolean ready() { + return true; + } + + @Override + public FightTeam team() { + return team; + } + + @Override + public CastableWeapon weapon() { + throw new FightException("The fighter do not have any weapon"); + } + + @Override + public int id() { + return id; + } + + @Override + public FighterSprite sprite() { + return sprite; + } + + @Override + public FighterSpellList spells() { + return EmptySpellList.INSTANCE; + } + + @Override + public FighterCharacteristics characteristics() { + return characteristics; + } + + @Override + public FighterLife life() { + return life; + } + + @Override + public @NonNull FighterData invoker() { + return invoker; + } +} diff --git a/src/main/java/fr/quatrevieux/araknemu/game/fight/fighter/invocation/DoubleFighterCharacteristics.java b/src/main/java/fr/quatrevieux/araknemu/game/fight/fighter/invocation/DoubleFighterCharacteristics.java new file mode 100644 index 000000000..9069cb57a --- /dev/null +++ b/src/main/java/fr/quatrevieux/araknemu/game/fight/fighter/invocation/DoubleFighterCharacteristics.java @@ -0,0 +1,75 @@ +/* + * 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-2023 Vincent Quatrevieux + */ + +package fr.quatrevieux.araknemu.game.fight.fighter.invocation; + +import fr.quatrevieux.araknemu.data.constant.Characteristic; +import fr.quatrevieux.araknemu.game.fight.fighter.Fighter; +import fr.quatrevieux.araknemu.game.fight.fighter.FighterCharacteristics; +import fr.quatrevieux.araknemu.game.fight.fighter.event.FighterCharacteristicChanged; +import fr.quatrevieux.araknemu.game.world.creature.characteristics.Characteristics; +import fr.quatrevieux.araknemu.game.world.creature.characteristics.DefaultCharacteristics; +import fr.quatrevieux.araknemu.game.world.creature.characteristics.MutableCharacteristics; + +/** + * Characteristics for a double + * This implements will simply retrieve the base characteristics from the invoker (i.e. {@link FighterCharacteristics#initial()}) + * and handle buffs. + */ +public final class DoubleFighterCharacteristics implements FighterCharacteristics { + private final Fighter fighter; + private final Characteristics base; + + private final MutableCharacteristics buffs = new DefaultCharacteristics(); + + /** + * @param fighter The double fighter + * @param invoker The invoker + */ + public DoubleFighterCharacteristics(Fighter fighter, Fighter invoker) { + this.fighter = fighter; + this.base = invoker.characteristics().initial(); + } + + @Override + public int initiative() { + return 0; // initiative is not used for invocations + } + + @Override + public int discernment() { + return 0; // monster do not have discernment + } + + @Override + public int get(Characteristic characteristic) { + return base.get(characteristic) + buffs.get(characteristic); + } + + @Override + public void alter(Characteristic characteristic, int value) { + buffs.add(characteristic, value); + fighter.dispatch(new FighterCharacteristicChanged(characteristic, value)); + } + + @Override + public Characteristics initial() { + return base; + } +} diff --git a/src/main/java/fr/quatrevieux/araknemu/game/fight/fighter/invocation/InvocationFighter.java b/src/main/java/fr/quatrevieux/araknemu/game/fight/fighter/invocation/InvocationFighter.java index 091e7756c..ca315da98 100644 --- a/src/main/java/fr/quatrevieux/araknemu/game/fight/fighter/invocation/InvocationFighter.java +++ b/src/main/java/fr/quatrevieux/araknemu/game/fight/fighter/invocation/InvocationFighter.java @@ -19,6 +19,7 @@ package fr.quatrevieux.araknemu.game.fight.fighter.invocation; +import fr.quatrevieux.araknemu.game.fight.FighterSprite; import fr.quatrevieux.araknemu.game.fight.castable.weapon.CastableWeapon; import fr.quatrevieux.araknemu.game.fight.exception.FightException; import fr.quatrevieux.araknemu.game.fight.fighter.AbstractPlayableFighter; @@ -32,7 +33,6 @@ import fr.quatrevieux.araknemu.game.fight.fighter.operation.FighterOperation; import fr.quatrevieux.araknemu.game.fight.team.FightTeam; import fr.quatrevieux.araknemu.game.monster.Monster; -import fr.quatrevieux.araknemu.game.world.creature.Sprite; import org.checkerframework.checker.index.qual.Positive; import org.checkerframework.checker.nullness.qual.NonNull; @@ -96,7 +96,7 @@ public int id() { } @Override - public Sprite sprite() { + public FighterSprite sprite() { return sprite; } diff --git a/src/main/java/fr/quatrevieux/araknemu/game/fight/fighter/invocation/StaticInvocationFighter.java b/src/main/java/fr/quatrevieux/araknemu/game/fight/fighter/invocation/StaticInvocationFighter.java index 52133a3e4..9c667f270 100644 --- a/src/main/java/fr/quatrevieux/araknemu/game/fight/fighter/invocation/StaticInvocationFighter.java +++ b/src/main/java/fr/quatrevieux/araknemu/game/fight/fighter/invocation/StaticInvocationFighter.java @@ -19,6 +19,7 @@ package fr.quatrevieux.araknemu.game.fight.fighter.invocation; +import fr.quatrevieux.araknemu.game.fight.FighterSprite; import fr.quatrevieux.araknemu.game.fight.castable.weapon.CastableWeapon; import fr.quatrevieux.araknemu.game.fight.exception.FightException; import fr.quatrevieux.araknemu.game.fight.fighter.AbstractFighter; @@ -32,7 +33,6 @@ import fr.quatrevieux.araknemu.game.fight.fighter.operation.FighterOperation; import fr.quatrevieux.araknemu.game.fight.team.FightTeam; import fr.quatrevieux.araknemu.game.monster.Monster; -import fr.quatrevieux.araknemu.game.world.creature.Sprite; import org.checkerframework.checker.index.qual.Positive; import org.checkerframework.checker.nullness.qual.NonNull; @@ -94,7 +94,7 @@ public int id() { } @Override - public Sprite sprite() { + public FighterSprite sprite() { return sprite; } diff --git a/src/main/java/fr/quatrevieux/araknemu/game/fight/fighter/monster/MonsterFighter.java b/src/main/java/fr/quatrevieux/araknemu/game/fight/fighter/monster/MonsterFighter.java index 891246add..df3247ea5 100644 --- a/src/main/java/fr/quatrevieux/araknemu/game/fight/fighter/monster/MonsterFighter.java +++ b/src/main/java/fr/quatrevieux/araknemu/game/fight/fighter/monster/MonsterFighter.java @@ -19,6 +19,7 @@ package fr.quatrevieux.araknemu.game.fight.fighter.monster; +import fr.quatrevieux.araknemu.game.fight.FighterSprite; import fr.quatrevieux.araknemu.game.fight.castable.weapon.CastableWeapon; import fr.quatrevieux.araknemu.game.fight.exception.FightException; import fr.quatrevieux.araknemu.game.fight.fighter.AbstractPlayableFighter; @@ -32,7 +33,6 @@ import fr.quatrevieux.araknemu.game.fight.team.FightTeam; import fr.quatrevieux.araknemu.game.monster.Monster; import fr.quatrevieux.araknemu.game.monster.reward.MonsterReward; -import fr.quatrevieux.araknemu.game.world.creature.Sprite; import org.checkerframework.checker.index.qual.Positive; import org.checkerframework.checker.nullness.qual.Nullable; @@ -67,7 +67,7 @@ public int id() { } @Override - public Sprite sprite() { + public FighterSprite sprite() { return sprite; } diff --git a/src/main/java/fr/quatrevieux/araknemu/game/fight/fighter/monster/MonsterFighterSprite.java b/src/main/java/fr/quatrevieux/araknemu/game/fight/fighter/monster/MonsterFighterSprite.java index 202244b79..00185076f 100644 --- a/src/main/java/fr/quatrevieux/araknemu/game/fight/fighter/monster/MonsterFighterSprite.java +++ b/src/main/java/fr/quatrevieux/araknemu/game/fight/fighter/monster/MonsterFighterSprite.java @@ -21,9 +21,9 @@ import fr.arakne.utils.maps.constant.Direction; import fr.quatrevieux.araknemu.data.constant.Characteristic; +import fr.quatrevieux.araknemu.game.fight.FighterSprite; import fr.quatrevieux.araknemu.game.fight.fighter.Fighter; import fr.quatrevieux.araknemu.game.monster.Monster; -import fr.quatrevieux.araknemu.game.world.creature.Sprite; /** * Sprite for monster @@ -32,7 +32,7 @@ * * https://github.com/Emudofus/Dofus/blob/1.29/dofus/aks/Game.as#L520 */ -public final class MonsterFighterSprite implements Sprite { +public final class MonsterFighterSprite implements FighterSprite { private final Fighter fighter; private final Monster monster; @@ -97,4 +97,9 @@ public String toString() { fighter.team().number() ; } + + @Override + public FighterSprite withFighter(Fighter fighter) { + return new MonsterFighterSprite(fighter, monster); + } } diff --git a/src/main/java/fr/quatrevieux/araknemu/game/fight/fighter/operation/FighterOperation.java b/src/main/java/fr/quatrevieux/araknemu/game/fight/fighter/operation/FighterOperation.java index f44f9f837..47b638ae5 100644 --- a/src/main/java/fr/quatrevieux/araknemu/game/fight/fighter/operation/FighterOperation.java +++ b/src/main/java/fr/quatrevieux/araknemu/game/fight/fighter/operation/FighterOperation.java @@ -20,6 +20,7 @@ package fr.quatrevieux.araknemu.game.fight.fighter.operation; import fr.quatrevieux.araknemu.game.fight.fighter.Fighter; +import fr.quatrevieux.araknemu.game.fight.fighter.invocation.DoubleFighter; import fr.quatrevieux.araknemu.game.fight.fighter.invocation.InvocationFighter; import fr.quatrevieux.araknemu.game.fight.fighter.invocation.StaticInvocationFighter; import fr.quatrevieux.araknemu.game.fight.fighter.monster.MonsterFighter; @@ -60,6 +61,13 @@ public default void onStaticInvocation(StaticInvocationFighter fighter) { onGenericFighter(fighter); } + /** + * Apply the operation to a DoubleFighter + */ + public default void onDouble(DoubleFighter fighter) { + onGenericFighter(fighter); + } + /** * Apply the operation to a generic fighter type */ diff --git a/src/main/java/fr/quatrevieux/araknemu/game/fight/fighter/player/PlayerFighter.java b/src/main/java/fr/quatrevieux/araknemu/game/fight/fighter/player/PlayerFighter.java index 85e1918b7..bd6d61c32 100644 --- a/src/main/java/fr/quatrevieux/araknemu/game/fight/fighter/player/PlayerFighter.java +++ b/src/main/java/fr/quatrevieux/araknemu/game/fight/fighter/player/PlayerFighter.java @@ -19,6 +19,7 @@ package fr.quatrevieux.araknemu.game.fight.fighter.player; +import fr.quatrevieux.araknemu.game.fight.FighterSprite; import fr.quatrevieux.araknemu.game.fight.castable.weapon.CastableWeapon; import fr.quatrevieux.araknemu.game.fight.event.FighterReadyStateChanged; import fr.quatrevieux.araknemu.game.fight.exception.FightException; @@ -36,7 +37,6 @@ import fr.quatrevieux.araknemu.game.player.inventory.slot.WeaponSlot; import fr.quatrevieux.araknemu.game.spell.boost.DispatcherSpellsBoosts; import fr.quatrevieux.araknemu.game.spell.boost.SimpleSpellsBoosts; -import fr.quatrevieux.araknemu.game.world.creature.Sprite; import fr.quatrevieux.araknemu.network.game.GameSession; import org.checkerframework.checker.index.qual.Positive; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; @@ -86,7 +86,7 @@ public int id() { } @Override - public Sprite sprite() { + public FighterSprite sprite() { return sprite; } diff --git a/src/main/java/fr/quatrevieux/araknemu/game/fight/fighter/player/PlayerFighterSprite.java b/src/main/java/fr/quatrevieux/araknemu/game/fight/fighter/player/PlayerFighterSprite.java index 2858b279c..b5a31d52e 100644 --- a/src/main/java/fr/quatrevieux/araknemu/game/fight/fighter/player/PlayerFighterSprite.java +++ b/src/main/java/fr/quatrevieux/araknemu/game/fight/fighter/player/PlayerFighterSprite.java @@ -21,21 +21,22 @@ import fr.arakne.utils.maps.constant.Direction; import fr.quatrevieux.araknemu.data.constant.Characteristic; +import fr.quatrevieux.araknemu.game.fight.FighterSprite; +import fr.quatrevieux.araknemu.game.fight.fighter.Fighter; import fr.quatrevieux.araknemu.game.player.sprite.SpriteInfo; -import fr.quatrevieux.araknemu.game.world.creature.Sprite; /** - * Sprite for fighter + * Sprite for player fighter * * The sprite type ID MUST be the class id * * https://github.com/Emudofus/Dofus/blob/1.29/dofus/aks/Game.as#L764 */ -public final class PlayerFighterSprite implements Sprite { - private final PlayerFighter fighter; +public final class PlayerFighterSprite implements FighterSprite { + private final Fighter fighter; private final SpriteInfo spriteInfo; - public PlayerFighterSprite(PlayerFighter fighter, SpriteInfo spriteInfo) { + public PlayerFighterSprite(Fighter fighter, SpriteInfo spriteInfo) { this.fighter = fighter; this.spriteInfo = spriteInfo; } @@ -81,7 +82,7 @@ public String toString() { spriteInfo.race().ordinal() + ";" + spriteInfo.gfxId() + "^" + spriteInfo.size() + ";" + spriteInfo.gender().ordinal() + ";" + - fighter.properties().experience().level() + ";" + + fighter.level() + ";" + "0,0,0,0;" + // @todo alignment spriteInfo.colors().toHexString(";") + ";" + spriteInfo.accessories() + ";" + @@ -99,4 +100,9 @@ public String toString() { ";" // @todo mount ; } + + @Override + public FighterSprite withFighter(Fighter fighter) { + return new PlayerFighterSprite(fighter, spriteInfo); + } } diff --git a/src/main/java/fr/quatrevieux/araknemu/game/fight/module/MonsterInvocationModule.java b/src/main/java/fr/quatrevieux/araknemu/game/fight/module/MonsterInvocationModule.java index 77e19f422..de787df1f 100644 --- a/src/main/java/fr/quatrevieux/araknemu/game/fight/module/MonsterInvocationModule.java +++ b/src/main/java/fr/quatrevieux/araknemu/game/fight/module/MonsterInvocationModule.java @@ -22,6 +22,7 @@ import fr.quatrevieux.araknemu.core.event.Listener; import fr.quatrevieux.araknemu.game.fight.Fight; import fr.quatrevieux.araknemu.game.fight.castable.effect.EffectsHandler; +import fr.quatrevieux.araknemu.game.fight.castable.effect.handler.invocations.CreateDoubleHandler; import fr.quatrevieux.araknemu.game.fight.castable.effect.handler.invocations.MonsterInvocationHandler; import fr.quatrevieux.araknemu.game.fight.castable.effect.handler.invocations.StaticInvocationHandler; import fr.quatrevieux.araknemu.game.fight.fighter.Fighter; @@ -50,8 +51,9 @@ public MonsterInvocationModule(MonsterService monsterService, FighterFactory fig @Override public void effects(EffectsHandler handler) { - // moving creatures + handler.register(180, new CreateDoubleHandler(fighterFactory, fight)); handler.register(181, new MonsterInvocationHandler(monsterService, fighterFactory, fight)); + handler.register(185, new StaticInvocationHandler(monsterService, fighterFactory, fight)); } diff --git a/src/main/java/fr/quatrevieux/araknemu/network/game/out/fight/action/ActionEffect.java b/src/main/java/fr/quatrevieux/araknemu/network/game/out/fight/action/ActionEffect.java index edce954a2..d8851d94c 100644 --- a/src/main/java/fr/quatrevieux/araknemu/network/game/out/fight/action/ActionEffect.java +++ b/src/main/java/fr/quatrevieux/araknemu/network/game/out/fight/action/ActionEffect.java @@ -353,6 +353,16 @@ public static ActionEffect addInvocation(FighterData caster, FighterData invocat return new ActionEffect(181, caster, "+" + invocation.sprite()); } + /** + * Add a new double invocation to the fight + * + * @param caster Invoker + * @param invocation Invocation to add + */ + public static ActionEffect addDouble(FighterData caster, FighterData invocation) { + return new ActionEffect(180, caster, "+" + invocation.sprite()); + } + /** * Add an invoked static creature to the fight * Unlike {@link #addInvocation(FighterData, FighterData)}, the client will take this creature in account diff --git a/src/test/java/fr/quatrevieux/araknemu/game/GameDataSet.java b/src/test/java/fr/quatrevieux/araknemu/game/GameDataSet.java index 910ebc550..ba8b54789 100644 --- a/src/test/java/fr/quatrevieux/araknemu/game/GameDataSet.java +++ b/src/test/java/fr/quatrevieux/araknemu/game/GameDataSet.java @@ -541,6 +541,7 @@ public GameDataSet pushFunctionalSpells() throws SQLException, ContainerExceptio "(16, 'Science du bâton', 101, '10,1,1', '142,5,,,5,0,0d0+5|142,7,,,5,0,0d0+7|3|0|1|40|100|false|false|false|true|0|0|0|6|PaPa||18;19;3;1;41|42|false', '142,6,,,5,0,0d0+6|142,8,,,5,0,0d0+8|3|0|2|40|100|false|false|false|true|0|0|0|6|PaPa||18;19;3;1;41|42|false', '142,8,,,5,0,0d0+8|142,11,,,5,0,0d0+11|3|0|3|40|100|false|false|false|true|0|0|0|6|PaPa||18;19;3;1;41|42|false', '142,10,,,5,0,0d0+10|142,15,,,5,0,0d0+15|3|0|4|40|100|false|false|false|true|0|0|0|6|PaPa||18;19;3;1;41|42|false', '142,15,,,5,0,0d0+15|142,20,,,5,0,0d0+20|3|0|5|40|100|false|false|false|true|0|0|0|6|PaPa||18;19;3;1;41|42|false', '142,25,,,5,0,0d0+25|142,30,,,5,0,0d0+30|2|0|6|40|100|false|false|false|true|0|0|0|6|PaPa||18;19;3;1;41|142|false', '')", "(197, 'Puissance Sylvestre', 0, '10,1,1', '149,,,8005,2,0;169,100,,,2,0,0d0+100;183,1000,,,2,0,0d0+1000;184,1000,,,2,0,0d0+1000;168,100,,,2,0,0d0+100;108,10,,,2,0,0d0+10||6|0|1|0|100|true|false|false|true|0|0|0|15|PaPaPaPaPaPa||8;18;19;3;1;41|17|false', '149,,,8005,2,0;169,100,,,2,0,0d0+100;183,1000,,,2,0,0d0+1000;184,1000,,,2,0,0d0+1000;168,100,,,2,0,0d0+100;108,11,,,2,0,0d0+11||5|0|2|0|100|true|false|false|true|0|0|0|14|PaPaPaPaPaPa||8;18;19;3;1;41|17|false', '149,,,8005,3,0;169,100,,,3,0,0d0+100;183,1000,,,3,0,0d0+1000;184,1000,,,3,0,0d0+1000;168,100,,,3,0,0d0+100;108,12,,,3,0,0d0+12||5|0|3|0|100|true|false|false|true|0|0|0|13|PaPaPaPaPaPa||8;18;19;3;1;41|17|false', '149,,,8005,3,0;169,100,,,3,0,0d0+100;183,1000,,,3,0,0d0+1000;184,1000,,,3,0,0d0+1000;168,100,,,3,0,0d0+100;108,13,,,3,0,0d0+13||4|0|4|0|100|true|false|false|true|0|0|0|12|PaPaPaPaPaPa||8;18;19;3;1;41|17|false', '149,,,8005,4,0;169,100,,,4,0,0d0+100;183,1000,,,4,0,0d0+1000;184,1000,,,4,0,0d0+1000;168,100,,,4,0,0d0+100;108,14,,,4,0,0d0+14||3|0|5|0|100|true|false|false|true|0|0|0|11|PaPaPaPaPaPa||8;18;19;3;1;41|17|false', '149,,,8005,4,0;169,100,,,4,0,0d0+100;183,1000,,,4,0,0d0+1000;184,1000,,,4,0,0d0+1000;168,100,,,4,0,0d0+100;108,16,,,4,0,0d0+16||2|0|6|0|100|true|false|false|true|0|0|0|10|PaPaPaPaPaPa||8;18;19;3;1;41|117|false', '4;4;4;4;4;4;5;5;5;5;5;5')", "(186, 'Arbre', 1100, '11,1,1', '185,282,1,,0,0||6|1|1|0|100|false|true|true|true|0|0|0|10|Pa||18;19;3;1;41|42|false', '185,282,2,,0,0||5|1|2|0|100|false|true|true|true|0|0|0|9|Pa||18;19;3;1;41|42|false', '185,282,3,,0,0||5|1|3|0|100|false|true|true|true|0|0|0|8|Pa||18;19;3;1;41|42|false', '185,282,4,,0,0||5|1|4|0|100|false|true|true|true|0|0|0|7|Pa||18;19;3;1;41|42|false', '185,282,5,,0,0||4|1|5|0|100|false|true|true|true|0|0|0|6|Pa||18;19;3;1;41|42|false', '185,282,6,,0,0||3|1|6|0|100|false|true|true|true|0|0|0|3|Pa||18;19;3;1;41|142|false', '')", + "(74, 'Double', 1100, '10,1,1', '180,,,,0,0||5|1|1|0|100|true|true|true|false|0|0|0|15|Pa||18;19;3;1;41|13|false', '180,,,,0,0||5|1|1|0|100|true|true|true|false|0|0|0|13|Pa||18;19;3;1;41|13|false', '180,,,,0,0||5|1|1|0|100|true|true|true|false|0|0|0|11|Pa||18;19;3;1;41|13|false', '180,,,,0,0||5|1|1|0|100|true|true|true|false|0|0|0|9|Pa||18;19;3;1;41|13|false', '180,,,,0,0||4|1|1|0|100|true|true|true|false|0|0|0|8|Pa||18;19;3;1;41|13|false', '180,,,,0,0||2|1|1|0|100|true|true|true|false|0|0|0|6|Pa||18;19;3;1;41|113|false', '')", }, ",") + ";" ); diff --git a/src/test/java/fr/quatrevieux/araknemu/game/GameModuleTest.java b/src/test/java/fr/quatrevieux/araknemu/game/GameModuleTest.java index 7760b64dd..9a9124639 100644 --- a/src/test/java/fr/quatrevieux/araknemu/game/GameModuleTest.java +++ b/src/test/java/fr/quatrevieux/araknemu/game/GameModuleTest.java @@ -68,6 +68,7 @@ import fr.quatrevieux.araknemu.game.fight.FightService; import fr.quatrevieux.araknemu.game.fight.ai.factory.AiFactory; import fr.quatrevieux.araknemu.game.fight.ai.factory.ChainAiFactory; +import fr.quatrevieux.araknemu.game.fight.ai.factory.DoubleAiFactory; import fr.quatrevieux.araknemu.game.fight.ai.factory.MonsterAiFactory; import fr.quatrevieux.araknemu.game.fight.ai.simulation.Simulator; import fr.quatrevieux.araknemu.game.fight.fighter.DefaultFighterFactory; @@ -162,6 +163,7 @@ void instances() throws ContainerException, SQLException { assertInstanceOf(ChallengeType.class, container.get(ChallengeType.class)); assertInstanceOf(ChainAiFactory.class, container.get(AiFactory.class)); assertInstanceOf(MonsterAiFactory.class, container.get(MonsterAiFactory.class)); + assertInstanceOf(DoubleAiFactory.class, container.get(DoubleAiFactory.class)); assertInstanceOf(ActivityService.class, container.get(ActivityService.class)); assertInstanceOf(DefaultExchangeFactory.class, container.get(ExchangeFactory.class)); assertInstanceOf(BankService.class, container.get(BankService.class)); diff --git a/src/test/java/fr/quatrevieux/araknemu/game/fight/ai/AiBaseCase.java b/src/test/java/fr/quatrevieux/araknemu/game/fight/ai/AiBaseCase.java index 29d4e50cd..27623cb33 100644 --- a/src/test/java/fr/quatrevieux/araknemu/game/fight/ai/AiBaseCase.java +++ b/src/test/java/fr/quatrevieux/araknemu/game/fight/ai/AiBaseCase.java @@ -51,6 +51,7 @@ import java.util.Optional; import java.util.Set; import java.util.function.Consumer; +import java.util.function.Function; import java.util.stream.Collectors; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -105,6 +106,27 @@ public void configureFight(Consumer configurator) { } } + public void configureFighterAi(PlayableFighter fighter) { + this.fighter = fighter; + + while (fight.turnList().currentFighter() != fighter) { + fight.turnList().current().get().stop(); + } + + ai = new FighterAI(fighter, fight, new DummyGenerator()); + ai.start(turn = fight.turnList().current().get()); + + if (action == null && actionFactory != null) { + GeneratorBuilder aiBuilder = new GeneratorBuilder<>(); + actionFactory.configure(aiBuilder, fighter); + action = aiBuilder.build(); + } + + if (action != null) { + action.initialize(ai); + } + } + public Optional generateAction() { lastAction = null; final Optional generated = action.generate(ai, new FightAiActionFactoryAdapter(ai.fighter(), fight, fight.actions())); diff --git a/src/test/java/fr/quatrevieux/araknemu/game/fight/ai/action/BlockNearestEnemyTest.java b/src/test/java/fr/quatrevieux/araknemu/game/fight/ai/action/BlockNearestEnemyTest.java new file mode 100644 index 000000000..2f2750ec9 --- /dev/null +++ b/src/test/java/fr/quatrevieux/araknemu/game/fight/ai/action/BlockNearestEnemyTest.java @@ -0,0 +1,94 @@ +/* + * 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-2023 Vincent Quatrevieux + */ + +package fr.quatrevieux.araknemu.game.fight.ai.action; + +import fr.quatrevieux.araknemu.data.constant.Characteristic; +import fr.quatrevieux.araknemu.game.fight.ai.AiBaseCase; +import fr.quatrevieux.araknemu.game.fight.fighter.invocation.DoubleFighter; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class BlockNearestEnemyTest extends AiBaseCase { + @Override + @BeforeEach + public void setUp() throws Exception { + super.setUp(); + + action = new BlockNearestEnemy(); + } + + @Test + void shouldMoveToEnemyNearTheInvoker() { + configureFight(fb -> fb + .addSelf(builder -> builder.cell(94).charac(Characteristic.AGILITY, 100)) + .addEnemy(builder -> builder.cell(124)) + .addEnemy(builder -> builder.cell(167)) + ); + + DoubleFighter invoc = new DoubleFighter(-10, player.fighter()); + fight.fighters().joinTurnList(invoc, fight.map().get(152)); // Adjacent to enemy 167 + invoc.init(); + + configureFighterAi(invoc); + + generateAndPerformMove(); + + assertEquals(138, fighter.cell().id()); + assertEquals(2, turn.points().movementPoints()); + } + + @Test + void shouldMoveToNearestEnemyIfInvokerIsInvisible() { + configureFight(fb -> fb + .addSelf(builder -> builder.cell(152).charac(Characteristic.AGILITY, 100)) + .addEnemy(builder -> builder.cell(167)) + .addEnemy(builder -> builder.cell(212)) + ); + + player.fighter().setHidden(player.fighter(), true); + + DoubleFighter invoc = new DoubleFighter(-10, player.fighter()); + fight.fighters().joinTurnList(invoc, fight.map().get(183)); + invoc.init(); + + configureFighterAi(invoc); + + generateAndPerformMove(); + + assertEquals(197, fighter.cell().id()); + assertEquals(2, turn.points().movementPoints()); + } + + @Test + void shouldMoveToNearestEnemyIfNotInvoked() { + configureFight(fb -> fb + .addSelf(builder -> builder.cell(152).charac(Characteristic.AGILITY, 100)) + .addEnemy(builder -> builder.cell(182)) + .addEnemy(builder -> builder.cell(110)) + ); + + generateAndPerformMove(); + + assertEquals(167, fighter.cell().id()); + assertEquals(2, turn.points().movementPoints()); + } +} diff --git a/src/test/java/fr/quatrevieux/araknemu/game/fight/ai/action/MoveNearFighterTest.java b/src/test/java/fr/quatrevieux/araknemu/game/fight/ai/action/MoveNearFighterTest.java new file mode 100644 index 000000000..abed82123 --- /dev/null +++ b/src/test/java/fr/quatrevieux/araknemu/game/fight/ai/action/MoveNearFighterTest.java @@ -0,0 +1,134 @@ +/* + * This file is part of Araknemu. + * + * Araknemu is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Araknemu is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Araknemu. If not, see . + * + * Copyright (c) 2017-2023 Vincent Quatrevieux + */ + +package fr.quatrevieux.araknemu.game.fight.ai.action; + +import fr.quatrevieux.araknemu.game.fight.ai.AI; +import fr.quatrevieux.araknemu.game.fight.ai.AiBaseCase; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +class MoveNearFighterTest extends AiBaseCase { + @Test + void generateNotInitialized() { + action = new MoveNearFighter<>(AI::enemy); + assertFalse(action.generate(Mockito.mock(AI.class), Mockito.mock(AiActionFactory.class)).isPresent()); + } + + @Test + void success() { + action = new MoveNearFighter<>(ai -> Optional.of(fight.map().get(222).fighter())); + configureFight(fb -> fb + .addSelf(builder -> builder.cell(122)) + .addEnemy(builder -> builder.cell(125)) + .addEnemy(builder -> builder.cell(126)) + .addAlly(builder -> builder.cell(222)) + ); + + generateAndPerformMove(); + assertEquals(165, fighter.cell().id()); + assertEquals(0, turn.points().movementPoints()); + } + + @Test + void withAllyOnPathShouldBeCircumvented() { + action = new MoveNearFighter<>(ai -> Optional.of(fight.map().get(181).fighter())); + configureFight(fb -> fb + .addSelf(builder -> builder.cell(151)) + .addAlly(builder -> builder.cell(166)) + .addEnemy(builder -> builder.cell(181)) + ); + + generateAndPerformMove(); + + assertEquals(195, fighter.cell().id()); + assertEquals(0, turn.points().movementPoints()); + } + + @Test + void whenAllyBlockAccess() { + action = new MoveNearFighter<>(ai -> Optional.of(fight.map().get(341).fighter())); + configureFight(fb -> fb + .addSelf(builder -> builder.cell(211)) + .addAlly(builder -> builder.cell(284)) + .addEnemy(builder -> builder.cell(341)) + ); + + generateAndPerformMove(); + + assertEquals(256, fighter.cell().id()); + assertEquals(0, turn.points().movementPoints()); + } + + // See: https://github.com/Arakne/Araknemu/issues/94 + @Test + void notAccessibleCellShouldTruncateToNearestCell() { + action = new MoveNearFighter<>(ai -> Optional.of(fight.map().get(69).fighter())); + configureFight(fb -> fb + .map(10342) + .addSelf(builder -> builder.cell(155)) + .addEnemy(builder -> builder.cell(69)) + ); + + generateAndPerformMove(); + + assertEquals(126, fighter.cell().id()); + assertEquals(1, turn.points().movementPoints()); + } + + @Test + void noMP() { + action = new MoveNearFighter<>(ai -> Optional.of(fight.map().get(125).fighter())); + configureFight(fb -> fb + .addSelf(builder -> builder.cell(122)) + .addEnemy(builder -> builder.cell(125)) + ); + + removeAllMP(); + + assertDotNotGenerateAction(); + } + + @Test + void onAdjacentCell() { + action = new MoveNearFighter<>(ai -> Optional.of(fight.map().get(125).fighter())); + configureFight(fb -> fb + .addSelf(builder -> builder.cell(110)) + .addEnemy(builder -> builder.cell(125)) + ); + + assertDotNotGenerateAction(); + } + + @Test + void noAvailableTarget() { + action = new MoveNearFighter<>(ai -> Optional.empty()); + configureFight(fb -> fb + .addSelf(builder -> builder.cell(90)) + .addEnemy(builder -> builder.cell(125)) + ); + + assertDotNotGenerateAction(); + } +} diff --git a/src/test/java/fr/quatrevieux/araknemu/game/fight/ai/action/builder/GeneratorBuilderTest.java b/src/test/java/fr/quatrevieux/araknemu/game/fight/ai/action/builder/GeneratorBuilderTest.java index 8d9de00bd..c8277a184 100644 --- a/src/test/java/fr/quatrevieux/araknemu/game/fight/ai/action/builder/GeneratorBuilderTest.java +++ b/src/test/java/fr/quatrevieux/araknemu/game/fight/ai/action/builder/GeneratorBuilderTest.java @@ -22,6 +22,7 @@ import fr.quatrevieux.araknemu._test.TestCase; import fr.quatrevieux.araknemu.game.fight.ai.action.ActionGenerator; import fr.quatrevieux.araknemu.game.fight.ai.action.Attack; +import fr.quatrevieux.araknemu.game.fight.ai.action.BlockNearestEnemy; import fr.quatrevieux.araknemu.game.fight.ai.action.Boost; import fr.quatrevieux.araknemu.game.fight.ai.action.Debuff; import fr.quatrevieux.araknemu.game.fight.ai.action.Heal; @@ -172,6 +173,11 @@ void debuff() { assertInstanceOf(Debuff.class, builder.debuff(simulator).build()); } + @Test + void blockNearestEnemy() { + assertInstanceOf(BlockNearestEnemy.class, builder.blockNearestEnemy().build()); + } + private void assertActions(ActionGenerator action, Class ...types) throws NoSuchFieldException, IllegalAccessException { assertInstanceOf(GeneratorAggregate.class, action); diff --git a/src/test/java/fr/quatrevieux/araknemu/game/fight/ai/factory/DoubleAiFactoryTest.java b/src/test/java/fr/quatrevieux/araknemu/game/fight/ai/factory/DoubleAiFactoryTest.java new file mode 100644 index 000000000..0e1e8a940 --- /dev/null +++ b/src/test/java/fr/quatrevieux/araknemu/game/fight/ai/factory/DoubleAiFactoryTest.java @@ -0,0 +1,58 @@ +/* + * 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-2023 Vincent Quatrevieux + */ + +package fr.quatrevieux.araknemu.game.fight.ai.factory; + +import fr.quatrevieux.araknemu.game.fight.Fight; +import fr.quatrevieux.araknemu.game.fight.FightBaseCase; +import fr.quatrevieux.araknemu.game.fight.ai.factory.type.Blocking; +import fr.quatrevieux.araknemu.game.fight.fighter.invocation.DoubleFighter; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class DoubleAiFactoryTest extends FightBaseCase { + private Fight fight; + private DoubleAiFactory factory; + + @Override + @BeforeEach + public void setUp() throws Exception { + super.setUp(); + + fight = createFight(); + factory = new DoubleAiFactory(new Blocking()); + } + + @Test + void createNotADouble() { + assertFalse(factory.create(player.fighter()).isPresent()); + } + + @Test + void createSuccess() { + DoubleFighter fighter = new DoubleFighter(-5, player.fighter()); + fight.nextState(); + fight.fighters().joinTurnList(fighter, fight.map().get(123)); + + assertTrue(factory.create(fighter).isPresent()); + } +} diff --git a/src/test/java/fr/quatrevieux/araknemu/game/fight/ai/factory/type/BlockingTest.java b/src/test/java/fr/quatrevieux/araknemu/game/fight/ai/factory/type/BlockingTest.java new file mode 100644 index 000000000..22ee68d62 --- /dev/null +++ b/src/test/java/fr/quatrevieux/araknemu/game/fight/ai/factory/type/BlockingTest.java @@ -0,0 +1,103 @@ +/* + * 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-2023 Vincent Quatrevieux + */ + +package fr.quatrevieux.araknemu.game.fight.ai.factory.type; + +import fr.quatrevieux.araknemu.data.constant.Characteristic; +import fr.quatrevieux.araknemu.game.fight.ai.AiBaseCase; +import fr.quatrevieux.araknemu.game.fight.ai.simulation.Simulator; +import fr.quatrevieux.araknemu.game.fight.fighter.Fighter; +import fr.quatrevieux.araknemu.game.fight.fighter.invocation.DoubleFighter; +import fr.quatrevieux.araknemu.game.player.spell.SpellBook; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.lang.reflect.Field; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; + +class BlockingTest extends AiBaseCase { + @BeforeEach + @Override + public void setUp() throws Exception { + super.setUp(); + + actionFactory = new Blocking(); + dataSet.pushFunctionalSpells(); + } + + @Test + void shouldMoveNearEnemy() { + configureFight(b -> b + .addSelf(fb -> fb.cell(210)) + .addEnemy(fb -> fb.cell(52)) + ); + + assertEquals(11, distance(getEnemy(0))); + + generateAndPerformMove(); + + assertEquals(165, fighter.cell().id()); + assertEquals(8, distance(getEnemy(0))); + } + + @Test + void shouldMoveNearInvokerEnemy() { + configureFight(fb -> fb + .addSelf(builder -> builder.cell(94).charac(Characteristic.AGILITY, 100)) + .addEnemy(builder -> builder.cell(124)) + .addEnemy(builder -> builder.cell(167)) + ); + + DoubleFighter invoc = new DoubleFighter(-10, player.fighter()); + fight.fighters().joinTurnList(invoc, fight.map().get(152)); // Adjacent to enemy 167 + invoc.init(); + + configureFighterAi(invoc); + + assertEquals(2, distance(getEnemy(0))); + + generateAndPerformMove(); + + assertEquals(138, invoc.cell().id()); + assertEquals(1, distance(getEnemy(0))); + } + + @Test + void shouldDoesNothingIfAlreadyAdjacentToEnemy() { + configureFight(fb -> fb + .addSelf(builder -> builder.cell(94).charac(Characteristic.AGILITY, 100)) + .addEnemy(builder -> builder.cell(167)) + ); + + DoubleFighter invoc = new DoubleFighter(-10, player.fighter()); + fight.fighters().joinTurnList(invoc, fight.map().get(152)); // Adjacent to enemy 167 + invoc.init(); + + configureFighterAi(invoc); + + generateAction(); + assertDotNotGenerateAction(); + } + + private int distance(Fighter other) { + return fighter.cell().coordinate().distance(other.cell()); + } +} 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 ff37e896b..c7db89fc9 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 @@ -26,11 +26,13 @@ import fr.quatrevieux.araknemu.game.fight.ai.factory.AiFactory; import fr.quatrevieux.araknemu.game.fight.castable.effect.buff.Buff; import fr.quatrevieux.araknemu.game.fight.castable.spell.SpellConstraintsValidator; +import fr.quatrevieux.araknemu.game.fight.exception.FightException; import fr.quatrevieux.araknemu.game.fight.fighter.ActiveFighter; import fr.quatrevieux.araknemu.game.fight.fighter.Fighter; import fr.quatrevieux.araknemu.game.fight.fighter.FighterFactory; import fr.quatrevieux.araknemu.game.fight.fighter.FighterData; import fr.quatrevieux.araknemu.game.fight.fighter.PlayableFighter; +import fr.quatrevieux.araknemu.game.fight.fighter.invocation.DoubleFighter; import fr.quatrevieux.araknemu.game.fight.fighter.invocation.InvocationFighter; import fr.quatrevieux.araknemu.game.fight.fighter.player.PlayerFighter; import fr.quatrevieux.araknemu.game.fight.map.BattlefieldObject; @@ -55,6 +57,7 @@ import fr.quatrevieux.araknemu.network.game.out.fight.turn.FighterTurnOrder; import fr.quatrevieux.araknemu.network.game.out.fight.turn.TurnMiddle; import fr.quatrevieux.araknemu.network.game.out.game.UpdateCells; +import fr.quatrevieux.araknemu.network.game.out.info.Error; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -1430,6 +1433,30 @@ void staticInvocation() throws SQLException { assertTrue(fight.map().get(213).hasFighter()); } + @Test + void doubleInvoc() { + castNormal(74, fight.map().get(199)); // Double + + assertTrue(fight.map().get(199).hasFighter()); + assertInstanceOf(DoubleFighter.class, fight.map().get(199).fighter()); + + Fighter invoc = fight.map().get(199).fighter(); + + assertEquals(invoc.life().current(), fighter1.life().current()); + assertEquals(invoc.life().max(), fighter1.life().max()); + assertTrue(fight.turnList().fighters().contains(invoc)); + + requestStack.assertOne(new ActionEffect(180, fighter1, "+" + invoc.sprite())); + requestStack.assertOne(ActionEffect.packet(fighter1, new FighterTurnOrder(fight.turnList()))); + + invoc.attach(FighterAI.class, null); // Remove AI, to ensure it doesn't play + + passTurns(9); // spell cooldown + + assertThrows(FightException.class, () -> castNormal(74, fight.map().get(200))); + requestStack.assertLast(Error.cantCastMaxSummonedCreaturesReached(1)); + } + private List configureFight(Consumer configurator) { fight.cancel(true); @@ -1450,8 +1477,9 @@ private List configureFight(Consumer configurator) { private void passTurns(int number) { for (; number > 0; --number) { - fighter1.turn().stop(); - fighter2.turn().stop(); + for (PlayableFighter fighter : fight.turnList().fighters()) { + fighter.turn().stop(); + } } } diff --git a/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/invocations/CreateDoubleHandlerTest.java b/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/invocations/CreateDoubleHandlerTest.java new file mode 100644 index 000000000..6dcb243ee --- /dev/null +++ b/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/invocations/CreateDoubleHandlerTest.java @@ -0,0 +1,135 @@ +/* + * 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-2019 Vincent Quatrevieux + */ + +package fr.quatrevieux.araknemu.game.fight.castable.effect.handler.invocations; + +import fr.quatrevieux.araknemu.data.constant.Characteristic; +import fr.quatrevieux.araknemu.game.fight.Fight; +import fr.quatrevieux.araknemu.game.fight.FightBaseCase; +import fr.quatrevieux.araknemu.game.fight.ai.FighterAI; +import fr.quatrevieux.araknemu.game.fight.ai.factory.AiFactory; +import fr.quatrevieux.araknemu.game.fight.castable.FightCastScope; +import fr.quatrevieux.araknemu.game.fight.fighter.Fighter; +import fr.quatrevieux.araknemu.game.fight.fighter.FighterData; +import fr.quatrevieux.araknemu.game.fight.fighter.FighterFactory; +import fr.quatrevieux.araknemu.game.fight.fighter.invocation.DoubleFighter; +import fr.quatrevieux.araknemu.game.fight.fighter.invocation.InvocationFighter; +import fr.quatrevieux.araknemu.game.fight.fighter.player.PlayerFighter; +import fr.quatrevieux.araknemu.game.fight.module.AiModule; +import fr.quatrevieux.araknemu.game.monster.MonsterService; +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.target.SpellEffectTarget; +import fr.quatrevieux.araknemu.network.game.out.fight.action.ActionEffect; +import fr.quatrevieux.araknemu.network.game.out.fight.turn.FighterTurnOrder; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class CreateDoubleHandlerTest extends FightBaseCase { + private Fight fight; + private PlayerFighter caster; + private CreateDoubleHandler handler; + + @Override + @BeforeEach + public void setUp() throws Exception { + super.setUp(); + + fight = createFight(); + fight.register(new AiModule(container.get(AiFactory.class))); + fight.nextState(); + + caster = player.fighter(); + + handler = new CreateDoubleHandler(container.get(FighterFactory.class), fight); + + requestStack.clear(); + } + + @Test + void handle() { + caster.life().alter(caster, -50); + requestStack.clear(); + + SpellEffect effect = Mockito.mock(SpellEffect.class); + Spell spell = Mockito.mock(Spell.class); + SpellConstraints constraints = Mockito.mock(SpellConstraints.class); + + Mockito.when(effect.area()).thenReturn(new CellArea()); + Mockito.when(effect.target()).thenReturn(SpellEffectTarget.DEFAULT); + Mockito.when(spell.constraints()).thenReturn(constraints); + Mockito.when(constraints.freeCell()).thenReturn(true); + + FightCastScope scope = makeCastScope(caster, spell, effect, fight.map().get(123)); + handler.handle(scope, scope.effects().get(0)); + + Fighter invoc = fight.map().get(123).fighter(); + + assertInstanceOf(DoubleFighter.class, invoc); + assertContains(invoc, fight.fighters().all()); + assertContains(invoc, fight.turnList().fighters()); + assertSame(caster.team(), invoc.team()); + assertEquals(caster.level(), invoc.level()); + assertInstanceOf(FighterAI.class, invoc.attachment(FighterAI.class)); + + assertEquals(caster.life().current(), invoc.life().current()); + assertEquals(caster.life().max(), invoc.life().max()); + + for (Characteristic characteristic : Characteristic.values()) { + assertEquals(caster.characteristics().get(characteristic), invoc.characteristics().get(characteristic)); + } + + assertEquals(invoc.id(), invoc.sprite().id()); + assertEquals(invoc.cell().id(), invoc.sprite().cell()); + assertEquals(invoc.orientation(), invoc.sprite().orientation()); + assertEquals(caster.sprite().type(), invoc.sprite().type()); + assertEquals(caster.sprite().gfxId(), invoc.sprite().gfxId()); + assertEquals(caster.sprite().name(), invoc.sprite().name()); + + assertEquals("123;1;0;-1;Bob;1;10^100x100;0;50;0,0,0,0;7b;1c8;315;,,,,;245;6;3;0;0;0;0;0;0;0;0;;", invoc.sprite().toString()); + + requestStack.assertAll( + new FighterTurnOrder(fight.turnList()), + new ActionEffect(180, caster, "+" + invoc.sprite()), + new ActionEffect(999, caster, (new FighterTurnOrder(fight.turnList())).toString()) + ); + } + + @Test + void buff() { + SpellEffect effect = Mockito.mock(SpellEffect.class); + Spell spell = Mockito.mock(Spell.class); + SpellConstraints constraints = Mockito.mock(SpellConstraints.class); + + Mockito.when(effect.area()).thenReturn(new CellArea()); + Mockito.when(effect.target()).thenReturn(SpellEffectTarget.DEFAULT); + Mockito.when(spell.constraints()).thenReturn(constraints); + Mockito.when(constraints.freeCell()).thenReturn(true); + + FightCastScope scope = makeCastScope(caster, spell, effect, fight.map().get(123)); + assertThrows(UnsupportedOperationException.class, () -> handler.buff(scope, scope.effects().get(0))); + } +} diff --git a/src/test/java/fr/quatrevieux/araknemu/game/fight/fighter/invocation/DoubleFighterCharacteristicsTest.java b/src/test/java/fr/quatrevieux/araknemu/game/fight/fighter/invocation/DoubleFighterCharacteristicsTest.java new file mode 100644 index 000000000..599ad4007 --- /dev/null +++ b/src/test/java/fr/quatrevieux/araknemu/game/fight/fighter/invocation/DoubleFighterCharacteristicsTest.java @@ -0,0 +1,94 @@ +/* + * 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-2023 Vincent Quatrevieux + */ + +package fr.quatrevieux.araknemu.game.fight.fighter.invocation; + +import fr.quatrevieux.araknemu.data.constant.Characteristic; +import fr.quatrevieux.araknemu.game.fight.Fight; +import fr.quatrevieux.araknemu.game.fight.FightBaseCase; +import fr.quatrevieux.araknemu.game.fight.fighter.event.FighterCharacteristicChanged; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.concurrent.atomic.AtomicReference; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; + +class DoubleFighterCharacteristicsTest extends FightBaseCase { + private DoubleFighterCharacteristics characteristics; + private DoubleFighter fighter; + + @Override + @BeforeEach + public void setUp() throws Exception { + super.setUp(); + + Fight fight = createFight(); + + fighter = new DoubleFighter(-5, player.fighter()); + fighter.joinFight(fight, fight.map().get(123)); + + characteristics = new DoubleFighterCharacteristics(fighter, player.fighter()); + } + + @Test + void initiative() { + assertEquals(0, characteristics.initiative()); + } + + @Test + void discernment() { + assertEquals(0, characteristics.discernment()); + } + + @Test + void get() { + assertEquals(50, characteristics.get(Characteristic.STRENGTH)); + assertEquals(6, characteristics.get(Characteristic.ACTION_POINT)); + assertEquals(3, characteristics.get(Characteristic.MOVEMENT_POINT)); + } + + @Test + void alter() { + AtomicReference ref = new AtomicReference<>(); + fighter.dispatcher().add(FighterCharacteristicChanged.class, ref::set); + + characteristics.alter(Characteristic.STRENGTH, 10); + assertEquals(60, characteristics.get(Characteristic.STRENGTH)); + + assertEquals(Characteristic.STRENGTH, ref.get().characteristic()); + assertEquals(10, ref.get().value()); + + characteristics.alter(Characteristic.STRENGTH, -10); + assertEquals(50, characteristics.get(Characteristic.STRENGTH)); + + assertEquals(Characteristic.STRENGTH, ref.get().characteristic()); + assertEquals(-10, ref.get().value()); + } + + @Test + void initial() { + assertSame(player.fighter().characteristics().initial(), characteristics.initial()); + assertEquals(50, characteristics.initial().get(Characteristic.STRENGTH)); + + characteristics.alter(Characteristic.STRENGTH, 10); + assertEquals(50, characteristics.initial().get(Characteristic.STRENGTH)); + } +} diff --git a/src/test/java/fr/quatrevieux/araknemu/game/fight/fighter/invocation/DoubleFighterTest.java b/src/test/java/fr/quatrevieux/araknemu/game/fight/fighter/invocation/DoubleFighterTest.java new file mode 100644 index 000000000..769edf4d7 --- /dev/null +++ b/src/test/java/fr/quatrevieux/araknemu/game/fight/fighter/invocation/DoubleFighterTest.java @@ -0,0 +1,392 @@ +/* + * 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-2023 Vincent Quatrevieux + */ + +package fr.quatrevieux.araknemu.game.fight.fighter.invocation; + +import fr.arakne.utils.maps.constant.Direction; +import fr.quatrevieux.araknemu.core.event.Listener; +import fr.quatrevieux.araknemu.data.constant.Characteristic; +import fr.quatrevieux.araknemu.game.fight.Fight; +import fr.quatrevieux.araknemu.game.fight.FightBaseCase; +import fr.quatrevieux.araknemu.game.fight.castable.effect.buff.BuffList; +import fr.quatrevieux.araknemu.game.fight.castable.spell.LaunchedSpells; +import fr.quatrevieux.araknemu.game.fight.exception.FightException; +import fr.quatrevieux.araknemu.game.fight.fighter.Fighter; +import fr.quatrevieux.araknemu.game.fight.fighter.States; +import fr.quatrevieux.araknemu.game.fight.fighter.event.FighterHidden; +import fr.quatrevieux.araknemu.game.fight.fighter.event.FighterInitialized; +import fr.quatrevieux.araknemu.game.fight.fighter.event.FighterMoved; +import fr.quatrevieux.araknemu.game.fight.fighter.event.FighterVisible; +import fr.quatrevieux.araknemu.game.fight.fighter.monster.MonsterFighter; +import fr.quatrevieux.araknemu.game.fight.fighter.monster.MonsterFighterSprite; +import fr.quatrevieux.araknemu.game.fight.fighter.operation.FighterOperation; +import fr.quatrevieux.araknemu.game.fight.fighter.player.PlayerFighterSprite; +import fr.quatrevieux.araknemu.game.fight.team.FightTeam; +import fr.quatrevieux.araknemu.game.fight.turn.FightTurn; +import fr.quatrevieux.araknemu.game.monster.MonsterService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import java.sql.SQLException; +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertIterableEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class DoubleFighterTest extends FightBaseCase { + private DoubleFighter fighter; + private FightTeam team; + private Fight fight; + + @Override + @BeforeEach + public void setUp() throws Exception { + super.setUp(); + + dataSet + .pushMonsterTemplates() + .pushMonsterTemplateInvocations() + .pushMonsterSpellsInvocations() + .pushMonsterSpells() + ; + + fight = createFight(); + team = fight.team(0); + + fighter = new DoubleFighter(-5, player.fighter()); + } + + @Test + void shouldNotInheritBuffs() { + player.fighter().characteristics().alter(Characteristic.STRENGTH, 100); + + fighter = new DoubleFighter(-5, player.fighter()); + assertEquals(50, fighter.characteristics().get(Characteristic.STRENGTH)); + + fighter.characteristics().alter(Characteristic.STRENGTH, 50); + assertEquals(100, fighter.characteristics().get(Characteristic.STRENGTH)); + assertEquals(150, player.fighter().characteristics().get(Characteristic.STRENGTH)); + } + + @Test + void shouldCopyLife() { + player.fighter().init(); + player.fighter().life().alter(player.fighter(), -50); + assertEquals(245, player.fighter().life().current()); + assertEquals(295, player.fighter().life().max()); + + fighter = new DoubleFighter(-5, player.fighter()); + fighter.joinFight(fight, fight.map().get(123)); + assertEquals(245, fighter.life().current()); + assertEquals(295, fighter.life().max()); + + player.fighter().life().alter(player.fighter(), -100); + assertEquals(145, player.fighter().life().current()); + assertEquals(245, fighter.life().current()); + + fighter.life().alter(fighter, -50); + assertEquals(145, player.fighter().life().current()); + assertEquals(195, fighter.life().current()); + } + + @Test + void values() { + assertSame(team, fighter.team()); + assertEquals(-5, fighter.id()); + assertEquals(Direction.SOUTH_EAST, fighter.orientation()); + assertFalse(fighter.dead()); + assertThrows(FightException.class, fighter::weapon); + assertInstanceOf(BuffList.class, fighter.buffs()); + assertInstanceOf(States.class, fighter.states()); + assertTrue(fighter.ready()); + assertEquals(player.fighter().level(), fighter.level()); + assertSame(player.fighter(), fighter.invoker()); + assertTrue(fighter.invoked()); + } + + @Test + void equals() throws SQLException { + assertEquals(fighter, fighter); + assertEquals(fighter.hashCode(), fighter.hashCode()); + assertNotEquals(fighter, makePlayerFighter(gamePlayer())); + + MonsterFighter other = new MonsterFighter( + -2, + container.get(MonsterService.class).load(36).all().get(0), + team + ); + + assertNotEquals(fighter, other); + } + + @Test + void attachments() { + fighter.attach("key", 42); + assertSame(42, fighter.attachment("key")); + + LaunchedSpells launchedSpells = new LaunchedSpells(); + + fighter.attach(launchedSpells); + assertSame(launchedSpells, fighter.attachment(LaunchedSpells.class)); + } + + @Test + void orientation() { + assertEquals(Direction.SOUTH_EAST, fighter.orientation()); + + fighter.setOrientation(Direction.NORTH_EAST); + assertEquals(Direction.NORTH_EAST, fighter.orientation()); + } + + @Test + void init() throws Exception { + Fight fight = createFight(); + + AtomicReference ref = new AtomicReference<>(); + fight.dispatcher().add(FighterInitialized.class, ref::set); + + fighter.joinFight(fight, fight.map().get(123)); + fighter.init(); + + assertSame(fighter, ref.get().fighter()); + } + + @Test + void dead() throws Exception { + Fight fight = createFight(); + + fighter.joinFight(fight, fight.map().get(123)); + fighter.init(); + assertFalse(fighter.dead()); + + fighter.life().alter(fighter, -10000); + + assertTrue(fighter.dead()); + } + + @Test + void joinFight() throws Exception { + Fight fight = createFight(); + + assertFalse(fighter.isOnFight()); + + fighter.joinFight(fight, fight.map().get(123)); + + assertSame(fight, fighter.fight()); + assertSame(fight.map().get(123), fighter.cell()); + assertSame(fighter, fighter.cell().fighter()); + assertTrue(fighter.isOnFight()); + } + + @Test + void joinFightAlreadyJoinedShouldRaisedException() throws Exception { + Fight fight = createFight(); + + fighter.joinFight(fight, fight.map().get(123)); + assertThrows(IllegalStateException.class, () -> fighter.joinFight(fight, fight.map().get(123))); + } + + @Test + void sprite() throws Exception { + Fight fight = createFight(); + fighter.joinFight(fight, fight.map().get(123)); + + assertInstanceOf(PlayerFighterSprite.class, fighter.sprite()); + assertEquals("123;1;0;-5;Bob;1;10^100x100;0;50;0,0,0,0;7b;1c8;315;,,,,;295;6;3;0;0;0;0;0;0;0;0;;", fighter.sprite().toString()); + + fighter.move(fight.map().get(124)); + assertEquals("124;1;0;-5;Bob;1;10^100x100;0;50;0,0,0,0;7b;1c8;315;,,,,;295;6;3;0;0;0;0;0;0;0;0;;", fighter.sprite().toString()); + } + + @Test + void moveFirstTime() throws Exception { + Fight fight = createFight(); + + fighter.move(fight.map().get(123)); + + assertSame(fight.map().get(123), fighter.cell()); + assertSame(fighter, fight.map().get(123).fighter()); + } + + @Test + void moveWillLeaveLastCell() throws Exception { + Fight fight = createFight(); + + fighter.move(fight.map().get(123)); + fighter.move(fight.map().get(124)); + + assertSame(fight.map().get(124), fighter.cell()); + assertSame(fighter, fight.map().get(124).fighter()); + + assertFalse(fight.map().get(123).hasFighter()); + } + + @Test + void moveRemoveCell() throws Exception { + Fight fight = createFight(); + + fighter.move(fight.map().get(123)); + fighter.move(null); + + assertThrows(IllegalStateException.class, fighter::cell); + assertFalse(fight.map().get(123).hasFighter()); + } + + @Test + void moveShouldDispatchFighterMoved() { + fighter.joinFight(fight, fight.map().get(123)); + + AtomicReference ref = new AtomicReference<>(); + fight.dispatcher().add(FighterMoved.class, ref::set); + + fighter.move(fight.map().get(126)); + + assertSame(fighter, ref.get().fighter()); + assertSame(fight.map().get(126), ref.get().cell()); + } + + @Test + void moveShouldNotDispatchFighterMovedWhenNullCellIsGiven() { + fighter.joinFight(fight, fight.map().get(123)); + + AtomicReference ref = new AtomicReference<>(); + fight.dispatcher().add(FighterMoved.class, ref::set); + + fighter.move(null); + + assertNull(ref.get()); + } + + @Test + void spells() { + assertIterableEquals(fighter.spells(), new ArrayList<>()); + } + + @Test + void dispatcher() { + Object event = new Object(); + + AtomicReference ref = new AtomicReference<>(); + fighter.dispatcher().add(Object.class, ref::set); + + fighter.dispatch(event); + + assertSame(event, ref.get()); + } + + @Test + void playStop() throws Exception { + Fight fight = createFight(); + fighter.joinFight(fight, fight.map().get(123)); + + FightTurn turn = new FightTurn(fighter, fight, Duration.ofSeconds(10)); + turn.start(); + + AtomicReference ref = new AtomicReference<>(); + fighter.play(turn); + + assertSame(turn, fighter.turn()); + assertTrue(fighter.isPlaying()); + + fighter.perform(ref::set); + assertSame(turn, ref.get()); + + ref.set(null); + fighter.stop(); + + fighter.perform(ref::set); + + assertNull(ref.get()); + assertFalse(fighter.isPlaying()); + assertThrows(FightException.class, () -> fighter.turn()); + } + + @Test + void apply() { + FighterOperation operation = Mockito.mock(FighterOperation.class); + + assertSame(operation, fighter.apply(operation)); + Mockito.verify(operation).onDouble(fighter); + } + + @Test + void hidden() throws Exception { + Fight fight = createFight(); + fighter.joinFight(fight, fight.map().get(123)); + + final List events = new ArrayList<>(); + + fight.dispatcher().add(new Listener() { + @Override + public void on(FighterHidden event) { + events.add(event); + } + + @Override + public Class event() { + return FighterHidden.class; + } + }); + fight.dispatcher().add(new Listener() { + @Override + public void on(FighterVisible event) { + events.add(event); + } + + @Override + public Class event() { + return FighterVisible.class; + } + }); + + Fighter caster = Mockito.mock(Fighter.class); + + assertFalse(fighter.hidden()); + fighter.setHidden(caster, true); + + assertTrue(fighter.hidden()); + assertEquals(1, events.size()); + assertEquals(fighter, FighterHidden.class.cast(events.get(0)).fighter()); + assertEquals(caster, FighterHidden.class.cast(events.get(0)).caster()); + + fighter.setHidden(caster, true); + assertEquals(1, events.size()); + assertTrue(fighter.hidden()); + + fighter.setHidden(caster, false); + assertEquals(2, events.size()); + assertFalse(fighter.hidden()); + assertEquals(fighter, FighterVisible.class.cast(events.get(1)).fighter()); + assertEquals(caster, FighterVisible.class.cast(events.get(1)).caster()); + + fighter.setHidden(caster, false); + assertEquals(2, events.size()); + assertFalse(fighter.hidden()); + } +} diff --git a/src/test/java/fr/quatrevieux/araknemu/game/fight/fighter/monster/MonsterFighterSpriteTest.java b/src/test/java/fr/quatrevieux/araknemu/game/fight/fighter/monster/MonsterFighterSpriteTest.java index cc45aa378..00418fdcf 100644 --- a/src/test/java/fr/quatrevieux/araknemu/game/fight/fighter/monster/MonsterFighterSpriteTest.java +++ b/src/test/java/fr/quatrevieux/araknemu/game/fight/fighter/monster/MonsterFighterSpriteTest.java @@ -20,13 +20,16 @@ package fr.quatrevieux.araknemu.game.fight.fighter.monster; import fr.arakne.utils.value.Interval; +import fr.quatrevieux.araknemu.data.constant.Characteristic; import fr.quatrevieux.araknemu.data.value.Position; import fr.quatrevieux.araknemu.data.world.entity.monster.MonsterGroupData; import fr.quatrevieux.araknemu.game.exploration.map.ExplorationMapService; import fr.quatrevieux.araknemu.game.fight.Fight; import fr.quatrevieux.araknemu.game.fight.FightBaseCase; import fr.quatrevieux.araknemu.game.fight.FightService; +import fr.quatrevieux.araknemu.game.fight.FighterSprite; import fr.quatrevieux.araknemu.game.fight.fighter.FighterFactory; +import fr.quatrevieux.araknemu.game.fight.fighter.invocation.DoubleFighter; import fr.quatrevieux.araknemu.game.fight.team.MonsterGroupTeam; import fr.quatrevieux.araknemu.game.monster.MonsterService; import fr.quatrevieux.araknemu.game.monster.environment.LivingMonsterGroupPosition; @@ -48,6 +51,7 @@ class MonsterFighterSpriteTest extends FightBaseCase { private MonsterFighterSprite sprite; private MonsterFighter fighter; + private Fight fight; @Override @BeforeEach @@ -84,7 +88,7 @@ public void setUp() throws Exception { fighter = (MonsterFighter) team.fighters().stream().findFirst().get(); sprite = new MonsterFighterSprite(fighter, service.load(31).all().get(2)); - Fight fight = createFight(); + fight = createFight(); fighter.joinFight(fight, fight.map().get(123)); } @@ -114,4 +118,23 @@ void values() { assertEquals("31", sprite.name()); assertEquals(1563, sprite.gfxId()); } + + @Test + void withFighter() { + DoubleFighter other = new DoubleFighter(-5, fighter); + other.move(fight.map().get(321)); + other.setOrientation(Direction.NORTH_WEST); + other.characteristics().alter(Characteristic.ACTION_POINT, 10); + + FighterSprite sprite = this.sprite.withFighter(other); + + assertEquals(-5, sprite.id()); + assertEquals(321, sprite.cell()); + assertEquals(Sprite.Type.MONSTER, sprite.type()); + assertEquals("31", sprite.name()); + assertEquals(Direction.NORTH_WEST, sprite.orientation()); + assertEquals(1563, sprite.gfxId()); + + assertEquals("321;5;0;-5;31;-2;1563^100;3;-1;-1;-1;0,0,0,0;20;14;2;3;7;7;-7;-7;7;5;1", sprite.toString()); + } } diff --git a/src/test/java/fr/quatrevieux/araknemu/game/fight/fighter/player/PlayerFighterSpriteTest.java b/src/test/java/fr/quatrevieux/araknemu/game/fight/fighter/player/PlayerFighterSpriteTest.java index 97e26b0e5..cafe12b9d 100644 --- a/src/test/java/fr/quatrevieux/araknemu/game/fight/fighter/player/PlayerFighterSpriteTest.java +++ b/src/test/java/fr/quatrevieux/araknemu/game/fight/fighter/player/PlayerFighterSpriteTest.java @@ -26,6 +26,8 @@ import fr.quatrevieux.araknemu.game.fight.Fight; import fr.quatrevieux.araknemu.game.fight.FightBaseCase; import fr.quatrevieux.araknemu.game.fight.FightService; +import fr.quatrevieux.araknemu.game.fight.FighterSprite; +import fr.quatrevieux.araknemu.game.fight.fighter.invocation.DoubleFighter; import fr.quatrevieux.araknemu.game.fight.map.FightMap; import fr.quatrevieux.araknemu.game.fight.team.SimpleTeam; import fr.quatrevieux.araknemu.game.world.creature.Sprite; @@ -40,6 +42,7 @@ class PlayerFighterSpriteTest extends FightBaseCase { private PlayerFighterSprite sprite; private PlayerFighter fighter; + private FightMap map; @Override @BeforeEach @@ -48,7 +51,7 @@ public void setUp() throws Exception { dataSet.pushMaps().pushSubAreas().pushAreas(); - FightMap map = container.get(FightService.class).map( + map = container.get(FightService.class).map( container.get(ExplorationMapService.class).load(10340) ); @@ -109,4 +112,26 @@ void getters() throws SQLException, ContainerException { assertEquals(Direction.SOUTH_EAST, sprite.orientation()); assertEquals(10, sprite.gfxId()); } + + @Test + void withFighter() { + DoubleFighter other = new DoubleFighter(-5, fighter); + other.move(map.get(123)); + other.setOrientation(Direction.NORTH_WEST); + other.characteristics().alter(Characteristic.ACTION_POINT, 10); + + FighterSprite sprite = this.sprite.withFighter(other); + + assertEquals(-5, sprite.id()); + assertEquals(123, sprite.cell()); + assertEquals(Sprite.Type.PLAYER, sprite.type()); + assertEquals("Bob", sprite.name()); + assertEquals(Direction.NORTH_WEST, sprite.orientation()); + assertEquals(10, sprite.gfxId()); + + assertEquals( + "123;5;0;-5;Bob;1;10^100x100;0;50;0,0,0,0;7b;1c8;315;,,,,;295;16;3;0;0;0;0;0;0;0;0;;", + sprite.toString() + ); + } } diff --git a/src/test/java/fr/quatrevieux/araknemu/game/fight/turn/order/AlternateTeamFighterOrderTest.java b/src/test/java/fr/quatrevieux/araknemu/game/fight/turn/order/AlternateTeamFighterOrderTest.java index d0d4e336d..e7066ddcd 100644 --- a/src/test/java/fr/quatrevieux/araknemu/game/fight/turn/order/AlternateTeamFighterOrderTest.java +++ b/src/test/java/fr/quatrevieux/araknemu/game/fight/turn/order/AlternateTeamFighterOrderTest.java @@ -24,6 +24,7 @@ import fr.quatrevieux.araknemu.data.constant.Alignment; import fr.quatrevieux.araknemu.data.constant.Characteristic; import fr.quatrevieux.araknemu.game.fight.Fight; +import fr.quatrevieux.araknemu.game.fight.FighterSprite; import fr.quatrevieux.araknemu.game.fight.JoinFightError; import fr.quatrevieux.araknemu.game.fight.castable.effect.buff.BuffList; import fr.quatrevieux.araknemu.game.fight.castable.weapon.CastableWeapon; @@ -159,7 +160,7 @@ public void setOrientation(Direction orientation) { public void move(FightCell cell) {} @Override - public Sprite sprite() { + public FighterSprite sprite() { return null; } diff --git a/src/test/java/fr/quatrevieux/araknemu/network/game/out/fight/action/ActionEffectTest.java b/src/test/java/fr/quatrevieux/araknemu/network/game/out/fight/action/ActionEffectTest.java index 8df7ca5fc..839013adb 100644 --- a/src/test/java/fr/quatrevieux/araknemu/network/game/out/fight/action/ActionEffectTest.java +++ b/src/test/java/fr/quatrevieux/araknemu/network/game/out/fight/action/ActionEffectTest.java @@ -21,8 +21,10 @@ import fr.quatrevieux.araknemu.game.fight.Fight; import fr.quatrevieux.araknemu.game.fight.FightBaseCase; +import fr.quatrevieux.araknemu.game.fight.FighterSprite; import fr.quatrevieux.araknemu.game.fight.castable.effect.buff.Buff; import fr.quatrevieux.araknemu.game.fight.fighter.Fighter; +import fr.quatrevieux.araknemu.game.fight.fighter.invocation.DoubleFighter; import fr.quatrevieux.araknemu.game.fight.fighter.invocation.InvocationFighter; import fr.quatrevieux.araknemu.game.fight.map.FightCell; import fr.quatrevieux.araknemu.game.monster.MonsterService; @@ -286,7 +288,7 @@ void changeAppearance() { Fighter caster = Mockito.mock(Fighter.class); Fighter target = Mockito.mock(Fighter.class); - Sprite sprite = Mockito.mock(Sprite.class); + FighterSprite sprite = Mockito.mock(FighterSprite.class); Mockito.when(caster.id()).thenReturn(456); Mockito.when(target.id()).thenReturn(123); @@ -301,7 +303,7 @@ void resetAppearance() { Fighter caster = Mockito.mock(Fighter.class); Fighter target = Mockito.mock(Fighter.class); - Sprite sprite = Mockito.mock(Sprite.class); + FighterSprite sprite = Mockito.mock(FighterSprite.class); Mockito.when(caster.id()).thenReturn(456); Mockito.when(target.id()).thenReturn(123); @@ -385,6 +387,21 @@ void addInvocation() throws Exception { assertEquals("GA;181;1;+118;1;0;-5;36;-2;1566^100;2;-1;-1;-1;0,0,0,0;60;5;3;30;0;-10;7;-45;16;16;0", ActionEffect.addInvocation(invoker, fighter).toString()); } + @Test + void addDouble() throws Exception { + dataSet + .pushMonsterTemplates() + .pushMonsterSpells() + ; + + Fight fight = createFight(); + Fighter invoker = fight.team(0).leader(); + DoubleFighter fighter = new DoubleFighter(-5, invoker); + fighter.joinFight(fight, fight.map().get(118)); + + assertEquals("GA;180;1;+118;1;0;-5;Bob;1;10^100x100;0;50;0,0,0,0;7b;1c8;315;,,,,;295;6;3;0;0;0;0;0;0;0;0;;", ActionEffect.addDouble(invoker, fighter).toString()); + } + @Test void addStaticInvocation() throws Exception { dataSet