Skip to content

Commit

Permalink
Merge pull request #335 from vincent4vx/feature-effect-hook
Browse files Browse the repository at this point in the history
feat(fight): Handle hooked spell effects
  • Loading branch information
vincent4vx authored Mar 8, 2024
2 parents 0d1850d + bd28882 commit 9cfd786
Show file tree
Hide file tree
Showing 76 changed files with 3,000 additions and 159 deletions.
10 changes: 10 additions & 0 deletions src/main/java/fr/quatrevieux/araknemu/game/GameModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -988,6 +988,11 @@ private void configureServices(ContainerConfigurator configurator) {
simulator.register(176, new AlterCharacteristicSimulator(20)); // discernment
simulator.register(178, new AlterCharacteristicSimulator(8)); // heal
simulator.register(182, new AddMaxSummonedCreatureSimulator(10)); // summoned creature
simulator.register(210, new AlterCharacteristicSimulator(5)); // %res earth
simulator.register(211, new AlterCharacteristicSimulator(5)); // %res water
simulator.register(212, new AlterCharacteristicSimulator(5)); // %res air
simulator.register(213, new AlterCharacteristicSimulator(5)); // %res fire
simulator.register(214, new AlterCharacteristicSimulator(5)); // %res neutral
simulator.register(606, new AlterCharacteristicSimulator()); // Wisdom not dispellable
simulator.register(607, new AlterCharacteristicSimulator()); // Strength not dispellable
simulator.register(608, new AlterCharacteristicSimulator()); // Luck not dispellable
Expand All @@ -1007,6 +1012,11 @@ private void configureServices(ContainerConfigurator configurator) {
simulator.register(171, new AlterCharacteristicSimulator(-5)); // -critical
simulator.register(179, new AlterCharacteristicSimulator(-8)); // -heal
simulator.register(186, new AlterCharacteristicSimulator(-2)); // -percent damage
simulator.register(215, new AlterCharacteristicSimulator(5)); // - %res earth
simulator.register(216, new AlterCharacteristicSimulator(5)); // - %res water
simulator.register(217, new AlterCharacteristicSimulator(5)); // - %res air
simulator.register(218, new AlterCharacteristicSimulator(5)); // - %res fire
simulator.register(219, new AlterCharacteristicSimulator(5)); // - %res neutral

// Steal characteristics
simulator.register(267, new StealCharacteristicSimulator()); // vitality
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ public Fight(int id, FightType type, FightMap map, List<FightTeam.Factory> teams
this.spectators = new Spectators(this);
this.fighters = new FighterList(this);
this.actions = actions.createForFight(this);
this.effects = new EffectsHandler(this);
this.effects = new EffectsHandler(this, logger);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,4 +138,9 @@ public void decreaseAbility(@NonNegative int malus) {
criticalEffects = null;
effects = null;
}

@Override
public String toString() {
return "Weapon{id=" + weapon.template().id() + ", name=" + weapon.template().name() + "}";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,15 @@
import fr.quatrevieux.araknemu.game.fight.castable.FightCastScope;
import fr.quatrevieux.araknemu.game.fight.castable.effect.buff.Buff;
import fr.quatrevieux.araknemu.game.fight.castable.effect.handler.EffectHandler;
import fr.quatrevieux.araknemu.game.fight.castable.effect.hook.EffectHookHandler;
import fr.quatrevieux.araknemu.game.fight.castable.validator.CastConstraintValidator;
import fr.quatrevieux.araknemu.game.fight.fighter.Fighter;
import fr.quatrevieux.araknemu.game.fight.fighter.FighterData;
import fr.quatrevieux.araknemu.game.fight.map.BattlefieldCell;
import fr.quatrevieux.araknemu.game.fight.turn.Turn;
import fr.quatrevieux.araknemu.game.spell.effect.SpellEffect;
import fr.quatrevieux.araknemu.game.spell.effect.target.EffectTarget;
import org.apache.logging.log4j.Logger;
import org.checkerframework.checker.nullness.qual.Nullable;

import java.util.Collections;
Expand All @@ -43,10 +46,13 @@
*/
public final class EffectsHandler implements CastConstraintValidator<Castable> {
private final Fight fight;
private final Logger logger;
private final Map<Integer, EffectHandler> handlers = new HashMap<>();
private final Map<Integer, EffectHookHandler> hooks = new HashMap<>();

public EffectsHandler(Fight fight) {
public EffectsHandler(Fight fight, Logger logger) {
this.fight = fight;
this.logger = logger;
}

@Override
Expand Down Expand Up @@ -85,13 +91,20 @@ public void register(int effectId, EffectHandler applier) {
handlers.put(effectId, applier);
}

public void registerHook(int hookId, EffectHookHandler applier) {
hooks.put(hookId, applier);
}

/**
* Apply a cast to the fight
*
* First, this method will call {@link fr.quatrevieux.araknemu.game.fight.castable.effect.buff.BuffHook#onCast(Buff, FightCastScope)} to caster
* Then call {@link fr.quatrevieux.araknemu.game.fight.castable.effect.buff.BuffHook#onCastTarget(Buff, FightCastScope)} to all targets
*
* After that, all effects will be applied by calling :
* - Checking if the effect has a hook with {@link EffectTarget#isHook()}
* - If the hook is found, call {@link EffectHookHandler#apply(EffectHandler, FightCastScope, FightCastScope.EffectScope)}
* - If no hook, or if the hook returns true, call the effect handler :
* - {@link EffectHandler#handle(FightCastScope, FightCastScope.EffectScope)} if duration is 0
* - {@link EffectHandler#buff(FightCastScope, FightCastScope.EffectScope)} if the effect has a duration
*/
Expand All @@ -101,19 +114,32 @@ public void apply(FightCastScope cast) {
applyCastTarget(cast);

for (FightCastScope.EffectScope effect : cast.effects()) {
final EffectHandler handler = handlers.get(effect.effect().effect());
// @todo Warning if handler is not found
if (handler != null) {
if (effect.effect().duration() == 0) {
handler.handle(cast, effect);
} else {
handler.buff(cast, effect);
}
final SpellEffect spellEffect = effect.effect();
final int effectId = spellEffect.effect();
final EffectHandler handler = handlers.get(effectId);

if (handler == null) {
logger.warn(
"No handler found for effect {} when casting {}. Ignoring...",
effectId,
cast.action()
);
continue;
}

// Do not apply next effects if the fight is finished
if (!fight.active()) {
break;
}
if (spellEffect.target().isHook() && !applyHook(handler, cast, effect)) {
continue;
}

if (spellEffect.duration() == 0) {
handler.handle(cast, effect);
} else {
handler.buff(cast, effect);
}

// Do not apply next effects if the fight is finished
if (!fight.active()) {
break;
}
}
}
Expand Down Expand Up @@ -153,4 +179,30 @@ private void applyCastTarget(FightCastScope cast) {
visitedTargets = currentTargets;
}
}

/**
* Apply the effect through the hook
*
* @param handler The resolved effect handler, which will be used to apply the effect
* @param cast The cast action
* @param effect The effect scope
*
* @return true if the effect should be applied directly, false to ignore it
*/
private boolean applyHook(EffectHandler handler, FightCastScope cast, FightCastScope.EffectScope effect) {
final int hookId = effect.effect().target().hookId();
final EffectHookHandler hook = hooks.get(hookId);

if (hook == null) {
logger.warn(
"Hook {} not found when casting {}. Ignoring...",
hookId,
cast.action()
);

return true;
}

return hook.apply(handler, cast, effect);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,17 @@

package fr.quatrevieux.araknemu.game.fight.castable.effect.buff;

import fr.quatrevieux.araknemu.data.constant.Characteristic;
import fr.quatrevieux.araknemu.game.fight.castable.CastScope;
import fr.quatrevieux.araknemu.game.fight.castable.FightCastScope;
import fr.quatrevieux.araknemu.game.fight.castable.effect.EffectValue;
import fr.quatrevieux.araknemu.game.fight.castable.effect.Element;
import fr.quatrevieux.araknemu.game.fight.castable.effect.handler.damage.Damage;
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.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 @@ -172,14 +175,63 @@ public default void onBuffDamage(Buff buff, Buff poison, Damage value) {
* @param buff The active buff
* @param value Altered life value. Negative for a damage, positive for a heal
*
* @see fr.quatrevieux.araknemu.game.fight.fighter.FighterLife#alter(FighterData, int)
* @see fr.quatrevieux.araknemu.game.fight.fighter.FighterLife#damage(Fighter, int)
* @see fr.quatrevieux.araknemu.game.fight.fighter.FighterLife#heal(Fighter, int)
*/
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);
}

/**
* Elemental damage has been applied to the current fighter
*
* This hook is called after {@link BuffHook#onDamageApplied(Buff, int)}, but only in case
* of damage related to an element (e.i. fire, water, air, earth, neutral)
*
* @param buff The active buff
* @param element The element of the damage
* @param value The damage value. Can be 0 if the damage is completely absorbed
*/
public default void onElementDamageApplied(Buff buff, Element element, @NonNegative int 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
*
* The target can be changed using {@link ReflectedDamage#changeTarget(FighterData)}
* The target can be changed using {@link ReflectedDamage#changeTarget(Fighter)}
* Or modified using {@link ReflectedDamage#multiply(int)}
*
* @param buff The active buff
Expand Down Expand Up @@ -218,4 +270,13 @@ public default void onEffectValueCast(Buff buff, EffectValue value) {}
* @param value The effect value which will be applied
*/
public default void onEffectValueTarget(Buff buff, EffectValue value) {}

/**
* A characteristic of the fighter has been altered
*
* @param buff The active buff
* @param characteristic The altered characteristic
* @param value The characteristic modifier. Positive for add the characteristic, or negative to remove.
*/
public default void onCharacteristicAltered(Buff buff, Characteristic characteristic, int value) {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,17 @@

package fr.quatrevieux.araknemu.game.fight.castable.effect.buff;

import fr.quatrevieux.araknemu.data.constant.Characteristic;
import fr.quatrevieux.araknemu.game.fight.castable.FightCastScope;
import fr.quatrevieux.araknemu.game.fight.castable.effect.EffectValue;
import fr.quatrevieux.araknemu.game.fight.castable.effect.Element;
import fr.quatrevieux.araknemu.game.fight.castable.effect.handler.damage.Damage;
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 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 +144,23 @@ 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);
}
}

@Override
public void onElementDamageApplied(Element element, @NonNegative int actualDamage) {
for (Buff buff : buffs) {
buff.hook().onElementDamageApplied(buff, element, actualDamage);
}
}

Expand Down Expand Up @@ -175,6 +192,13 @@ public void onEffectValueTarget(EffectValue value) {
}
}

@Override
public void onCharacteristicAltered(Characteristic characteristic, int value) {
for (Buff buff : buffs) {
buff.hook().onCharacteristicAltered(buff, characteristic, value);
}
}

@Override
public void refresh() {
// invalidate buffs before removing it in case of buffs are iterated by onBuffTerminated() hook
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,15 @@

package fr.quatrevieux.araknemu.game.fight.castable.effect.buff;

import fr.quatrevieux.araknemu.data.constant.Characteristic;
import fr.quatrevieux.araknemu.game.fight.castable.FightCastScope;
import fr.quatrevieux.araknemu.game.fight.castable.effect.EffectValue;
import fr.quatrevieux.araknemu.game.fight.castable.effect.Element;
import fr.quatrevieux.araknemu.game.fight.castable.effect.handler.damage.Damage;
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 +77,19 @@ public interface Buffs extends Iterable<Buff> {
public void onDirectDamageApplied(Fighter caster, @Positive int value);

/**
* @see BuffHook#onLifeAltered(Buff, int)
* @see BuffHook#onElementDamageApplied(Buff, Element element, int)
*/
public void onLifeAltered(int value);
public void onElementDamageApplied(Element element, @NonNegative int actualDamage);

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

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

/**
* @see BuffHook#onReflectedDamage(Buff, ReflectedDamage)
Expand Down Expand Up @@ -108,6 +121,11 @@ public interface Buffs extends Iterable<Buff> {
*/
public void onEffectValueTarget(EffectValue value);

/**
* @see BuffHook#onCharacteristicAltered(Buff, Characteristic, int)
*/
public void onCharacteristicAltered(Characteristic characteristic, int value);

/**
* Refresh the buff list after turn end
*/
Expand Down
Loading

0 comments on commit 9cfd786

Please sign in to comment.