From bcff245a3154262c61633573d17604e7eebeb589 Mon Sep 17 00:00:00 2001
From: Susucre <34709007+Susucre@users.noreply.github.com>
Date: Thu, 23 May 2024 19:48:44 +0200
Subject: [PATCH] rework more Prevention Effects involving counters. Implement
[PIP] Bloatfly Swarm (#12205)
---
Mage.Sets/src/mage/cards/b/BloatflySwarm.java | 80 ++++++++++++
Mage.Sets/src/mage/cards/m/MagmaPummeler.java | 70 ++++++-----
.../src/mage/cards/o/OathswornKnight.java | 4 +-
.../src/mage/cards/p/PhantomCentaur.java | 6 +-
Mage.Sets/src/mage/cards/p/PhantomFlock.java | 6 +-
.../src/mage/cards/p/PhantomNantuko.java | 6 +-
.../src/mage/cards/p/PhantomNishoba.java | 6 +-
Mage.Sets/src/mage/cards/p/PhantomNomad.java | 6 +-
Mage.Sets/src/mage/cards/p/PhantomTiger.java | 6 +-
Mage.Sets/src/mage/cards/p/PhantomWurm.java | 6 +-
.../src/mage/cards/p/PolukranosUnchained.java | 4 +-
Mage.Sets/src/mage/cards/p/ProteanHydra.java | 10 +-
.../src/mage/cards/u/UginsConjurant.java | 22 ++--
.../src/mage/cards/u/UnbreathingHorde.java | 5 +-
.../src/mage/cards/u/UndergrowthChampion.java | 10 +-
Mage.Sets/src/mage/sets/Fallout.java | 1 +
.../PreventDamageRemoveCountersTest.java | 115 +++++++++++++++++-
.../cards/single/pip/BloatflySwarmTest.java | 91 ++++++++++++++
.../common/SavedCounterRemovedValue.java | 46 +++++++
.../effects/PhantomPreventionEffect.java | 105 ----------------
.../PreventDamageAndRemoveCountersEffect.java | 114 +++++++++++++++--
Mage/src/main/java/mage/game/Game.java | 3 +-
Mage/src/main/java/mage/game/GameImpl.java | 12 +-
23 files changed, 545 insertions(+), 189 deletions(-)
create mode 100644 Mage.Sets/src/mage/cards/b/BloatflySwarm.java
create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/single/pip/BloatflySwarmTest.java
create mode 100644 Mage/src/main/java/mage/abilities/dynamicvalue/common/SavedCounterRemovedValue.java
delete mode 100644 Mage/src/main/java/mage/abilities/effects/PhantomPreventionEffect.java
diff --git a/Mage.Sets/src/mage/cards/b/BloatflySwarm.java b/Mage.Sets/src/mage/cards/b/BloatflySwarm.java
new file mode 100644
index 00000000000..2d8bd4db927
--- /dev/null
+++ b/Mage.Sets/src/mage/cards/b/BloatflySwarm.java
@@ -0,0 +1,80 @@
+package mage.cards.b;
+
+import mage.MageInt;
+import mage.abilities.Ability;
+import mage.abilities.common.EntersBattlefieldAbility;
+import mage.abilities.common.SimpleStaticAbility;
+import mage.abilities.effects.PreventDamageAndRemoveCountersEffect;
+import mage.abilities.effects.common.counter.AddCountersPlayersEffect;
+import mage.abilities.effects.common.counter.AddCountersSourceEffect;
+import mage.abilities.keyword.FlyingAbility;
+import mage.cards.CardImpl;
+import mage.cards.CardSetInfo;
+import mage.constants.CardType;
+import mage.constants.SubType;
+import mage.constants.TargetController;
+import mage.counters.CounterType;
+import mage.game.Game;
+import mage.game.events.GameEvent;
+
+import java.util.UUID;
+
+/**
+ * @author Susucr
+ */
+public final class BloatflySwarm extends CardImpl {
+
+ public BloatflySwarm(UUID ownerId, CardSetInfo setInfo) {
+ super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{B}");
+
+ this.subtype.add(SubType.INSECT);
+ this.subtype.add(SubType.MUTANT);
+ this.power = new MageInt(0);
+ this.toughness = new MageInt(0);
+
+ // Flying
+ this.addAbility(FlyingAbility.getInstance());
+
+ // Bloatfly Swarm enters the battlefield with five +1/+1 counters on it.
+ this.addAbility(new EntersBattlefieldAbility(
+ new AddCountersSourceEffect(CounterType.P1P1.createInstance(5)),
+ "with five +1/+1 counters on it"
+ ));
+
+ // If damage would be dealt to Bloatfly Swarm while it has a +1/+1 counter on it, prevent that damage, remove that many +1/+1 counters from it, then give each player a rad counter for each +1/+1 counter removed this way.
+ this.addAbility(new SimpleStaticAbility(new BloatflySwarmPreventionEffect()), PreventDamageAndRemoveCountersEffect.createWatcher());
+ }
+
+ private BloatflySwarm(final BloatflySwarm card) {
+ super(card);
+ }
+
+ @Override
+ public BloatflySwarm copy() {
+ return new BloatflySwarm(this);
+ }
+}
+
+class BloatflySwarmPreventionEffect extends PreventDamageAndRemoveCountersEffect {
+
+ BloatflySwarmPreventionEffect() {
+ super(true, true, true);
+ staticText += ", then give each player a rad counter for each +1/+1 counter removed this way";
+ }
+
+ private BloatflySwarmPreventionEffect(final BloatflySwarmPreventionEffect effect) {
+ super(effect);
+ }
+
+ @Override
+ public BloatflySwarmPreventionEffect copy() {
+ return new BloatflySwarmPreventionEffect(this);
+ }
+
+ @Override
+ protected void onDamagePrevented(GameEvent event, Ability source, Game game, int amountRemovedInTotal, int amountRemovedThisTime) {
+ super.onDamagePrevented(event, source, game, amountRemovedInTotal, amountRemovedThisTime);
+ new AddCountersPlayersEffect(CounterType.RAD.createInstance(amountRemovedThisTime), TargetController.EACH_PLAYER)
+ .apply(game, source);
+ }
+}
\ No newline at end of file
diff --git a/Mage.Sets/src/mage/cards/m/MagmaPummeler.java b/Mage.Sets/src/mage/cards/m/MagmaPummeler.java
index 967f112b5d8..809775c9bd0 100644
--- a/Mage.Sets/src/mage/cards/m/MagmaPummeler.java
+++ b/Mage.Sets/src/mage/cards/m/MagmaPummeler.java
@@ -2,9 +2,11 @@ package mage.cards.m;
import mage.MageInt;
import mage.abilities.Ability;
+import mage.abilities.DelayedTriggeredAbility;
import mage.abilities.common.EntersBattlefieldAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.common.delayed.ReflexiveTriggeredAbility;
+import mage.abilities.dynamicvalue.common.SavedCounterRemovedValue;
import mage.abilities.effects.PreventDamageAndRemoveCountersEffect;
import mage.abilities.effects.common.DamageTargetEffect;
import mage.abilities.effects.common.EntersBattlefieldWithXCountersEffect;
@@ -15,13 +17,12 @@ import mage.constants.SubType;
import mage.counters.CounterType;
import mage.game.Game;
import mage.game.events.GameEvent;
-import mage.game.permanent.Permanent;
import mage.target.common.TargetAnyTarget;
import java.util.UUID;
/**
- * @author TheElk801
+ * @author TheElk801, Susucr
*/
public final class MagmaPummeler extends CardImpl {
@@ -35,9 +36,9 @@ public final class MagmaPummeler extends CardImpl {
// Magma Pummeler enters the battlefield with X +1/+1 counters on it.
this.addAbility(new EntersBattlefieldAbility(new EntersBattlefieldWithXCountersEffect(CounterType.P1P1.createInstance())));
- // If damage would be dealt to Magma Pummeler while it has a +1/+1 counter on it, prevent that damage and remove that many +1/+1 counters from it.
- // When one or more counters are removed from Magma Pummeler this way, it deals that much damage to any target.
- this.addAbility(new SimpleStaticAbility(new MagmaPummelerEffect()));
+ // If damage would be dealt to Magma Pummeler while it has a +1/+1 counter on it, prevent that damage and remove that many +1/+1 counters from it. When one or more counters are removed from Magma Pummeler this way, it deals that much damage to any target.
+ Ability ability = new SimpleStaticAbility(new MagmaPummelerPreventionEffect());
+ this.addAbility(ability, PreventDamageAndRemoveCountersEffect.createWatcher());
}
private MagmaPummeler(final MagmaPummeler card) {
@@ -50,40 +51,53 @@ public final class MagmaPummeler extends CardImpl {
}
}
-class MagmaPummelerEffect extends PreventDamageAndRemoveCountersEffect {
+class MagmaPummelerPreventionEffect extends PreventDamageAndRemoveCountersEffect {
- MagmaPummelerEffect() {
+ // This is not standard for the codebase, but we need to trigger only once if multiple
+ // source deal damage at the same time.
+ // To achieve that, we store the delayedId's is created on first instance,
+ // and its inner value gets modified if not triggered yet.
+ private UUID reflexiveId;
+
+ MagmaPummelerPreventionEffect() {
super(true, true, true);
- staticText += ". When one or more counters are removed from {this} this way, it deals that much damage to any target";
+ staticText = "If damage would be dealt to {this} while it has a +1/+1 counter on it, "
+ + "prevent that damage and remove that many +1/+1 counters from it. "
+ + "When one or more counters are removed from {this} this way, it deals that much damage to any target.";
+ this.reflexiveId = null;
}
- private MagmaPummelerEffect(final MagmaPummelerEffect effect) {
+ private MagmaPummelerPreventionEffect(final MagmaPummelerPreventionEffect effect) {
super(effect);
+ this.reflexiveId = effect.reflexiveId;
}
@Override
- public MagmaPummelerEffect copy() {
- return new MagmaPummelerEffect(this);
+ public PreventDamageAndRemoveCountersEffect copy() {
+ return new MagmaPummelerPreventionEffect(this);
}
@Override
- public boolean replaceEvent(GameEvent event, Ability source, Game game) {
- Permanent permanent = game.getPermanent(source.getSourceId());
- if (permanent == null) {
- return false;
- }
- int beforeCounters = permanent.getCounters(game).getCount(CounterType.P1P1);
- super.replaceEvent(event, source, game);
- int countersRemoved = beforeCounters - permanent.getCounters(game).getCount(CounterType.P1P1);
- if (countersRemoved > 0) {
- ReflexiveTriggeredAbility ability = new ReflexiveTriggeredAbility(
- new DamageTargetEffect(countersRemoved), false,
- "{this} deals that much damage to any target"
+ protected void onDamagePrevented(GameEvent event, Ability source, Game game, int amountRemovedInTotal, int amountRemovedThisTime) {
+ super.onDamagePrevented(event, source, game, amountRemovedInTotal, amountRemovedThisTime);
+
+ if (amountRemovedInTotal == amountRemovedThisTime && amountRemovedInTotal > 0) {
+ // First instance of damage prevention, we create a new reflexive ability.
+ ReflexiveTriggeredAbility reflexive = new ReflexiveTriggeredAbility(
+ new DamageTargetEffect(SavedCounterRemovedValue.MUCH), false,
+ "When one or more counters are removed from {this} this way, it deals that much damage to any target."
);
- ability.addTarget(new TargetAnyTarget());
- game.fireReflexiveTriggeredAbility(ability, source);
+ reflexive.addTarget(new TargetAnyTarget());
+ reflexiveId = game.fireReflexiveTriggeredAbility(reflexive, source, true);
+ }
+ if (reflexiveId != null) {
+ // Set the amount of counters removed to the latest known info.
+ DelayedTriggeredAbility reflexive = game.getState().getDelayed().get(reflexiveId).orElse(null);
+ if (reflexive instanceof ReflexiveTriggeredAbility) {
+ reflexive.getEffects().setValue(SavedCounterRemovedValue.VALUE_KEY, amountRemovedInTotal);
+ } else {
+ reflexiveId = null;
+ }
}
- return false;
}
-
-}
+}
\ No newline at end of file
diff --git a/Mage.Sets/src/mage/cards/o/OathswornKnight.java b/Mage.Sets/src/mage/cards/o/OathswornKnight.java
index bd0b07f9278..093fe782c6a 100644
--- a/Mage.Sets/src/mage/cards/o/OathswornKnight.java
+++ b/Mage.Sets/src/mage/cards/o/OathswornKnight.java
@@ -36,7 +36,9 @@ public final class OathswornKnight extends CardImpl {
this.addAbility(new AttacksEachCombatStaticAbility());
// If damage would be dealt to Oathsworn Knight while it has a +1/+1 counter on it, prevent that damage and remove a +1/+1 counter from it.
- this.addAbility(new SimpleStaticAbility(new PreventDamageAndRemoveCountersEffect(false, true, true)));
+ this.addAbility(new SimpleStaticAbility(
+ new PreventDamageAndRemoveCountersEffect(false, true, true)
+ ), PreventDamageAndRemoveCountersEffect.createWatcher());
}
private OathswornKnight(final OathswornKnight card) {
diff --git a/Mage.Sets/src/mage/cards/p/PhantomCentaur.java b/Mage.Sets/src/mage/cards/p/PhantomCentaur.java
index 18682c4b0f7..9ed817b06d6 100644
--- a/Mage.Sets/src/mage/cards/p/PhantomCentaur.java
+++ b/Mage.Sets/src/mage/cards/p/PhantomCentaur.java
@@ -5,7 +5,7 @@ import mage.MageInt;
import mage.ObjectColor;
import mage.abilities.common.EntersBattlefieldAbility;
import mage.abilities.common.SimpleStaticAbility;
-import mage.abilities.effects.PhantomPreventionEffect;
+import mage.abilities.effects.PreventDamageAndRemoveCountersEffect;
import mage.abilities.effects.common.counter.AddCountersSourceEffect;
import mage.abilities.keyword.ProtectionAbility;
import mage.cards.CardImpl;
@@ -36,7 +36,9 @@ 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(new PhantomPreventionEffect()), PhantomPreventionEffect.createWatcher());
+ this.addAbility(new SimpleStaticAbility(
+ new PreventDamageAndRemoveCountersEffect(false, false, false).withPhantomText()
+ ), PreventDamageAndRemoveCountersEffect.createWatcher());
}
private PhantomCentaur(final PhantomCentaur card) {
diff --git a/Mage.Sets/src/mage/cards/p/PhantomFlock.java b/Mage.Sets/src/mage/cards/p/PhantomFlock.java
index ef667a25540..d0cbf7ce647 100644
--- a/Mage.Sets/src/mage/cards/p/PhantomFlock.java
+++ b/Mage.Sets/src/mage/cards/p/PhantomFlock.java
@@ -4,7 +4,7 @@ package mage.cards.p;
import mage.MageInt;
import mage.abilities.common.EntersBattlefieldAbility;
import mage.abilities.common.SimpleStaticAbility;
-import mage.abilities.effects.PhantomPreventionEffect;
+import mage.abilities.effects.PreventDamageAndRemoveCountersEffect;
import mage.abilities.effects.common.counter.AddCountersSourceEffect;
import mage.abilities.keyword.FlyingAbility;
import mage.cards.CardImpl;
@@ -35,7 +35,9 @@ 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(new PhantomPreventionEffect()), PhantomPreventionEffect.createWatcher());
+ this.addAbility(new SimpleStaticAbility(
+ new PreventDamageAndRemoveCountersEffect(false, false, false).withPhantomText()
+ ), PreventDamageAndRemoveCountersEffect.createWatcher());
}
private PhantomFlock(final PhantomFlock card) {
diff --git a/Mage.Sets/src/mage/cards/p/PhantomNantuko.java b/Mage.Sets/src/mage/cards/p/PhantomNantuko.java
index 073074502e8..e4d22e5c842 100644
--- a/Mage.Sets/src/mage/cards/p/PhantomNantuko.java
+++ b/Mage.Sets/src/mage/cards/p/PhantomNantuko.java
@@ -6,7 +6,7 @@ import mage.abilities.common.EntersBattlefieldAbility;
import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.costs.common.TapSourceCost;
-import mage.abilities.effects.PhantomPreventionEffect;
+import mage.abilities.effects.PreventDamageAndRemoveCountersEffect;
import mage.abilities.effects.common.counter.AddCountersSourceEffect;
import mage.abilities.keyword.TrampleAbility;
import mage.cards.CardImpl;
@@ -36,7 +36,9 @@ 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(new PhantomPreventionEffect()), PhantomPreventionEffect.createWatcher());
+ this.addAbility(new SimpleStaticAbility(
+ new PreventDamageAndRemoveCountersEffect(false, false, false).withPhantomText()
+ ), PreventDamageAndRemoveCountersEffect.createWatcher());
// {tap}: Put a +1/+1 counter on Phantom Nantuko.
this.addAbility(new SimpleActivatedAbility(Zone.BATTLEFIELD, new AddCountersSourceEffect(CounterType.P1P1.createInstance()), new TapSourceCost()));
}
diff --git a/Mage.Sets/src/mage/cards/p/PhantomNishoba.java b/Mage.Sets/src/mage/cards/p/PhantomNishoba.java
index a66ae3b4fd8..bf61b40e26c 100644
--- a/Mage.Sets/src/mage/cards/p/PhantomNishoba.java
+++ b/Mage.Sets/src/mage/cards/p/PhantomNishoba.java
@@ -5,7 +5,7 @@ import mage.MageInt;
import mage.abilities.common.DealsDamageGainLifeSourceTriggeredAbility;
import mage.abilities.common.EntersBattlefieldAbility;
import mage.abilities.common.SimpleStaticAbility;
-import mage.abilities.effects.PhantomPreventionEffect;
+import mage.abilities.effects.PreventDamageAndRemoveCountersEffect;
import mage.abilities.effects.common.counter.AddCountersSourceEffect;
import mage.abilities.keyword.TrampleAbility;
import mage.cards.CardImpl;
@@ -39,7 +39,9 @@ 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(new PhantomPreventionEffect()), PhantomPreventionEffect.createWatcher());
+ this.addAbility(new SimpleStaticAbility(
+ new PreventDamageAndRemoveCountersEffect(false, false, false).withPhantomText()
+ ), PreventDamageAndRemoveCountersEffect.createWatcher());
}
private PhantomNishoba(final PhantomNishoba card) {
diff --git a/Mage.Sets/src/mage/cards/p/PhantomNomad.java b/Mage.Sets/src/mage/cards/p/PhantomNomad.java
index 7052b8243c5..0cc83aebf06 100644
--- a/Mage.Sets/src/mage/cards/p/PhantomNomad.java
+++ b/Mage.Sets/src/mage/cards/p/PhantomNomad.java
@@ -4,7 +4,7 @@ package mage.cards.p;
import mage.MageInt;
import mage.abilities.common.EntersBattlefieldAbility;
import mage.abilities.common.SimpleStaticAbility;
-import mage.abilities.effects.PhantomPreventionEffect;
+import mage.abilities.effects.PreventDamageAndRemoveCountersEffect;
import mage.abilities.effects.common.counter.AddCountersSourceEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
@@ -32,7 +32,9 @@ 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(new PhantomPreventionEffect()), PhantomPreventionEffect.createWatcher());
+ this.addAbility(new SimpleStaticAbility(
+ new PreventDamageAndRemoveCountersEffect(false, false, false).withPhantomText()
+ ), PreventDamageAndRemoveCountersEffect.createWatcher());
}
diff --git a/Mage.Sets/src/mage/cards/p/PhantomTiger.java b/Mage.Sets/src/mage/cards/p/PhantomTiger.java
index 1521811f679..7ff64b715d2 100644
--- a/Mage.Sets/src/mage/cards/p/PhantomTiger.java
+++ b/Mage.Sets/src/mage/cards/p/PhantomTiger.java
@@ -4,7 +4,7 @@ package mage.cards.p;
import mage.MageInt;
import mage.abilities.common.EntersBattlefieldAbility;
import mage.abilities.common.SimpleStaticAbility;
-import mage.abilities.effects.PhantomPreventionEffect;
+import mage.abilities.effects.PreventDamageAndRemoveCountersEffect;
import mage.abilities.effects.common.counter.AddCountersSourceEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
@@ -30,7 +30,9 @@ 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(new PhantomPreventionEffect()), PhantomPreventionEffect.createWatcher());
+ this.addAbility(new SimpleStaticAbility(
+ new PreventDamageAndRemoveCountersEffect(false, false, false).withPhantomText()
+ ), PreventDamageAndRemoveCountersEffect.createWatcher());
}
private PhantomTiger(final PhantomTiger card) {
diff --git a/Mage.Sets/src/mage/cards/p/PhantomWurm.java b/Mage.Sets/src/mage/cards/p/PhantomWurm.java
index 291b1b2367f..36a849e80e4 100644
--- a/Mage.Sets/src/mage/cards/p/PhantomWurm.java
+++ b/Mage.Sets/src/mage/cards/p/PhantomWurm.java
@@ -4,7 +4,7 @@ package mage.cards.p;
import mage.MageInt;
import mage.abilities.common.EntersBattlefieldAbility;
import mage.abilities.common.SimpleStaticAbility;
-import mage.abilities.effects.PhantomPreventionEffect;
+import mage.abilities.effects.PreventDamageAndRemoveCountersEffect;
import mage.abilities.effects.common.counter.AddCountersSourceEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
@@ -32,7 +32,9 @@ 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(new PhantomPreventionEffect()), PhantomPreventionEffect.createWatcher());
+ this.addAbility(new SimpleStaticAbility(
+ new PreventDamageAndRemoveCountersEffect(false, false, false).withPhantomText()
+ ), PreventDamageAndRemoveCountersEffect.createWatcher());
}
private PhantomWurm(final PhantomWurm card) {
diff --git a/Mage.Sets/src/mage/cards/p/PolukranosUnchained.java b/Mage.Sets/src/mage/cards/p/PolukranosUnchained.java
index 73555a78335..b8cf9c491f8 100644
--- a/Mage.Sets/src/mage/cards/p/PolukranosUnchained.java
+++ b/Mage.Sets/src/mage/cards/p/PolukranosUnchained.java
@@ -51,7 +51,9 @@ public final class PolukranosUnchained extends CardImpl {
this.addAbility(new EntersBattlefieldAbility(new PolukranosUnchainedEffect()));
// If damage would be dealt to Polukranos while it has a +1/+1 counter on it, prevent that damage and remove that many +1/+1 counters from it.
- this.addAbility(new SimpleStaticAbility(new PreventDamageAndRemoveCountersEffect(true, true, true)));
+ this.addAbility(new SimpleStaticAbility(
+ new PreventDamageAndRemoveCountersEffect(true, true, true)
+ ), PreventDamageAndRemoveCountersEffect.createWatcher());
// {1}{B}{G}: Polukranos fights another target creature.
Ability ability = new SimpleActivatedAbility(new FightTargetSourceEffect(), new ManaCostsImpl<>("{1}{B}{G}"));
diff --git a/Mage.Sets/src/mage/cards/p/ProteanHydra.java b/Mage.Sets/src/mage/cards/p/ProteanHydra.java
index 335e9602a78..e7d5640e7d1 100644
--- a/Mage.Sets/src/mage/cards/p/ProteanHydra.java
+++ b/Mage.Sets/src/mage/cards/p/ProteanHydra.java
@@ -1,6 +1,5 @@
package mage.cards.p;
-import java.util.UUID;
import mage.MageInt;
import mage.abilities.DelayedTriggeredAbility;
import mage.abilities.TriggeredAbilityImpl;
@@ -19,14 +18,15 @@ import mage.counters.CounterType;
import mage.game.Game;
import mage.game.events.GameEvent;
+import java.util.UUID;
+
/**
- *
* @author BetaSteward_at_googlemail.com
*/
public final class ProteanHydra extends CardImpl {
public ProteanHydra(UUID ownerId, CardSetInfo setInfo) {
- super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{X}{G}");
+ super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{X}{G}");
this.subtype.add(SubType.HYDRA);
this.power = new MageInt(0);
@@ -36,7 +36,9 @@ public final class ProteanHydra extends CardImpl {
this.addAbility(new EntersBattlefieldAbility(new EntersBattlefieldWithXCountersEffect(CounterType.P1P1.createInstance())));
// If damage would be dealt to Protean Hydra, prevent that damage and remove that many +1/+1 counters from it.
- this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new PreventDamageAndRemoveCountersEffect(true, false, true)));
+ this.addAbility(new SimpleStaticAbility(
+ new PreventDamageAndRemoveCountersEffect(true, false, true)
+ ), PreventDamageAndRemoveCountersEffect.createWatcher());
// Whenever a +1/+1 counter is removed from Protean Hydra, put two +1/+1 counters on it at the beginning of the next end step.
this.addAbility(new ProteanHydraAbility());
diff --git a/Mage.Sets/src/mage/cards/u/UginsConjurant.java b/Mage.Sets/src/mage/cards/u/UginsConjurant.java
index 1c6f87732bd..0c9ebd1f097 100644
--- a/Mage.Sets/src/mage/cards/u/UginsConjurant.java
+++ b/Mage.Sets/src/mage/cards/u/UginsConjurant.java
@@ -1,28 +1,26 @@
package mage.cards.u;
-import java.util.UUID;
import mage.MageInt;
-import mage.constants.SubType;
+import mage.abilities.common.EntersBattlefieldAbility;
+import mage.abilities.common.SimpleStaticAbility;
+import mage.abilities.effects.PreventDamageAndRemoveCountersEffect;
+import mage.abilities.effects.common.EntersBattlefieldWithXCountersEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
-
-import mage.abilities.common.EntersBattlefieldAbility;
-import mage.abilities.effects.common.EntersBattlefieldWithXCountersEffect;
-import mage.abilities.effects.PreventDamageAndRemoveCountersEffect;
-import mage.abilities.common.SimpleStaticAbility;
+import mage.constants.SubType;
import mage.counters.CounterType;
-import mage.constants.Zone;
+
+import java.util.UUID;
/**
- *
* @author antoni-g
*/
public final class UginsConjurant extends CardImpl {
public UginsConjurant(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{X}");
-
+
this.subtype.add(SubType.SPIRIT);
this.subtype.add(SubType.MONK);
this.power = new MageInt(0);
@@ -31,7 +29,9 @@ public final class UginsConjurant extends CardImpl {
// Ugin’s Conjurant enters the battlefield with X +1/+1 counters on it.
this.addAbility(new EntersBattlefieldAbility(new EntersBattlefieldWithXCountersEffect(CounterType.P1P1.createInstance())));
// If damage would be dealt to Ugin’s Conjurant while it has a +1/+1 counter on it, prevent that damage and remove that many +1/+1 counters from Ugin’s Conjurant.
- this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new PreventDamageAndRemoveCountersEffect(true, true, false)));
+ this.addAbility(new SimpleStaticAbility(
+ new PreventDamageAndRemoveCountersEffect(true, true, false)
+ ), PreventDamageAndRemoveCountersEffect.createWatcher());
}
private UginsConjurant(final UginsConjurant card) {
diff --git a/Mage.Sets/src/mage/cards/u/UnbreathingHorde.java b/Mage.Sets/src/mage/cards/u/UnbreathingHorde.java
index 733e9124824..1fd11461fc1 100644
--- a/Mage.Sets/src/mage/cards/u/UnbreathingHorde.java
+++ b/Mage.Sets/src/mage/cards/u/UnbreathingHorde.java
@@ -34,8 +34,9 @@ public final class UnbreathingHorde extends CardImpl {
this.addAbility(new EntersBattlefieldAbility(new UnbreathingHordeEntersEffect(), "with a +1/+1 counter on it for each other Zombie you control and each Zombie card in your graveyard"));
// If Unbreathing Horde would be dealt damage, prevent that damage and remove a +1/+1 counter from it.
- this.addAbility(new SimpleStaticAbility(new PreventDamageAndRemoveCountersEffect(false, false, true)
- .setText("if {this} would be dealt damage, prevent that damage and remove a +1/+1 counter from it")));
+ this.addAbility(new SimpleStaticAbility(
+ new PreventDamageAndRemoveCountersEffect(false, false, true)
+ ), PreventDamageAndRemoveCountersEffect.createWatcher());
}
private UnbreathingHorde(final UnbreathingHorde card) {
diff --git a/Mage.Sets/src/mage/cards/u/UndergrowthChampion.java b/Mage.Sets/src/mage/cards/u/UndergrowthChampion.java
index 5016251c3fc..46af1295609 100644
--- a/Mage.Sets/src/mage/cards/u/UndergrowthChampion.java
+++ b/Mage.Sets/src/mage/cards/u/UndergrowthChampion.java
@@ -1,7 +1,6 @@
package mage.cards.u;
-import java.util.UUID;
import mage.MageInt;
import mage.abilities.common.LandfallAbility;
import mage.abilities.common.SimpleStaticAbility;
@@ -13,20 +12,23 @@ import mage.constants.CardType;
import mage.constants.SubType;
import mage.counters.CounterType;
+import java.util.UUID;
+
/**
- *
* @author fireshoes
*/
public final class UndergrowthChampion extends CardImpl {
public UndergrowthChampion(UUID ownerId, CardSetInfo setInfo) {
- super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{1}{G}{G}");
+ super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{G}{G}");
this.subtype.add(SubType.ELEMENTAL);
this.power = new MageInt(2);
this.toughness = new MageInt(2);
// If damage would be dealt to Undergrowth Champion while it has a +1/+1 counter on it, prevent that damage and remove a +1/+1 counter from Undergrowth Champion.
- this.addAbility(new SimpleStaticAbility(new PreventDamageAndRemoveCountersEffect(false, true, false)));
+ this.addAbility(new SimpleStaticAbility(
+ new PreventDamageAndRemoveCountersEffect(false, true, false)
+ ), PreventDamageAndRemoveCountersEffect.createWatcher());
// Landfall-Whenever a land enters the battlefield under your control, put a +1/+1 counter on Undergrowth Champion.
this.addAbility(new LandfallAbility(new AddCountersSourceEffect(CounterType.P1P1.createInstance()), false));
diff --git a/Mage.Sets/src/mage/sets/Fallout.java b/Mage.Sets/src/mage/sets/Fallout.java
index cd99b28d7a7..193859d42b8 100644
--- a/Mage.Sets/src/mage/sets/Fallout.java
+++ b/Mage.Sets/src/mage/sets/Fallout.java
@@ -54,6 +54,7 @@ public final class Fallout extends ExpansionSet {
cards.add(new SetCardInfo("Biomass Mutation", 212, Rarity.RARE, mage.cards.b.BiomassMutation.class));
cards.add(new SetCardInfo("Black Market", 183, Rarity.RARE, mage.cards.b.BlackMarket.class));
cards.add(new SetCardInfo("Blasphemous Act", 188, Rarity.RARE, mage.cards.b.BlasphemousAct.class));
+ cards.add(new SetCardInfo("Bloatfly Swarm", 42, Rarity.UNCOMMON, mage.cards.b.BloatflySwarm.class));
cards.add(new SetCardInfo("Bloodforged Battle-Axe", 226, Rarity.RARE, mage.cards.b.BloodforgedBattleAxe.class));
cards.add(new SetCardInfo("Boomer Scrapper", 95, Rarity.RARE, mage.cards.b.BoomerScrapper.class));
cards.add(new SetCardInfo("Bottle-Cap Blast", 55, Rarity.UNCOMMON, mage.cards.b.BottleCapBlast.class));
diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/prevention/PreventDamageRemoveCountersTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/prevention/PreventDamageRemoveCountersTest.java
index 4ea3ac7d895..bb9407453f1 100644
--- a/Mage.Tests/src/test/java/org/mage/test/cards/prevention/PreventDamageRemoveCountersTest.java
+++ b/Mage.Tests/src/test/java/org/mage/test/cards/prevention/PreventDamageRemoveCountersTest.java
@@ -2,14 +2,13 @@ package org.mage.test.cards.prevention;
import mage.constants.PhaseStep;
import mage.constants.Zone;
-import org.junit.Ignore;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
public class PreventDamageRemoveCountersTest extends CardTestPlayerBase {
@Test
- public void testCounterRemoval() {
+ public void test_OathswornKnight_CounterRemoval() {
setStrictChooseMode(true);
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2);
@@ -32,7 +31,7 @@ public class PreventDamageRemoveCountersTest extends CardTestPlayerBase {
}
@Test
- public void testMagmaPummeler() {
+ public void test_MagmaPummeler() {
setStrictChooseMode(true);
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 8);
@@ -50,11 +49,117 @@ public class PreventDamageRemoveCountersTest extends CardTestPlayerBase {
assertPermanentCount(playerA, "Magma Pummeler", 1);
assertPowerToughness(playerA, "Magma Pummeler", 3, 3); // 2 counters removed
assertLife(playerB, 18); // 2 damage dealt
-
}
@Test
- public void testProteanHydraBoosted() {
+ public void test_MagmaPummeler_DoubleBlocked() {
+ // The part of this one that is weird is that there should be only a single trigger, that sums
+ // all the counter removed by multiple prevention effects occuring at the same time.
+ setStrictChooseMode(true);
+
+ addCard(Zone.BATTLEFIELD, playerA, "Mountain", 8);
+ addCard(Zone.HAND, playerA, "Magma Pummeler", 1);
+ addCard(Zone.BATTLEFIELD, playerB, "Memnite", 1);
+ addCard(Zone.BATTLEFIELD, playerB, "Goblin Piker", 1);
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Magma Pummeler");
+ setChoice(playerA, "X=5");
+
+ attack(3, playerA, "Magma Pummeler", playerB);
+ block(3, playerB, "Memnite", "Magma Pummeler");
+ block(3, playerB, "Goblin Piker", "Magma Pummeler");
+ setChoice(playerA, "X=5"); // damage for Pummeler, does not really matter for this test.
+ addTarget(playerA, playerB); // For the one trigger
+
+ setStopAt(3, PhaseStep.END_TURN);
+ execute();
+
+ assertPermanentCount(playerA, "Magma Pummeler", 1);
+ assertPowerToughness(playerA, "Magma Pummeler", 2, 2); // 3 counters removed
+ assertLife(playerB, 20 - 3); // 3 damage dealt by the 1 trigger.
+ }
+
+ @Test
+ public void test_MagmaPummeler_KilledByMore() {
+
+ setStrictChooseMode(true);
+
+ addCard(Zone.BATTLEFIELD, playerA, "Mountain", 7 + 5);
+ addCard(Zone.HAND, playerA, "Magma Pummeler", 1);
+ addCard(Zone.HAND, playerA, "Shivan Meteor", 1); // 13 damage to target creature
+
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Magma Pummeler");
+ setChoice(playerA, "X=5");
+ waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Shivan Meteor", "Magma Pummeler");
+ addTarget(playerA, playerB); // For the reflective trigger
+
+ setStopAt(1, PhaseStep.BEGIN_COMBAT);
+ execute();
+
+ assertGraveyardCount(playerA, "Magma Pummeler", 1);
+ assertGraveyardCount(playerA, "Shivan Meteor", 1);
+ assertLife(playerB, 20 - 5); // 5 counters removed in total.
+ }
+
+ @Test
+ public void test_MagmaPummeler_DoubleBlocked_And_Die() {
+ // The part of this one that is weird is that there should be only a single trigger, that sums
+ // all the counter removed by multiple prevention effects occuring at the same time.
+ setStrictChooseMode(true);
+
+ addCard(Zone.BATTLEFIELD, playerA, "Mountain", 8);
+ addCard(Zone.HAND, playerA, "Magma Pummeler", 1);
+ addCard(Zone.BATTLEFIELD, playerB, "Centaur Courser", 1); // 3/3
+ addCard(Zone.BATTLEFIELD, playerB, "Air Elemental", 1); // 4/4
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Magma Pummeler");
+ setChoice(playerA, "X=5");
+
+ attack(3, playerA, "Magma Pummeler", playerB);
+ block(3, playerB, "Centaur Courser", "Magma Pummeler");
+ block(3, playerB, "Air Elemental", "Magma Pummeler");
+ setChoice(playerA, "X=5"); // damage for Pummeler, does not really matter for this test.
+ addTarget(playerA, playerB); // For the one trigger
+
+ setStopAt(3, PhaseStep.END_TURN);
+ execute();
+
+ assertGraveyardCount(playerA, "Magma Pummeler", 1);
+ assertLife(playerB, 20 - 5); // 5 counters prevented, Pummeler's trigger dealt 5.
+ }
+
+
+ @Test
+ public void test_UndergrowthChampion_DoubleBlocked() {
+ setStrictChooseMode(true);
+
+ // Undergrowth Champion {1}{G}{G}
+ // Creature — Elemental
+ // If damage would be dealt to Undergrowth Champion while it has a +1/+1 counter on it, prevent that damage and remove a +1/+1 counter from Undergrowth Champion.
+ // Landfall — Whenever a land enters the battlefield under your control, put a +1/+1 counter on Undergrowth Champion.
+ // 2/2
+ addCard(Zone.BATTLEFIELD, playerA, "Undergrowth Champion");
+ addCard(Zone.HAND, playerA, "Plains");
+ addCard(Zone.BATTLEFIELD, playerB, "Grizzly Bears");
+ addCard(Zone.BATTLEFIELD, playerB, "Elite Vanguard");
+
+ playLand(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Plains");
+ // Champion now has a +1/+1 counter
+
+ attack(1, playerA, "Undergrowth Champion", playerB);
+ block(1, playerB, "Grizzly Bears", "Undergrowth Champion");
+ block(1, playerB, "Elite Vanguard", "Undergrowth Champion");
+ setChoice(playerA, "X=2"); // damage attribution
+
+ setStopAt(1, PhaseStep.END_COMBAT);
+ execute();
+
+ assertGraveyardCount(playerB, 2);
+ assertDamageReceived(playerA, "Undergrowth Champion", 0); // All the damage should be prevented.
+ assertPowerToughness(playerA, "Undergrowth Champion", 2, 2);
+ }
+
+ @Test
+ public void test_ProteanHydra_Boosted() {
setStrictChooseMode(true);
addCard(Zone.BATTLEFIELD, playerA, "Forest", 2);
diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/pip/BloatflySwarmTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/pip/BloatflySwarmTest.java
new file mode 100644
index 00000000000..18669190a78
--- /dev/null
+++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/pip/BloatflySwarmTest.java
@@ -0,0 +1,91 @@
+package org.mage.test.cards.single.pip;
+
+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 BloatflySwarmTest extends CardTestPlayerBase {
+
+ /**
+ * {@link mage.cards.b.BloatflySwarm Bloatfly Swarm} {3}{B}
+ * Creature — Insect Mutant
+ * Flying
+ * Bloatfly Swarm enters the battlefield with five +1/+1 counters on it.
+ * If damage would be dealt to Bloatfly Swarm while it has a +1/+1 counter on it, prevent that damage, remove that many +1/+1 counters from it, then give each player a rad counter for each +1/+1 counter removed this way.
+ * 0/0
+ */
+ private static final String swarm = "Bloatfly Swarm";
+
+ @Test
+ public void test_Bolt() {
+ setStrictChooseMode(true);
+
+ addCard(Zone.HAND, playerA, swarm);
+ addCard(Zone.BATTLEFIELD, playerA, "Badlands", 6);
+ addCard(Zone.HAND, playerA, "Lightning Bolt");
+
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, swarm, true);
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", swarm);
+
+ setStopAt(1, PhaseStep.BEGIN_COMBAT);
+ execute();
+
+ assertPowerToughness(playerA, swarm, 2, 2);
+ assertCounterCount(playerA, CounterType.RAD, 3);
+ assertCounterCount(playerB, CounterType.RAD, 3);
+ }
+
+ @Test
+ public void test_Combat() {
+ setStrictChooseMode(true);
+
+ addCard(Zone.BATTLEFIELD, playerA, "Concordant Crossroads"); // For haste
+ addCard(Zone.HAND, playerA, swarm);
+ addCard(Zone.BATTLEFIELD, playerA, "Swamp", 5);
+ addCard(Zone.BATTLEFIELD, playerB, "Brimstone Dragon"); // 6/6 Flying Haste
+ addCard(Zone.BATTLEFIELD, playerB, "Giant Spider"); // 2/4 Reach
+
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, swarm);
+
+ attack(1, playerA, swarm);
+ block(1, playerB, "Brimstone Dragon", swarm);
+ block(1, playerB, "Giant Spider", swarm);
+
+ setStopAt(1, PhaseStep.END_TURN);
+ execute();
+
+ assertGraveyardCount(playerA, swarm, 1);
+ assertCounterCount(playerA, CounterType.RAD, 5);
+ assertCounterCount(playerB, CounterType.RAD, 5);
+ }
+
+ @Test
+ public void test_Combat_Small() {
+ setStrictChooseMode(true);
+
+ addCard(Zone.BATTLEFIELD, playerA, "Concordant Crossroads"); // For haste
+ addCard(Zone.HAND, playerA, swarm);
+ addCard(Zone.BATTLEFIELD, playerA, "Swamp", 5);
+ addCard(Zone.BATTLEFIELD, playerB, "Wind Drake"); // 2/2 Flying
+ addCard(Zone.BATTLEFIELD, playerB, "Giant Spider"); // 2/4 Reach
+
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, swarm);
+
+ attack(1, playerA, swarm);
+ block(1, playerB, "Wind Drake", swarm);
+ block(1, playerB, "Giant Spider", swarm);
+ setChoice(playerA, "X=5"); // damage attribution
+
+ setStopAt(1, PhaseStep.END_TURN);
+ execute();
+
+ assertPowerToughness(playerA, swarm, 1, 1);
+ assertCounterCount(playerA, CounterType.RAD, 4);
+ assertCounterCount(playerB, CounterType.RAD, 4);
+ }
+}
diff --git a/Mage/src/main/java/mage/abilities/dynamicvalue/common/SavedCounterRemovedValue.java b/Mage/src/main/java/mage/abilities/dynamicvalue/common/SavedCounterRemovedValue.java
new file mode 100644
index 00000000000..fa9ec3898a2
--- /dev/null
+++ b/Mage/src/main/java/mage/abilities/dynamicvalue/common/SavedCounterRemovedValue.java
@@ -0,0 +1,46 @@
+package mage.abilities.dynamicvalue.common;
+
+import mage.abilities.Ability;
+import mage.abilities.dynamicvalue.DynamicValue;
+import mage.abilities.effects.Effect;
+import mage.game.Game;
+
+import java.util.Optional;
+
+/**
+ * For trigger/prevention effects that save a value of removed counters.
+ * Retrieve the value in resulting effects without need for custom ones.
+ *
+ * @author Susucr
+ */
+public enum SavedCounterRemovedValue implements DynamicValue {
+ MANY("many"),
+ MUCH("much");
+
+ private final String message;
+ public static final String VALUE_KEY = "CounterRemoved";
+
+ SavedCounterRemovedValue(String message) {
+ this.message = "that " + message;
+ }
+
+ @Override
+ public int calculate(Game game, Ability sourceAbility, Effect effect) {
+ return Optional.ofNullable((Integer) effect.getValue(VALUE_KEY)).orElse(0);
+ }
+
+ @Override
+ public SavedCounterRemovedValue copy() {
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return message;
+ }
+
+ @Override
+ public String getMessage() {
+ return "";
+ }
+}
diff --git a/Mage/src/main/java/mage/abilities/effects/PhantomPreventionEffect.java b/Mage/src/main/java/mage/abilities/effects/PhantomPreventionEffect.java
deleted file mode 100644
index 5eb8a35de7a..00000000000
--- a/Mage/src/main/java/mage/abilities/effects/PhantomPreventionEffect.java
+++ /dev/null
@@ -1,105 +0,0 @@
-package mage.abilities.effects;
-
-import mage.MageObjectReference;
-import mage.abilities.Ability;
-import mage.constants.Duration;
-import mage.constants.WatcherScope;
-import mage.counters.CounterType;
-import mage.game.Game;
-import mage.game.events.GameEvent;
-import mage.game.permanent.Permanent;
-import mage.watchers.Watcher;
-
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * This requires to add the PhantomPreventionWatcher
- *
- * @author Susucr
- */
-public class PhantomPreventionEffect extends PreventionEffectImpl {
-
- public static PhantomPreventionWatcher createWatcher() {
- return new PhantomPreventionWatcher();
- }
-
- public PhantomPreventionEffect() {
- super(Duration.WhileOnBattlefield);
- staticText = "If damage would be dealt to {this}, prevent that damage. Remove a +1/+1 counter from {this}";
- }
-
- protected PhantomPreventionEffect(final PhantomPreventionEffect effect) {
- super(effect);
- }
-
- @Override
- public PhantomPreventionEffect copy() {
- return new PhantomPreventionEffect(this);
- }
-
- @Override
- public boolean replaceEvent(GameEvent event, Ability source, Game game) {
- preventDamageAction(event, source, game);
- Permanent permanent = game.getPermanent(source.getSourceId());
- 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());
- }
- }
- }
-
- return false;
- }
-
- @Override
- public boolean applies(GameEvent event, Ability source, Game game) {
- if (super.applies(event, source, game)) {
- if (event.getTargetId().equals(source.getSourceId())) {
- return true;
- }
- }
- 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 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);
- }
-}
\ No newline at end of file
diff --git a/Mage/src/main/java/mage/abilities/effects/PreventDamageAndRemoveCountersEffect.java b/Mage/src/main/java/mage/abilities/effects/PreventDamageAndRemoveCountersEffect.java
index acc5eab775a..c9faecee202 100644
--- a/Mage/src/main/java/mage/abilities/effects/PreventDamageAndRemoveCountersEffect.java
+++ b/Mage/src/main/java/mage/abilities/effects/PreventDamageAndRemoveCountersEffect.java
@@ -1,18 +1,31 @@
package mage.abilities.effects;
+import mage.MageObjectReference;
import mage.abilities.Ability;
+import mage.abilities.dynamicvalue.common.SavedCounterRemovedValue;
import mage.constants.Duration;
+import mage.constants.WatcherScope;
import mage.counters.CounterType;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
+import mage.watchers.Watcher;
+
+import java.util.HashSet;
+import java.util.Set;
/**
- * @author antoni-g
+ * This requires to add the Watcher from createWatcher() together with the Prevention Effect.
+ *
+ * @author Susucr
*/
public class PreventDamageAndRemoveCountersEffect extends PreventionEffectImpl {
- private final boolean thatMany;
- private final boolean whileHasCounter;
+ private final boolean thatMany; // If true, remove as many counters as damage prevent. If false, remove 1 counter.
+ private final boolean whileHasCounter; // If true, the creature need a counter for the effect to be active.
+
+ public static Watcher createWatcher() {
+ return new PreventDamageAndRemoveCountersWatcher();
+ }
public PreventDamageAndRemoveCountersEffect(boolean thatMany, boolean whileHasCounter, boolean textFromIt) {
super(Duration.WhileOnBattlefield, Integer.MAX_VALUE, false, false);
@@ -25,6 +38,14 @@ public class PreventDamageAndRemoveCountersEffect extends PreventionEffectImpl {
" from " + (textFromIt ? "it" : "{this}");
}
+ /**
+ * A specific wording for the old "Phantom [...]" not covered by the new wording using "and"
+ */
+ public PreventDamageAndRemoveCountersEffect withPhantomText() {
+ staticText = "If damage would be dealt to {this}, prevent that damage. Remove a +1/+1 counter from {this}.";
+ return this;
+ }
+
protected PreventDamageAndRemoveCountersEffect(final PreventDamageAndRemoveCountersEffect effect) {
super(effect);
this.thatMany = effect.thatMany;
@@ -38,25 +59,94 @@ public class PreventDamageAndRemoveCountersEffect extends PreventionEffectImpl {
@Override
public boolean replaceEvent(GameEvent event, Ability source, Game game) {
- int damage = event.getAmount();
+ int damageAmount = event.getAmount();
+ // Prevent the damage.
+ // Note that removing counters does not care if prevention did work.
+ // Ruling on Phantom Wurm for instance:
+ // > If damage that can't be prevented is be dealt to Phantom Wurm,
+ // > you still remove a counter even though the damage is dealt. (2021-03-19)
preventDamageAction(event, source, game);
Permanent permanent = game.getPermanent(source.getSourceId());
- if (permanent == null) {
+ PreventDamageAndRemoveCountersWatcher watcher = game.getState().getWatcher(PreventDamageAndRemoveCountersWatcher.class);
+ if (permanent == null || watcher == null || damageAmount <= 0) {
return false;
}
- if (!thatMany) {
- damage = 1;
+ MageObjectReference mor = new MageObjectReference(source.getId(), source.getSourceObjectZoneChangeCounter(), game);
+ int beforeCount = permanent.getCounters(game).getCount(CounterType.P1P1);
+ if (thatMany) {
+ // Remove them.
+ permanent.removeCounters(CounterType.P1P1.createInstance(damageAmount), source, game);
+ } else if (!watcher.hadMORCounterRemovedThisBatch(mor)) {
+ // Remove one.
+ permanent.removeCounters(CounterType.P1P1.createInstance(), source, game);
}
- permanent.removeCounters(CounterType.P1P1.createInstance(damage), source, game); //MTG ruling (this) loses counters even if the damage isn't prevented
+ int amountRemovedThisTime = beforeCount - permanent.getCounters(game).getCount(CounterType.P1P1);
+ int amountRemovedInTotal = amountRemovedThisTime;
+ if (!watcher.hadMORCounterRemovedThisBatch(mor)) {
+ watcher.addMOR(mor);
+ } else {
+ // Sum the previous added counter
+ amountRemovedInTotal += (Integer) source.getEffects().get(0).getValue(SavedCounterRemovedValue.VALUE_KEY);
+ }
+ onDamagePrevented(event, source, game, amountRemovedInTotal, amountRemovedThisTime);
return false;
}
+ /**
+ * Meant to be Overriden if needs be.
+ */
+ protected void onDamagePrevented(GameEvent event, Ability source, Game game, int amountRemovedInTotal, int amountRemovedThisTime) {
+ source.getEffects().setValue(SavedCounterRemovedValue.VALUE_KEY, amountRemovedInTotal);
+ }
+
@Override
public boolean applies(GameEvent event, Ability source, Game game) {
Permanent permanent = game.getPermanent(event.getTargetId());
- return super.applies(event, source, game)
- && permanent != null
- && event.getTargetId().equals(source.getSourceId())
- && (!whileHasCounter || permanent.getCounters(game).containsKey(CounterType.P1P1));
+ if (!super.applies(event, source, game)
+ || permanent == null
+ || !event.getTargetId().equals(source.getSourceId())) {
+ return false;
+ }
+ if (whileHasCounter && !permanent.getCounters(game).containsKey(CounterType.P1P1)) {
+ // If the last counter has already be removed for the same batch of prevention, we still want to prevent the damage.
+ PreventDamageAndRemoveCountersWatcher watcher = game.getState().getWatcher(PreventDamageAndRemoveCountersWatcher.class);
+ MageObjectReference mor = new MageObjectReference(source.getId(), source.getSourceObjectZoneChangeCounter(), game);
+ return watcher != null && watcher.hadMORCounterRemovedThisBatch(mor);
+ }
+ return true;
}
}
+
+class PreventDamageAndRemoveCountersWatcher extends Watcher {
+
+ // We keep a very short-lived set of which PreventDamageAndRemoveCountersEffect caused
+ // +1/+1 to get removed during the current damage batch.
+ private final Set morRemovedCounterThisDamageBatch = new HashSet<>();
+
+ PreventDamageAndRemoveCountersWatcher() {
+ 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);
+ }
+}
\ No newline at end of file
diff --git a/Mage/src/main/java/mage/game/Game.java b/Mage/src/main/java/mage/game/Game.java
index b870e19124a..c12cfcba855 100644
--- a/Mage/src/main/java/mage/game/Game.java
+++ b/Mage/src/main/java/mage/game/Game.java
@@ -29,7 +29,6 @@ import mage.game.match.MatchType;
import mage.game.mulligan.Mulligan;
import mage.game.permanent.Battlefield;
import mage.game.permanent.Permanent;
-import mage.game.permanent.PermanentCard;
import mage.game.stack.Spell;
import mage.game.stack.SpellStack;
import mage.game.turn.Phase;
@@ -500,6 +499,8 @@ public interface Game extends MageItem, Serializable, Copyable {
UUID fireReflexiveTriggeredAbility(ReflexiveTriggeredAbility reflexiveAbility, Ability source);
+ UUID fireReflexiveTriggeredAbility(ReflexiveTriggeredAbility reflexiveAbility, Ability source, boolean fireAsSimultaneousEvent);
+
/**
* Inner game engine call to reset game objects to actual versions
* (reset all objects and apply all effects due layer system)
diff --git a/Mage/src/main/java/mage/game/GameImpl.java b/Mage/src/main/java/mage/game/GameImpl.java
index 7ca471242c5..9b548e4098c 100644
--- a/Mage/src/main/java/mage/game/GameImpl.java
+++ b/Mage/src/main/java/mage/game/GameImpl.java
@@ -2192,8 +2192,18 @@ public abstract class GameImpl implements Game {
@Override
public UUID fireReflexiveTriggeredAbility(ReflexiveTriggeredAbility reflexiveAbility, Ability source) {
+ return fireReflexiveTriggeredAbility(reflexiveAbility, source, false);
+ }
+
+ @Override
+ public UUID fireReflexiveTriggeredAbility(ReflexiveTriggeredAbility reflexiveAbility, Ability source, boolean fireAsSimultaneousEvent) {
UUID uuid = this.addDelayedTriggeredAbility(reflexiveAbility, source);
- this.fireEvent(GameEvent.getEvent(GameEvent.EventType.OPTION_USED, source.getOriginalId(), source, source.getControllerId()));
+ GameEvent event = GameEvent.getEvent(GameEvent.EventType.OPTION_USED, source.getOriginalId(), source, source.getControllerId());
+ if (fireAsSimultaneousEvent) {
+ this.getState().addSimultaneousEvent(event, this);
+ } else {
+ this.fireEvent(event);
+ }
return uuid;
}