Skip to content

Commit

Permalink
[fight] Arakne#27 Add give percent life effect
Browse files Browse the repository at this point in the history
  • Loading branch information
vincent4vx committed Jan 23, 2022
1 parent 396f302 commit db2ba8d
Show file tree
Hide file tree
Showing 5 changed files with 305 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*
* Copyright (c) 2017-2021 Vincent Quatrevieux
*/

package fr.quatrevieux.araknemu.game.fight.castable.effect.handler.heal;

import fr.quatrevieux.araknemu.game.fight.castable.CastScope;
import fr.quatrevieux.araknemu.game.fight.castable.effect.EffectValue;
import fr.quatrevieux.araknemu.game.fight.castable.effect.handler.EffectHandler;
import fr.quatrevieux.araknemu.game.fight.fighter.ActiveFighter;
import fr.quatrevieux.araknemu.game.fight.fighter.PassiveFighter;

/**
* Heal fighters by giving its own life, in percent of current life
*/
public final class GivePercentLifeHandler implements EffectHandler {
@Override
public void handle(CastScope cast, CastScope.EffectScope effect) {
final ActiveFighter caster = cast.caster();
final int heal = new EffectValue(effect.effect()).value() * caster.life().current() / 100;

caster.life().alter(caster, -heal);

for (PassiveFighter target : effect.targets()) {
if (!target.equals(caster)) {
target.life().alter(caster, heal);
}
}
}

@Override
public void buff(CastScope cast, CastScope.EffectScope effect) {
throw new UnsupportedOperationException("Cannot use give percent life effect as buff");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
import fr.quatrevieux.araknemu.game.fight.castable.effect.handler.damage.PunishmentHandler;
import fr.quatrevieux.araknemu.game.fight.castable.effect.handler.damage.StealLifeHandler;
import fr.quatrevieux.araknemu.game.fight.castable.effect.handler.heal.FixedHealHandler;
import fr.quatrevieux.araknemu.game.fight.castable.effect.handler.heal.GivePercentLifeHandler;
import fr.quatrevieux.araknemu.game.fight.castable.effect.handler.heal.HealHandler;
import fr.quatrevieux.araknemu.game.fight.castable.effect.handler.heal.HealOnDamageHandler;
import fr.quatrevieux.araknemu.game.fight.castable.effect.handler.misc.DispelHandler;
Expand Down Expand Up @@ -110,6 +111,7 @@ public void effects(EffectsHandler handler) {
handler.register(672, new PunishmentHandler(fight));

handler.register(81, new HealOnDamageHandler());
handler.register(90, new GivePercentLifeHandler());
handler.register(108, new HealHandler());
handler.register(143, new FixedHealHandler());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,7 @@ public GameDataSet pushFunctionalSpells() throws SQLException, ContainerExceptio
"(951, 'Rocaille', -1, '0,0,0', '86,15,,,0,0,0d0+15|86,30,,,0,0,0d0+30|4|1|1|50|50|false|true|false|false|0|0|0|0|PaPa||18;19;3;1;41|0|true', '86,15,,,0,0,0d0+15|86,30,,,0,0,0d0+30|4|1|1|50|50|false|true|false|false|0|0|0|0|PaPa||18;19;3;1;41|0|true', '86,15,,,0,0,0d0+15|86,30,,,0,0,0d0+30|4|1|1|50|50|false|true|false|false|0|0|0|0|PaPa||18;19;3;1;41|0|true', '86,15,,,0,0,0d0+15|86,30,,,0,0,0d0+30|4|1|1|50|50|false|true|false|false|0|0|0|0|PaPa||18;19;3;1;41|0|true', '86,15,,,0,0,0d0+15|86,30,,,0,0,0d0+30|4|1|1|50|50|false|true|false|false|0|0|0|0|PaPa||18;19;3;1;41|0|true', '', '')",
"(1708, 'Correction Bwork', 0, '0,1,1', '279,30,,,0,0,0d0+30|279,35,,,0,0,0d0+35|5|1|1|50|100|false|true|false|false|0|0|0|2|PaPa||18;19;3;1;41|0|false', '279,30,,,0,0,0d0+30|279,35,,,0,0,0d0+35|5|1|1|50|100|false|true|false|false|0|0|0|2|PaPa||18;19;3;1;41|0|false', '279,30,,,0,0,0d0+30|279,35,,,0,0,0d0+35|5|1|1|50|100|false|true|false|false|0|0|0|2|PaPa||18;19;3;1;41|0|false', '279,30,,,0,0,0d0+30|279,35,,,0,0,0d0+35|5|1|1|50|100|false|true|false|false|0|0|0|2|PaPa||18;19;3;1;41|0|false', '279,30,,,0,0,0d0+30|279,35,,,0,0,0d0+35|5|1|1|50|100|false|true|false|false|0|0|0|2|PaPa||18;19;3;1;41|0|false', '', '')",
"(446, 'Punition', 0, '0,6,1', '672,30,,,0,0,0d0+30|672,35,,,0,0,0d0+35|5|1|1|50|100|false|true|false|false|0|0|0|6|PaPa||18;19;3;1;41|60|false', '672,30,,,0,0,0d0+30|672,35,,,0,0,0d0+35|5|1|1|50|100|false|true|false|false|0|0|0|5|PaPa||18;19;3;1;41|60|false', '672,30,,,0,0,0d0+30|672,35,,,0,0,0d0+35|5|1|1|50|100|false|true|false|false|0|0|0|4|PaPa||18;19;3;1;41|60|false', '672,30,,,0,0,0d0+30|672,35,,,0,0,0d0+35|5|1|1|50|100|false|true|false|false|0|0|0|3|PaPa||18;19;3;1;41|60|false', '672,30,,,0,0,0d0+30|672,35,,,0,0,0d0+35|5|1|1|50|100|false|true|false|false|0|0|0|2|PaPa||18;19;3;1;41|60|false', '672,30,,,0,0,0d0+30|672,35,,,0,0,0d0+35|4|1|1|50|100|false|true|false|false|0|0|0|2|PaPa||18;19;3;1;41|160|false', '')",
"(435, 'Transfert de Vie', 1050, '51,1,1', '90,10,,,0,0,0d0+10|90,10,,,0,0,0d0+10;108,10,,,0,0,0d0+10|2|0|0|50|100|false|false|false|false|0|1|0|0|CbCbPa||18;19;3;1;41|90|false', '90,10,,,0,0,0d0+10|90,10,,,0,0,0d0+10;108,10,,,0,0,0d0+10|2|0|0|50|100|false|false|false|false|0|2|0|0|CbCbPa||18;19;3;1;41|90|false', '90,10,,,0,0,0d0+10|90,10,,,0,0,0d0+10;108,10,,,0,0,0d0+10|2|0|0|50|100|false|false|false|false|0|3|0|0|CbCbPa||18;19;3;1;41|90|false', '90,10,,,0,0,0d0+10|90,10,,,0,0,0d0+10;108,10,,,0,0,0d0+10|2|0|0|50|100|false|false|false|false|0|4|0|0|CcCcPa||18;19;3;1;41|90|false', '90,10,,,0,0,0d0+10|90,10,,,0,0,0d0+10;108,10,,,0,0,0d0+10|2|0|0|50|100|false|false|false|false|0|5|0|0|CdCdPa||18;19;3;1;41|90|false', '90,10,,,0,0,0d0+10|90,10,,,0,0,0d0+10;108,10,,,0,0,0d0+10|2|0|0|50|100|false|false|false|false|0|6|0|0|CeCePa||18;19;3;1;41|190|false', '6')",
}, ",") + ";"
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -805,6 +805,23 @@ void punishment() {
assertEquals(122, damage);
}

@Test
void givePercentLife() {
List<Fighter> fighters = configureFight(builder -> builder
.addSelf(fb -> fb.cell(185).charac(Characteristic.LUCK, 100).currentLife(200).maxLife(200))
.addAlly(fb -> fb.cell(199).currentLife(100).maxLife(200))
.addEnemy(fb -> fb.cell(221))
);

castNormal(435, fighters.get(0).cell());

assertEquals(120, fighters.get(1).life().current());
assertEquals(180, fighters.get(0).life().current());

requestStack.assertOne(ActionEffect.alterLifePoints(fighters.get(0), fighters.get(0), -20));
requestStack.assertOne(ActionEffect.alterLifePoints(fighters.get(0), fighters.get(1), 20));
}

private List<Fighter> configureFight(Consumer<FightBuilder> configurator) {
fight.cancel(true);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*
* Copyright (c) 2017-2022 Vincent Quatrevieux
*/

package fr.quatrevieux.araknemu.game.fight.castable.effect.handler.heal;

import fr.quatrevieux.araknemu.data.value.EffectArea;
import fr.quatrevieux.araknemu.game.fight.Fight;
import fr.quatrevieux.araknemu.game.fight.FightBaseCase;
import fr.quatrevieux.araknemu.game.fight.castable.CastScope;
import fr.quatrevieux.araknemu.game.fight.fighter.player.PlayerFighter;
import fr.quatrevieux.araknemu.game.spell.Spell;
import fr.quatrevieux.araknemu.game.spell.SpellConstraints;
import fr.quatrevieux.araknemu.game.spell.effect.SpellEffect;
import fr.quatrevieux.araknemu.game.spell.effect.area.CellArea;
import fr.quatrevieux.araknemu.game.spell.effect.area.CircleArea;
import fr.quatrevieux.araknemu.game.spell.effect.target.SpellEffectTarget;
import fr.quatrevieux.araknemu.network.game.out.fight.action.ActionEffect;
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.assertThrows;

class GivePercentLifeHandlerTest extends FightBaseCase {
private Fight fight;
private PlayerFighter caster;
private PlayerFighter target;
private GivePercentLifeHandler handler;
private int lastTargetLife;

@Override
@BeforeEach
public void setUp() throws Exception {
super.setUp();

fight = createFight();
fight.nextState();

caster = player.fighter();
target = other.fighter();

target.move(fight.map().get(123));
target.life().alter(target, -30);
lastTargetLife = target.life().current();

handler = new GivePercentLifeHandler();

requestStack.clear();
}

@Test
void applyRandomEffect() {
SpellEffect effect = Mockito.mock(SpellEffect.class);
Spell spell = Mockito.mock(Spell.class);
SpellConstraints constraints = Mockito.mock(SpellConstraints.class);

Mockito.when(effect.min()).thenReturn(5);
Mockito.when(effect.max()).thenReturn(10);
Mockito.when(effect.area()).thenReturn(new CellArea());
Mockito.when(effect.target()).thenReturn(SpellEffectTarget.DEFAULT);
Mockito.when(spell.constraints()).thenReturn(constraints);
Mockito.when(constraints.freeCell()).thenReturn(false);

CastScope scope = makeCastScope(caster, spell, effect, target.cell());
handler.handle(scope, scope.effects().get(0));

assertBetween(14, 29, computeHeal());

requestStack.assertAll(
ActionEffect.alterLifePoints(caster, caster, -computeHeal()),
ActionEffect.alterLifePoints(caster, target, computeHeal())
);
}

@Test
void applyFixedEffect() {
SpellEffect effect = Mockito.mock(SpellEffect.class);
Spell spell = Mockito.mock(Spell.class);
SpellConstraints constraints = Mockito.mock(SpellConstraints.class);

Mockito.when(effect.min()).thenReturn(10);
Mockito.when(effect.area()).thenReturn(new CellArea());
Mockito.when(effect.target()).thenReturn(SpellEffectTarget.DEFAULT);
Mockito.when(spell.constraints()).thenReturn(constraints);
Mockito.when(constraints.freeCell()).thenReturn(false);

CastScope scope = makeCastScope(caster, spell, effect, target.cell());
handler.handle(scope, scope.effects().get(0));

assertEquals(29, computeHeal());
requestStack.assertAll(
ActionEffect.alterLifePoints(caster, caster, -29),
ActionEffect.alterLifePoints(caster, target, 29)
);
}

@Test
void applyToEmptyCellShouldRemoveCasterLife() {
SpellEffect effect = Mockito.mock(SpellEffect.class);
Spell spell = Mockito.mock(Spell.class);
SpellConstraints constraints = Mockito.mock(SpellConstraints.class);

Mockito.when(effect.min()).thenReturn(10);
Mockito.when(effect.area()).thenReturn(new CellArea());
Mockito.when(effect.target()).thenReturn(SpellEffectTarget.DEFAULT);
Mockito.when(spell.constraints()).thenReturn(constraints);
Mockito.when(constraints.freeCell()).thenReturn(false);

CastScope scope = makeCastScope(caster, spell, effect, fight.map().get(5));
handler.handle(scope, scope.effects().get(0));

requestStack.assertLast(ActionEffect.alterLifePoints(caster, caster, -29));
}

@Test
void applyToEmptyCellWithArea() {
SpellEffect effect = Mockito.mock(SpellEffect.class);
Spell spell = Mockito.mock(Spell.class);
SpellConstraints constraints = Mockito.mock(SpellConstraints.class);

Mockito.when(effect.min()).thenReturn(10);
Mockito.when(effect.area()).thenReturn(new CircleArea(new EffectArea(EffectArea.Type.CIRCLE, 2)));
Mockito.when(effect.target()).thenReturn(SpellEffectTarget.DEFAULT);
Mockito.when(spell.constraints()).thenReturn(constraints);
Mockito.when(constraints.freeCell()).thenReturn(false);

CastScope scope = makeCastScope(caster, spell, effect, fight.map().get(122));
handler.handle(scope, scope.effects().get(0));

assertEquals(29, computeHeal());

requestStack.assertAll(
ActionEffect.alterLifePoints(caster, caster, -29),
ActionEffect.alterLifePoints(caster, target, 29)
);
}

@Test
void applyShouldIgnoreSelfTarget() {
SpellEffect effect = Mockito.mock(SpellEffect.class);
Spell spell = Mockito.mock(Spell.class);
SpellConstraints constraints = Mockito.mock(SpellConstraints.class);

caster.life().alter(caster, -30);
requestStack.clear();

Mockito.when(effect.min()).thenReturn(10);
Mockito.when(effect.area()).thenReturn(new CircleArea(new EffectArea(EffectArea.Type.CIRCLE, 20)));
Mockito.when(effect.target()).thenReturn(SpellEffectTarget.DEFAULT);
Mockito.when(spell.constraints()).thenReturn(constraints);
Mockito.when(constraints.freeCell()).thenReturn(false);

CastScope scope = makeCastScope(caster, spell, effect, fight.map().get(122));
handler.handle(scope, scope.effects().get(0));

requestStack.assertAll(
ActionEffect.alterLifePoints(caster, caster, -26),
ActionEffect.alterLifePoints(caster, target, 26)
);
}

@Test
void withMultipleAlliesShouldOnlyRemoveCasterLifeOnce() {
fight = fightBuilder()
.addSelf(fb -> fb.currentLife(250).maxLife(250).cell(298))
.addAlly(fb -> fb.currentLife(50).maxLife(100).cell(312))
.addAlly(fb -> fb.currentLife(50).maxLife(100).cell(313))
.addEnemy(fb -> fb.cell(293))
.build(true)
;

fight.nextState();

caster = (PlayerFighter) fight.fighters().get(0);

SpellEffect effect = Mockito.mock(SpellEffect.class);
Spell spell = Mockito.mock(Spell.class);
SpellConstraints constraints = Mockito.mock(SpellConstraints.class);

requestStack.clear();

Mockito.when(effect.min()).thenReturn(10);
Mockito.when(effect.area()).thenReturn(new CircleArea(new EffectArea(EffectArea.Type.CIRCLE, 1)));
Mockito.when(effect.target()).thenReturn(SpellEffectTarget.DEFAULT);
Mockito.when(spell.constraints()).thenReturn(constraints);
Mockito.when(constraints.freeCell()).thenReturn(false);

CastScope scope = makeCastScope(caster, spell, effect, fight.map().get(298));
handler.handle(scope, scope.effects().get(0));

requestStack.assertAll(
ActionEffect.alterLifePoints(caster, caster, -25),
ActionEffect.alterLifePoints(caster, fight.fighters().get(1), 25),
ActionEffect.alterLifePoints(caster, fight.fighters().get(2), 25)
);
}

@Test
void buffNotSupported() {
SpellEffect effect = Mockito.mock(SpellEffect.class);
Spell spell = Mockito.mock(Spell.class);
SpellConstraints constraints = Mockito.mock(SpellConstraints.class);

Mockito.when(effect.min()).thenReturn(10);
Mockito.when(effect.area()).thenReturn(new CellArea());
Mockito.when(effect.target()).thenReturn(SpellEffectTarget.DEFAULT);
Mockito.when(effect.duration()).thenReturn(5);
Mockito.when(spell.constraints()).thenReturn(constraints);
Mockito.when(constraints.freeCell()).thenReturn(false);

CastScope scope = makeCastScope(caster, spell, effect, target.cell());
assertThrows(UnsupportedOperationException.class, () -> handler.buff(scope, scope.effects().get(0)));
}

private int computeHeal() {
return target.life().current() - lastTargetLife;
}
}

0 comments on commit db2ba8d

Please sign in to comment.