mirror of
https://github.com/magefree/mage.git
synced 2025-12-25 21:12:04 -08:00
rework PhantomReplacementEffect used by 7 Phantom cards (#12189)
This commit is contained in:
parent
6193c9aee6
commit
d645facdc0
15 changed files with 366 additions and 62 deletions
|
|
@ -1,22 +1,28 @@
|
|||
package mage.abilities.effects;
|
||||
|
||||
import mage.MageObjectReference;
|
||||
import mage.abilities.Ability;
|
||||
import mage.constants.Duration;
|
||||
import mage.constants.PhaseStep;
|
||||
import mage.constants.WatcherScope;
|
||||
import mage.counters.CounterType;
|
||||
import mage.game.Game;
|
||||
import mage.game.events.GameEvent;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.game.turn.Step;
|
||||
import mage.watchers.Watcher;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Created by IGOUDT on 22-3-2017.
|
||||
* This requires to add the PhantomPreventionWatcher
|
||||
*
|
||||
* @author Susucr
|
||||
*/
|
||||
public class PhantomPreventionEffect extends PreventionEffectImpl {
|
||||
|
||||
// remember turn and phase step to check if counter in this step was already removed
|
||||
private int turn = 0;
|
||||
private Step combatPhaseStep = null;
|
||||
public static PhantomPreventionWatcher createWatcher() {
|
||||
return new PhantomPreventionWatcher();
|
||||
}
|
||||
|
||||
public PhantomPreventionEffect() {
|
||||
super(Duration.WhileOnBattlefield);
|
||||
|
|
@ -25,8 +31,6 @@ public class PhantomPreventionEffect extends PreventionEffectImpl {
|
|||
|
||||
protected PhantomPreventionEffect(final PhantomPreventionEffect effect) {
|
||||
super(effect);
|
||||
this.turn = effect.turn;
|
||||
this.combatPhaseStep = effect.combatPhaseStep;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -37,27 +41,19 @@ public class PhantomPreventionEffect extends PreventionEffectImpl {
|
|||
@Override
|
||||
public boolean replaceEvent(GameEvent event, Ability source, Game game) {
|
||||
preventDamageAction(event, source, game);
|
||||
|
||||
Permanent permanent = game.getPermanent(source.getSourceId());
|
||||
if (permanent != null) {
|
||||
boolean removeCounter = true;
|
||||
// check if in the same combat damage step already a counter was removed
|
||||
if (game.getTurn().getPhase().getStep().getType() == PhaseStep.COMBAT_DAMAGE) {
|
||||
if (game.getTurnNum() == turn
|
||||
&& game.getTurn().getStep().equals(combatPhaseStep)) {
|
||||
removeCounter = false;
|
||||
} else {
|
||||
turn = game.getTurnNum();
|
||||
combatPhaseStep = game.getTurn().getStep();
|
||||
PhantomPreventionWatcher watcher = game.getState().getWatcher(PhantomPreventionWatcher.class);
|
||||
if (permanent != null && watcher != null) {
|
||||
MageObjectReference mor = new MageObjectReference(source.getId(), source.getSourceObjectZoneChangeCounter(), game);
|
||||
if (!watcher.hadMORCounterRemovedThisBatch(mor)) {
|
||||
watcher.addMOR(mor);
|
||||
if (permanent.getCounters(game).containsKey(CounterType.P1P1)) {
|
||||
StringBuilder sb = new StringBuilder(permanent.getName()).append(": ");
|
||||
permanent.removeCounters(CounterType.P1P1.createInstance(), source, game);
|
||||
sb.append("Removed a +1/+1 counter ");
|
||||
game.informPlayers(sb.toString());
|
||||
}
|
||||
}
|
||||
|
||||
if (removeCounter && permanent.getCounters(game).containsKey(CounterType.P1P1)) {
|
||||
StringBuilder sb = new StringBuilder(permanent.getName()).append(": ");
|
||||
permanent.removeCounters(CounterType.P1P1.createInstance(), source, game);
|
||||
sb.append("Removed a +1/+1 counter ");
|
||||
game.informPlayers(sb.toString());
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
|
|
@ -72,5 +68,38 @@ public class PhantomPreventionEffect extends PreventionEffectImpl {
|
|||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class PhantomPreventionWatcher extends Watcher {
|
||||
|
||||
// We keep a very short-lived set of which PhantomPreventionEffect caused
|
||||
// +1/+1 to get removed during the current damage batch.
|
||||
private final Set<MageObjectReference> morRemovedCounterThisDamageBatch = new HashSet<>();
|
||||
|
||||
PhantomPreventionWatcher() {
|
||||
super(WatcherScope.GAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void watch(GameEvent event, Game game) {
|
||||
// This watcher resets every time a Damage Batch could have fired (even if all damage was prevented)
|
||||
if (event.getType() != GameEvent.EventType.DAMAGED_BATCH_COULD_HAVE_FIRED) {
|
||||
return;
|
||||
}
|
||||
morRemovedCounterThisDamageBatch.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
super.reset();
|
||||
morRemovedCounterThisDamageBatch.clear();
|
||||
}
|
||||
|
||||
boolean hadMORCounterRemovedThisBatch(MageObjectReference mor) {
|
||||
return morRemovedCounterThisDamageBatch.contains(mor);
|
||||
}
|
||||
|
||||
void addMOR(MageObjectReference mor) {
|
||||
morRemovedCounterThisDamageBatch.add(mor);
|
||||
}
|
||||
}
|
||||
|
|
@ -818,6 +818,16 @@ public class GameState implements Serializable, Copyable<GameState> {
|
|||
return !simultaneousEvents.isEmpty();
|
||||
}
|
||||
|
||||
// There might be no damage dealt, but we want to fire that damage (in a batch) could have been dealt.
|
||||
public void addBatchDamageCouldHaveBeenFired(boolean combat, Game game) {
|
||||
for (GameEvent event : simultaneousEvents) {
|
||||
if (event instanceof DamagedBatchCouldHaveFiredEvent && event.getFlag() == combat) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
addSimultaneousEvent(new DamagedBatchCouldHaveFiredEvent(combat), game);
|
||||
}
|
||||
|
||||
public void addSimultaneousDamage(DamagedEvent damagedEvent, Game game) {
|
||||
// Combine multiple damage events in the single event (batch)
|
||||
// Note: one event can be stored in multiple batches
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
package mage.game.events;
|
||||
|
||||
/**
|
||||
* Does not contain any info on damage events, and can fire even when all damage is prevented.
|
||||
* Fire any time a DAMAGED_BATCH_FOR_ALL could have fired (combat & noncombat).
|
||||
* It is not a batch event (doesn't contain sub events), the name is a little ambiguous.
|
||||
*
|
||||
* @author Susucr
|
||||
*/
|
||||
public class DamagedBatchCouldHaveFiredEvent extends GameEvent {
|
||||
|
||||
public DamagedBatchCouldHaveFiredEvent(boolean combat) {
|
||||
super(EventType.DAMAGED_BATCH_COULD_HAVE_FIRED, null, null, null, 0, combat);
|
||||
}
|
||||
}
|
||||
|
|
@ -129,6 +129,12 @@ public class GameEvent implements Serializable {
|
|||
includes all damage events, both permanent damage and player damage, in single batch event
|
||||
*/
|
||||
DAMAGED_BATCH_FOR_ALL,
|
||||
/* DAMAGED_BATCH_FIRED
|
||||
* Does not contain any info on damage events, and can fire even when all damage is prevented.
|
||||
* Fire any time a DAMAGED_BATCH_FOR_ALL could have fired (combat & noncombat).
|
||||
* It is not a batch event (doesn't contain sub events), the name is a little ambiguous.
|
||||
*/
|
||||
DAMAGED_BATCH_COULD_HAVE_FIRED,
|
||||
|
||||
/* DAMAGE_CAUSES_LIFE_LOSS,
|
||||
targetId the id of the damaged player
|
||||
|
|
|
|||
|
|
@ -1024,6 +1024,9 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
|
|||
}
|
||||
DamageEvent event = new DamagePermanentEvent(objectId, attackerId, controllerId, damageAmount, preventable, combat);
|
||||
event.setAppliedEffects(appliedEffects);
|
||||
// Even if no damage was dealt, some watchers would need a reset next time actions are processed.
|
||||
// For instance PhantomPreventionWatcher used by the [[Phantom Wurm]] type of replacement effect.
|
||||
game.getState().addBatchDamageCouldHaveBeenFired(combat, game);
|
||||
if (game.replaceEvent(event)) {
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
package mage.game.turn;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import mage.constants.PhaseStep;
|
||||
import mage.game.Game;
|
||||
import mage.game.combat.CombatGroup;
|
||||
import mage.game.events.GameEvent;
|
||||
import mage.game.events.GameEvent.EventType;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author BetaSteward_at_googlemail.com
|
||||
*/
|
||||
|
|
@ -65,6 +65,9 @@ public class CombatDamageStep extends Step {
|
|||
for (CombatGroup group : game.getCombat().getBlockingGroups()) {
|
||||
group.applyDamage(game);
|
||||
}
|
||||
|
||||
// Even if no damage was dealt, some watchers need a reset. For instance PhantomPreventionWatcher.
|
||||
game.getState().addBatchDamageCouldHaveBeenFired(true, game);
|
||||
// Must fire damage batch events now, before SBA (https://github.com/magefree/mage/issues/9129)
|
||||
game.getState().handleSimultaneousEvent(game);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2255,7 +2255,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
return doDamage(damage, attackerId, source, game, combatDamage, preventable, appliedEffects);
|
||||
}
|
||||
|
||||
private int doDamage(int damage, UUID attackerId, Ability source, Game game, boolean combatDamage, boolean preventable, List<UUID> appliedEffects) {
|
||||
private int doDamage(int damage, UUID attackerId, Ability source, Game game, boolean combat, boolean preventable, List<UUID> appliedEffects) {
|
||||
if (!this.isInGame()) {
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -2263,8 +2263,11 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
if (damage < 1) {
|
||||
return 0;
|
||||
}
|
||||
DamageEvent event = new DamagePlayerEvent(playerId, attackerId, playerId, damage, preventable, combatDamage);
|
||||
DamageEvent event = new DamagePlayerEvent(playerId, attackerId, playerId, damage, preventable, combat);
|
||||
event.setAppliedEffects(appliedEffects);
|
||||
// Even if no damage was dealt, some watchers would need a reset next time actions are processed.
|
||||
// For instance PhantomPreventionWatcher used by the [[Phantom Wurm]] type of replacement effect.
|
||||
game.getState().addBatchDamageCouldHaveBeenFired(combat, game);
|
||||
if (game.replaceEvent(event)) {
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -2300,20 +2303,20 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
addCounters(CounterType.POISON.createInstance(actualDamage), sourceControllerId, source, game);
|
||||
} else {
|
||||
GameEvent damageToLifeLossEvent = new GameEvent(GameEvent.EventType.DAMAGE_CAUSES_LIFE_LOSS,
|
||||
playerId, source, playerId, actualDamage, combatDamage);
|
||||
playerId, source, playerId, actualDamage, combat);
|
||||
if (!game.replaceEvent(damageToLifeLossEvent)) {
|
||||
this.loseLife(damageToLifeLossEvent.getAmount(), game, source, combatDamage, attackerId);
|
||||
this.loseLife(damageToLifeLossEvent.getAmount(), game, source, combat, attackerId);
|
||||
}
|
||||
}
|
||||
if (sourceAbilities != null && sourceAbilities.containsKey(LifelinkAbility.getInstance().getId())) {
|
||||
if (combatDamage) {
|
||||
if (combat) {
|
||||
game.getPermanent(attackerId).markLifelink(actualDamage);
|
||||
} else {
|
||||
Player player = game.getPlayer(sourceControllerId);
|
||||
player.gainLife(actualDamage, game, source);
|
||||
}
|
||||
}
|
||||
if (combatDamage && sourceAbilities != null && sourceAbilities.containsClass(ToxicAbility.class)) {
|
||||
if (combat && sourceAbilities != null && sourceAbilities.containsClass(ToxicAbility.class)) {
|
||||
int countersToAdd = CardUtil
|
||||
.castStream(sourceAbilities.stream(), ToxicAbility.class)
|
||||
.mapToInt(ToxicAbility::getAmount)
|
||||
|
|
@ -2325,7 +2328,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
Player player = game.getPlayer(sourceControllerId);
|
||||
new SquirrelToken().putOntoBattlefield(actualDamage, game, source, player.getId());
|
||||
}
|
||||
DamagedEvent damagedEvent = new DamagedPlayerEvent(playerId, attackerId, playerId, actualDamage, combatDamage);
|
||||
DamagedEvent damagedEvent = new DamagedPlayerEvent(playerId, attackerId, playerId, actualDamage, combat);
|
||||
game.fireEvent(damagedEvent);
|
||||
game.getState().addSimultaneousDamage(damagedEvent, game);
|
||||
return actualDamage;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue