rework PhantomReplacementEffect used by 7 Phantom cards (#12189)

This commit is contained in:
Susucre 2024-04-27 17:34:59 +02:00 committed by GitHub
parent 6193c9aee6
commit d645facdc0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 366 additions and 62 deletions

View file

@ -1,7 +1,6 @@
package mage.cards.p;
import java.util.UUID;
import mage.MageInt;
import mage.ObjectColor;
import mage.abilities.common.EntersBattlefieldAbility;
@ -13,9 +12,10 @@ import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.constants.Zone;
import mage.counters.CounterType;
import java.util.UUID;
/**
* @author LevelX2
*/
@ -36,7 +36,7 @@ public final class PhantomCentaur extends CardImpl {
this.addAbility(new EntersBattlefieldAbility(new AddCountersSourceEffect(CounterType.P1P1.createInstance(3)), "with three +1/+1 counters on it"));
// If damage would be dealt to Phantom Centaur, prevent that damage. Remove a +1/+1 counter from Phantom Centaur.
this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new PhantomPreventionEffect()));
this.addAbility(new SimpleStaticAbility(new PhantomPreventionEffect()), PhantomPreventionEffect.createWatcher());
}
private PhantomCentaur(final PhantomCentaur card) {

View file

@ -1,7 +1,6 @@
package mage.cards.p;
import java.util.UUID;
import mage.MageInt;
import mage.abilities.common.EntersBattlefieldAbility;
import mage.abilities.common.SimpleStaticAbility;
@ -12,9 +11,10 @@ import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.constants.Zone;
import mage.counters.CounterType;
import java.util.UUID;
/**
* @author emerald000
*/
@ -35,7 +35,7 @@ public final class PhantomFlock extends CardImpl {
this.addAbility(new EntersBattlefieldAbility(new AddCountersSourceEffect(CounterType.P1P1.createInstance(3)), "with three +1/+1 counters on it"));
// If damage would be dealt to Phantom Flock, prevent that damage. Remove a +1/+1 counter from Phantom Flock.
this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new PhantomPreventionEffect()));
this.addAbility(new SimpleStaticAbility(new PhantomPreventionEffect()), PhantomPreventionEffect.createWatcher());
}
private PhantomFlock(final PhantomFlock card) {

View file

@ -1,7 +1,6 @@
package mage.cards.p;
import java.util.UUID;
import mage.MageInt;
import mage.abilities.common.EntersBattlefieldAbility;
import mage.abilities.common.SimpleActivatedAbility;
@ -17,14 +16,15 @@ import mage.constants.SubType;
import mage.constants.Zone;
import mage.counters.CounterType;
import java.util.UUID;
/**
*
* @author LevelX2
*/
public final class PhantomNantuko extends CardImpl {
public PhantomNantuko(UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{2}{G}");
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}");
this.subtype.add(SubType.INSECT);
this.subtype.add(SubType.SPIRIT);
@ -36,7 +36,7 @@ public final class PhantomNantuko extends CardImpl {
// Phantom Nantuko enters the battlefield with two +1/+1 counters on it.
this.addAbility(new EntersBattlefieldAbility(new AddCountersSourceEffect(CounterType.P1P1.createInstance(2), true), "with two +1/+1 counters on it"));
// If damage would be dealt to Phantom Nantuko, prevent that damage. Remove a +1/+1 counter from Phantom Nantuko.
this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new PhantomPreventionEffect()));
this.addAbility(new SimpleStaticAbility(new PhantomPreventionEffect()), PhantomPreventionEffect.createWatcher());
// {tap}: Put a +1/+1 counter on Phantom Nantuko.
this.addAbility(new SimpleActivatedAbility(Zone.BATTLEFIELD, new AddCountersSourceEffect(CounterType.P1P1.createInstance()), new TapSourceCost()));
}

View file

@ -1,7 +1,6 @@
package mage.cards.p;
import java.util.UUID;
import mage.MageInt;
import mage.abilities.common.DealsDamageGainLifeSourceTriggeredAbility;
import mage.abilities.common.EntersBattlefieldAbility;
@ -13,9 +12,10 @@ import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.constants.Zone;
import mage.counters.CounterType;
import java.util.UUID;
/**
* @author fireshoes
*/
@ -39,7 +39,7 @@ public final class PhantomNishoba extends CardImpl {
this.addAbility(new DealsDamageGainLifeSourceTriggeredAbility());
// If damage would be dealt to Phantom Nishoba, prevent that damage. Remove a +1/+1 counter from Phantom Nishoba.
this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new PhantomPreventionEffect()));
this.addAbility(new SimpleStaticAbility(new PhantomPreventionEffect()), PhantomPreventionEffect.createWatcher());
}
private PhantomNishoba(final PhantomNishoba card) {

View file

@ -1,7 +1,6 @@
package mage.cards.p;
import java.util.UUID;
import mage.MageInt;
import mage.abilities.common.EntersBattlefieldAbility;
import mage.abilities.common.SimpleStaticAbility;
@ -11,17 +10,17 @@ import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.constants.Zone;
import mage.counters.CounterType;
import java.util.UUID;
/**
*
* @author LevelX2
*/
public final class PhantomNomad extends CardImpl {
public PhantomNomad(UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{1}{W}");
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}");
this.subtype.add(SubType.SPIRIT);
this.subtype.add(SubType.NOMAD);
@ -33,7 +32,7 @@ public final class PhantomNomad extends CardImpl {
"with two +1/+1 counters on it"));
// If damage would be dealt to Phantom Nomad, prevent that damage. Remove a +1/+1 counter from Phantom Nomad.
this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new PhantomPreventionEffect()));
this.addAbility(new SimpleStaticAbility(new PhantomPreventionEffect()), PhantomPreventionEffect.createWatcher());
}

View file

@ -1,7 +1,6 @@
package mage.cards.p;
import java.util.UUID;
import mage.MageInt;
import mage.abilities.common.EntersBattlefieldAbility;
import mage.abilities.common.SimpleStaticAbility;
@ -11,9 +10,10 @@ import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.constants.Zone;
import mage.counters.CounterType;
import java.util.UUID;
/**
* @author Temba
*/
@ -30,7 +30,7 @@ public final class PhantomTiger extends CardImpl {
this.addAbility(new EntersBattlefieldAbility(new AddCountersSourceEffect(CounterType.P1P1.createInstance(2)), "with two +1/+1 counters on it"));
// If damage would be dealt to Phantom Tiger, prevent that damage. Remove a +1/+1 counter from Phantom Tiger.
this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new PhantomPreventionEffect()));
this.addAbility(new SimpleStaticAbility(new PhantomPreventionEffect()), PhantomPreventionEffect.createWatcher());
}
private PhantomTiger(final PhantomTiger card) {

View file

@ -1,7 +1,6 @@
package mage.cards.p;
import java.util.UUID;
import mage.MageInt;
import mage.abilities.common.EntersBattlefieldAbility;
import mage.abilities.common.SimpleStaticAbility;
@ -11,17 +10,17 @@ import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.constants.Zone;
import mage.counters.CounterType;
import java.util.UUID;
/**
*
* @author LevelX2
*/
public final class PhantomWurm extends CardImpl {
public PhantomWurm(UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{4}{G}{G}");
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{G}{G}");
this.subtype.add(SubType.WURM);
this.subtype.add(SubType.SPIRIT);
@ -33,7 +32,7 @@ public final class PhantomWurm extends CardImpl {
"with four +1/+1 counters on it"));
// If damage would be dealt to Phantom Wurm, prevent that damage. Remove a +1/+1 counter from Phantom Wurm.
this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new PhantomPreventionEffect()));
this.addAbility(new SimpleStaticAbility(new PhantomPreventionEffect()), PhantomPreventionEffect.createWatcher());
}
private PhantomWurm(final PhantomWurm card) {

View file

@ -0,0 +1,237 @@
package org.mage.test.cards.single.tsp;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import mage.counters.CounterType;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
* @author Susucr
*/
public class PhantomWurmTest extends CardTestPlayerBase {
/**
* {@link mage.cards.p.PhantomWurm Phantom Wurm} {4}{G}{G}
* Creature Wurm Spirit
* Phantom Wurm enters the battlefield with four +1/+1 counters on it.
* If damage would be dealt to Phantom Wurm, prevent that damage. Remove a +1/+1 counter from Phantom Wurm.
* 2/0
*/
private static final String wurm = "Phantom Wurm";
@Test
public void test_DoubleBlocked() {
setStrictChooseMode(true);
addCard(Zone.BATTLEFIELD, playerA, wurm);
addCard(Zone.BATTLEFIELD, playerB, "Memnite");
addCard(Zone.BATTLEFIELD, playerB, "Eager Cadet");
attack(1, playerA, wurm, playerB);
block(1, playerB, "Memnite", wurm);
block(1, playerB, "Eager Cadet", wurm);
setChoice(playerA, "X=1"); // damage assignment
setChoice(playerA, "X=3"); // damage assignment
setStopAt(1, PhaseStep.END_TURN);
execute();
assertGraveyardCount(playerB, "Memnite", 1);
assertGraveyardCount(playerB, "Eager Cadet", 1);
assertDamageReceived(playerA, wurm, 0); // all is prevented
assertCounterCount(wurm, CounterType.P1P1, 4 - 1);
}
@Test
public void test_BlockedByAnotherPhantom() {
setStrictChooseMode(true);
addCard(Zone.BATTLEFIELD, playerA, wurm);
addCard(Zone.BATTLEFIELD, playerB, "Phantom Nomad");
attack(1, playerA, wurm, playerB);
block(1, playerB, "Phantom Nomad", wurm);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertDamageReceived(playerA, wurm, 0); // all is prevented
assertCounterCount(wurm, CounterType.P1P1, 4 - 1);
assertDamageReceived(playerB, "Phantom Nomad", 0); // all is prevented
assertCounterCount("Phantom Nomad", CounterType.P1P1, 2 - 1);
}
@Test
public void test_BlockedByAnotherPhantom_ThenBolt() {
setStrictChooseMode(true);
addCard(Zone.BATTLEFIELD, playerA, wurm);
addCard(Zone.BATTLEFIELD, playerB, "Phantom Nomad");
addCard(Zone.BATTLEFIELD, playerB, "Mountain");
addCard(Zone.HAND, playerB, "Lightning Bolt");
attack(1, playerA, wurm, playerB);
block(1, playerB, "Phantom Nomad", wurm);
castSpell(1, PhaseStep.COMBAT_DAMAGE, playerB, "Lightning Bolt", wurm);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertDamageReceived(playerA, wurm, 0); // all is prevented
assertCounterCount(wurm, CounterType.P1P1, 4 - 2);
assertDamageReceived(playerB, "Phantom Nomad", 0); // all is prevented
assertCounterCount("Phantom Nomad", CounterType.P1P1, 2 - 1);
}
@Test
public void test_DoubleStrike() {
setStrictChooseMode(true);
addCard(Zone.BATTLEFIELD, playerA, wurm);
addCard(Zone.BATTLEFIELD, playerB, "Adorned Pouncer"); // 1/1 double strike
attack(1, playerA, wurm, playerB);
block(1, playerB, "Adorned Pouncer", wurm);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertGraveyardCount(playerB, "Adorned Pouncer", 1);
assertDamageReceived(playerA, wurm, 0); // all is prevented
assertCounterCount(wurm, CounterType.P1P1, 4 - 2);
}
@Test
public void test_DoubleBlocked_OneFirstStrikeOneNormal() {
setStrictChooseMode(true);
addCard(Zone.BATTLEFIELD, playerA, wurm);
addCard(Zone.BATTLEFIELD, playerB, "Memnite");
addCard(Zone.BATTLEFIELD, playerB, "Goblin Striker", 1); // 1/1 first strike haste
attack(1, playerA, wurm, playerB);
block(1, playerB, "Memnite", wurm);
block(1, playerB, "Goblin Striker", wurm);
setChoice(playerA, "X=1"); // damage assignment
setChoice(playerA, "X=3"); // damage assignment
setStopAt(1, PhaseStep.END_TURN);
execute();
assertGraveyardCount(playerB, "Memnite", 1);
assertGraveyardCount(playerB, "Goblin Striker", 1);
assertDamageReceived(playerA, wurm, 0); // all is prevented
assertCounterCount(wurm, CounterType.P1P1, 4 - 2);
}
@Test
public void test_DoubleBlocked_TwoFirstStrike() {
setStrictChooseMode(true);
addCard(Zone.BATTLEFIELD, playerA, wurm);
addCard(Zone.BATTLEFIELD, playerB, "Boros Recruit"); // 1/1 first strike
addCard(Zone.BATTLEFIELD, playerB, "Goblin Striker", 1); // 1/1 first strike haste
attack(1, playerA, wurm, playerB);
block(1, playerB, "Boros Recruit", wurm);
block(1, playerB, "Goblin Striker", wurm);
setChoice(playerA, "X=1"); // damage assignment
setChoice(playerA, "X=3"); // damage assignment
setStopAt(1, PhaseStep.END_TURN);
execute();
assertGraveyardCount(playerB, "Boros Recruit", 1);
assertGraveyardCount(playerB, "Goblin Striker", 1);
assertDamageReceived(playerA, wurm, 0); // all is prevented
assertCounterCount(wurm, CounterType.P1P1, 4 - 1);
}
@Test
public void test_Blocked_ThenBolt() {
setStrictChooseMode(true);
addCard(Zone.BATTLEFIELD, playerA, wurm);
addCard(Zone.BATTLEFIELD, playerB, "Memnite");
addCard(Zone.BATTLEFIELD, playerB, "Mountain");
addCard(Zone.HAND, playerB, "Lightning Bolt");
attack(1, playerA, wurm, playerB);
block(1, playerB, "Memnite", wurm);
castSpell(1, PhaseStep.COMBAT_DAMAGE, playerB, "Lightning Bolt", wurm);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertGraveyardCount(playerB, "Memnite", 1);
assertDamageReceived(playerA, wurm, 0); // all is prevented
assertCounterCount(wurm, CounterType.P1P1, 4 - 2);
}
@Test
public void test_Blocked_FirstStrike_ThenBolt() {
setStrictChooseMode(true);
addCard(Zone.BATTLEFIELD, playerA, wurm);
addCard(Zone.BATTLEFIELD, playerB, "Boros Recruit");
addCard(Zone.BATTLEFIELD, playerB, "Mountain");
addCard(Zone.HAND, playerB, "Lightning Bolt");
attack(1, playerA, wurm, playerB);
block(1, playerB, "Boros Recruit", wurm);
castSpell(1, PhaseStep.FIRST_COMBAT_DAMAGE, playerB, "Lightning Bolt", wurm);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertGraveyardCount(playerB, "Boros Recruit", 1);
assertDamageReceived(playerA, wurm, 0); // all is prevented
assertCounterCount(wurm, CounterType.P1P1, 4 - 2);
}
@Test
public void test_Simultanous_NonCombat() {
setStrictChooseMode(true);
addCard(Zone.BATTLEFIELD, playerA, wurm);
addCard(Zone.BATTLEFIELD, playerA, "Memnite");
addCard(Zone.BATTLEFIELD, playerA, "Boros Recruit");
addCard(Zone.BATTLEFIELD, playerA, "Forest", 3);
addCard(Zone.HAND, playerA, "Band Together"); // Up to two target creatures you control each deal damage equal to their power to another target creature.
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Band Together");
addTarget(playerA, "Memnite^Boros Recruit");
addTarget(playerA, wurm);
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
assertDamageReceived(playerA, wurm, 0); // all is prevented
assertCounterCount(wurm, CounterType.P1P1, 4 - 1);
}
@Test
public void test_Bolt_ThenBolt() {
setStrictChooseMode(true);
addCard(Zone.BATTLEFIELD, playerA, wurm);
addCard(Zone.BATTLEFIELD, playerB, "Mountain", 2);
addCard(Zone.HAND, playerB, "Lightning Bolt", 2);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Lightning Bolt", wurm);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Lightning Bolt", wurm);
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
assertDamageReceived(playerA, wurm, 0); // all is prevented
assertCounterCount(wurm, CounterType.P1P1, 4 - 2);
}
}

View file

@ -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);
}
}

View file

@ -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

View file

@ -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);
}
}

View file

@ -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

View file

@ -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;
}

View file

@ -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);
}

View file

@ -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;