Skip to content

Commit

Permalink
feat(fight): Add hooks onDamageApplied and onHealApplied
Browse files Browse the repository at this point in the history
  • Loading branch information
vincent4vx committed Mar 2, 2024
1 parent d5356cd commit 2ab07c7
Show file tree
Hide file tree
Showing 11 changed files with 197 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import fr.quatrevieux.araknemu.game.fight.fighter.Fighter;
import fr.quatrevieux.araknemu.game.fight.fighter.FighterData;
import fr.quatrevieux.araknemu.game.fight.turn.Turn;
import org.checkerframework.checker.index.qual.NonNegative;
import org.checkerframework.checker.index.qual.Positive;

/**
Expand Down Expand Up @@ -177,6 +178,42 @@ public default void onBuffDamage(Buff buff, Buff poison, Damage value) {
*/
public default void onLifeAltered(Buff buff, int value) {}

/**
* The fighter has suffered a damage, so its life has been altered
* By default, this method will forward the call to {@link BuffHook#onLifeAltered(Buff, int)}
*
* This buff is always called when damage is applied, even if the damage is completely absorbed,
* or in case of direct or indirect damage.
*
* Unlike {@link BuffHook#onDamage(Buff, Damage)}, the effects has already been applied
*
* Note: this hook is not called if the attack has killed the fighter
*
* @param buff The active buff
* @param value Altered life value. Can be 0 when the effect is completely absorbed
*
* @see fr.quatrevieux.araknemu.game.fight.fighter.FighterLife#damage(Fighter, int)
* @see #onDirectDamageApplied(Buff, Fighter, int) To hook only damage applied by direct attack
*/
public default void onDamageApplied(Buff buff, @NonNegative int value) {
onLifeAltered(buff, -value);
}

/**
* The fighter life has been healed
* By default, this method will forward the call to {@link BuffHook#onLifeAltered(Buff, int)}
*
* This hook is called after heal is applied.
*
* @param buff The active buff
* @param value Altered life value. Can be 0 when the fighter is already full life, so heal has no effect
*
* @see fr.quatrevieux.araknemu.game.fight.fighter.FighterLife#heal(Fighter, int)
*/
public default void onHealApplied(Buff buff, @NonNegative int value) {
onLifeAltered(buff, value);
}

/**
* Damage has been reflected by the cast target
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import fr.quatrevieux.araknemu.game.fight.turn.Turn;
import fr.quatrevieux.araknemu.network.game.out.fight.AddBuff;
import fr.quatrevieux.araknemu.util.SafeLinkedList;
import org.checkerframework.checker.index.qual.NonNegative;
import org.checkerframework.checker.index.qual.Positive;

import java.util.Iterator;
Expand Down Expand Up @@ -141,9 +142,16 @@ public void onDirectDamageApplied(Fighter caster, @Positive int value) {
}

@Override
public void onLifeAltered(int value) {
public void onHealApplied(@NonNegative int value) {
for (Buff buff : buffs) {
buff.hook().onLifeAltered(buff, value);
buff.hook().onHealApplied(buff, value);
}
}

@Override
public void onDamageApplied(@NonNegative int value) {
for (Buff buff : buffs) {
buff.hook().onDamageApplied(buff, value);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import fr.quatrevieux.araknemu.game.fight.castable.effect.handler.damage.ReflectedDamage;
import fr.quatrevieux.araknemu.game.fight.fighter.Fighter;
import fr.quatrevieux.araknemu.game.fight.turn.Turn;
import org.checkerframework.checker.index.qual.NonNegative;
import org.checkerframework.checker.index.qual.Positive;

/**
Expand Down Expand Up @@ -74,9 +75,14 @@ public interface Buffs extends Iterable<Buff> {
public void onDirectDamageApplied(Fighter caster, @Positive int value);

/**
* @see BuffHook#onLifeAltered(Buff, int)
* @see BuffHook#onDamageApplied(Buff, int)
*/
public void onLifeAltered(int value);
public void onDamageApplied(@NonNegative int value);

/**
* @see BuffHook#onHealApplied(Buff, int)
*/
public void onHealApplied(@NonNegative int value);

/**
* @see BuffHook#onReflectedDamage(Buff, ReflectedDamage)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,7 @@ public final class ApplyOnHeal extends AbstractEffectHookHandler {
protected BuffHook createHook(EffectHandler handler) {
return new BuffHook() {
@Override
public void onLifeAltered(Buff buff, int value) {
// @todo should be triggered even if heal is 0
if (value <= 0) {
return;
}

public void onHealApplied(Buff buff, int value) {
handler.applyFromHook(buff);
}
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ public boolean dead() {
this.current = current + actualHeal;

fighter.fight().dispatch(new FighterLifeChanged(fighter, caster, actualHeal));
fighter.buffs().onLifeAltered(actualHeal);
fighter.buffs().onHealApplied(actualHeal);

return actualHeal;
}
Expand All @@ -101,7 +101,7 @@ public boolean dead() {
dead = true;
fighter.fight().dispatch(new FighterDie(fighter, caster));
} else {
fighter.buffs().onLifeAltered(-actualDamage);
fighter.buffs().onDamageApplied(actualDamage);
}

return actualDamage;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public interface FighterLife extends Life {
* If the fighter is dead, or is full life, this method will do nothing
* If the value is higher that the current life left, the life will be set to the max life
*
* This method will trigger buffs {@link fr.quatrevieux.araknemu.game.fight.castable.effect.buff.Buffs#onLifeAltered(int)}
* This method will trigger buffs {@link fr.quatrevieux.araknemu.game.fight.castable.effect.buff.Buffs#onHealApplied(int)}
*
* @param caster The caster of the heal effect
* @param value The heal value
Expand All @@ -53,7 +53,7 @@ public interface FighterLife extends Life {
* If the fighter is dead, this method will do nothing
* If the value is higher that the current life, the fighter will be considered as dead, and its life will be set to 0
*
* This method will trigger buffs {@link fr.quatrevieux.araknemu.game.fight.castable.effect.buff.Buffs#onLifeAltered(int)}
* This method will trigger buffs {@link fr.quatrevieux.araknemu.game.fight.castable.effect.buff.Buffs#onDamageApplied(int)}
*
* @param caster The caster of the damage effect
* @param value The damage value
Expand All @@ -72,7 +72,7 @@ public interface FighterLife extends Life {
* If the fighter is dead, this method will do nothing
* If the value is higher that the current life, the fighter will be considered as dead, and its life will be set to 0
*
* This method will trigger buffs {@link fr.quatrevieux.araknemu.game.fight.castable.effect.buff.Buffs#onLifeAltered(int)}
* This method will trigger buffs {@link fr.quatrevieux.araknemu.game.fight.castable.effect.buff.Buffs#onDamageApplied(int)}
*
* @param caster The caster of the damage effect
* @param value The damage value
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,8 @@ void effectsHammer() {
assertEquals(0, weapon.effects().get(0).duration());
assertEquals(0, weapon.effects().get(0).probability());
assertEquals(WeaponEffectTarget.INSTANCE, weapon.effects().get(0).target());
assertFalse(weapon.effects().get(0).target().isHook());
assertEquals(0, weapon.effects().get(0).target().hookId());
assertEquals(0, weapon.effects().get(0).boost());
assertEquals(1, weapon.effects().get(0).area().size());
assertInstanceOf(CrossArea.class, weapon.effects().get(0).area());
Expand Down Expand Up @@ -150,6 +152,8 @@ void effectsSword() {
assertEquals(0, weapon.effects().get(0).duration());
assertEquals(0, weapon.effects().get(0).probability());
assertEquals(WeaponEffectTarget.INSTANCE, weapon.effects().get(0).target());
assertFalse(weapon.effects().get(0).target().isHook());
assertEquals(0, weapon.effects().get(0).target().hookId());
assertEquals(0, weapon.effects().get(0).boost());
assertEquals(0, weapon.effects().get(0).area().size());
assertInstanceOf(CellArea.class, weapon.effects().get(0).area());
Expand All @@ -169,6 +173,8 @@ void criticalEffect() {
assertEquals(0, weapon.criticalEffects().get(0).duration());
assertEquals(0, weapon.criticalEffects().get(0).probability());
assertEquals(WeaponEffectTarget.INSTANCE, weapon.criticalEffects().get(0).target());
assertFalse(weapon.criticalEffects().get(0).target().isHook());
assertEquals(0, weapon.criticalEffects().get(0).target().hookId());
assertEquals(0, weapon.criticalEffects().get(0).boost());
assertEquals(0, weapon.criticalEffects().get(0).area().size());
assertInstanceOf(CellArea.class, weapon.criticalEffects().get(0).area());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2170,6 +2170,23 @@ void applyEffectOnHeal() {
assertBetween(650 + 2, 650 + 22, target.life().current()); // 250 damage + [2-22] heal
}

@Test
void applyEffectOnHealWithNonEffectiveHeal() {
List<Fighter> fighters = configureFight(builder -> builder
.addSelf(fb -> fb.cell(136).maxLife(500).currentLife(500))
.addEnemy(fb -> fb.cell(122).maxLife(1000).currentLife(1000))
);

Fighter target = fighters.get(1);

castNormal(1009, target.cell()); // Peste noire
assertEquals(1000, target.life().current());
assertTrue(target.buffs().stream().anyMatch(b -> b.effect().effect() == 87));

castNormal(130, target.cell()); // Mot revitalisant
assertEquals(750, target.life().current()); // 250 damage
}

@Test
void applyEffectOnHealShouldIgnoreArmor() {
List<Fighter> fighters = configureFight(builder -> builder
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -402,7 +402,7 @@ void onBuffDamage() {
}

@Test
void onLifeAltered() {
void onDamageApplied() {
BuffHook hook1, hook2, hook3;

Buff buff1 = new Buff(Mockito.mock(SpellEffect.class), Mockito.mock(Spell.class), other.fighter(), player.fighter(), hook1 = Mockito.mock(BuffHook.class));
Expand All @@ -413,11 +413,30 @@ void onLifeAltered() {
list.add(buff2);
list.add(buff3);

list.onLifeAltered(10);
list.onDamageApplied(10);

Mockito.verify(hook1).onLifeAltered(buff1, 10);
Mockito.verify(hook2).onLifeAltered(buff2, 10);
Mockito.verify(hook3).onLifeAltered(buff3, 10);
Mockito.verify(hook1).onDamageApplied(buff1, 10);
Mockito.verify(hook2).onDamageApplied(buff2, 10);
Mockito.verify(hook3).onDamageApplied(buff3, 10);
}

@Test
void onHealApplied() {
BuffHook hook1, hook2, hook3;

Buff buff1 = new Buff(Mockito.mock(SpellEffect.class), Mockito.mock(Spell.class), other.fighter(), player.fighter(), hook1 = Mockito.mock(BuffHook.class));
Buff buff2 = new Buff(Mockito.mock(SpellEffect.class), Mockito.mock(Spell.class), other.fighter(), player.fighter(), hook2 = Mockito.mock(BuffHook.class));
Buff buff3 = new Buff(Mockito.mock(SpellEffect.class), Mockito.mock(Spell.class), other.fighter(), player.fighter(), hook3 = Mockito.mock(BuffHook.class));

list.add(buff1);
list.add(buff2);
list.add(buff3);

list.onHealApplied(10);

Mockito.verify(hook1).onHealApplied(buff1, 10);
Mockito.verify(hook2).onHealApplied(buff2, 10);
Mockito.verify(hook3).onHealApplied(buff3, 10);
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,45 @@ void apply() {
assertEquals(life, target.life().current()); // Not triggered by damage
}

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

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

FightCastScope scope = makeCastScope(caster, spell, effect, target.cell());

assertFalse(hook.apply(handler, scope, scope.effects().get(0)));

Buff buff = target.buffs().stream().filter(b -> b.effect().effect() == 96).findFirst().get();
requestStack.assertLast(new AddBuff(buff));
requestStack.clear();

target.life().heal(target, 1);

int damage = target.life().max() - target.life().current();
assertBetween(10, 15, damage);
requestStack.assertAll(
ActionEffect.alterLifePoints(target, target, 0),
ActionEffect.alterLifePoints(caster, target, -damage)
);

int life = target.life().current();

requestStack.clear();
target.life().damage(target, 0);

assertEquals(life, target.life().current()); // Not triggered by damage
}

@Test
void applyArea() {
target.life().damage(target, 1);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -319,22 +319,72 @@ void healShouldCallOnLifeAlteredBuffs() {
Buff buff = new Buff(Mockito.mock(SpellEffect.class), Mockito.mock(Spell.class), fighter, fighter, hook);
fighter.buffs().add(buff);

Mockito.doCallRealMethod().when(hook).onHealApplied(Mockito.any(Buff.class), Mockito.anyInt());

life.heal(fighter, 10);

Mockito.verify(hook).onLifeAltered(buff, 10);
}

@Test
void healShouldCallOnHealAppliedBuffs() {
life.damage(fighter, 50);

BuffHook hook = Mockito.mock(BuffHook.class);
Buff buff = new Buff(Mockito.mock(SpellEffect.class), Mockito.mock(Spell.class), fighter, fighter, hook);
fighter.buffs().add(buff);

life.heal(fighter, 10);

Mockito.verify(hook).onHealApplied(buff, 10);
}

@Test
void healShouldCallOnHealAppliedBuffsEvenIfNoEffect() {
BuffHook hook = Mockito.mock(BuffHook.class);
Buff buff = new Buff(Mockito.mock(SpellEffect.class), Mockito.mock(Spell.class), fighter, fighter, hook);
fighter.buffs().add(buff);

life.heal(fighter, 10);

Mockito.verify(hook).onHealApplied(buff, 0);
}

@Test
void damageShouldCallOnLifeAlteredBuffs() {
BuffHook hook = Mockito.mock(BuffHook.class);
Buff buff = new Buff(Mockito.mock(SpellEffect.class), Mockito.mock(Spell.class), fighter, fighter, hook);
fighter.buffs().add(buff);

Mockito.doCallRealMethod().when(hook).onDamageApplied(Mockito.any(Buff.class), Mockito.anyInt());

life.damage(fighter, 10);

Mockito.verify(hook).onLifeAltered(buff, -10);
}

@Test
void damageShouldCallOnDamageAppliedBuffs() {
BuffHook hook = Mockito.mock(BuffHook.class);
Buff buff = new Buff(Mockito.mock(SpellEffect.class), Mockito.mock(Spell.class), fighter, fighter, hook);
fighter.buffs().add(buff);

life.damage(fighter, 10);

Mockito.verify(hook).onDamageApplied(buff, 10);
}

@Test
void damageShouldCallOnDamageAppliedBuffsEvenWithNoDamage() {
BuffHook hook = Mockito.mock(BuffHook.class);
Buff buff = new Buff(Mockito.mock(SpellEffect.class), Mockito.mock(Spell.class), fighter, fighter, hook);
fighter.buffs().add(buff);

life.damage(fighter, 0);

Mockito.verify(hook).onDamageApplied(buff, 0);
}

@Test
void damageShouldNotCallOnLifeAlteredBuffsWhenDie() {
BuffHook hook = Mockito.mock(BuffHook.class);
Expand Down

0 comments on commit 2ab07c7

Please sign in to comment.