From 2dbb4c659b121ae990b307aea592458449df40cb Mon Sep 17 00:00:00 2001 From: Vincent Quatrevieux Date: Mon, 20 May 2024 15:10:07 +0200 Subject: [PATCH] fix(ai): Stop turn when AI throws exception --- .../core/event/DefaultListenerAggregate.java | 2 +- .../quatrevieux/araknemu/game/GameModule.java | 2 +- .../araknemu/game/fight/ai/FighterAI.java | 37 ++++++++++-------- .../araknemu/game/fight/module/AiModule.java | 25 ++++++++++-- .../araknemu/game/fight/turn/FightTurn.java | 2 +- .../araknemu/game/fight/ai/AiBaseCase.java | 4 +- .../araknemu/game/fight/ai/FighterAITest.java | 34 ++++++++++++++++- .../game/fight/ai/action/DummyGenerator.java | 34 ++++++++++++++++- .../fight/ai/simulation/SimulatorTest.java | 4 +- .../fight/castable/effect/FunctionalTest.java | 4 +- .../invocations/CreateDoubleHandlerTest.java | 3 +- .../InvocationCountValidatorTest.java | 3 +- ...KillAndReplaceByInvocationHandlerTest.java | 3 +- .../MonsterInvocationHandlerTest.java | 3 +- .../StaticInvocationHandlerTest.java | 3 +- .../game/fight/module/AiModuleTest.java | 38 +++++++++++++++++-- 16 files changed, 165 insertions(+), 36 deletions(-) diff --git a/src/main/java/fr/quatrevieux/araknemu/core/event/DefaultListenerAggregate.java b/src/main/java/fr/quatrevieux/araknemu/core/event/DefaultListenerAggregate.java index 4b9d8e275..262813e01 100644 --- a/src/main/java/fr/quatrevieux/araknemu/core/event/DefaultListenerAggregate.java +++ b/src/main/java/fr/quatrevieux/araknemu/core/event/DefaultListenerAggregate.java @@ -75,7 +75,7 @@ public void dispatch(Object event) { for (Listener listener : container) { try { listener.on(event); - } catch (RuntimeException e) { + } catch (Exception e) { logger.error("Error during execution of listener " + listener.getClass().getName(), e); } } diff --git a/src/main/java/fr/quatrevieux/araknemu/game/GameModule.java b/src/main/java/fr/quatrevieux/araknemu/game/GameModule.java index fcd874265..b22262a68 100644 --- a/src/main/java/fr/quatrevieux/araknemu/game/GameModule.java +++ b/src/main/java/fr/quatrevieux/araknemu/game/GameModule.java @@ -705,7 +705,7 @@ private void configureServices(ContainerConfigurator configurator) { StatesModule::new, RaulebaqueModule::new, LaunchedSpellsModule::new, - fight -> new AiModule(container.get(AiFactory.class)), + fight -> new AiModule(container.get(AiFactory.class), fight, container.get(Logger.class)), fight -> new MonsterInvocationModule(container.get(MonsterService.class), container.get(FighterFactory.class), fight), SpiritualLeashModule::new, CarryingModule::new, diff --git a/src/main/java/fr/quatrevieux/araknemu/game/fight/ai/FighterAI.java b/src/main/java/fr/quatrevieux/araknemu/game/fight/ai/FighterAI.java index b57a9d696..89795fa1d 100644 --- a/src/main/java/fr/quatrevieux/araknemu/game/fight/ai/FighterAI.java +++ b/src/main/java/fr/quatrevieux/araknemu/game/fight/ai/FighterAI.java @@ -91,29 +91,34 @@ public void run() { } final Turn currentTurn = turn; + boolean stop = true; if (!currentTurn.active()) { turn = null; return; } - final Optional action = generator.generate( - this, - new FightAiActionFactoryAdapter( - fighter, - fight, - fight.actions() - ) - ); - - if (action.isPresent()) { - currentTurn.perform(action.get()); - currentTurn.later(() -> fight.schedule(this, Duration.ofMillis(800))); - return; + try { + final Optional action = generator.generate( + this, + new FightAiActionFactoryAdapter( + fighter, + fight, + fight.actions() + ) + ); + + if (action.isPresent()) { + currentTurn.perform(action.get()); + currentTurn.later(() -> fight.schedule(this, Duration.ofMillis(800))); + stop = false; + } + } finally { + if (stop) { + turn = null; + currentTurn.stop(); + } } - - turn = null; - currentTurn.stop(); } @Override diff --git a/src/main/java/fr/quatrevieux/araknemu/game/fight/module/AiModule.java b/src/main/java/fr/quatrevieux/araknemu/game/fight/module/AiModule.java index e61499140..08dce10a9 100644 --- a/src/main/java/fr/quatrevieux/araknemu/game/fight/module/AiModule.java +++ b/src/main/java/fr/quatrevieux/araknemu/game/fight/module/AiModule.java @@ -20,6 +20,7 @@ package fr.quatrevieux.araknemu.game.fight.module; import fr.quatrevieux.araknemu.core.event.Listener; +import fr.quatrevieux.araknemu.game.fight.Fight; import fr.quatrevieux.araknemu.game.fight.ai.FighterAI; import fr.quatrevieux.araknemu.game.fight.ai.factory.AiFactory; import fr.quatrevieux.araknemu.game.fight.fighter.Fighter; @@ -27,15 +28,20 @@ import fr.quatrevieux.araknemu.game.fight.fighter.event.FighterInitialized; import fr.quatrevieux.araknemu.game.fight.turn.FightTurn; import fr.quatrevieux.araknemu.game.fight.turn.event.TurnStarted; +import org.apache.logging.log4j.Logger; /** * Fight module for enable AI */ public final class AiModule implements FightModule { private final AiFactory factory; + private final Fight fight; + private final Logger logger; - public AiModule(AiFactory factory) { + public AiModule(AiFactory factory, Fight fight, Logger logger) { this.factory = factory; + this.fight = fight; + this.logger = logger; } @Override @@ -74,7 +80,10 @@ public Class event() { * Initialize the AI for the fighter (if supported) */ private void init(PlayableFighter fighter) { - factory.create(fighter).ifPresent(fighter::attach); + factory.create(fighter).ifPresent(ai -> { + logger.debug("AI initialized for {}", fighter); + fighter.attach(ai); + }); } /** @@ -83,8 +92,18 @@ private void init(PlayableFighter fighter) { private void start(FightTurn turn) { final FighterAI ai = turn.fighter().attachment(FighterAI.class); - if (ai != null) { + if (ai == null) { + return; + } + + logger.debug("Starting AI for {}", turn.fighter()); + + try { ai.start(turn); + } catch (Exception e) { + logger.error("Error during AI execution. Stop the turn.", e); + // Should be done asynchronously because the turn is not totally started + fight.execute(turn::stop); } } } diff --git a/src/main/java/fr/quatrevieux/araknemu/game/fight/turn/FightTurn.java b/src/main/java/fr/quatrevieux/araknemu/game/fight/turn/FightTurn.java index 106fb2960..2517f63e0 100644 --- a/src/main/java/fr/quatrevieux/araknemu/game/fight/turn/FightTurn.java +++ b/src/main/java/fr/quatrevieux/araknemu/game/fight/turn/FightTurn.java @@ -108,8 +108,8 @@ public boolean start() { fighter.play(this); active.set(true); - fight.dispatch(new TurnStarted(this)); timer = fight.schedule(this::stop, duration); + fight.dispatch(new TurnStarted(this)); return true; } diff --git a/src/test/java/fr/quatrevieux/araknemu/game/fight/ai/AiBaseCase.java b/src/test/java/fr/quatrevieux/araknemu/game/fight/ai/AiBaseCase.java index f97c9ce47..9f73b40aa 100644 --- a/src/test/java/fr/quatrevieux/araknemu/game/fight/ai/AiBaseCase.java +++ b/src/test/java/fr/quatrevieux/araknemu/game/fight/ai/AiBaseCase.java @@ -44,6 +44,8 @@ import fr.quatrevieux.araknemu.game.player.spell.SpellBook; import fr.quatrevieux.araknemu.game.spell.SpellService; import fr.quatrevieux.araknemu.game.spell.effect.SpellEffect; +import org.apache.logging.log4j.Logger; +import org.mockito.Mockito; import java.lang.reflect.Field; import java.util.Map; @@ -78,7 +80,7 @@ public void configureFight(Consumer configurator) { configurator.accept(builder); fight = builder.build(true); - fight.register(new AiModule(new ChainAiFactory())); + fight.register(new AiModule(new ChainAiFactory(), fight, Mockito.mock(Logger.class))); fight.register(new CommonEffectsModule(fight)); fighter = player.fighter(); diff --git a/src/test/java/fr/quatrevieux/araknemu/game/fight/ai/FighterAITest.java b/src/test/java/fr/quatrevieux/araknemu/game/fight/ai/FighterAITest.java index f17a00ae6..e2b06a559 100644 --- a/src/test/java/fr/quatrevieux/araknemu/game/fight/ai/FighterAITest.java +++ b/src/test/java/fr/quatrevieux/araknemu/game/fight/ai/FighterAITest.java @@ -27,6 +27,7 @@ import fr.quatrevieux.araknemu.game.fight.ai.action.logic.NullGenerator; import fr.quatrevieux.araknemu.game.fight.ai.memory.MemoryKey; import fr.quatrevieux.araknemu.game.fight.ai.util.AIHelper; +import fr.quatrevieux.araknemu.game.fight.fighter.ActiveFighter; import fr.quatrevieux.araknemu.game.fight.fighter.Fighter; import fr.quatrevieux.araknemu.game.fight.fighter.PlayableFighter; import fr.quatrevieux.araknemu.game.fight.fighter.invocation.DoubleFighter; @@ -34,12 +35,16 @@ import fr.quatrevieux.araknemu.game.fight.state.PlacementState; import fr.quatrevieux.araknemu.game.fight.turn.FightTurn; import fr.quatrevieux.araknemu.game.fight.turn.action.Action; +import fr.quatrevieux.araknemu.game.fight.turn.action.ActionResult; +import fr.quatrevieux.araknemu.game.fight.turn.action.ActionType; +import fr.quatrevieux.araknemu.game.fight.turn.action.FightAction; import io.github.artsok.RepeatedIfExceptionsTest; import org.checkerframework.checker.nullness.qual.Nullable; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mockito; +import java.time.Duration; import java.util.Optional; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -195,7 +200,10 @@ void startUnit() throws InterruptedException { FighterAI ai = new FighterAI(fighter, fight, new GeneratorAggregate(new ActionGenerator[] {generator1, generator2})); - Mockito.when(generator1.generate(Mockito.eq(ai), Mockito.any(AiActionFactory.class))).thenReturn(Optional.of(Mockito.mock(Action.class))); + FightAction action = Mockito.mock(FightAction.class); + Mockito.when(action.validate(Mockito.any())).thenReturn(true); + Mockito.when(action.start()).thenReturn(Mockito.mock(ActionResult.class)); + Mockito.when(generator1.generate(Mockito.eq(ai), Mockito.any(AiActionFactory.class))).thenReturn(Optional.of(action)); ai.start(turn); @@ -209,6 +217,25 @@ void startUnit() throws InterruptedException { assertTrue(turn.active()); } + @RepeatedIfExceptionsTest + void startWithExceptionShouldStopTurn() throws InterruptedException { + ActionGenerator generator1 = Mockito.mock(ActionGenerator.class); + Mockito.when(generator1.generate(Mockito.any(), Mockito.any())).thenThrow(new RuntimeException("Test")); + + fight.turnList().start(); + FightTurn turn = fight.turnList().current().get(); + + FighterAI ai = new FighterAI(fighter, fight, new GeneratorAggregate(new ActionGenerator[] {generator1})); + + ai.start(turn); + + Mockito.verify(generator1).initialize(ai); + + Mockito.verify(generator1).generate(Mockito.eq(ai), Mockito.any(AiActionFactory.class)); + + assertFalse(turn.active()); + } + @RepeatedIfExceptionsTest void startShouldCallMemoryRefresh() throws InterruptedException { ActionGenerator generator = Mockito.mock(ActionGenerator.class); @@ -222,7 +249,10 @@ void startShouldCallMemoryRefresh() throws InterruptedException { ai.set(key, value); - Mockito.when(generator.generate(Mockito.eq(ai), Mockito.any(AiActionFactory.class))).thenReturn(Optional.of(Mockito.mock(Action.class))); + FightAction action = Mockito.mock(FightAction.class); + Mockito.when(action.validate(Mockito.any())).thenReturn(true); + Mockito.when(action.start()).thenReturn(Mockito.mock(ActionResult.class)); + Mockito.when(generator.generate(Mockito.eq(ai), Mockito.any(AiActionFactory.class))).thenReturn(Optional.of(action)); ai.start(turn); diff --git a/src/test/java/fr/quatrevieux/araknemu/game/fight/ai/action/DummyGenerator.java b/src/test/java/fr/quatrevieux/araknemu/game/fight/ai/action/DummyGenerator.java index 3ca8a097f..344485f6d 100644 --- a/src/test/java/fr/quatrevieux/araknemu/game/fight/ai/action/DummyGenerator.java +++ b/src/test/java/fr/quatrevieux/araknemu/game/fight/ai/action/DummyGenerator.java @@ -21,6 +21,8 @@ import fr.quatrevieux.araknemu.game.fight.ai.AI; import fr.quatrevieux.araknemu.game.fight.fighter.Fighter; +import fr.quatrevieux.araknemu.game.fight.fighter.PlayableFighter; +import fr.quatrevieux.araknemu.game.fight.turn.FightTurn; import fr.quatrevieux.araknemu.game.fight.turn.Turn; import fr.quatrevieux.araknemu.game.fight.turn.action.Action; import fr.quatrevieux.araknemu.game.fight.turn.action.ActionResult; @@ -60,7 +62,37 @@ public boolean validate(Turn turn) { @Override public ActionResult start() { - return null; + return new ActionResult() { + @Override + public int action() { + return 0; + } + + @Override + public PlayableFighter performer() { + return (PlayableFighter) ai.fighter(); + } + + @Override + public Object[] arguments() { + return new Object[0]; + } + + @Override + public boolean success() { + return false; + } + + @Override + public boolean secret() { + return false; + } + + @Override + public void apply(FightTurn turn) { + + } + }; } } ); diff --git a/src/test/java/fr/quatrevieux/araknemu/game/fight/ai/simulation/SimulatorTest.java b/src/test/java/fr/quatrevieux/araknemu/game/fight/ai/simulation/SimulatorTest.java index 899cc2b71..277fb9b3c 100644 --- a/src/test/java/fr/quatrevieux/araknemu/game/fight/ai/simulation/SimulatorTest.java +++ b/src/test/java/fr/quatrevieux/araknemu/game/fight/ai/simulation/SimulatorTest.java @@ -31,8 +31,10 @@ import fr.quatrevieux.araknemu.game.fight.turn.action.util.BaseCriticalityStrategy; import fr.quatrevieux.araknemu.game.spell.Spell; import fr.quatrevieux.araknemu.game.spell.SpellService; +import org.apache.logging.log4j.Logger; 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.assertFalse; @@ -53,7 +55,7 @@ public void setUp() throws Exception { fight = createFight(); fighter = player.fighter(); - fight.register(new AiModule(new ChainAiFactory())); + fight.register(new AiModule(new ChainAiFactory(), fight, Mockito.mock(Logger.class))); simulator = container.get(Simulator.class); ai = new FighterAI(fighter, fight, new NullGenerator()); } 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 1ce97988c..f82391acc 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 @@ -67,8 +67,10 @@ import fr.quatrevieux.araknemu.network.game.out.game.AddSprites; import fr.quatrevieux.araknemu.network.game.out.game.UpdateCells; import fr.quatrevieux.araknemu.network.game.out.info.Error; +import org.apache.logging.log4j.Logger; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.mockito.Mockito; import java.sql.SQLException; import java.util.ArrayList; @@ -110,7 +112,7 @@ public void setUp() throws Exception { 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 SpiritualLeashModule(fight)); - fight.register(new AiModule(container.get(AiFactory.class))); + fight.register(new AiModule(container.get(AiFactory.class), fight, Mockito.mock(Logger.class))); fight.register(new FighterInitializationModule(container.get(GameConfiguration.class).fight())); fighter1 = player.fighter(); diff --git a/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/invocations/CreateDoubleHandlerTest.java b/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/invocations/CreateDoubleHandlerTest.java index 92ec81946..e0ef0cf9b 100644 --- a/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/invocations/CreateDoubleHandlerTest.java +++ b/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/invocations/CreateDoubleHandlerTest.java @@ -40,6 +40,7 @@ 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.info.Error; +import org.apache.logging.log4j.Logger; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mockito; @@ -66,7 +67,7 @@ public void setUp() throws Exception { super.setUp(); fight = createFight(); - fight.register(new AiModule(container.get(AiFactory.class))); + fight.register(new AiModule(container.get(AiFactory.class), fight, Mockito.mock(Logger.class))); fight.nextState(); caster = player.fighter(); diff --git a/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/invocations/InvocationCountValidatorTest.java b/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/invocations/InvocationCountValidatorTest.java index ec9e98df5..efec88a5d 100644 --- a/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/invocations/InvocationCountValidatorTest.java +++ b/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/invocations/InvocationCountValidatorTest.java @@ -31,6 +31,7 @@ import fr.quatrevieux.araknemu.game.spell.Spell; import fr.quatrevieux.araknemu.game.spell.effect.SpellEffect; import fr.quatrevieux.araknemu.network.game.out.info.Error; +import org.apache.logging.log4j.Logger; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mockito; @@ -57,7 +58,7 @@ public void setUp() throws Exception { dataSet.pushMonsterTemplateInvocations().pushMonsterSpellsInvocations().pushRewardItems(); fight = createFight(); - fight.register(new AiModule(container.get(AiFactory.class))); + fight.register(new AiModule(container.get(AiFactory.class), fight, Mockito.mock(Logger.class))); fight.nextState(); caster = player.fighter(); diff --git a/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/invocations/KillAndReplaceByInvocationHandlerTest.java b/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/invocations/KillAndReplaceByInvocationHandlerTest.java index 40be5321b..ea4265ac2 100644 --- a/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/invocations/KillAndReplaceByInvocationHandlerTest.java +++ b/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/invocations/KillAndReplaceByInvocationHandlerTest.java @@ -42,6 +42,7 @@ import fr.quatrevieux.araknemu.game.spell.effect.target.SpellEffectTarget; import fr.quatrevieux.araknemu.network.game.out.fight.action.ActionEffect; import fr.quatrevieux.araknemu.network.game.out.fight.turn.FighterTurnOrder; +import org.apache.logging.log4j.Logger; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mockito; @@ -77,7 +78,7 @@ public void setUp() throws Exception { .build(true) ; - fight.register(new AiModule(container.get(AiFactory.class))); + fight.register(new AiModule(container.get(AiFactory.class), fight, Mockito.mock(Logger.class))); fight.nextState(); caster = player.fighter(); 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 9b75eb8a2..0b8c61839 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 @@ -43,6 +43,7 @@ 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.info.Error; +import org.apache.logging.log4j.Logger; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mockito; @@ -70,7 +71,7 @@ public void setUp() throws Exception { dataSet.pushMonsterTemplateInvocations().pushMonsterSpellsInvocations().pushRewardItems(); fight = createFight(); - fight.register(new AiModule(container.get(AiFactory.class))); + fight.register(new AiModule(container.get(AiFactory.class), fight, Mockito.mock(Logger.class))); fight.nextState(); caster = player.fighter(); diff --git a/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/invocations/StaticInvocationHandlerTest.java b/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/invocations/StaticInvocationHandlerTest.java index bb831f228..ed07e8cbf 100644 --- a/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/invocations/StaticInvocationHandlerTest.java +++ b/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/invocations/StaticInvocationHandlerTest.java @@ -36,6 +36,7 @@ import fr.quatrevieux.araknemu.game.spell.effect.area.CellArea; import fr.quatrevieux.araknemu.game.spell.effect.target.SpellEffectTarget; import fr.quatrevieux.araknemu.network.game.out.fight.action.ActionEffect; +import org.apache.logging.log4j.Logger; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mockito; @@ -59,7 +60,7 @@ public void setUp() throws Exception { dataSet.pushMonsterTemplateInvocations().pushMonsterSpellsInvocations().pushRewardItems(); fight = createFight(); - fight.register(new AiModule(container.get(AiFactory.class))); + fight.register(new AiModule(container.get(AiFactory.class), fight, Mockito.mock(Logger.class))); fight.nextState(); caster = player.fighter(); diff --git a/src/test/java/fr/quatrevieux/araknemu/game/fight/module/AiModuleTest.java b/src/test/java/fr/quatrevieux/araknemu/game/fight/module/AiModuleTest.java index 5f855741a..29075ffcb 100644 --- a/src/test/java/fr/quatrevieux/araknemu/game/fight/module/AiModuleTest.java +++ b/src/test/java/fr/quatrevieux/araknemu/game/fight/module/AiModuleTest.java @@ -23,12 +23,17 @@ 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.ai.memory.MemoryKey; import fr.quatrevieux.araknemu.game.fight.fighter.Fighter; import fr.quatrevieux.araknemu.game.fight.turn.FightTurn; +import groovy.util.logging.Log; import io.github.artsok.RepeatedIfExceptionsTest; +import org.apache.logging.log4j.Logger; +import org.checkerframework.checker.nullness.qual.Nullable; 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.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -38,7 +43,7 @@ class AiModuleTest extends FightBaseCase { void fighterInitialized() throws Exception { Fight fight = createPvmFight(); - fight.register(new AiModule(container.get(AiFactory.class))); + fight.register(new AiModule(container.get(AiFactory.class), fight, Mockito.mock(Logger.class))); fight.nextState(); for (Fighter monster : fight.team(1).fighters()) { @@ -51,7 +56,7 @@ void fighterInitialized() throws Exception { @Test void turnStartedWithoutAi() throws Exception { Fight fight = createPvmFight(); - fight.register(new AiModule(container.get(AiFactory.class))); + fight.register(new AiModule(container.get(AiFactory.class), fight, Mockito.mock(Logger.class))); fight.nextState(); fight.turnList().start(); @@ -70,7 +75,7 @@ void turnStartedWithoutAi() throws Exception { @RepeatedIfExceptionsTest void turnStartedWithAi() throws Exception { Fight fight = createPvmFight(); - fight.register(new AiModule(container.get(AiFactory.class))); + fight.register(new AiModule(container.get(AiFactory.class), fight, Mockito.mock(Logger.class))); fight.nextState(); fight.turnList().start(); requestStack.clear(); @@ -89,4 +94,31 @@ void turnStartedWithAi() throws Exception { // Move action started requestStack.assertOne("GA0;1;-2;ab-fbGdbU"); } + + @RepeatedIfExceptionsTest + void exceptionOnTurnStartShouldStopTheTurn() throws Exception { + Logger logger = Mockito.mock(Logger.class); + + Fight fight = createPvmFight(); + fight.register(new AiModule(container.get(AiFactory.class), fight, logger)); + fight.nextState(); + fight.turnList().start(); + + // The AI will throw an exception on start + Fighter fighter = fight.turnList().fighters().get(1); + fighter.attachment(FighterAI.class).set(new MemoryKey() { + @Override + public @Nullable Object refresh(Object value) { + throw new RuntimeException("error"); + } + }, true); + + requestStack.clear(); + fight.turnList().current().get().stop(); + + Mockito.verify(logger).debug("Starting AI for {}", fighter); + Mockito.verify(logger).error(Mockito.eq("Error during AI execution. Stop the turn."), Mockito.any(RuntimeException.class)); + + assertEquals(fight.turnList().fighters().get(2), fight.turnList().current().get().fighter()); + } }