mirror of
https://github.com/magefree/mage.git
synced 2026-01-26 21:29:17 -08:00
rework more Prevention Effects involving counters. Implement [PIP] Bloatfly Swarm (#12205)
This commit is contained in:
parent
e3e34dae33
commit
bcff245a31
23 changed files with 545 additions and 189 deletions
80
Mage.Sets/src/mage/cards/b/BloatflySwarm.java
Normal file
80
Mage.Sets/src/mage/cards/b/BloatflySwarm.java
Normal file
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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()));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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}"));
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
||||
// <i>Landfall</i>-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));
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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 "";
|
||||
}
|
||||
}
|
||||
|
|
@ -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<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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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<MageObjectReference> 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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<Game> {
|
|||
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue