Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[fight] #27 Add switch position effects #206

Merged
merged 3 commits into from
Dec 3, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,16 @@ public FightCell target() {

/**
* Get the cast targets
*
* This method will not resolve target mapping, nor effect target mapping
* It will return all targets, before the mapping is resolved
* So if {@link CastScope#replaceTarget(PassiveFighter, PassiveFighter)} is called,
* the new target will be added on this set
*
* Note: a new instance is returned to ensure that concurrent modification will not occur
*/
public Set<PassiveFighter> targets() {
return new HashSet<>(targetMapping.values());
return new HashSet<>(targetMapping.keySet());
}

/**
Expand All @@ -106,13 +113,23 @@ public Set<PassiveFighter> targets() {
*/
public void replaceTarget(PassiveFighter originalTarget, PassiveFighter newTarget) {
targetMapping.put(originalTarget, newTarget);

// Add new target as target if not yet defined
if (!targetMapping.containsKey(newTarget)) {
targetMapping.put(newTarget, newTarget);
}
}

/**
* Remove a target of the cast
*
* Note: this method will definitively remove the target,
* even if {@link CastScope#replaceTarget(PassiveFighter, PassiveFighter)} is called
*/
public void removeTarget(PassiveFighter target) {
targetMapping.remove(target);
// Set target to null without remove the key to ensure that it will effectively remove
// even if a replaceTarget() point to it
targetMapping.put(target, null);
}

/**
Expand Down Expand Up @@ -203,6 +220,40 @@ private Collection<PassiveFighter> resolveTargets(SpellEffect effect) {
;
}

/**
* Resolve the target mapping
*
* @param baseTarget The base target of the effect
*
* @return Resolved target. Null if the target is removed
*/
private PassiveFighter resolveTarget(PassiveFighter baseTarget) {
PassiveFighter target = targetMapping.get(baseTarget);

// Target is removed, or it's the original one : do not resolve chaining
if (target == null || target.equals(baseTarget)) {
return target;
}

// Keep list of visited mapping to break recursion
final Set<PassiveFighter> resolved = new HashSet<>();

resolved.add(baseTarget);
resolved.add(target);

// Resolve chaining
for (;;) {
target = targetMapping.get(target);

// The target is removed, or already visited (can be itself)
if (target == null || resolved.contains(target)) {
return target;
}

resolved.add(target);
}
}

public final class EffectScope {
private final SpellEffect effect;
private final Collection<PassiveFighter> targets;
Expand All @@ -224,7 +275,7 @@ public SpellEffect effect() {
*/
public Collection<PassiveFighter> targets() {
return targets.stream()
.map(targetMapping::get)
.map(CastScope.this::resolveTarget)
.filter(fighter -> fighter != null && !fighter.dead())
.collect(Collectors.toList())
;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@
import fr.quatrevieux.araknemu.game.fight.castable.effect.handler.EffectHandler;
import fr.quatrevieux.araknemu.game.fight.fighter.PassiveFighter;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
* Handle fight effects
Expand All @@ -40,9 +42,7 @@ public void register(int effectId, EffectHandler applier) {
* Apply a cast to the fight
*/
public void apply(CastScope cast) {
for (PassiveFighter target : cast.targets()) {
target.buffs().onCastTarget(cast);
}
applyCastTarget(cast);

for (CastScope.EffectScope effect : cast.effects()) {
// @todo Warning if handler is not found
Expand All @@ -57,4 +57,40 @@ public void apply(CastScope cast) {
}
}
}

/**
* Call {@link fr.quatrevieux.araknemu.game.fight.castable.effect.buff.Buffs#onCastTarget(CastScope)}
* on each target.
*
* If a target is changed (by calling {@link CastScope#replaceTarget(PassiveFighter, PassiveFighter)}),
* new targets will also be called
*/
private void applyCastTarget(CastScope cast) {
Set<PassiveFighter> visitedTargets = Collections.emptySet();

for (;;) {
final Set<PassiveFighter> currentTargets = cast.targets();

boolean hasChanged = false;

for (PassiveFighter target : currentTargets) {
// Ignore already called targets
if (!visitedTargets.contains(target)) {
if (!target.buffs().onCastTarget(cast)) {
// The hook notify a target change
hasChanged = true;
}
}
}

// There is no new targets, we can stop here
if (!hasChanged) {
return;
}

// cast#targets() always contains all resolved targets, including removed ones
// so simple change visitedTargets by this value is enough to keep track of all already called fighters
visitedTargets = currentTargets;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import fr.quatrevieux.araknemu.game.fight.castable.CastScope;
import fr.quatrevieux.araknemu.game.fight.castable.effect.handler.damage.Damage;
import fr.quatrevieux.araknemu.game.fight.fighter.ActiveFighter;
import fr.quatrevieux.araknemu.game.fight.fighter.PassiveFighter;

/**
* Hook action for apply buff effects
Expand Down Expand Up @@ -53,8 +54,59 @@ public default void onBuffTerminated(Buff buff) {}

/**
* The fighter is a target of a cast
*
* To get the spell caster, use {@code cast.caster()}, and to get the target, use {@code buff.target()}.
*
* If the target is changed or removed, this method must return false.
* Retuning false permit to notify that the spell targets has changed to ensure that new
* target will also be taken in account, but also stop applying other "onCastTarget" hooks
* on the current fighter.
*
* This method will be called on direct and indirect (like spell returned) targets.
*
* Implementation:
* <pre>{@code
* class MyEffectHandler implements EffectHandler, BuffHook {
* public void handle(CastScope cast, CastScope.EffectScope effect) {
* // ...
* }
*
* // Add the buff to targets
* public void buff(CastScope cast, CastScope.EffectScope effect) {
* for (PassiveFighter target : effect.targets()) {
* target.buffs().add(new Buff(effect.effect(), cast.action(), cast.caster(), target, this));
* }
* }
*
* // Implements the buff hook
* public void onCastTarget(Buff buff, CastScope cast) {
* if (!checkCast(cast)) {
* return true; // ignore the hook for this cast : return true to continue on this target
* }
*
* // Apply buff effect...
* // ...
*
* // Change the target
* // buff.target() is cast target / fighter who has the given buff applied
* cast.replaceTarget(buff.target(), getNewTarget(cast));
*
* // You can also remove the current fighter from spell targets
* cast.removeTarget(buff.target());
*
* return false; // The target has been changed (or removed)
* }
* }
* }</pre>
*
* @return true to continue, or false if the cast target has changed (removed or replaced)
*
* @see CastScope#removeTarget(PassiveFighter)
* @see CastScope#replaceTarget(PassiveFighter, PassiveFighter)
*/
public default void onCastTarget(Buff buff, CastScope cast) {}
public default boolean onCastTarget(Buff buff, CastScope cast) {
return true;
}

/**
* The fighter will take damages
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,14 @@ public void onEndTurn() {
}

@Override
public void onCastTarget(CastScope cast) {
public boolean onCastTarget(CastScope cast) {
for (Buff buff : buffs) {
buff.hook().onCastTarget(buff, cast);
if (!buff.hook().onCastTarget(buff, cast)) {
return false;
}
}

return true;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,16 @@ public interface Buffs extends Iterable<Buff> {
public void add(Buff buff);

/**
* Apply buffs when the fighter is a target of a cast
*
* If false is returned, the scope targets should be reloaded to call this hook on new targets,
* and following hooks will be ignored.
*
* @return true to continue, or false if the target has changed (or is removed)
*
* @see BuffHook#onCastTarget(Buff, CastScope)
*/
public void onCastTarget(CastScope cast);
public boolean onCastTarget(CastScope cast);

/**
* @see BuffHook#onDirectDamage(Buff, ActiveFighter, Damage)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,20 +56,20 @@ public void buff(CastScope cast, CastScope.EffectScope effect) {
}

@Override
public void onCastTarget(Buff buff, CastScope cast) {
public boolean onCastTarget(Buff buff, CastScope cast) {
if (buff.target().equals(cast.caster()) || !isReturnableCast(cast)) {
return;
return true;
}

if (!cast.spell().filter(spell -> checkSpellReturned(spell, buff.effect())).isPresent()) {
fight.send(ActionEffect.returnSpell(buff.target(), false));
return true;
}

cast.spell().ifPresent(spell -> {
if (!checkSpellReturned(spell, buff.effect())) {
fight.send(ActionEffect.returnSpell(buff.target(), false));
return;
}
cast.replaceTarget(buff.target(), cast.caster());
fight.send(ActionEffect.returnSpell(buff.target(), true));

cast.replaceTarget(buff.target(), cast.caster());
fight.send(ActionEffect.returnSpell(buff.target(), true));
});
return false;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public void buff(CastScope cast, EffectScope effect) {

@Override
public void handle(CastScope cast, EffectScope effect) {
for (PassiveFighter fighter : cast.targets()) {
for (PassiveFighter fighter : effect.targets()) {
fighter.buffs().removeAll();
fight.send(ActionEffect.dispelBuffs(cast.caster(), fighter));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,17 +57,19 @@ public void buff(CastScope cast, CastScope.EffectScope effect) {
}

@Override
public void onCastTarget(Buff buff, CastScope cast) {
public boolean onCastTarget(Buff buff, CastScope cast) {
if (!isDamageCast(cast) || buff.target().cell().coordinate().distance(cast.caster().cell()) != 1) {
return;
return true;
}

if (!random.bool(buff.effect().min())) {
return;
return true;
}

applier.apply(cast.caster(), buff.target(), buff.effect().max());
cast.removeTarget(buff.target());

return false;
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* 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.shifting;

import fr.quatrevieux.araknemu.game.fight.Fight;
import fr.quatrevieux.araknemu.game.fight.fighter.ActiveFighter;
import fr.quatrevieux.araknemu.game.fight.fighter.PassiveFighter;
import fr.quatrevieux.araknemu.game.fight.map.FightCell;
import fr.quatrevieux.araknemu.network.game.out.fight.action.ActionEffect;

/**
* Perform the switch position of two fighters
*/
public final class SwitchPositionApplier {
private final Fight fight;

public SwitchPositionApplier(Fight fight) {
this.fight = fight;
}

/**
* Apply the switch effect
*
* @param caster The switch spell caster
* @param target The other fighter to switch with
*/
public void apply(ActiveFighter caster, PassiveFighter target) {
final FightCell casterCell = caster.cell();
final FightCell targetCell = target.cell();

// Unset cells
caster.move(null);
target.move(null);

// Switch cells
caster.move(targetCell);
target.move(casterCell);

// Synchronize positions with client
fight.send(ActionEffect.teleport(caster, caster, targetCell));
fight.send(ActionEffect.teleport(caster, target, casterCell));
}
}
Loading