From 1a2feb64359dca3d29b9bbd702b8e6b2b3cc8828 Mon Sep 17 00:00:00 2001 From: Vincent Quatrevieux Date: Thu, 15 Sep 2022 16:23:34 +0200 Subject: [PATCH] feat(fight): Fix invocation effect (#27) --- checkstyle.xml | 2 +- .../araknemu/game/fight/Fight.java | 45 +-- .../fight/ai/factory/MonsterAiFactory.java | 22 +- .../araknemu/game/fight/ai/proxy/ProxyAI.java | 4 +- .../game/fight/builder/PvmBuilder.java | 2 +- .../invocations/MonsterInvocationHandler.java | 56 +-- .../game/fight/fighter/AbstractFighter.java | 1 - .../fight/fighter/DefaultFighterFactory.java | 30 ++ .../game/fight/fighter/FighterFactory.java | 22 ++ .../game/fight/fighter/PassiveFighter.java | 5 + .../fighter/invocation/InvocationFighter.java | 129 +++++++ .../InvocationFighterCharacteristics.java | 92 +++++ .../fighter/monster/InvocationFighter.java | 228 ------------ .../monster/InvocationFighterFactory.java | 28 -- .../fight/fighter/monster/MonsterFighter.java | 1 + .../fighter/monster/MonsterFighterSprite.java | 5 +- .../fighter/operation/FighterOperation.java | 4 +- .../fight/module/MonsterInvocationModule.java | 61 ++-- .../game/fight/team/MonsterGroupTeam.java | 23 +- .../araknemu/game/fight/team/SimpleTeam.java | 21 +- .../game/fight/turn/FightTurnList.java | 23 +- .../effect/target/SpellEffectTarget.java | 52 ++- .../game/out/fight/action/ActionEffect.java | 20 ++ .../araknemu/game/GameDataSet.java | 3 +- .../araknemu/game/fight/FightBaseCase.java | 3 +- .../araknemu/game/fight/FightTest.java | 38 ++ .../ai/factory/MonsterAiFactoryTest.java | 22 ++ .../game/fight/ai/proxy/ProxyAITest.java | 20 +- .../fight/ai/proxy/ProxyBattlefieldTest.java | 2 +- .../game/fight/ai/util/AIHelperTest.java | 2 +- .../fight/castable/effect/FunctionalTest.java | 48 ++- .../heal/GivePercentLifeHandlerTest.java | 2 +- .../MonsterInvocationHandlerTest.java | 50 ++- .../fight/fighter/BaseFighterLifeTest.java | 3 +- .../fighter/DefaultFighterFactoryTest.java | 60 +++- .../InvocationFighterCharacteristicsTest.java | 114 ++++++ .../invocation/InvocationFighterTest.java | 331 ++++++++++++++++++ .../MonsterFighterCharacteristicsTest.java | 4 +- .../monster/MonsterFighterSpriteTest.java | 4 +- .../fighter/monster/MonsterFighterTest.java | 5 +- .../fighter/player/PlayerFighterTest.java | 5 + .../module/MonsterInvocationModuleTest.java | 134 +++++++ .../game/fight/team/MonsterGroupTeamTest.java | 4 +- .../game/fight/turn/FightTurnListTest.java | 38 ++ .../effect/target/SpellEffectTargetTest.java | 32 ++ .../out/fight/action/ActionEffectTest.java | 29 +- 46 files changed, 1401 insertions(+), 428 deletions(-) create mode 100644 src/main/java/fr/quatrevieux/araknemu/game/fight/fighter/invocation/InvocationFighter.java create mode 100644 src/main/java/fr/quatrevieux/araknemu/game/fight/fighter/invocation/InvocationFighterCharacteristics.java delete mode 100644 src/main/java/fr/quatrevieux/araknemu/game/fight/fighter/monster/InvocationFighter.java delete mode 100644 src/main/java/fr/quatrevieux/araknemu/game/fight/fighter/monster/InvocationFighterFactory.java create mode 100644 src/test/java/fr/quatrevieux/araknemu/game/fight/fighter/invocation/InvocationFighterCharacteristicsTest.java create mode 100644 src/test/java/fr/quatrevieux/araknemu/game/fight/fighter/invocation/InvocationFighterTest.java create mode 100644 src/test/java/fr/quatrevieux/araknemu/game/fight/module/MonsterInvocationModuleTest.java diff --git a/checkstyle.xml b/checkstyle.xml index 7c260e03d..dee0a8a4a 100644 --- a/checkstyle.xml +++ b/checkstyle.xml @@ -275,7 +275,7 @@ - + diff --git a/src/main/java/fr/quatrevieux/araknemu/game/fight/Fight.java b/src/main/java/fr/quatrevieux/araknemu/game/fight/Fight.java index 9dd5d5d7a..90b9010d9 100644 --- a/src/main/java/fr/quatrevieux/araknemu/game/fight/Fight.java +++ b/src/main/java/fr/quatrevieux/araknemu/game/fight/Fight.java @@ -29,8 +29,6 @@ import fr.quatrevieux.araknemu.game.fight.event.FightStopped; import fr.quatrevieux.araknemu.game.fight.exception.InvalidFightStateException; import fr.quatrevieux.araknemu.game.fight.fighter.Fighter; -import fr.quatrevieux.araknemu.game.fight.fighter.monster.InvocationFighterFactory; -import fr.quatrevieux.araknemu.game.fight.map.FightCell; import fr.quatrevieux.araknemu.game.fight.map.FightMap; import fr.quatrevieux.araknemu.game.fight.module.FightModule; import fr.quatrevieux.araknemu.game.fight.spectator.Spectators; @@ -57,6 +55,7 @@ import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.stream.Collectors; +import java.util.stream.Stream; /** * Handle fight @@ -96,7 +95,7 @@ public Fight(int id, FightType type, FightMap map, List teams, States this.spectators = new Spectators(this); this.actions = actions; - teams.forEach(team -> team.setFight(this)); + teams.forEach(team -> team.setFight(this)); // @todo team factory ? } /** @@ -135,9 +134,12 @@ public FightTeam team(int number) { * Get all fighters on the fight */ public List fighters() { - return teams - .stream() - .flatMap(fightTeam -> fightTeam.fighters().stream()) + final Stream fighterStream = turnList.isInitialized() + ? turnList.fighters().stream() + : teams.stream().flatMap(fightTeam -> fightTeam.fighters().stream()) + ; + + return fighterStream .filter(Fighter::isOnFight) .collect(Collectors.toList()) ; @@ -153,6 +155,10 @@ public List fighters(boolean onlyInitialized) { return fighters(); } + if (turnList.isInitialized()) { + return turnList.fighters(); + } + return teams .stream() .flatMap(fightTeam -> fightTeam.fighters().stream()) @@ -252,10 +258,17 @@ public void dispatch(Object event) { * @see Fight#dispatch(Object) To dispatch on the Fight's listeners */ public void dispatchToAll(Object event) { - for (FightTeam team : teams) { - for (Fighter fighter : team.fighters()) { - if (fighter.isOnFight()) { - fighter.dispatch(event); + if (turnList.isInitialized()) { + turnList.fighters().stream() + .filter(Fighter::isOnFight) + .forEach(fighter -> fighter.dispatch(event)) + ; + } else { + for (FightTeam team : teams) { + for (Fighter fighter : team.fighters()) { + if (fighter.isOnFight()) { + fighter.dispatch(event); + } } } } @@ -428,16 +441,4 @@ public void run() { } } } - - public Fighter addInvocation(InvocationFighterFactory factory, FightCell cell) { - final Fighter invocation = factory.create(fighters().stream().mapToInt(Fighter::id).min().getAsInt() - 1); - - invocation.joinFight(this, cell); - turnList.currentFighter().team().join(invocation); - turnList.add(invocation); - - invocation.init(); - - return invocation; - } } diff --git a/src/main/java/fr/quatrevieux/araknemu/game/fight/ai/factory/MonsterAiFactory.java b/src/main/java/fr/quatrevieux/araknemu/game/fight/ai/factory/MonsterAiFactory.java index ae67995dd..c6506d126 100644 --- a/src/main/java/fr/quatrevieux/araknemu/game/fight/ai/factory/MonsterAiFactory.java +++ b/src/main/java/fr/quatrevieux/araknemu/game/fight/ai/factory/MonsterAiFactory.java @@ -21,6 +21,7 @@ import fr.quatrevieux.araknemu.game.fight.ai.AI; import fr.quatrevieux.araknemu.game.fight.fighter.Fighter; +import fr.quatrevieux.araknemu.game.fight.fighter.invocation.InvocationFighter; import fr.quatrevieux.araknemu.game.fight.fighter.monster.MonsterFighter; import fr.quatrevieux.araknemu.game.fight.fighter.operation.FighterOperation; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; @@ -49,10 +50,23 @@ class ResolveAi implements FighterOperation { @Override public void onMonster(MonsterFighter fighter) { - final AiFactory factory = factories.get(fighter.monster().ai()); + resolve(fighter, fighter.monster().ai()); + } + + @Override + public void onInvocation(InvocationFighter fighter) { + resolve(fighter, fighter.monster().ai()); + } + + public Optional> get() { + return Optional.ofNullable(ai); + } + + private void resolve(Fighter fighter, String type) { + final AiFactory factory = factories.get(type); if (factory == null) { - throw new IllegalArgumentException("Unsupported AI type " + fighter.monster().ai()); + throw new IllegalArgumentException("Unsupported AI type " + type); } factory @@ -60,9 +74,5 @@ public void onMonster(MonsterFighter fighter) { .ifPresent(ai -> this.ai = ai) ; } - - public Optional> get() { - return Optional.ofNullable(ai); - } } } diff --git a/src/main/java/fr/quatrevieux/araknemu/game/fight/ai/proxy/ProxyAI.java b/src/main/java/fr/quatrevieux/araknemu/game/fight/ai/proxy/ProxyAI.java index 1033a249e..2e6cb2756 100644 --- a/src/main/java/fr/quatrevieux/araknemu/game/fight/ai/proxy/ProxyAI.java +++ b/src/main/java/fr/quatrevieux/araknemu/game/fight/ai/proxy/ProxyAI.java @@ -27,9 +27,9 @@ import fr.quatrevieux.araknemu.game.fight.turn.Turn; import org.checkerframework.checker.index.qual.NonNegative; -import java.util.HashMap; import java.util.Map; import java.util.Optional; +import java.util.WeakHashMap; import java.util.stream.Stream; /** @@ -44,7 +44,7 @@ public final class ProxyAI implements AI { private ProxyActiveFighter fighter; private ProxyTurn turn; - private final Map fighters = new HashMap<>(); + private final Map fighters = new WeakHashMap<>(); public ProxyAI(AI ai) { this.ai = ai; diff --git a/src/main/java/fr/quatrevieux/araknemu/game/fight/builder/PvmBuilder.java b/src/main/java/fr/quatrevieux/araknemu/game/fight/builder/PvmBuilder.java index 24c6dba5c..0173e43a6 100644 --- a/src/main/java/fr/quatrevieux/araknemu/game/fight/builder/PvmBuilder.java +++ b/src/main/java/fr/quatrevieux/araknemu/game/fight/builder/PvmBuilder.java @@ -73,7 +73,7 @@ public Fight build(int fightId) { startPlaces, number )); - builder.addTeam((number, startPlaces) -> new MonsterGroupTeam(group, startPlaces, number)); + builder.addTeam((number, startPlaces) -> new MonsterGroupTeam(group, startPlaces, number, fighterFactory)); return builder.build(fightId); } diff --git a/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/invocations/MonsterInvocationHandler.java b/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/invocations/MonsterInvocationHandler.java index bfa33eed2..04a1cda5b 100644 --- a/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/invocations/MonsterInvocationHandler.java +++ b/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/invocations/MonsterInvocationHandler.java @@ -19,50 +19,60 @@ package fr.quatrevieux.araknemu.game.fight.castable.effect.handler.invocations; -import java.util.Collections; - import fr.quatrevieux.araknemu.game.fight.Fight; import fr.quatrevieux.araknemu.game.fight.castable.CastScope; import fr.quatrevieux.araknemu.game.fight.castable.CastScope.EffectScope; 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.monster.InvocationFighter; -import fr.quatrevieux.araknemu.game.fight.fighter.monster.MonsterFighter; +import fr.quatrevieux.araknemu.game.fight.fighter.FighterFactory; +import fr.quatrevieux.araknemu.game.fight.fighter.invocation.InvocationFighter; +import fr.quatrevieux.araknemu.game.fight.team.FightTeam; import fr.quatrevieux.araknemu.game.monster.MonsterService; import fr.quatrevieux.araknemu.network.game.out.fight.action.ActionEffect; import fr.quatrevieux.araknemu.network.game.out.fight.turn.FighterTurnOrder; -import fr.quatrevieux.araknemu.network.game.out.game.AddSprites; -final public class MonsterInvocationHandler implements EffectHandler { - final private MonsterService monsterService; - final private Fight fight; +/** + * Handle monster invocation + * + * A new fighter will be created and added to fight and timeline (turn list) + * + * Effect parameters : + * - #1 (min) : monster id + * - #2 (max) : grade number + * + * @see InvocationFighter Invoked fighter + */ +public final class MonsterInvocationHandler implements EffectHandler { + private final MonsterService monsterService; + private final FighterFactory fighterFactory; + private final Fight fight; - public MonsterInvocationHandler(MonsterService monsterService, Fight fight) { + public MonsterInvocationHandler(MonsterService monsterService, FighterFactory fighterFactory, Fight fight) { this.monsterService = monsterService; + this.fighterFactory = fighterFactory; this.fight = fight; } @Override public void buff(CastScope cast, EffectScope effect) { - addMonsterToFight(cast, effect); // sadida lvl 100 puppet hit here + handle(cast, effect); } @Override public void handle(CastScope cast, EffectScope effect) { - addMonsterToFight(cast, effect); // normal invocations - } - - private void addMonsterToFight(CastScope cast, EffectScope effect) { - final Fighter invocation = fight.addInvocation((id) -> new InvocationFighter( - new MonsterFighter( - id, - monsterService.load(effect.effect().min()).get(effect.effect().max()), - fight.turnList().currentFighter().team() - ), + final Fighter invocation = fighterFactory.generate(id -> new InvocationFighter( + id, + monsterService.load(effect.effect().min()).get(effect.effect().max()), + (FightTeam) cast.caster().team(), cast.caster() - ), cast.target()); + )); + + invocation.joinFight(fight, cast.target()); + fight.turnList().add(invocation); + + invocation.init(); - fight.send(new ActionEffect(181, cast.caster(), (new AddSprites(Collections.singleton(invocation.sprite()))).toString())); - fight.send(new ActionEffect(999, cast.caster(), (new FighterTurnOrder(fight.turnList())).toString())); + fight.send(ActionEffect.addInvocation(cast.caster(), invocation)); + fight.send(ActionEffect.packet(cast.caster(), new FighterTurnOrder(fight.turnList()))); } } diff --git a/src/main/java/fr/quatrevieux/araknemu/game/fight/fighter/AbstractFighter.java b/src/main/java/fr/quatrevieux/araknemu/game/fight/fighter/AbstractFighter.java index 1d4cebceb..ee3167e25 100644 --- a/src/main/java/fr/quatrevieux/araknemu/game/fight/fighter/AbstractFighter.java +++ b/src/main/java/fr/quatrevieux/araknemu/game/fight/fighter/AbstractFighter.java @@ -35,7 +35,6 @@ import java.util.HashMap; import java.util.Map; -import java.util.Optional; import java.util.function.Consumer; /** diff --git a/src/main/java/fr/quatrevieux/araknemu/game/fight/fighter/DefaultFighterFactory.java b/src/main/java/fr/quatrevieux/araknemu/game/fight/fighter/DefaultFighterFactory.java index 4bfd7ce67..894de6f6c 100644 --- a/src/main/java/fr/quatrevieux/araknemu/game/fight/fighter/DefaultFighterFactory.java +++ b/src/main/java/fr/quatrevieux/araknemu/game/fight/fighter/DefaultFighterFactory.java @@ -21,7 +21,9 @@ import fr.quatrevieux.araknemu.core.event.Dispatcher; import fr.quatrevieux.araknemu.game.fight.fighter.event.PlayerFighterCreated; +import fr.quatrevieux.araknemu.game.fight.fighter.monster.MonsterFighter; import fr.quatrevieux.araknemu.game.fight.fighter.player.PlayerFighter; +import fr.quatrevieux.araknemu.game.fight.team.FightTeam; import fr.quatrevieux.araknemu.game.listener.fight.SendFightJoined; import fr.quatrevieux.araknemu.game.listener.fight.fighter.ApplyEndFightReward; import fr.quatrevieux.araknemu.game.listener.fight.fighter.ApplyLeaveReward; @@ -30,16 +32,28 @@ import fr.quatrevieux.araknemu.game.listener.fight.fighter.SendSpellBoosted; import fr.quatrevieux.araknemu.game.listener.fight.fighter.SendStats; import fr.quatrevieux.araknemu.game.listener.fight.fighter.StopFightSession; +import fr.quatrevieux.araknemu.game.monster.Monster; import fr.quatrevieux.araknemu.game.player.GamePlayer; +import java.util.concurrent.atomic.AtomicInteger; + /** * Default implementation of the fighter factory */ public final class DefaultFighterFactory implements FighterFactory { + private static final int MIN_ID = -1_000_000_000; + private final Dispatcher dispatcher; + private final AtomicInteger lastId = new AtomicInteger(); + private final int minId; public DefaultFighterFactory(Dispatcher dispatcher) { + this(dispatcher, MIN_ID); + } + + public DefaultFighterFactory(Dispatcher dispatcher, int minId) { this.dispatcher = dispatcher; + this.minId = minId; } @Override @@ -59,4 +73,20 @@ public PlayerFighter create(GamePlayer player) { return fighter; } + + @Override + public Fighter create(Monster monster, FightTeam team) { + return generate(id -> new MonsterFighter(id, monster, team)); + } + + @Override + public Fighter generate(FighterGenerator generator) { + final int id = lastId.updateAndGet(last -> { + final int next = last - 1; + + return next < minId ? -1 : next; + }); + + return generator.create(id); + } } diff --git a/src/main/java/fr/quatrevieux/araknemu/game/fight/fighter/FighterFactory.java b/src/main/java/fr/quatrevieux/araknemu/game/fight/fighter/FighterFactory.java index 8c694b756..758e67e80 100644 --- a/src/main/java/fr/quatrevieux/araknemu/game/fight/fighter/FighterFactory.java +++ b/src/main/java/fr/quatrevieux/araknemu/game/fight/fighter/FighterFactory.java @@ -20,6 +20,9 @@ package fr.quatrevieux.araknemu.game.fight.fighter; import fr.quatrevieux.araknemu.game.fight.fighter.player.PlayerFighter; +import fr.quatrevieux.araknemu.game.fight.team.FightTeam; +import fr.quatrevieux.araknemu.game.fight.team.MonsterGroupTeam; +import fr.quatrevieux.araknemu.game.monster.Monster; import fr.quatrevieux.araknemu.game.player.GamePlayer; /** @@ -34,4 +37,23 @@ public interface FighterFactory { * @return The PlayerFighter */ public PlayerFighter create(GamePlayer player); + + /** + * Create a monster fighter + * An unique ID will be generated for the fighter + */ + public Fighter create(Monster monster, FightTeam team); + + /** + * Generate a fighter with a unique ID + * The generated ID will be a negative integer + */ + public Fighter generate(FighterGenerator generator); + + public static interface FighterGenerator { + /** + * Create a fighter with a generated ID + */ + public Fighter create(int id); + } } diff --git a/src/main/java/fr/quatrevieux/araknemu/game/fight/fighter/PassiveFighter.java b/src/main/java/fr/quatrevieux/araknemu/game/fight/fighter/PassiveFighter.java index 2981cbb7f..e468ccdb5 100644 --- a/src/main/java/fr/quatrevieux/araknemu/game/fight/fighter/PassiveFighter.java +++ b/src/main/java/fr/quatrevieux/araknemu/game/fight/fighter/PassiveFighter.java @@ -105,5 +105,10 @@ public default boolean dead() { return life().dead(); } + /** + * Get the invoker fighter + * + * @todo nullable not optional + */ public Optional invoker(); } 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 new file mode 100644 index 000000000..486508c6e --- /dev/null +++ b/src/main/java/fr/quatrevieux/araknemu/game/fight/fighter/invocation/InvocationFighter.java @@ -0,0 +1,129 @@ +/* + * This file is part of Araknemu. + * + * Araknemu is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Araknemu is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Araknemu. If not, see . + * + * Copyright (c) 2017-2022 Vincent Quatrevieux + */ + +package fr.quatrevieux.araknemu.game.fight.fighter.invocation; + +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; +import fr.quatrevieux.araknemu.game.fight.fighter.BaseFighterLife; +import fr.quatrevieux.araknemu.game.fight.fighter.BaseFighterSpellList; +import fr.quatrevieux.araknemu.game.fight.fighter.FighterCharacteristics; +import fr.quatrevieux.araknemu.game.fight.fighter.FighterLife; +import fr.quatrevieux.araknemu.game.fight.fighter.FighterSpellList; +import fr.quatrevieux.araknemu.game.fight.fighter.PassiveFighter; +import fr.quatrevieux.araknemu.game.fight.fighter.monster.MonsterFighterSprite; +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 java.util.Optional; + +/** + * Fighter for invoked monster + * Its characteristics are modified by the invoker level + */ +public final class InvocationFighter extends AbstractFighter { + private final int id; + private final Monster monster; + private final FightTeam team; + private final BaseFighterLife life; + private final FighterCharacteristics characteristics; + private final MonsterFighterSprite sprite; + private final FighterSpellList spells; + private final PassiveFighter invoker; + + public InvocationFighter(int id, Monster monster, FightTeam team, PassiveFighter invoker) { + this.id = id; + this.monster = monster; + this.team = team; + this.invoker = invoker; + + this.life = new BaseFighterLife(this, Math.round(monster.life() * InvocationFighterCharacteristics.modifier(invoker))); + this.characteristics = new InvocationFighterCharacteristics(monster, this, invoker); + this.sprite = new MonsterFighterSprite(this, monster); + this.spells = new BaseFighterSpellList(monster.spells()); + } + + @Override + public O apply(O operation) { + operation.onInvocation(this); + + return operation; + } + + @Override + public @Positive int level() { + return monster.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 Sprite sprite() { + return sprite; + } + + @Override + public FighterSpellList spells() { + return spells; + } + + @Override + public FighterCharacteristics characteristics() { + return characteristics; + } + + @Override + public FighterLife life() { + return life; + } + + @Override + public Optional invoker() { + return Optional.of(invoker); + } + + /** + * Get the invoked monster + */ + public Monster monster() { + return monster; + } +} diff --git a/src/main/java/fr/quatrevieux/araknemu/game/fight/fighter/invocation/InvocationFighterCharacteristics.java b/src/main/java/fr/quatrevieux/araknemu/game/fight/fighter/invocation/InvocationFighterCharacteristics.java new file mode 100644 index 000000000..73aeb2e07 --- /dev/null +++ b/src/main/java/fr/quatrevieux/araknemu/game/fight/fighter/invocation/InvocationFighterCharacteristics.java @@ -0,0 +1,92 @@ +/* + * This file is part of Araknemu. + * + * Araknemu is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Araknemu is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Araknemu. If not, see . + * + * Copyright (c) 2017-2022 Vincent Quatrevieux + */ + +package fr.quatrevieux.araknemu.game.fight.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.PassiveFighter; +import fr.quatrevieux.araknemu.game.fight.fighter.event.FighterCharacteristicChanged; +import fr.quatrevieux.araknemu.game.monster.Monster; +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 an invocation + * Element characteristics (i.e. strength, intelligence, agility, luck) are altered by the invoker level, each level given 1% boost + */ +public final class InvocationFighterCharacteristics implements FighterCharacteristics { + private final Characteristics base; + private final Fighter fighter; + private final MutableCharacteristics boost; + private final float rate; + + public InvocationFighterCharacteristics(Monster monster, Fighter fighter, PassiveFighter invoker) { + this.base = monster.characteristics(); + this.fighter = fighter; + this.boost = new DefaultCharacteristics(); + rate = modifier(invoker); + } + + @Override + public int initiative() { + return 0; + } + + @Override + public int discernment() { + return 0; + } + + @Override + public void alter(Characteristic characteristic, int value) { + boost.add(characteristic, value); + fighter.dispatch(new FighterCharacteristicChanged(characteristic, value)); + } + + @Override + public Characteristics initial() { + return base; + } + + @Override + public int get(Characteristic characteristic) { + int value = base.get(characteristic); + + if (characteristic == Characteristic.STRENGTH + || characteristic == Characteristic.INTELLIGENCE + || characteristic == Characteristic.LUCK + || characteristic == Characteristic.AGILITY + ) { + value *= rate; + } + + return value + boost.get(characteristic); + } + + /** + * Get the characteristics modifier for the given invoke + * The value is always higher than 1 + */ + public static float modifier(PassiveFighter invoker) { + return 1 + invoker.level() / 100f; + } +} diff --git a/src/main/java/fr/quatrevieux/araknemu/game/fight/fighter/monster/InvocationFighter.java b/src/main/java/fr/quatrevieux/araknemu/game/fight/fighter/monster/InvocationFighter.java deleted file mode 100644 index ff5d51713..000000000 --- a/src/main/java/fr/quatrevieux/araknemu/game/fight/fighter/monster/InvocationFighter.java +++ /dev/null @@ -1,228 +0,0 @@ -/* - * This file is part of Araknemu. - * - * Araknemu is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Araknemu is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Araknemu. If not, see . - * - * Copyright (c) 2017-2021 Vincent Quatrevieux Jean-Alexandre Valentin - */ - -package fr.quatrevieux.araknemu.game.fight.fighter.monster; - -import java.util.Optional; -import java.util.function.Consumer; - -import fr.arakne.utils.maps.constant.Direction; -import fr.quatrevieux.araknemu.data.constant.Characteristic; -import fr.quatrevieux.araknemu.game.fight.Fight; -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.BaseFighterLife; -import fr.quatrevieux.araknemu.game.fight.fighter.Fighter; -import fr.quatrevieux.araknemu.game.fight.fighter.FighterCharacteristics; -import fr.quatrevieux.araknemu.game.fight.fighter.FighterLife; -import fr.quatrevieux.araknemu.game.fight.fighter.FighterSpellList; -import fr.quatrevieux.araknemu.game.fight.fighter.PassiveFighter; -import fr.quatrevieux.araknemu.game.fight.fighter.States; -import fr.quatrevieux.araknemu.game.fight.fighter.operation.FighterOperation; -import fr.quatrevieux.araknemu.game.fight.map.FightCell; -import fr.quatrevieux.araknemu.game.fight.team.FightTeam; -import fr.quatrevieux.araknemu.game.fight.turn.FightTurn; -import fr.quatrevieux.araknemu.game.spell.SpellList; -import fr.quatrevieux.araknemu.game.world.creature.Sprite; - -final public class InvocationFighter implements Fighter { - final private Fighter fighter; - final private PassiveFighter invoker; - final private FighterLife life; - - public InvocationFighter(Fighter fighter, PassiveFighter invoker) { - this.fighter = fighter; - this.invoker = invoker; - - float rate = 1 + ((Fighter)invoker).level() / 100 ; - int vitality = Math.round(fighter.life().max() * rate); - - this.life = new BaseFighterLife(fighter, vitality, vitality); - this.scaleCharacteristicsBasedOnInvokerLevel(rate); - } - - private void scaleCharacteristicsBasedOnInvokerLevel(float rate) { - int strength = fighter.characteristics().get(Characteristic.STRENGTH); - int wisdom = fighter.characteristics().get(Characteristic.WISDOM); - int luck = fighter.characteristics().get(Characteristic.LUCK); - int agility = fighter.characteristics().get(Characteristic.AGILITY); - - fighter.characteristics().alter(Characteristic.STRENGTH, Math.round(strength * rate)); - fighter.characteristics().alter(Characteristic.WISDOM, Math.round(wisdom * rate)); - fighter.characteristics().alter(Characteristic.LUCK, Math.round(luck * rate)); - fighter.characteristics().alter(Characteristic.AGILITY, Math.round(agility * rate)); - } - - @Override - public O apply(O operation) { - operation.onInvocation(this); - - return operation; - } - - @Override - public int level() { - return fighter.level(); - } - - @Override - public boolean ready() { - return fighter.ready(); - } - - @Override - public FightTeam team() { - return fighter.team(); - } - - @Override - public CastableWeapon weapon() { - return fighter.weapon(); - } - - @Override - public int id() { - return fighter.id(); - } - - @Override - public Sprite sprite() { - return fighter.sprite(); - } - - @Override - public FighterSpellList spells() { - return fighter.spells(); - } - - @Override - public FighterCharacteristics characteristics() { - return fighter.characteristics(); - } - - @Override - public FighterLife life() { - return life; - } - - @Override - public Optional invoker() { - return Optional.of(invoker); - } - - @Override - public void attach(Object key, Object value) { - fighter.attach(key, value); - } - - @Override - public Fight fight() { - return fighter.fight(); - } - - @Override - public void init() { - fighter.init(); - } - - @Override - public boolean isOnFight() { - return fighter.isOnFight(); - } - - @Override - public void joinFight(Fight fight, FightCell startCell) { - fighter.joinFight(fight, startCell); - } - - @Override - public void play(FightTurn turn) { - fighter.play(turn); - } - - @Override - public void setOrientation(Direction orientation) { - fighter.setOrientation(orientation); - } - - @Override - public void stop() { - fighter.stop(); - } - - @Override - public FightCell cell() { - return fighter.cell(); - } - - @Override - public Direction orientation() { - return fighter.orientation(); - } - - @Override - public void dispatch(Object event) { - fighter.dispatch(event); - } - - @Override - public Object attachment(Object key) { - return fighter.attachment(key); - } - - @Override - public void move(FightCell cell) { - fighter.move(cell); - } - - @Override - public States states() { - return fighter.states(); - } - - @Override - public FightTurn turn() { - return fighter.turn(); - } - - @Override - public BuffList buffs() { - return fighter.buffs(); - } - - @Override - public void perform(Consumer action) { - fighter.perform(action); - } - - @Override - public boolean isPlaying() { - return fighter.isPlaying(); - } - - @Override - public boolean hidden() { - return fighter.hidden(); - } - - @Override - public void setHidden(PassiveFighter caster, boolean hidden) { - fighter.setHidden(caster, hidden); - } -} diff --git a/src/main/java/fr/quatrevieux/araknemu/game/fight/fighter/monster/InvocationFighterFactory.java b/src/main/java/fr/quatrevieux/araknemu/game/fight/fighter/monster/InvocationFighterFactory.java deleted file mode 100644 index 0b404df73..000000000 --- a/src/main/java/fr/quatrevieux/araknemu/game/fight/fighter/monster/InvocationFighterFactory.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * This file is part of Araknemu. - * - * Araknemu is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Araknemu is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Araknemu. If not, see . - * - * Copyright (c) 2017-2021 Vincent Quatrevieux Jean-Alexandre Valentin - */ - -package fr.quatrevieux.araknemu.game.fight.fighter.monster; - -import fr.quatrevieux.araknemu.game.fight.fighter.Fighter; - -@FunctionalInterface -public interface InvocationFighterFactory { - - public Fighter create(int id); -} 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 a26dbe19e..77059e366 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 @@ -130,6 +130,7 @@ public Monster monster() { return monster; } + @Override public Optional invoker() { return Optional.empty(); } 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 2f0eb876f..202244b79 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,6 +21,7 @@ import fr.arakne.utils.maps.constant.Direction; import fr.quatrevieux.araknemu.data.constant.Characteristic; +import fr.quatrevieux.araknemu.game.fight.fighter.Fighter; import fr.quatrevieux.araknemu.game.monster.Monster; import fr.quatrevieux.araknemu.game.world.creature.Sprite; @@ -32,10 +33,10 @@ * https://github.com/Emudofus/Dofus/blob/1.29/dofus/aks/Game.as#L520 */ public final class MonsterFighterSprite implements Sprite { - private final MonsterFighter fighter; + private final Fighter fighter; private final Monster monster; - public MonsterFighterSprite(MonsterFighter fighter, Monster monster) { + public MonsterFighterSprite(Fighter fighter, Monster monster) { this.fighter = fighter; this.monster = 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 a902e5313..170ac9ba4 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,7 +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.monster.InvocationFighter; +import fr.quatrevieux.araknemu.game.fight.fighter.invocation.InvocationFighter; import fr.quatrevieux.araknemu.game.fight.fighter.monster.MonsterFighter; import fr.quatrevieux.araknemu.game.fight.fighter.player.PlayerFighter; @@ -48,7 +48,7 @@ public default void onMonster(MonsterFighter fighter) { /** * Apply the operation to an InvocationFighter */ - default public void onInvocation(InvocationFighter fighter) { + public default void onInvocation(InvocationFighter fighter) { onGenericFighter(fighter); } 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 2cbb0ae22..7d858ff93 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 @@ -23,24 +23,35 @@ 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.MonsterInvocationHandler; -import fr.quatrevieux.araknemu.game.fight.event.FightStopped; 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.event.FighterDie; import fr.quatrevieux.araknemu.game.monster.MonsterService; -public class MonsterInvocationModule implements FightModule { - final private MonsterService monsterService; - final private Fight fight; +import java.util.List; +import java.util.stream.Collectors; - public MonsterInvocationModule(MonsterService monsterService, Fight fight) { +/** + * Module for enable invocation effects + * + * @see MonsterInvocationHandler + */ +public final class MonsterInvocationModule implements FightModule { + private final MonsterService monsterService; + private final FighterFactory fighterFactory; + private final Fight fight; + + public MonsterInvocationModule(MonsterService monsterService, FighterFactory fighterFactory, Fight fight) { this.monsterService = monsterService; + this.fighterFactory = fighterFactory; this.fight = fight; } @Override public void effects(EffectsHandler handler) { // moving creatures - handler.register(181, new MonsterInvocationHandler(monsterService, fight)); + handler.register(181, new MonsterInvocationHandler(monsterService, fighterFactory, fight)); } @Override @@ -49,33 +60,27 @@ public Listener[] listeners() { new Listener() { @Override public void on(FighterDie event) { - fight.fighters().forEach(fighter -> { - if (fighter.invoker().isPresent() && fighter.invoker().get().equals(event.fighter())) { - fighter.life().kill((ActiveFighter) event.fighter()); - fight.turnList().remove(fighter); - } - }); - } + // Remove all invocations of the fighter + // Make a copy to ensure that no concurrent modification occur + final List invocations = fight.fighters().stream() + .filter(fighter -> fighter.invoker().isPresent() && fighter.invoker().get().equals(event.fighter())) + .collect(Collectors.toList()) + ; - @Override - public Class event() { - return FighterDie.class; - } - }, + if (!invocations.isEmpty()) { + // Kill all invocations asynchronously + fight.execute(() -> invocations.forEach(fighter -> fighter.life().kill((ActiveFighter) event.caster()))); + } - new Listener() { - @Override - public void on(FightStopped event) { - event.fight().fighters().forEach(fighter -> { - if (fighter.invoker().isPresent()) { - fighter.team().kick(fighter); - } - }); + // If the creature is an invocation, delete from turn list + if (event.fighter().invoker().isPresent()) { + fight.turnList().remove((Fighter) event.fighter()); + } } @Override - public Class event() { - return FightStopped.class; + public Class event() { + return FighterDie.class; } }, }; diff --git a/src/main/java/fr/quatrevieux/araknemu/game/fight/team/MonsterGroupTeam.java b/src/main/java/fr/quatrevieux/araknemu/game/fight/team/MonsterGroupTeam.java index 41c1f4ff7..9c2cdb98a 100644 --- a/src/main/java/fr/quatrevieux/araknemu/game/fight/team/MonsterGroupTeam.java +++ b/src/main/java/fr/quatrevieux/araknemu/game/fight/team/MonsterGroupTeam.java @@ -24,6 +24,7 @@ import fr.quatrevieux.araknemu.game.fight.JoinFightError; import fr.quatrevieux.araknemu.game.fight.exception.JoinFightException; import fr.quatrevieux.araknemu.game.fight.fighter.Fighter; +import fr.quatrevieux.araknemu.game.fight.fighter.FighterFactory; import fr.quatrevieux.araknemu.game.fight.fighter.monster.MonsterFighter; import fr.quatrevieux.araknemu.game.fight.map.FightCell; import fr.quatrevieux.araknemu.game.monster.Monster; @@ -50,12 +51,12 @@ public final class MonsterGroupTeam implements FightTeam { private final TeamOptions options; @SuppressWarnings({"assignment", "argument"}) - public MonsterGroupTeam(MonsterGroup monsterGroup, List startPlaces, int number) { + public MonsterGroupTeam(MonsterGroup monsterGroup, List startPlaces, int number, FighterFactory fighterFactory) { this.monsterGroup = monsterGroup; this.number = number; this.startPlaces = startPlaces; - this.fighters = makeFighters(this, monsterGroup.monsters()); + this.fighters = makeFighters(this, monsterGroup.monsters(), fighterFactory); this.options = new DefaultTeamOptions(this); } @@ -116,20 +117,12 @@ public TeamOptions options() { @Override public void join(Fighter fighter) throws JoinFightException { - if (!fighter.invoker().isPresent()) { - throw new JoinFightException(JoinFightError.TEAM_CLOSED); - } - - fighters.add(fighter); + throw new JoinFightException(JoinFightError.TEAM_CLOSED); } @Override public void kick(Fighter fighter) { - if (!fighter.invoker().isPresent()) { - throw new UnsupportedOperationException("Read-only team"); - } - - fighters.remove(fighter); + throw new UnsupportedOperationException("Read-only team"); } @Override @@ -148,13 +141,11 @@ public MonsterGroup group() { * Creates fighters from monsters of the group * Ids of monsters are negative integer sequence (starting at -1 for the first monster) */ - private static List makeFighters(MonsterGroupTeam team, List monsters) { + private static List makeFighters(MonsterGroupTeam team, List monsters, FighterFactory fighterFactory) { final List fighters = new ArrayList<>(monsters.size()); - int id = 0; - for (Monster monster : monsters) { - fighters.add(new MonsterFighter(--id, monster, team)); + fighters.add(fighterFactory.create(monster, team)); } return fighters; diff --git a/src/main/java/fr/quatrevieux/araknemu/game/fight/team/SimpleTeam.java b/src/main/java/fr/quatrevieux/araknemu/game/fight/team/SimpleTeam.java index 225720e2c..aacc17f3e 100644 --- a/src/main/java/fr/quatrevieux/araknemu/game/fight/team/SimpleTeam.java +++ b/src/main/java/fr/quatrevieux/araknemu/game/fight/team/SimpleTeam.java @@ -24,8 +24,8 @@ import fr.quatrevieux.araknemu.game.fight.JoinFightError; import fr.quatrevieux.araknemu.game.fight.exception.JoinFightException; import fr.quatrevieux.araknemu.game.fight.fighter.Fighter; -import fr.quatrevieux.araknemu.game.fight.fighter.monster.InvocationFighter; import fr.quatrevieux.araknemu.game.fight.fighter.operation.FighterOperation; +import fr.quatrevieux.araknemu.game.fight.fighter.operation.SendPacket; import fr.quatrevieux.araknemu.game.fight.fighter.player.PlayerFighter; import fr.quatrevieux.araknemu.game.fight.map.FightCell; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; @@ -99,14 +99,7 @@ public Collection fighters() { @Override public void send(Object packet) { - fighters.forEach(fighter -> { - fighter.apply(new FighterOperation(){ - @Override - public void onPlayer(PlayerFighter player) { - player.send(packet); - } - }); - }); + fighters.forEach(fighter -> fighter.apply(new SendPacket(packet))); } @Override @@ -125,7 +118,6 @@ public ConfigurableTeamOptions options() { @Override public void join(Fighter fighter) throws JoinFightException { - // @todo ne pas faire pour une invocation if (options != null && !options.allowJoinTeam()) { throw new JoinFightException(JoinFightError.TEAM_CLOSED); } @@ -145,15 +137,6 @@ public void onPlayer(PlayerFighter fighter) { public void onGenericFighter(Fighter fighter) { throw new JoinFightException(JoinFightError.TEAM_CLOSED); } - - @Override - public void onInvocation(InvocationFighter fighter) { - if (!fighter.invoker().isPresent()) { - throw new JoinFightException(JoinFightError.TEAM_CLOSED); - } - - fighters.add(fighter); - } }); } diff --git a/src/main/java/fr/quatrevieux/araknemu/game/fight/turn/FightTurnList.java b/src/main/java/fr/quatrevieux/araknemu/game/fight/turn/FightTurnList.java index 2cb3efd88..f40863cf1 100644 --- a/src/main/java/fr/quatrevieux/araknemu/game/fight/turn/FightTurnList.java +++ b/src/main/java/fr/quatrevieux/araknemu/game/fight/turn/FightTurnList.java @@ -51,6 +51,8 @@ public FightTurnList(Fight fight) { /** * Initialise the fighters order + * + * @todo remove: initialize in constructor */ public void init(FighterOrderStrategy orderStrategy) { if (fighters != null) { @@ -81,6 +83,8 @@ public List fighters() { * Remove a fighter from turn list * * @param fighter Fighter to remove + * + * @see TurnListChanged Event triggered after the list is updated */ public void remove(Fighter fighter) { if (fighters == null) { @@ -109,13 +113,19 @@ public void remove(Fighter fighter) { fight.dispatch(new TurnListChanged(this)); } + /** + * Add a fighter after the current one + * + * @param fighter Fighter to add + * + * @see TurnListChanged Event triggered after the list is updated + */ public void add(Fighter fighter) { - if(!active.get()) { - return; + if (fighters == null) { + throw new IllegalStateException("FightTurnList must be initialised"); } fighters.add(index + 1, fighter); - fight.dispatch(new TurnListChanged(this)); } @@ -160,6 +170,13 @@ public void stop() { } } + /** + * @todo remove when init is done on constructor + */ + public boolean isInitialized() { + return fighters != null; + } + /** * Stop the current turn and start the next * diff --git a/src/main/java/fr/quatrevieux/araknemu/game/spell/effect/target/SpellEffectTarget.java b/src/main/java/fr/quatrevieux/araknemu/game/spell/effect/target/SpellEffectTarget.java index 171000992..d2003a8fb 100644 --- a/src/main/java/fr/quatrevieux/araknemu/game/spell/effect/target/SpellEffectTarget.java +++ b/src/main/java/fr/quatrevieux/araknemu/game/spell/effect/target/SpellEffectTarget.java @@ -21,6 +21,7 @@ import fr.quatrevieux.araknemu.game.fight.fighter.ActiveFighter; import fr.quatrevieux.araknemu.game.fight.fighter.PassiveFighter; +import fr.quatrevieux.araknemu.game.fight.team.Team; import org.checkerframework.checker.nullness.qual.Nullable; import java.util.Objects; @@ -51,23 +52,10 @@ public boolean onlyCaster() { @Override public boolean test(ActiveFighter caster, PassiveFighter fighter) { - if (check(NOT_TEAM) && caster.team().equals(fighter.team())) { - return false; - } - - if (check(NOT_SELF) && caster.equals(fighter)) { - return false; - } - - if (check(NOT_ENEMY) && !caster.team().equals(fighter.team())) { - return false; - } - - if (check(ONLY_INVOC)) { - return false; - } - - return true; + return checkSelf(caster, fighter) + && checkTeam(caster.team(), fighter.team()) + && checkInvocation(fighter) + ; } private boolean check(int flag) { @@ -91,4 +79,34 @@ public boolean equals(@Nullable Object obj) { public int hashCode() { return Objects.hash(flags); } + + private boolean checkTeam(Team casterTeam, Team targetTeam) { + if (check(NOT_TEAM) && casterTeam.equals(targetTeam)) { + return false; + } + + if (check(NOT_ENEMY) && !casterTeam.equals(targetTeam)) { + return false; + } + + return true; + } + + private boolean checkInvocation(PassiveFighter target) { + final boolean isInvocation = target.invoker().isPresent(); + + if (check(ONLY_INVOC) && !isInvocation) { + return false; + } + + if (check(NOT_INVOC) && isInvocation) { + return false; + } + + return true; + } + + private boolean checkSelf(ActiveFighter caster, PassiveFighter target) { + return !check(NOT_SELF) || !caster.equals(target); + } } 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 da51fa244..281192e77 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 @@ -342,4 +342,24 @@ public static ActionEffect fighterHidden(PassiveFighter caster, PassiveFighter t public static ActionEffect fighterVisible(PassiveFighter caster, PassiveFighter target) { return new ActionEffect(150, caster, target.id(), 0); } + + /** + * Add an invoked creature to the fight + * + * @param caster Invoker + * @param invocation Invocation to add + */ + public static ActionEffect addInvocation(PassiveFighter caster, PassiveFighter invocation) { + return new ActionEffect(181, caster, "+" + invocation.sprite()); + } + + /** + * Send a custom packet, but queued on the caster sequencer + * + * @param caster Effect caster + * @param packet Packet to send + */ + public static ActionEffect packet(PassiveFighter caster, Object packet) { + return new ActionEffect(999, caster, packet); + } } diff --git a/src/test/java/fr/quatrevieux/araknemu/game/GameDataSet.java b/src/test/java/fr/quatrevieux/araknemu/game/GameDataSet.java index 298793aa0..d22523dca 100644 --- a/src/test/java/fr/quatrevieux/araknemu/game/GameDataSet.java +++ b/src/test/java/fr/quatrevieux/araknemu/game/GameDataSet.java @@ -525,6 +525,7 @@ public GameDataSet pushFunctionalSpells() throws SQLException, ContainerExceptio "(64, 'Repérage', 0, '10,1,1', '202,,,,0,0;117,1,,,2,0,0d0+1|202,,,,0,0;117,2,,,2,0,0d0+2|2|0|1|30|100|false|true|false|true|0|0|0|7|CgPaCgPa||18;19;3;1;41|17|false', '202,,,,0,0;117,1,,,2,0,0d0+1|202,,,,0,0;117,2,,,2,0,0d0+2|2|0|2|30|100|false|true|false|true|0|0|0|6|CgPaCgPa||18;19;3;1;41|17|false', '202,,,,0,0;117,1,,,2,0,0d0+1|202,,,,0,0;117,2,,,2,0,0d0+2|2|0|3|30|100|false|true|false|true|0|0|0|5|CgPaCgPa||18;19;3;1;41|17|false', '202,,,,0,0;117,1,,,2,0,0d0+1|202,,,,0,0;117,2,,,2,0,0d0+2|2|0|4|30|100|false|true|false|true|0|0|0|4|CgPaCgPa||18;19;3;1;41|17|false', '202,,,,0,0;117,1,,,2,0,0d0+1|202,,,,0,0;117,2,,,2,0,0d0+2|2|0|5|30|100|false|true|false|true|0|0|0|3|CgPaCgPa||18;19;3;1;41|17|false', '202,,,,0,0;117,1,,,2,0,0d0+1|202,,,,0,0;117,2,,,2,0,0d0+2|2|0|6|30|100|false|true|false|true|0|0|0|2|CiPaCiPa||18;19;3;1;41|117|false', '3;32')", "(130, 'Mot Revitalisant', 703, '11,1,1', '108,1,5,,0,0,1d5+0|108,6,,,0,0,0d0+6|3|0|4|50|100|false|true|false|true|0|3|0|0|CcCc||50;18;19;3;1;41|21|false', '108,1,6,,0,0,1d6+0|108,7,,,0,0,0d0+7|3|0|4|50|100|false|true|false|true|0|3|0|0|CcCc||50;18;19;3;1;41|21|false', '108,1,7,,0,0,1d7+0|108,8,,,0,0,0d0+8|3|0|4|50|100|false|true|false|true|0|3|0|0|CdCd||50;18;19;3;1;41|21|false', '108,1,8,,0,0,1d8+0|108,9,,,0,0,0d0+9|3|0|4|50|100|false|true|false|true|0|3|0|0|CdCd||50;18;19;3;1;41|21|false', '108,1,9,,0,0,1d9+0|108,10,,,0,0,0d0+10|2|0|4|50|100|false|true|false|true|0|3|0|0|CdCd||50;18;19;3;1;41|21|false', '108,7,11,,0,0,1d5+6|108,12,,,0,0,0d0+12|2|0|4|50|100|false|true|false|true|0|3|0|0|CdCd||50;18;19;3;1;41|121|false', '')", "(102, 'Pile ou Face', 601, '31,1,1', '97,4,11,,0,0,1d8+3;108,1,7,,0,0,1d7+0;108,6,10,,0,0,1d5+5|97,4,12,,0,0,1d9+3;108,1,3,,0,0,1d3+0;108,11,15,,0,0,1d5+10|3|0|5|50|100|false|true|false|true|0|0|0|0|PaPaPaPaPaPa||18;19;3;1;41|1|false', '97,4,12,,0,0,1d9+3;108,2,9,,0,0,1d8+1;108,6,12,,0,0,1d7+5|97,4,13,,0,0,1d10+3;108,1,4,,0,0,1d4+0;108,13,17,,0,0,1d5+12|3|0|5|50|100|false|true|false|true|0|0|0|0|PaPaPaPaPaPa||18;19;3;1;41|1|false', '97,4,13,,0,0,1d10+3;108,2,9,,0,0,1d8+1;108,6,14,,0,0,1d9+5|97,4,14,,0,0,1d11+3;108,1,4,,0,0,1d4+0;108,15,19,,0,0,1d5+14|3|0|6|50|100|false|true|false|true|0|0|0|0|PaPaPaPaPaPa||18;19;3;1;41|1|false', '97,4,14,,0,0,1d11+3;108,3,10,,0,0,1d8+2;108,6,16,,0,0,1d11+5|97,4,15,,0,0,1d12+3;108,1,5,,0,0,1d5+0;108,17,21,,0,0,1d5+16|3|0|6|50|100|false|true|false|true|0|0|0|0|PaPaPaPaPaPa||18;19;3;1;41|1|false', '97,4,15,,0,0,1d12+3;108,4,11,,0,0,1d8+3;108,6,18,,0,0,1d13+5|97,4,17,,0,0,1d14+3;108,1,5,,0,0,1d5+0;108,19,23,,0,0,1d5+18|3|0|7|50|100|false|true|false|true|0|0|0|0|PaPaPaPaPaPa||18;19;3;1;41|1|false', '97,6,19,,0,0,1d14+5;108,5,12,,0,0,1d8+4;108,8,22,,0,0,1d15+7|97,9,24,,0,0,1d16+8;108,1,5,,0,0,1d5+0;108,23,27,,0,0,1d5+22|3|0|7|50|100|false|true|false|true|0|0|0|0|PaPaPaPaPaPa||18;19;3;1;41|101|false', '0;1;4')", + "(35, 'Invocation de Bouftou', 1100, '11,1,1', '181,36,1,,0,0||6|1|1|0|0|true|true|true|false|0|0|0|5|Pa||18;19;3;1;41|9|false', '181,36,2,,0,0||6|1|1|0|0|true|true|true|false|0|0|0|5|Pa||18;19;3;1;41|9|true', '181,36,3,,0,0||6|1|1|0|0|true|true|true|false|0|0|0|5|Pa||18;19;3;1;41|9|true', '181,36,4,,0,0||6|1|1|0|0|true|true|true|false|0|0|0|4|Pa||18;19;3;1;41|9|true', '181,36,5,,0,0||6|1|1|0|0|true|true|true|false|0|0|0|3|Pa||18;19;3;1;41|9|true', '181,36,6,,0,0||4|1|1|0|0|true|true|true|false|0|0|0|2|Pa||18;19;3;1;41|109|false', '')", }, ",") + ";" ); @@ -923,7 +924,7 @@ public GameDataSet pushMonsterTemplateInvocations() throws SQLException, Contain "(36, 'Bouftou', 1566, '-1,-1,-1', 'AGGRESSIVE', '1@v:p;1f:-c;17:6;1b:-1i;s:f;t:f;a:2g;c:1s;f:2g;d:2g;e:26;8:5;9:3;|2@v:u;1f:-a;17:7;1b:-1d;s:g;t:g;a:2l;c:21;f:2l;d:2l;e:2b;8:5;9:3;|3@v:13;1f:-9;17:8;1b:-18;s:h;t:h;a:2q;c:26;f:2q;d:2q;e:2b;8:5;9:3;|4@v:18;1f:-8;17:9;1b:-13;s:i;t:i;a:2v;c:2l;f:2v;d:2v;e:2b;8:5;9:3;|5@v:1d;1f:-7;17:a;1b:-u;s:k;t:k;a:34;c:2q;f:34;d:34;e:2g;8:5;9:4;|6@v:1i;1f:-6;17:c;1b:-p;s:p;t:p;a:4m;c:4m;f:4m;d:7q;e:7q;8:6;9:4;', '30|40|50|60|70|140', '12|15|20|21|23|25', '2000@1;202@1;1709@1|2000@2;202@2;1709@2|2000@3;202@3;1709@3|2000@4;202@4;1709@4|2000@5;202@5;1709@5|2000@6;1709@6')" ); - use(MonsterRewardData.class); + use(MonsterRewardData.class, MonsterRewardItem.class); connection.query( "INSERT INTO `MONSTER_REWARD` (`MONSTER_ID`, `MIN_KAMAS`, `MAX_KAMAS`, `EXPERIENCES`) VALUES " + diff --git a/src/test/java/fr/quatrevieux/araknemu/game/fight/FightBaseCase.java b/src/test/java/fr/quatrevieux/araknemu/game/fight/FightBaseCase.java index 0c60963b0..87893ab6a 100644 --- a/src/test/java/fr/quatrevieux/araknemu/game/fight/FightBaseCase.java +++ b/src/test/java/fr/quatrevieux/araknemu/game/fight/FightBaseCase.java @@ -225,7 +225,8 @@ public FightTeam createMonsterTeam(FightMap map) throws ContainerException, SQLE new Position(0, 0) ), Arrays.asList(map.get(125), map.get(126), map.get(127)), - 1 + 1, + container.get(FighterFactory.class) ); } diff --git a/src/test/java/fr/quatrevieux/araknemu/game/fight/FightTest.java b/src/test/java/fr/quatrevieux/araknemu/game/fight/FightTest.java index 924045840..4be80913e 100644 --- a/src/test/java/fr/quatrevieux/araknemu/game/fight/FightTest.java +++ b/src/test/java/fr/quatrevieux/araknemu/game/fight/FightTest.java @@ -29,6 +29,8 @@ import fr.quatrevieux.araknemu.game.fight.event.FightStarted; import fr.quatrevieux.araknemu.game.fight.event.FightStopped; import fr.quatrevieux.araknemu.game.fight.exception.InvalidFightStateException; +import fr.quatrevieux.araknemu.game.fight.fighter.Fighter; +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.FightMap; import fr.quatrevieux.araknemu.game.fight.module.FightModule; @@ -41,6 +43,7 @@ import fr.quatrevieux.araknemu.game.fight.turn.action.factory.FightActionsFactoryRegistry; import fr.quatrevieux.araknemu.game.fight.turn.order.AlternateTeamFighterOrder; import fr.quatrevieux.araknemu.game.fight.type.ChallengeType; +import fr.quatrevieux.araknemu.game.monster.MonsterService; import fr.quatrevieux.araknemu.network.game.GameSession; import fr.quatrevieux.araknemu.util.ExecutorFactory; import io.github.artsok.RepeatedIfExceptionsTest; @@ -132,6 +135,10 @@ void fighters() { new PlacementState().start(fight); assertEquals(Arrays.asList(fighter1, fighter2), fight.fighters(true)); + + fight.turnList().init(new AlternateTeamFighterOrder()); + assertEquals(Arrays.asList(fighter1, fighter2), fight.fighters()); + assertEquals(Arrays.asList(fighter1, fighter2), fight.fighters(false)); } @Test @@ -401,4 +408,35 @@ class Foo {} assertEquals(3, ai.get()); } + + @Test + void dispatchToAllWithInitializedTurnList() throws SQLException { + dataSet + .pushMonsterSpellsInvocations() + .pushMonsterTemplateInvocations() + ; + + class Foo {} + AtomicInteger ai = new AtomicInteger(); + + Spectator spectator = new Spectator(makeSimpleGamePlayer(10), fight); + spectator.join(); + + new PlacementState().start(fight); // Init cell to ensure that Fighter#isOnFight() is true + fight.turnList().init(new AlternateTeamFighterOrder()); + + InvocationFighter invoc = new InvocationFighter(-5, container.get(MonsterService.class).load(36).get(1), fighter1.team(), fighter1); + fight.turnList().add(invoc); + invoc.joinFight(fight, fight.map().get(122)); + invoc.init(); + + fighter1.dispatcher().add(Foo.class, foo -> ai.incrementAndGet()); + fighter2.dispatcher().add(Foo.class, foo -> ai.incrementAndGet()); + invoc.dispatcher().add(Foo.class, foo -> ai.incrementAndGet()); + spectator.dispatcher().add(Foo.class, foo -> ai.incrementAndGet()); + + fight.dispatchToAll(new Foo()); + + assertEquals(4, ai.get()); + } } diff --git a/src/test/java/fr/quatrevieux/araknemu/game/fight/ai/factory/MonsterAiFactoryTest.java b/src/test/java/fr/quatrevieux/araknemu/game/fight/ai/factory/MonsterAiFactoryTest.java index f754b0126..7580229c2 100644 --- a/src/test/java/fr/quatrevieux/araknemu/game/fight/ai/factory/MonsterAiFactoryTest.java +++ b/src/test/java/fr/quatrevieux/araknemu/game/fight/ai/factory/MonsterAiFactoryTest.java @@ -23,10 +23,16 @@ import fr.quatrevieux.araknemu.game.fight.FightBaseCase; import fr.quatrevieux.araknemu.game.fight.ai.factory.type.Aggressive; import fr.quatrevieux.araknemu.game.fight.ai.simulation.Simulator; +import fr.quatrevieux.araknemu.game.fight.fighter.Fighter; +import fr.quatrevieux.araknemu.game.fight.fighter.FighterFactory; +import fr.quatrevieux.araknemu.game.fight.fighter.invocation.InvocationFighter; import fr.quatrevieux.araknemu.game.fight.module.AiModule; +import fr.quatrevieux.araknemu.game.monster.MonsterService; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import java.sql.SQLException; + import static org.junit.jupiter.api.Assertions.*; class MonsterAiFactoryTest extends FightBaseCase { @@ -53,6 +59,22 @@ void createSuccess() { assertTrue(factory.create(fight.team(1).fighters().stream().findFirst().get()).isPresent()); } + @Test + void createSuccessWithInvocation() throws SQLException { + dataSet.pushMonsterTemplateInvocations(); + + Fighter fighter = new InvocationFighter( + -1, + container.get(MonsterService.class).load(36).get(3), + fight.team(1), + fight.fighters().get(0) + ); + + fighter.joinFight(fight, fight.map().get(123)); + + assertTrue(factory.create(fighter).isPresent()); + } + @Test void createInvalidAiType() { factory = new MonsterAiFactory(); diff --git a/src/test/java/fr/quatrevieux/araknemu/game/fight/ai/proxy/ProxyAITest.java b/src/test/java/fr/quatrevieux/araknemu/game/fight/ai/proxy/ProxyAITest.java index cf1d93b98..46878d120 100644 --- a/src/test/java/fr/quatrevieux/araknemu/game/fight/ai/proxy/ProxyAITest.java +++ b/src/test/java/fr/quatrevieux/araknemu/game/fight/ai/proxy/ProxyAITest.java @@ -61,7 +61,7 @@ void baseProxy() { assertSame(proxy.fighter(), fighters.get(0)); assertInstanceOf(ProxyPassiveFighter.class, fighters.get(1)); assertInstanceOf(ProxyPassiveFighter.class, fighters.get(2)); - assertSame(proxy.enemy().get(), fighters.get(2)); + assertSame(proxy.enemy().get(), fighters.get(1)); assertSame(proxy.fighter().cell(), ai.fighter().cell()); assertSame(ai.map().get(fighters.get(1).cell().id()), fighters.get(1).cell()); @@ -101,7 +101,7 @@ void withPosition() { assertSame(proxy.fighter(), fighters.get(0)); assertInstanceOf(ProxyPassiveFighter.class, fighters.get(1)); assertInstanceOf(ProxyPassiveFighter.class, fighters.get(2)); - assertSame(proxy.enemy().get(), fighters.get(2)); + assertSame(proxy.enemy().get(), fighters.get(1)); assertNotSame(proxy.fighter().cell(), ai.fighter().cell()); assertNotSame(ai.map().get(fighters.get(1).cell().id()), fighters.get(1).cell()); @@ -110,11 +110,11 @@ void withPosition() { assertEquals(123, proxy.fighter().cell().id()); assertSame(proxy.map().get(123), proxy.fighter().cell()); assertSame(proxy.fighter(), proxy.fighter().cell().fighter().get()); - assertEquals(166, fighters.get(1).cell().id()); - assertSame(proxy.map().get(166), fighters.get(1).cell()); + assertEquals(167, fighters.get(1).cell().id()); + assertSame(proxy.map().get(167), fighters.get(1).cell()); assertSame(fighters.get(1), fighters.get(1).cell().fighter().get()); - assertEquals(167, fighters.get(2).cell().id()); - assertSame(proxy.map().get(167), fighters.get(2).cell()); + assertEquals(166, fighters.get(2).cell().id()); + assertSame(proxy.map().get(166), fighters.get(2).cell()); assertSame(fighters.get(2), fighters.get(2).cell().fighter().get()); assertFalse(proxy.map().get(152).fighter().isPresent()); @@ -128,11 +128,11 @@ void withPosition() { assertEquals(125, proxy.fighter().cell().id()); assertSame(proxy.map().get(125), proxy.fighter().cell()); assertSame(proxy.fighter(), proxy.fighter().cell().fighter().get()); - assertEquals(166, fighters.get(1).cell().id()); - assertSame(proxy.map().get(166), fighters.get(1).cell()); + assertEquals(167, fighters.get(1).cell().id()); + assertSame(proxy.map().get(167), fighters.get(1).cell()); assertSame(fighters.get(1), fighters.get(1).cell().fighter().get()); - assertEquals(167, fighters.get(2).cell().id()); - assertSame(proxy.map().get(167), fighters.get(2).cell()); + assertEquals(166, fighters.get(2).cell().id()); + assertSame(proxy.map().get(166), fighters.get(2).cell()); assertSame(fighters.get(2), fighters.get(2).cell().fighter().get()); assertFalse(proxy.map().get(123).fighter().isPresent()); diff --git a/src/test/java/fr/quatrevieux/araknemu/game/fight/ai/proxy/ProxyBattlefieldTest.java b/src/test/java/fr/quatrevieux/araknemu/game/fight/ai/proxy/ProxyBattlefieldTest.java index 01e081718..6df012408 100644 --- a/src/test/java/fr/quatrevieux/araknemu/game/fight/ai/proxy/ProxyBattlefieldTest.java +++ b/src/test/java/fr/quatrevieux/araknemu/game/fight/ai/proxy/ProxyBattlefieldTest.java @@ -77,7 +77,7 @@ void modifyWithFreeCell() { assertTrue(modified.get(152).walkable()); assertFalse(modified.get(152).sightBlocking()); - assertSame(ai.fighters().collect(Collectors.toList()).get(1), modified.get(166).fighter().get()); + assertSame(ai.fighters().collect(Collectors.toList()).get(2), modified.get(166).fighter().get()); assertTrue(modified.get(166).walkableIgnoreFighter()); assertFalse(modified.get(166).walkable()); assertTrue(modified.get(166).sightBlocking()); diff --git a/src/test/java/fr/quatrevieux/araknemu/game/fight/ai/util/AIHelperTest.java b/src/test/java/fr/quatrevieux/araknemu/game/fight/ai/util/AIHelperTest.java index 8a59efa96..0953682d0 100644 --- a/src/test/java/fr/quatrevieux/araknemu/game/fight/ai/util/AIHelperTest.java +++ b/src/test/java/fr/quatrevieux/araknemu/game/fight/ai/util/AIHelperTest.java @@ -183,7 +183,7 @@ void alliesShouldIgnoreSelf() { ); assertEquals(2, ai.helper().allies().count()); - assertArrayEquals(new int [] {135, 150}, ai.helper().allies().cells().mapToInt(MapCell::id).toArray()); + assertArrayEquals(new int [] {135, 150}, ai.helper().allies().cells().mapToInt(MapCell::id).sorted().toArray()); } @Test 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 a1a24c2ae..d5e5e442f 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 @@ -22,35 +22,49 @@ 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.effect.buff.Buff; import fr.quatrevieux.araknemu.game.fight.castable.spell.SpellConstraintsValidator; 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.PassiveFighter; +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.FightCell; +import fr.quatrevieux.araknemu.game.fight.module.AiModule; import fr.quatrevieux.araknemu.game.fight.module.CommonEffectsModule; import fr.quatrevieux.araknemu.game.fight.module.IndirectSpellApplyEffectsModule; +import fr.quatrevieux.araknemu.game.fight.module.MonsterInvocationModule; import fr.quatrevieux.araknemu.game.fight.state.PlacementState; import fr.quatrevieux.araknemu.game.fight.turn.FightTurn; import fr.quatrevieux.araknemu.game.fight.turn.action.cast.Cast; import fr.quatrevieux.araknemu.game.fight.turn.action.cast.CastSuccess; import fr.quatrevieux.araknemu.game.fight.turn.action.util.CriticalityStrategy; +import fr.quatrevieux.araknemu.game.monster.MonsterService; import fr.quatrevieux.araknemu.game.spell.Spell; import fr.quatrevieux.araknemu.game.spell.SpellService; import fr.quatrevieux.araknemu.network.game.out.fight.CellShown; import fr.quatrevieux.araknemu.network.game.out.fight.action.ActionEffect; import fr.quatrevieux.araknemu.network.game.out.fight.action.FightAction; +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.AddSprites; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import java.sql.SQLException; import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.function.Consumer; -import java.util.stream.Collectors; -import static org.junit.jupiter.api.Assertions.*; +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.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; public class FunctionalTest extends FightBaseCase { private SpellService service; @@ -71,6 +85,8 @@ public void setUp() throws Exception { fight = createFight(); fight.register(new CommonEffectsModule(fight)); fight.register(new IndirectSpellApplyEffectsModule(fight, container.get(SpellService.class))); + fight.register(new MonsterInvocationModule(container.get(MonsterService.class), container.get(FighterFactory.class), fight)); + fight.register(new AiModule(container.get(AiFactory.class))); fighter1 = player.fighter(); fighter2 = other.fighter(); @@ -1132,6 +1148,34 @@ void revealInvisibleFighter() { requestStack.assertOne(ActionEffect.fighterVisible(fighter1, fighter2)); } + @Test + void invocation() throws SQLException { + dataSet + .pushMonsterTemplateInvocations() + .pushMonsterSpellsInvocations() + ; + + castNormal(35, fight.map().get(199)); // Invocation de Bouftou + + assertTrue(fight.map().get(199).fighter().isPresent()); + + PassiveFighter invocation = fight.map().get(199).fighter().get(); + + assertInstanceOf(InvocationFighter.class, invocation); + + requestStack.assertOne(new ActionEffect(181, fighter1, "+" + invocation.sprite())); + requestStack.assertOne(new ActionEffect(999, fighter1, (new FighterTurnOrder(fight.turnList())).toString())); + + assertTrue(fight.fighters().contains(invocation)); + assertEquals(1, fight.turnList().fighters().indexOf(invocation)); + assertSame(fighter1.team(), invocation.team()); + assertSame(fighter1, invocation.invoker().get()); + assertEquals(36, InvocationFighter.class.cast(invocation).monster().id()); + assertSame(fight.map().get(199), invocation.cell()); + + assertInstanceOf(FighterAI.class, ((ActiveFighter) invocation).attachment(FighterAI.class)); + } + private List configureFight(Consumer configurator) { fight.cancel(true); diff --git a/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/heal/GivePercentLifeHandlerTest.java b/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/heal/GivePercentLifeHandlerTest.java index 2b4285fdd..f6adcc5ee 100644 --- a/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/heal/GivePercentLifeHandlerTest.java +++ b/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/heal/GivePercentLifeHandlerTest.java @@ -207,7 +207,7 @@ void withMultipleAlliesShouldOnlyRemoveCasterLifeOnce() { requestStack.assertAll( ActionEffect.alterLifePoints(caster, caster, -25), - ActionEffect.alterLifePoints(caster, fight.fighters().get(1), 25), + ActionEffect.alterLifePoints(caster, fight.fighters().get(3), 25), ActionEffect.alterLifePoints(caster, fight.fighters().get(2), 25) ); } diff --git a/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/invocations/MonsterInvocationHandlerTest.java b/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/invocations/MonsterInvocationHandlerTest.java index 4fac08f9f..044267306 100644 --- a/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/invocations/MonsterInvocationHandlerTest.java +++ b/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/invocations/MonsterInvocationHandlerTest.java @@ -22,6 +22,10 @@ import fr.quatrevieux.araknemu.game.fight.Fight; import fr.quatrevieux.araknemu.game.fight.FightBaseCase; import fr.quatrevieux.araknemu.game.fight.castable.CastScope; +import fr.quatrevieux.araknemu.game.fight.fighter.Fighter; +import fr.quatrevieux.araknemu.game.fight.fighter.FighterFactory; +import fr.quatrevieux.araknemu.game.fight.fighter.PassiveFighter; +import fr.quatrevieux.araknemu.game.fight.fighter.invocation.InvocationFighter; import fr.quatrevieux.araknemu.game.fight.fighter.player.PlayerFighter; import fr.quatrevieux.araknemu.game.monster.MonsterService; import fr.quatrevieux.araknemu.game.spell.Spell; @@ -31,16 +35,19 @@ 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 fr.quatrevieux.araknemu.network.game.out.game.AddSprites; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mockito; +import java.util.Collections; + import static org.junit.jupiter.api.Assertions.*; class MonsterInvocationHandlerTest extends FightBaseCase { private Fight fight; private PlayerFighter caster; - private PlayerFighter target; private MonsterInvocationHandler handler; @Override @@ -54,9 +61,8 @@ public void setUp() throws Exception { fight.nextState(); caster = player.fighter(); - target = other.fighter(); - handler = new MonsterInvocationHandler(container.get(MonsterService.class), fight); + handler = new MonsterInvocationHandler(container.get(MonsterService.class), container.get(FighterFactory.class), fight); requestStack.clear(); } @@ -77,11 +83,45 @@ void handle() { CastScope scope = makeCastScope(caster, spell, effect, fight.map().get(123)); handler.handle(scope, scope.effects().get(0)); - assertSame(2, caster.team().fighters().size()); + PassiveFighter invoc = fight.map().get(123).fighter().get(); + + assertInstanceOf(InvocationFighter.class, invoc); + assertContains(invoc, fight.fighters()); + assertContains(invoc, fight.turnList().fighters()); + assertSame(caster.team(), invoc.team()); + assertEquals(1, invoc.level()); + assertEquals(36, ((InvocationFighter) invoc).monster().id()); + + requestStack.assertAll( + new FighterTurnOrder(fight.turnList()), + new ActionEffect(181, 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.min()).thenReturn(36); // bouftou + Mockito.when(effect.max()).thenReturn(1); // grade 1 + Mockito.when(effect.target()).thenReturn(SpellEffectTarget.DEFAULT); + Mockito.when(spell.constraints()).thenReturn(constraints); + Mockito.when(constraints.freeCell()).thenReturn(true); + + CastScope scope = makeCastScope(caster, spell, effect, fight.map().get(123)); + handler.buff(scope, scope.effects().get(0)); + + PassiveFighter invoc = fight.map().get(123).fighter().get(); + + assertInstanceOf(InvocationFighter.class, invoc); + assertContains(invoc, fight.fighters()); + assertContains(invoc, fight.turnList().fighters()); + assertSame(caster.team(), invoc.team()); + assertEquals(1, invoc.level()); + assertEquals(36, ((InvocationFighter) invoc).monster().id()); } } diff --git a/src/test/java/fr/quatrevieux/araknemu/game/fight/fighter/BaseFighterLifeTest.java b/src/test/java/fr/quatrevieux/araknemu/game/fight/fighter/BaseFighterLifeTest.java index 3507a5009..674138342 100644 --- a/src/test/java/fr/quatrevieux/araknemu/game/fight/fighter/BaseFighterLifeTest.java +++ b/src/test/java/fr/quatrevieux/araknemu/game/fight/fighter/BaseFighterLifeTest.java @@ -86,7 +86,8 @@ public void setUp() throws Exception { new Position(0, 0) ), Collections.singletonList(loadFightMap(10340).get(123)), - 1 + 1, + container.get(FighterFactory.class) ); fighter = (MonsterFighter) team.fighters().stream().findFirst().get(); diff --git a/src/test/java/fr/quatrevieux/araknemu/game/fight/fighter/DefaultFighterFactoryTest.java b/src/test/java/fr/quatrevieux/araknemu/game/fight/fighter/DefaultFighterFactoryTest.java index 82d3b8bb2..663108c49 100644 --- a/src/test/java/fr/quatrevieux/araknemu/game/fight/fighter/DefaultFighterFactoryTest.java +++ b/src/test/java/fr/quatrevieux/araknemu/game/fight/fighter/DefaultFighterFactoryTest.java @@ -23,20 +23,27 @@ import fr.quatrevieux.araknemu.core.event.DefaultListenerAggregate; import fr.quatrevieux.araknemu.core.event.ListenerAggregate; import fr.quatrevieux.araknemu.game.GameBaseCase; +import fr.quatrevieux.araknemu.game.fight.Fight; +import fr.quatrevieux.araknemu.game.fight.FightBaseCase; import fr.quatrevieux.araknemu.game.fight.fighter.event.PlayerFighterCreated; +import fr.quatrevieux.araknemu.game.fight.fighter.monster.MonsterFighter; import fr.quatrevieux.araknemu.game.fight.fighter.player.PlayerFighter; +import fr.quatrevieux.araknemu.game.fight.team.MonsterGroupTeam; import fr.quatrevieux.araknemu.game.listener.fight.SendFightJoined; import fr.quatrevieux.araknemu.game.listener.fight.fighter.*; +import fr.quatrevieux.araknemu.game.monster.Monster; +import fr.quatrevieux.araknemu.game.monster.MonsterService; import fr.quatrevieux.araknemu.game.player.GamePlayer; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.mockito.Mockito; import java.sql.SQLException; import java.util.concurrent.atomic.AtomicReference; import static org.junit.jupiter.api.Assertions.*; -class DefaultFighterFactoryTest extends GameBaseCase { +class DefaultFighterFactoryTest extends FightBaseCase { private DefaultFighterFactory factory; private ListenerAggregate dispatcher; @@ -71,4 +78,55 @@ void createPlayerFighter() throws SQLException, ContainerException { assertTrue(fighter.dispatcher().has(SendStats.class)); assertTrue(fighter.dispatcher().has(SendSpellBoosted.class)); } + + @Test + void generate() { + Fighter fighter = Mockito.mock(Fighter.class); + + assertSame(fighter, factory.generate(id -> { + assertEquals(-1, id); + return fighter; + })); + assertSame(fighter, factory.generate(id -> { + assertEquals(-2, id); + return fighter; + })); + } + + @Test + void createMonsterFighter() throws Exception { + dataSet.pushMonsterTemplates(); + dataSet.pushMonsterSpells(); + + Fight fight = createFight(); + Monster monster = container.get(MonsterService.class).load(36).get(1); + + Fighter fighter = factory.create(monster, fight.team(1)); + + assertInstanceOf(MonsterFighter.class, fighter); + assertEquals(-1, fighter.id()); + assertSame(fight.team(1), fighter.team()); + assertSame(monster, ((MonsterFighter) fighter).monster()); + + assertEquals(-2, factory.create(monster, fight.team(1)).id()); + assertEquals(-3, factory.create(monster, fight.team(1)).id()); + } + + @Test + void generatedIdOverflow() throws Exception { + dataSet.pushMonsterTemplates(); + dataSet.pushMonsterSpells(); + + Fight fight = createFight(); + Monster monster = container.get(MonsterService.class).load(36).get(1); + + factory = new DefaultFighterFactory(new DefaultListenerAggregate(), -3); + + assertEquals(-1, factory.create(monster, fight.team(1)).id()); + assertEquals(-2, factory.create(monster, fight.team(1)).id()); + assertEquals(-3, factory.create(monster, fight.team(1)).id()); + assertEquals(-1, factory.create(monster, fight.team(1)).id()); + assertEquals(-2, factory.create(monster, fight.team(1)).id()); + assertEquals(-3, factory.create(monster, fight.team(1)).id()); + } } diff --git a/src/test/java/fr/quatrevieux/araknemu/game/fight/fighter/invocation/InvocationFighterCharacteristicsTest.java b/src/test/java/fr/quatrevieux/araknemu/game/fight/fighter/invocation/InvocationFighterCharacteristicsTest.java new file mode 100644 index 000000000..93c203b9d --- /dev/null +++ b/src/test/java/fr/quatrevieux/araknemu/game/fight/fighter/invocation/InvocationFighterCharacteristicsTest.java @@ -0,0 +1,114 @@ +/* + * This file is part of Araknemu. + * + * Araknemu is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Araknemu is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Araknemu. If not, see . + * + * Copyright (c) 2017-2022 Vincent Quatrevieux + */ + +package fr.quatrevieux.araknemu.game.fight.fighter.invocation; + +import fr.quatrevieux.araknemu.data.constant.Characteristic; +import fr.quatrevieux.araknemu.game.fight.FightBaseCase; +import fr.quatrevieux.araknemu.game.fight.fighter.Fighter; +import fr.quatrevieux.araknemu.game.fight.fighter.event.FighterCharacteristicChanged; +import fr.quatrevieux.araknemu.game.monster.MonsterService; +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; + +class InvocationFighterCharacteristicsTest extends FightBaseCase { + @Override + @BeforeEach + public void setUp() throws Exception { + super.setUp(); + createFight(); + + dataSet + .pushMonsterTemplateInvocations() + .pushMonsterSpellsInvocations() + ; + } + + @Test + void withLevel1Invoker() { + InvocationFighterCharacteristics characteristics = new InvocationFighterCharacteristics( + container.get(MonsterService.class).load(36).get(1), + Mockito.mock(Fighter.class), + other.fighter() + ); + + assertEquals(80, characteristics.get(Characteristic.STRENGTH)); + assertEquals(80, characteristics.get(Characteristic.INTELLIGENCE)); + assertEquals(80, characteristics.get(Characteristic.LUCK)); + assertEquals(70, characteristics.get(Characteristic.AGILITY)); + assertEquals(60, characteristics.get(Characteristic.WISDOM)); + + System.out.println(player.fighter().level()); + } + + @Test + void withLevel50Invoker() { + InvocationFighterCharacteristics characteristics = new InvocationFighterCharacteristics( + container.get(MonsterService.class).load(36).get(1), + Mockito.mock(Fighter.class), + player.fighter() + ); + + assertEquals(120, characteristics.get(Characteristic.STRENGTH)); + assertEquals(120, characteristics.get(Characteristic.INTELLIGENCE)); + assertEquals(120, characteristics.get(Characteristic.LUCK)); + assertEquals(105, characteristics.get(Characteristic.AGILITY)); + assertEquals(60, characteristics.get(Characteristic.WISDOM)); + } + + @Test + void baseMethods() { + InvocationFighterCharacteristics characteristics = new InvocationFighterCharacteristics( + container.get(MonsterService.class).load(36).get(1), + Mockito.mock(Fighter.class), + player.fighter() + ); + + assertEquals(0, characteristics.discernment()); + assertEquals(0, characteristics.initiative()); + assertSame(container.get(MonsterService.class).load(36).get(1).characteristics(), characteristics.initial()); + } + + @Test + void alter() { + Fighter invoc = Mockito.mock(Fighter.class); + + InvocationFighterCharacteristics characteristics = new InvocationFighterCharacteristics( + container.get(MonsterService.class).load(36).get(1), + invoc, + player.fighter() + ); + + characteristics.alter(Characteristic.STRENGTH, 20); + Mockito.verify(invoc).dispatch(Mockito.argThat(event -> event instanceof FighterCharacteristicChanged && ((FighterCharacteristicChanged) event).characteristic() == Characteristic.STRENGTH && ((FighterCharacteristicChanged) event).value() == 20)); + + assertEquals(140, characteristics.get(Characteristic.STRENGTH)); + assertEquals(120, characteristics.get(Characteristic.INTELLIGENCE)); + + characteristics.alter(Characteristic.INTELLIGENCE, -50); + Mockito.verify(invoc).dispatch(Mockito.argThat(event -> event instanceof FighterCharacteristicChanged && ((FighterCharacteristicChanged) event).characteristic() == Characteristic.INTELLIGENCE && ((FighterCharacteristicChanged) event).value() == -50)); + + assertEquals(140, characteristics.get(Characteristic.STRENGTH)); + assertEquals(70, characteristics.get(Characteristic.INTELLIGENCE)); + } +} diff --git a/src/test/java/fr/quatrevieux/araknemu/game/fight/fighter/invocation/InvocationFighterTest.java b/src/test/java/fr/quatrevieux/araknemu/game/fight/fighter/invocation/InvocationFighterTest.java new file mode 100644 index 000000000..72f9f8ac7 --- /dev/null +++ b/src/test/java/fr/quatrevieux/araknemu/game/fight/fighter/invocation/InvocationFighterTest.java @@ -0,0 +1,331 @@ +/* + * This file is part of Araknemu. + * + * Araknemu is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Araknemu is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Araknemu. If not, see . + * + * Copyright (c) 2017-2022 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.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.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.team.FightTeam; +import fr.quatrevieux.araknemu.game.fight.turn.FightTurn; +import fr.quatrevieux.araknemu.game.monster.Monster; +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 InvocationFighterTest extends FightBaseCase { + private InvocationFighter 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); + + MonsterService service = container.get(MonsterService.class); + fighter = new InvocationFighter(-5, service.load(36).get(1), team, player.fighter()); + } + + @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(1, fighter.level()); + assertInstanceOf(Monster.class, fighter.monster()); + assertEquals(36, fighter.monster().id()); + assertSame(player.fighter(), fighter.invoker().get()); + } + + @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().get()); + 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(MonsterFighterSprite.class, fighter.sprite()); + assertEquals("123;1;0;-5;36;-2;1566^100;1;-1;-1;-1;0,0,0,0;45;5;3;25;0;-12;6;-50;15;15;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().get()); + } + + @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().get()); + + assertFalse(fight.map().get(123).fighter().isPresent()); + } + + @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).fighter().isPresent()); + } + + @Test + void spells() { + assertIterableEquals(fighter.spells(), container.get(MonsterService.class).load(36).all().get(0).spells()); + } + + @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).onInvocation(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/MonsterFighterCharacteristicsTest.java b/src/test/java/fr/quatrevieux/araknemu/game/fight/fighter/monster/MonsterFighterCharacteristicsTest.java index f8091dd97..69cfe0b61 100644 --- a/src/test/java/fr/quatrevieux/araknemu/game/fight/fighter/monster/MonsterFighterCharacteristicsTest.java +++ b/src/test/java/fr/quatrevieux/araknemu/game/fight/fighter/monster/MonsterFighterCharacteristicsTest.java @@ -27,6 +27,7 @@ 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.fighter.FighterFactory; import fr.quatrevieux.araknemu.game.fight.fighter.event.FighterCharacteristicChanged; import fr.quatrevieux.araknemu.game.fight.map.FightMap; import fr.quatrevieux.araknemu.game.fight.team.MonsterGroupTeam; @@ -80,7 +81,8 @@ public void setUp() throws Exception { new Position(0, 0) ), Collections.singletonList(map.get(123)), - 1 + 1, + container.get(FighterFactory.class) ); fighter = (MonsterFighter) team.fighters().stream().findFirst().get(); 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 92b3a5ef4..cc45aa378 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 @@ -26,6 +26,7 @@ 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.fighter.FighterFactory; import fr.quatrevieux.araknemu.game.fight.team.MonsterGroupTeam; import fr.quatrevieux.araknemu.game.monster.MonsterService; import fr.quatrevieux.araknemu.game.monster.environment.LivingMonsterGroupPosition; @@ -76,7 +77,8 @@ public void setUp() throws Exception { new Position(0, 0) ), Collections.singletonList(loadFightMap(10340).get(123)), - 1 + 1, + container.get(FighterFactory.class) ); fighter = (MonsterFighter) team.fighters().stream().findFirst().get(); diff --git a/src/test/java/fr/quatrevieux/araknemu/game/fight/fighter/monster/MonsterFighterTest.java b/src/test/java/fr/quatrevieux/araknemu/game/fight/fighter/monster/MonsterFighterTest.java index 40c108bfe..75230711f 100644 --- a/src/test/java/fr/quatrevieux/araknemu/game/fight/fighter/monster/MonsterFighterTest.java +++ b/src/test/java/fr/quatrevieux/araknemu/game/fight/fighter/monster/MonsterFighterTest.java @@ -31,6 +31,7 @@ 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.FighterFactory; 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; @@ -92,7 +93,8 @@ public void setUp() throws Exception { new Position(0, 0) ), Collections.singletonList(loadFightMap(10340).get(123)), - 1 + 1, + container.get(FighterFactory.class) ); fighter = (MonsterFighter) team.fighters().stream().findFirst().get(); @@ -110,6 +112,7 @@ void values() { assertTrue(fighter.ready()); assertEquals(4, fighter.level()); assertInstanceOf(Monster.class, fighter.monster()); + assertFalse(fighter.invoker().isPresent()); assertEquals(new Interval(50, 70), fighter.reward().kamas()); assertEquals(12, fighter.reward().experience()); diff --git a/src/test/java/fr/quatrevieux/araknemu/game/fight/fighter/player/PlayerFighterTest.java b/src/test/java/fr/quatrevieux/araknemu/game/fight/fighter/player/PlayerFighterTest.java index b2360b71f..88327a2cb 100644 --- a/src/test/java/fr/quatrevieux/araknemu/game/fight/fighter/player/PlayerFighterTest.java +++ b/src/test/java/fr/quatrevieux/araknemu/game/fight/fighter/player/PlayerFighterTest.java @@ -85,6 +85,11 @@ void player() throws SQLException, ContainerException { assertSame(gamePlayer(), fighter.player()); } + @Test + void invoker() { + assertFalse(fighter.invoker().isPresent()); + } + @Test void sprite() { assertInstanceOf(PlayerFighterSprite.class, fighter.sprite()); diff --git a/src/test/java/fr/quatrevieux/araknemu/game/fight/module/MonsterInvocationModuleTest.java b/src/test/java/fr/quatrevieux/araknemu/game/fight/module/MonsterInvocationModuleTest.java new file mode 100644 index 000000000..7952ee276 --- /dev/null +++ b/src/test/java/fr/quatrevieux/araknemu/game/fight/module/MonsterInvocationModuleTest.java @@ -0,0 +1,134 @@ +/* + * This file is part of Araknemu. + * + * Araknemu is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Araknemu is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Araknemu. If not, see . + * + * Copyright (c) 2017-2022 Vincent Quatrevieux + */ + +package fr.quatrevieux.araknemu.game.fight.module; + +import fr.quatrevieux.araknemu.game.fight.Fight; +import fr.quatrevieux.araknemu.game.fight.FightBaseCase; +import fr.quatrevieux.araknemu.game.fight.fighter.FighterFactory; +import fr.quatrevieux.araknemu.game.fight.fighter.invocation.InvocationFighter; +import fr.quatrevieux.araknemu.game.fight.state.PlacementState; +import fr.quatrevieux.araknemu.game.monster.MonsterService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class MonsterInvocationModuleTest extends FightBaseCase { + private MonsterService monsterService; + + @Override + @BeforeEach + public void setUp() throws Exception { + super.setUp(); + + dataSet + .pushMonsterTemplateInvocations() + .pushMonsterSpellsInvocations() + ; + + monsterService = container.get(MonsterService.class); + } + + @Test + void onFighterDieShouldKillAllItsInvocation() throws Exception { + FightBuilder builder = fightBuilder(); + builder + .addSelf(fb -> fb.cell(222)) + .addAlly(fb -> fb.player(other).cell(223)) + .addEnemy(fb -> fb.cell(224)) + ; + + Fight fight = builder.build(true); + + fight.register(new MonsterInvocationModule(monsterService, container.get(FighterFactory.class), fight)); + fight.state(PlacementState.class).startFight(); + fight.turnList().start(); + + InvocationFighter invoc1 = new InvocationFighter(-5, monsterService.load(36).get(2), player.fighter().team(), player.fighter()); + InvocationFighter invoc2 = new InvocationFighter(-6, monsterService.load(36).get(2), player.fighter().team(), player.fighter()); + InvocationFighter invoc3 = new InvocationFighter(-7, monsterService.load(36).get(4), other.fighter().team(), other.fighter()); + + invoc1.joinFight(fight, fight.map().get(123)); + invoc2.joinFight(fight, fight.map().get(124)); + invoc3.joinFight(fight, fight.map().get(125)); + + fight.turnList().add(invoc1); + fight.turnList().add(invoc2); + fight.turnList().add(invoc3); + + invoc1.init(); + invoc2.init(); + invoc3.init(); + + player.fighter().life().kill(player.fighter()); + + assertTrue(invoc1.dead()); + assertTrue(invoc2.dead()); + assertFalse(fight.map().get(123).fighter().isPresent()); + assertFalse(fight.map().get(124).fighter().isPresent()); + assertFalse(fight.turnList().fighters().contains(invoc1)); + assertFalse(fight.turnList().fighters().contains(invoc2)); + assertFalse(fight.fighters().contains(invoc1)); + assertFalse(fight.fighters().contains(invoc2)); + + // Do not change invocation of other fighter + assertFalse(invoc3.dead()); + assertTrue(fight.map().get(125).fighter().isPresent()); + assertTrue(fight.turnList().fighters().contains(invoc3)); + assertTrue(fight.fighters().contains(invoc3)); + } + + @Test + void onFighterDieInvocationShouldBeRemovedFromTurnList() throws Exception { + Fight fight = createFight(true); + + fight.register(new MonsterInvocationModule(monsterService, container.get(FighterFactory.class), fight)); + fight.state(PlacementState.class).startFight(); + fight.turnList().start(); + + InvocationFighter invoc = new InvocationFighter(-5, monsterService.load(36).get(2), player.fighter().team(), player.fighter()); + invoc.joinFight(fight, fight.map().get(123)); + fight.turnList().add(invoc); + invoc.init(); + + invoc.life().kill(player.fighter()); + + assertTrue(invoc.dead()); + assertFalse(fight.map().get(123).fighter().isPresent()); + assertFalse(fight.turnList().fighters().contains(invoc)); + assertFalse(fight.fighters().contains(invoc)); + } + + @Test + void onFighterDieWithEndFightShouldNotCauseConcurrentModification() throws Exception { + Fight fight = createFight(true); + + fight.register(new MonsterInvocationModule(monsterService, container.get(FighterFactory.class), fight)); + fight.state(PlacementState.class).startFight(); + fight.turnList().start(); + + InvocationFighter invoc = new InvocationFighter(-5, monsterService.load(36).get(2), player.fighter().team(), player.fighter()); + invoc.joinFight(fight, fight.map().get(123)); + fight.turnList().add(invoc); + invoc.init(); + + player.fighter().life().kill(player.fighter()); + } +} diff --git a/src/test/java/fr/quatrevieux/araknemu/game/fight/team/MonsterGroupTeamTest.java b/src/test/java/fr/quatrevieux/araknemu/game/fight/team/MonsterGroupTeamTest.java index d7aafdc46..efc563e44 100644 --- a/src/test/java/fr/quatrevieux/araknemu/game/fight/team/MonsterGroupTeamTest.java +++ b/src/test/java/fr/quatrevieux/araknemu/game/fight/team/MonsterGroupTeamTest.java @@ -29,6 +29,7 @@ import fr.quatrevieux.araknemu.game.fight.FightService; import fr.quatrevieux.araknemu.game.fight.exception.JoinFightException; import fr.quatrevieux.araknemu.game.fight.fighter.Fighter; +import fr.quatrevieux.araknemu.game.fight.fighter.FighterFactory; import fr.quatrevieux.araknemu.game.fight.fighter.monster.MonsterFighter; import fr.quatrevieux.araknemu.game.fight.map.FightMap; import fr.quatrevieux.araknemu.game.monster.MonsterService; @@ -84,7 +85,8 @@ public void setUp() throws Exception { new Position(0, 0) ), Arrays.asList(map.get(123), map.get(321)), - 1 + 1, + container.get(FighterFactory.class) ); } diff --git a/src/test/java/fr/quatrevieux/araknemu/game/fight/turn/FightTurnListTest.java b/src/test/java/fr/quatrevieux/araknemu/game/fight/turn/FightTurnListTest.java index 6aedfa751..73792b8ed 100644 --- a/src/test/java/fr/quatrevieux/araknemu/game/fight/turn/FightTurnListTest.java +++ b/src/test/java/fr/quatrevieux/araknemu/game/fight/turn/FightTurnListTest.java @@ -30,7 +30,9 @@ import fr.quatrevieux.araknemu.game.fight.turn.order.AlternateTeamFighterOrder; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import java.lang.reflect.Field; import java.sql.SQLException; import java.util.ArrayList; import java.util.Arrays; @@ -39,6 +41,7 @@ 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.assertNotNull; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -295,4 +298,39 @@ void removeFighterNotFound() throws SQLException { assertThrows(NoSuchElementException.class, () -> turnList.remove(third)); } + + @Test + void add() throws NoSuchFieldException, IllegalAccessException { + turnList.init(new AlternateTeamFighterOrder()); + turnList.start(); + + assertSame(player.fighter(), turnList.currentFighter()); + + Fighter f1 = Mockito.mock(Fighter.class); + Fighter f2 = Mockito.mock(Fighter.class); + Fighter f3 = Mockito.mock(Fighter.class); + + turnList.add(f1); + assertSame(player.fighter(), turnList.currentFighter()); + assertIterableEquals(turnList.fighters(), Arrays.asList(player.fighter(), f1, other.fighter())); + + turnList.add(f2); + assertIterableEquals(turnList.fighters(), Arrays.asList(player.fighter(), f2, f1, other.fighter())); + + setIndex(3); + turnList.add(f3); + assertIterableEquals(turnList.fighters(), Arrays.asList(player.fighter(), f2, f1, other.fighter(), f3)); + } + + @Test + void addNotInitializedShouldThrowError() { + assertThrows(IllegalStateException.class, () -> turnList.add(Mockito.mock(Fighter.class))); + } + + private void setIndex(int index) throws NoSuchFieldException, IllegalAccessException { + Field field = FightTurnList.class.getDeclaredField("index"); + field.setAccessible(true); + + field.set(turnList, index); + } } diff --git a/src/test/java/fr/quatrevieux/araknemu/game/spell/effect/target/SpellEffectTargetTest.java b/src/test/java/fr/quatrevieux/araknemu/game/spell/effect/target/SpellEffectTargetTest.java index 4f94e2f97..664a10dd9 100644 --- a/src/test/java/fr/quatrevieux/araknemu/game/spell/effect/target/SpellEffectTargetTest.java +++ b/src/test/java/fr/quatrevieux/araknemu/game/spell/effect/target/SpellEffectTargetTest.java @@ -21,11 +21,15 @@ import fr.quatrevieux.araknemu.game.fight.Fight; import fr.quatrevieux.araknemu.game.fight.FightBaseCase; +import fr.quatrevieux.araknemu.game.fight.fighter.invocation.InvocationFighter; import fr.quatrevieux.araknemu.game.fight.fighter.player.PlayerFighter; import fr.quatrevieux.araknemu.game.fight.state.PlacementState; +import fr.quatrevieux.araknemu.game.monster.MonsterService; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import java.sql.SQLException; + import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; @@ -104,6 +108,34 @@ void testNotEnemyAndNotSelf() { assertTrue(et.test(caster, teammate)); } + @Test + void invocation() throws SQLException { + dataSet + .pushMonsterTemplateInvocations() + .pushMonsterSpellsInvocations() + ; + + SpellEffectTarget et = new SpellEffectTarget(SpellEffectTarget.NOT_INVOC); + + InvocationFighter invoc = new InvocationFighter( + -5, container.get(MonsterService.class).load(36).get(1), + caster.team(), + caster + ); + + assertFalse(et.test(caster, invoc)); + assertTrue(et.test(caster, caster)); + assertTrue(et.test(caster, enemy)); + assertTrue(et.test(caster, teammate)); + + et = new SpellEffectTarget(SpellEffectTarget.ONLY_INVOC); + + assertTrue(et.test(caster, invoc)); + assertFalse(et.test(caster, caster)); + assertFalse(et.test(caster, enemy)); + assertFalse(et.test(caster, teammate)); + } + @Test void equalsAndHash() { SpellEffectTarget et = new SpellEffectTarget(SpellEffectTarget.NOT_ENEMY | SpellEffectTarget.NOT_SELF); 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 000f0d7ac..98ff4c52c 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 @@ -19,18 +19,23 @@ package fr.quatrevieux.araknemu.network.game.out.fight.action; +import fr.quatrevieux.araknemu.game.fight.Fight; +import fr.quatrevieux.araknemu.game.fight.FightBaseCase; import fr.quatrevieux.araknemu.game.fight.castable.effect.buff.Buff; import fr.quatrevieux.araknemu.game.fight.fighter.Fighter; +import fr.quatrevieux.araknemu.game.fight.fighter.invocation.InvocationFighter; import fr.quatrevieux.araknemu.game.fight.map.FightCell; +import fr.quatrevieux.araknemu.game.monster.MonsterService; import fr.quatrevieux.araknemu.game.spell.Spell; import fr.quatrevieux.araknemu.game.spell.effect.SpellEffect; import fr.quatrevieux.araknemu.game.world.creature.Sprite; +import fr.quatrevieux.araknemu.network.game.out.basic.Noop; import org.junit.jupiter.api.Test; import org.mockito.Mockito; import static org.junit.jupiter.api.Assertions.assertEquals; -class ActionEffectTest { +class ActionEffectTest extends FightBaseCase { @Test void usedMovementPoints() { Fighter fighter = Mockito.mock(Fighter.class); @@ -364,4 +369,26 @@ void fighterVisible() { assertEquals("GA;150;456;123,0", ActionEffect.fighterVisible(caster, target).toString()); } + + @Test + void addInvocation() throws Exception { + dataSet + .pushMonsterTemplates() + .pushMonsterSpells() + ; + + Fight fight = createFight(); + InvocationFighter fighter = new InvocationFighter(-5, container.get(MonsterService.class).load(36).get(2), fight.team(0), fight.fighters().get(0)); + fighter.joinFight(fight, fight.map().get(118)); + + 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(fight.fighters().get(0), fighter).toString()); + } + + @Test + void packet() { + Fighter caster = Mockito.mock(Fighter.class); + Mockito.when(caster.id()).thenReturn(456); + + assertEquals("GA;999;456;BN", ActionEffect.packet(caster, new Noop()).toString()); + } }