diff --git a/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/buff/Buff.java b/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/buff/Buff.java index 55f4d2dce..985e4f0de 100644 --- a/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/buff/Buff.java +++ b/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/buff/Buff.java @@ -108,7 +108,7 @@ public void decrementRemainingTurns() { * Increment remaining turns * Use this method when a self-buff is added */ - void incrementRemainingTurns() { + public void incrementRemainingTurns() { if (remainingTurns != -1) { ++remainingTurns; } diff --git a/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/buff/BuffList.java b/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/buff/BuffList.java index 3877e5276..693172805 100644 --- a/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/buff/BuffList.java +++ b/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/buff/BuffList.java @@ -59,17 +59,19 @@ public Stream stream() { @Override public void add(Buff buff) { + final boolean isPlayingFighter = fighter.isPlaying(); + buffs.add(buff); buff.hook().onBuffStarted(buff); - if (buff.remainingTurns() == 0) { + if (buff.remainingTurns() == 0 && !isPlayingFighter) { buff.incrementRemainingTurns(); } fighter.fight().send(new AddBuff(buff)); // Add one turn when it's the turn of the current fighter - if (fighter.isPlaying()) { + if (isPlayingFighter) { buff.incrementRemainingTurns(); } } diff --git a/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/AbstractAlterCharacteristicHandler.java b/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/AbstractAlterCharacteristicHandler.java index 350fd2747..89470c095 100644 --- a/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/AbstractAlterCharacteristicHandler.java +++ b/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/AbstractAlterCharacteristicHandler.java @@ -46,7 +46,7 @@ public AbstractAlterCharacteristicHandler(AlterCharacteristicHook hook, boolean @Override public void handle(FightCastScope cast, FightCastScope.EffectScope effect) { - throw new UnsupportedOperationException("Alter characteristic effect must be used as a buff"); + buff(cast, effect); } @Override diff --git a/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/misc/ApplySpellOnStartTurnHandler.java b/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/misc/ApplySpellOnStartTurnHandler.java index 52b70bd84..7726ee1bd 100644 --- a/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/misc/ApplySpellOnStartTurnHandler.java +++ b/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/misc/ApplySpellOnStartTurnHandler.java @@ -51,7 +51,13 @@ public ApplySpellOnStartTurnHandler(Fight fight, SpellService spellService) { @Override public void handle(FightCastScope cast, FightCastScope.EffectScope effect) { - buff(cast, effect); + for (Fighter target : effect.targets()) { + final Buff buff = new Buff(effect.effect(), cast.action(), cast.caster(), target, this); + + // The duration must be at least 1 to ensure that the effect will be applied at the next start turn + buff.incrementRemainingTurns(); + target.buffs().add(buff); + } } @Override diff --git a/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/misc/SkipTurnHandler.java b/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/misc/SkipTurnHandler.java index f41c7e4f2..8102aafa3 100644 --- a/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/misc/SkipTurnHandler.java +++ b/src/main/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/misc/SkipTurnHandler.java @@ -39,7 +39,13 @@ public SkipTurnHandler(Fight fight) { @Override public void handle(FightCastScope cast, FightCastScope.EffectScope effect) { - buff(cast, effect); + for (Fighter target : effect.targets()) { + final Buff buff = new Buff(effect.effect(), cast.action(), cast.caster(), target, this); + + // The duration must be at least 1 to ensure that the next turn will be skipped + buff.incrementRemainingTurns(); + target.buffs().add(buff); + } } @Override diff --git a/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/buff/BuffListTest.java b/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/buff/BuffListTest.java index 870ee776e..114049bbd 100644 --- a/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/buff/BuffListTest.java +++ b/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/buff/BuffListTest.java @@ -140,6 +140,23 @@ void addWhenTurnIsActiveShouldIncrementRemainingTurns() { assertEquals(2, buff.remainingTurns()); } + @Test + void addWhenTurnIsActiveShouldIncrementRemainingTurnsWithADurationOfZero() { + fight.nextState(); + fight.turnList().start(); + + SpellEffect effect = Mockito.mock(SpellEffect.class); + BuffHook hook = Mockito.mock(BuffHook.class); + + Mockito.when(effect.duration()).thenReturn(0); + + Buff buff = new Buff(effect, Mockito.mock(Spell.class), player.fighter(), player.fighter(), hook); + + list.add(buff); + + assertEquals(1, buff.remainingTurns()); + } + @Test void onStartTurnWithoutBuff() { assertTrue(list.onStartTurn()); diff --git a/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/AddCharacteristicHandlerTest.java b/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/AddCharacteristicHandlerTest.java index 95585c53f..bdbdc1e7f 100644 --- a/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/AddCharacteristicHandlerTest.java +++ b/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/AddCharacteristicHandlerTest.java @@ -42,6 +42,7 @@ import java.util.Optional; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -77,12 +78,22 @@ void handle() { SpellConstraints constraints = Mockito.mock(SpellConstraints.class); Mockito.when(effect.area()).thenReturn(new CellArea()); + Mockito.when(effect.effect()).thenReturn(123); + Mockito.when(effect.min()).thenReturn(50); + Mockito.when(effect.max()).thenReturn(60); Mockito.when(effect.target()).thenReturn(SpellEffectTarget.DEFAULT); Mockito.when(spell.constraints()).thenReturn(constraints); Mockito.when(constraints.freeCell()).thenReturn(false); FightCastScope scope = makeCastScope(caster, spell, effect, caster.cell()); - assertThrows(UnsupportedOperationException.class, () -> handler.handle(scope, scope.effects().get(0))); + handler.handle(scope, scope.effects().get(0)); + + Optional buff1 = caster.buffs().stream().filter(buff -> buff.effect().effect() == 123).findFirst(); + + assertTrue(buff1.isPresent()); + assertBetween(50, 60, buff1.get().effect().min()); + assertEquals(1, buff1.get().remainingTurns()); + assertTrue(buff1.get().canBeDispelled()); } @Test diff --git a/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/AddNotDispellableCharacteristicHandlerTest.java b/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/AddNotDispellableCharacteristicHandlerTest.java index 54681310b..4f1a41e05 100644 --- a/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/AddNotDispellableCharacteristicHandlerTest.java +++ b/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/AddNotDispellableCharacteristicHandlerTest.java @@ -78,12 +78,22 @@ void handle() { SpellConstraints constraints = Mockito.mock(SpellConstraints.class); Mockito.when(effect.area()).thenReturn(new CellArea()); + Mockito.when(effect.effect()).thenReturn(123); + Mockito.when(effect.min()).thenReturn(50); + Mockito.when(effect.max()).thenReturn(60); Mockito.when(effect.target()).thenReturn(SpellEffectTarget.DEFAULT); Mockito.when(spell.constraints()).thenReturn(constraints); Mockito.when(constraints.freeCell()).thenReturn(false); FightCastScope scope = makeCastScope(caster, spell, effect, caster.cell()); - assertThrows(UnsupportedOperationException.class, () -> handler.handle(scope, scope.effects().get(0))); + handler.handle(scope, scope.effects().get(0)); + + Optional buff1 = caster.buffs().stream().filter(buff -> buff.effect().effect() == 123).findFirst(); + + assertTrue(buff1.isPresent()); + assertBetween(50, 60, buff1.get().effect().min()); + assertEquals(1, buff1.get().remainingTurns()); + assertFalse(buff1.get().canBeDispelled()); } @Test diff --git a/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/AddVitalityHandlerTest.java b/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/AddVitalityHandlerTest.java index 5b489cc80..6d5355cf9 100644 --- a/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/AddVitalityHandlerTest.java +++ b/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/AddVitalityHandlerTest.java @@ -78,12 +78,22 @@ void handle() { SpellConstraints constraints = Mockito.mock(SpellConstraints.class); Mockito.when(effect.area()).thenReturn(new CellArea()); + Mockito.when(effect.effect()).thenReturn(123); + Mockito.when(effect.min()).thenReturn(50); + Mockito.when(effect.max()).thenReturn(60); Mockito.when(effect.target()).thenReturn(SpellEffectTarget.DEFAULT); Mockito.when(spell.constraints()).thenReturn(constraints); Mockito.when(constraints.freeCell()).thenReturn(false); FightCastScope scope = makeCastScope(caster, spell, effect, caster.cell()); - assertThrows(UnsupportedOperationException.class, () -> handler.handle(scope, scope.effects().get(0))); + handler.handle(scope, scope.effects().get(0)); + + Optional buff1 = caster.buffs().stream().filter(buff -> buff.effect().effect() == 123).findFirst(); + + assertTrue(buff1.isPresent()); + assertBetween(50, 60, buff1.get().effect().min()); + assertEquals(1, buff1.get().remainingTurns()); + assertTrue(buff1.get().canBeDispelled()); } @Test diff --git a/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/AddVitalityNotDispellableHandlerTest.java b/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/AddVitalityNotDispellableHandlerTest.java index 3cf76e013..553c5f92c 100644 --- a/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/AddVitalityNotDispellableHandlerTest.java +++ b/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/AddVitalityNotDispellableHandlerTest.java @@ -79,12 +79,22 @@ void handle() { SpellConstraints constraints = Mockito.mock(SpellConstraints.class); Mockito.when(effect.area()).thenReturn(new CellArea()); + Mockito.when(effect.effect()).thenReturn(123); + Mockito.when(effect.min()).thenReturn(50); + Mockito.when(effect.max()).thenReturn(60); Mockito.when(effect.target()).thenReturn(SpellEffectTarget.DEFAULT); Mockito.when(spell.constraints()).thenReturn(constraints); Mockito.when(constraints.freeCell()).thenReturn(false); FightCastScope scope = makeCastScope(caster, spell, effect, caster.cell()); - assertThrows(UnsupportedOperationException.class, () -> handler.handle(scope, scope.effects().get(0))); + handler.handle(scope, scope.effects().get(0)); + + Optional buff1 = caster.buffs().stream().filter(buff -> buff.effect().effect() == 123).findFirst(); + + assertTrue(buff1.isPresent()); + assertBetween(50, 60, buff1.get().effect().min()); + assertEquals(1, buff1.get().remainingTurns()); + assertFalse(buff1.get().canBeDispelled()); } @Test diff --git a/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/RemoveCharacteristicHandlerTest.java b/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/RemoveCharacteristicHandlerTest.java index b230d89b3..0bf2f2c81 100644 --- a/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/RemoveCharacteristicHandlerTest.java +++ b/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/RemoveCharacteristicHandlerTest.java @@ -40,6 +40,7 @@ import java.util.Optional; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -75,12 +76,22 @@ void handle() { SpellConstraints constraints = Mockito.mock(SpellConstraints.class); Mockito.when(effect.area()).thenReturn(new CellArea()); + Mockito.when(effect.effect()).thenReturn(123); + Mockito.when(effect.min()).thenReturn(50); + Mockito.when(effect.max()).thenReturn(60); Mockito.when(effect.target()).thenReturn(SpellEffectTarget.DEFAULT); Mockito.when(spell.constraints()).thenReturn(constraints); Mockito.when(constraints.freeCell()).thenReturn(false); FightCastScope scope = makeCastScope(caster, spell, effect, caster.cell()); - assertThrows(UnsupportedOperationException.class, () -> handler.handle(scope, scope.effects().get(0))); + handler.handle(scope, scope.effects().get(0)); + + Optional buff1 = caster.buffs().stream().filter(buff -> buff.effect().effect() == 123).findFirst(); + + assertTrue(buff1.isPresent()); + assertBetween(50, 60, buff1.get().effect().min()); + assertEquals(1, buff1.get().remainingTurns()); + assertTrue(buff1.get().canBeDispelled()); } @Test diff --git a/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/RemoveVitalityHandlerTest.java b/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/RemoveVitalityHandlerTest.java index b07a3b5d9..4d96e9ee2 100644 --- a/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/RemoveVitalityHandlerTest.java +++ b/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/characteristic/RemoveVitalityHandlerTest.java @@ -78,12 +78,22 @@ void handle() { SpellConstraints constraints = Mockito.mock(SpellConstraints.class); Mockito.when(effect.area()).thenReturn(new CellArea()); + Mockito.when(effect.effect()).thenReturn(123); + Mockito.when(effect.min()).thenReturn(50); + Mockito.when(effect.max()).thenReturn(60); Mockito.when(effect.target()).thenReturn(SpellEffectTarget.DEFAULT); Mockito.when(spell.constraints()).thenReturn(constraints); Mockito.when(constraints.freeCell()).thenReturn(false); FightCastScope scope = makeCastScope(caster, spell, effect, caster.cell()); - assertThrows(UnsupportedOperationException.class, () -> handler.handle(scope, scope.effects().get(0))); + handler.handle(scope, scope.effects().get(0)); + + Optional buff1 = caster.buffs().stream().filter(buff -> buff.effect().effect() == 123).findFirst(); + + assertTrue(buff1.isPresent()); + assertBetween(50, 60, buff1.get().effect().min()); + assertEquals(1, buff1.get().remainingTurns()); + assertTrue(buff1.get().canBeDispelled()); } @Test diff --git a/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/misc/ApplySpellOnStartTurnHandlerTest.java b/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/misc/ApplySpellOnStartTurnHandlerTest.java index 97234e442..7c0996864 100644 --- a/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/misc/ApplySpellOnStartTurnHandlerTest.java +++ b/src/test/java/fr/quatrevieux/araknemu/game/fight/castable/effect/handler/misc/ApplySpellOnStartTurnHandlerTest.java @@ -151,6 +151,35 @@ void handleShouldBeConsideredAsBuff() { assertEquals(1, found.get().remainingTurns()); } + @Test + void handleOnSelfShouldBeConsideredAsBuff() { + SpellEffect effect = Mockito.mock(SpellEffect.class); + Spell spell = Mockito.mock(Spell.class); + SpellConstraints constraints = Mockito.mock(SpellConstraints.class); + + Mockito.when(effect.effect()).thenReturn(787); + Mockito.when(effect.min()).thenReturn(183); + Mockito.when(effect.max()).thenReturn(1); + Mockito.when(effect.area()).thenReturn(new CellArea()); + Mockito.when(effect.target()).thenReturn(SpellEffectTarget.DEFAULT); + Mockito.when(effect.duration()).thenReturn(0); + Mockito.when(spell.constraints()).thenReturn(constraints); + Mockito.when(constraints.freeCell()).thenReturn(false); + + FightCastScope scope = makeCastScope(caster, spell, effect, caster.cell()); + handler.handle(scope, scope.effects().get(0)); + + Optional found = caster.buffs().stream().filter(buff -> buff.effect().equals(effect)).findFirst(); + + assertTrue(found.isPresent()); + assertEquals(caster, found.get().caster()); + assertEquals(caster, found.get().target()); + assertEquals(effect, found.get().effect()); + assertEquals(spell, found.get().action()); + assertEquals(handler, found.get().hook()); + assertEquals(2, found.get().remainingTurns()); + } + @Test void startTurnShouldApplySpell() { SpellEffect effect = Mockito.mock(SpellEffect.class);