Cleanup: PreventDamageAndRemoveCountersEffect (#10321)

* Add tests (two passing, one failing due to incorrect implementation)

* Cleanup PreventDamageAndRemoveCountersEffect; fix Protean Hydra
This commit is contained in:
xenohedron 2023-06-02 11:29:51 +03:00 committed by GitHub
parent 0df610fbe7
commit 913d5dfee8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 111 additions and 175 deletions

View file

@ -5,13 +5,12 @@ import mage.abilities.Ability;
import mage.abilities.common.EntersBattlefieldAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.common.delayed.ReflexiveTriggeredAbility;
import mage.abilities.effects.PreventionEffectImpl;
import mage.abilities.effects.PreventDamageAndRemoveCountersEffect;
import mage.abilities.effects.common.DamageTargetEffect;
import mage.abilities.effects.common.EntersBattlefieldWithXCountersEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.SubType;
import mage.counters.CounterType;
import mage.game.Game;
@ -36,7 +35,8 @@ 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.
// 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()));
}
@ -50,13 +50,11 @@ public final class MagmaPummeler extends CardImpl {
}
}
class MagmaPummelerEffect extends PreventionEffectImpl {
class MagmaPummelerEffect extends PreventDamageAndRemoveCountersEffect {
public MagmaPummelerEffect() {
super(Duration.WhileOnBattlefield, Integer.MAX_VALUE, false, false);
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.";
super(true, true, true);
staticText += ". When one or more counters are removed from {this} this way, it deals that much damage to any target";
}
private MagmaPummelerEffect(final MagmaPummelerEffect effect) {
@ -68,21 +66,14 @@ class MagmaPummelerEffect extends PreventionEffectImpl {
return new MagmaPummelerEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
return true;
}
@Override
public boolean replaceEvent(GameEvent event, Ability source, Game game) {
int damage = event.getAmount();
preventDamageAction(event, source, game);
Permanent permanent = game.getPermanent(source.getSourceId());
if (permanent == null) {
return false;
}
int beforeCounters = permanent.getCounters(game).getCount(CounterType.P1P1);
permanent.removeCounters(CounterType.P1P1.createInstance(damage), source, game);
super.replaceEvent(event, source, game);
int countersRemoved = beforeCounters - permanent.getCounters(game).getCount(CounterType.P1P1);
if (countersRemoved > 0) {
ReflexiveTriggeredAbility ability = new ReflexiveTriggeredAbility(
@ -95,12 +86,4 @@ class MagmaPummelerEffect extends PreventionEffectImpl {
return false;
}
@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())
&& permanent.getCounters(game).containsKey(CounterType.P1P1);
}
}

View file

@ -36,7 +36,7 @@ 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)));
this.addAbility(new SimpleStaticAbility(new PreventDamageAndRemoveCountersEffect(false, true, true)));
}
private OathswornKnight(final OathswornKnight card) {

View file

@ -51,7 +51,7 @@ 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)));
this.addAbility(new SimpleStaticAbility(new PreventDamageAndRemoveCountersEffect(true, true, true)));
// {1}{B}{G}: Polukranos fights another target creature.
Ability ability = new SimpleActivatedAbility(new FightTargetSourceEffect(), new ManaCostsImpl<>("{1}{B}{G}"));

View file

@ -19,7 +19,6 @@ import mage.constants.Zone;
import mage.counters.CounterType;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.events.GameEvent.EventType;
/**
*
@ -38,7 +37,7 @@ 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)));
this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new PreventDamageAndRemoveCountersEffect(true, false, true)));
// 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());
@ -76,10 +75,7 @@ public final class ProteanHydra extends CardImpl {
@Override
public boolean checkTrigger(GameEvent event, Game game) {
if (event.getData().equals("+1/+1") && event.getTargetId().equals(this.getSourceId())) {
return true;
}
return false;
return event.getData().equals("+1/+1") && event.getTargetId().equals(this.getSourceId());
}
@Override

View file

@ -31,7 +31,7 @@ public final class UginsConjurant extends CardImpl {
// Ugins 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 Ugins Conjurant while it has a +1/+1 counter on it, prevent that damage and remove that many +1/+1 counters from Ugins Conjurant.
this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new PreventDamageAndRemoveCountersEffect(true)));
this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new PreventDamageAndRemoveCountersEffect(true, true, false)));
}
private UginsConjurant(final UginsConjurant card) {

View file

@ -5,7 +5,7 @@ import mage.abilities.Ability;
import mage.abilities.common.EntersBattlefieldAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.PreventionEffectImpl;
import mage.abilities.effects.PreventDamageAndRemoveCountersEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
@ -13,10 +13,6 @@ import mage.counters.CounterType;
import mage.filter.common.FilterCreatureCard;
import mage.filter.common.FilterCreaturePermanent;
import mage.game.Game;
import mage.game.events.DamageEvent;
import mage.game.events.GameEvent;
import mage.game.events.PreventDamageEvent;
import mage.game.events.PreventedDamageEvent;
import mage.game.permanent.Permanent;
import mage.players.Player;
@ -35,10 +31,11 @@ public final class UnbreathingHorde extends CardImpl {
this.toughness = new MageInt(0);
// Unbreathing Horde enters the battlefield with a +1/+1 counter on it for each other Zombie you control and each Zombie card in your graveyard.
this.addAbility(new EntersBattlefieldAbility(new UnbreathingHordeEffect1(), "with a +1/+1 counter on it for each other Zombie you control and each Zombie card in your graveyard"));
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(Zone.BATTLEFIELD, new UnbreathingHordeEffect2()));
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")));
}
private UnbreathingHorde(final UnbreathingHorde card) {
@ -51,7 +48,7 @@ public final class UnbreathingHorde extends CardImpl {
}
}
class UnbreathingHordeEffect1 extends OneShotEffect {
class UnbreathingHordeEntersEffect extends OneShotEffect {
private static final FilterCreaturePermanent filter1 = new FilterCreaturePermanent();
private static final FilterCreatureCard filter2 = new FilterCreatureCard();
@ -61,12 +58,12 @@ class UnbreathingHordeEffect1 extends OneShotEffect {
filter2.add(SubType.ZOMBIE.getPredicate());
}
public UnbreathingHordeEffect1() {
public UnbreathingHordeEntersEffect() {
super(Outcome.BoostCreature);
staticText = "{this} enters the battlefield with a +1/+1 counter on it for each other Zombie you control and each Zombie card in your graveyard";
}
public UnbreathingHordeEffect1(final UnbreathingHordeEffect1 effect) {
public UnbreathingHordeEntersEffect(final UnbreathingHordeEntersEffect effect) {
super(effect);
}
@ -86,56 +83,8 @@ class UnbreathingHordeEffect1 extends OneShotEffect {
}
@Override
public UnbreathingHordeEffect1 copy() {
return new UnbreathingHordeEffect1(this);
}
}
class UnbreathingHordeEffect2 extends PreventionEffectImpl {
public UnbreathingHordeEffect2() {
super(Duration.WhileOnBattlefield);
staticText = "If damage would be dealt to {this}, prevent that damage and remove a +1/+1 counter from it";
}
public UnbreathingHordeEffect2(final UnbreathingHordeEffect2 effect) {
super(effect);
}
@Override
public UnbreathingHordeEffect2 copy() {
return new UnbreathingHordeEffect2(this);
}
@Override
public boolean apply(Game game, Ability source) {
return true;
}
@Override
public boolean replaceEvent(GameEvent event, Ability source, Game game) {
boolean retValue = false;
GameEvent preventEvent = new PreventDamageEvent(event.getTargetId(), source.getSourceId(), source, source.getControllerId(), event.getAmount(), ((DamageEvent) event).isCombatDamage());
int damage = event.getAmount();
if (!game.replaceEvent(preventEvent)) {
event.setAmount(0);
game.fireEvent(new PreventedDamageEvent(event.getTargetId(), source.getSourceId(), source, source.getControllerId(), damage));
retValue = true;
}
Permanent permanent = game.getPermanent(source.getSourceId());
if (permanent != null) {
permanent.removeCounters(CounterType.P1P1.createInstance(), source, game);
}
return retValue;
}
@Override
public boolean applies(GameEvent event, Ability source, Game game) {
if (super.applies(event, source, game)) {
return event.getTargetId().equals(source.getSourceId());
}
return false;
public UnbreathingHordeEntersEffect copy() {
return new UnbreathingHordeEntersEffect(this);
}
}

View file

@ -3,23 +3,15 @@ package mage.cards.u;
import java.util.UUID;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.LandfallAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.PreventionEffectImpl;
import mage.abilities.effects.PreventDamageAndRemoveCountersEffect;
import mage.abilities.effects.common.counter.AddCountersSourceEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.constants.Duration;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import mage.counters.CounterType;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.game.turn.Step;
/**
*
@ -34,7 +26,7 @@ public final class UndergrowthChampion extends CardImpl {
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(Zone.BATTLEFIELD, new UndergrowthChampionPreventionEffect()));
this.addAbility(new SimpleStaticAbility(new PreventDamageAndRemoveCountersEffect(false, true, false)));
// <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));
@ -49,70 +41,3 @@ public final class UndergrowthChampion extends CardImpl {
return new UndergrowthChampion(this);
}
}
class UndergrowthChampionPreventionEffect extends PreventionEffectImpl {
// remember turn and phase step to check if counter in this step was already removed
private int turn = 0;
private Step combatPhaseStep = null;
public UndergrowthChampionPreventionEffect() {
super(Duration.WhileOnBattlefield);
staticText = "If damage would be dealt to {this} while it has a +1/+1 counter on it, prevent that damage and remove a +1/+1 counter from {this}";
}
public UndergrowthChampionPreventionEffect(final UndergrowthChampionPreventionEffect effect) {
super(effect);
this.turn = effect.turn;
this.combatPhaseStep = effect.combatPhaseStep;
}
@Override
public UndergrowthChampionPreventionEffect copy() {
return new UndergrowthChampionPreventionEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
return true;
}
@Override
public boolean replaceEvent(GameEvent event, Ability source, Game game) {
Permanent permanent = game.getPermanent(source.getSourceId());
if (permanent != null) {
boolean removeCounter = true;
// check if in the same combat damage step already a counter was removed
if (game.getTurn().getPhase().getStep().getType() == PhaseStep.COMBAT_DAMAGE) {
if (game.getTurnNum() == turn
&& game.getTurn().getStep().equals(combatPhaseStep)) {
removeCounter = false;
} else {
turn = game.getTurnNum();
combatPhaseStep = game.getTurn().getStep();
}
}
if(removeCounter && permanent.getCounters(game).containsKey(CounterType.P1P1)) {
preventDamageAction(event, source, game);
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;
}
}

View file

@ -0,0 +1,77 @@
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() {
setStrictChooseMode(true);
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2);
addCard(Zone.HAND, playerA, "Flame Slash", 1);
addCard(Zone.HAND, playerA, "Shock", 1);
addCard(Zone.BATTLEFIELD, playerA, "Oathsworn Knight", 1);
addCard(Zone.BATTLEFIELD, playerA, "Polukranos, Unchained", 1);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Flame Slash", "Oathsworn Knight");
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Shock", "Polukranos, Unchained");
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
assertPermanentCount(playerA, "Oathsworn Knight", 1);
assertPermanentCount(playerA, "Polukranos, Unchained", 1);
assertPowerToughness(playerA, "Oathsworn Knight", 3, 3); // 1 counter removed
assertPowerToughness(playerA, "Polukranos, Unchained", 4, 4); // 2 counters removed
}
@Test
public void testMagmaPummeler() {
setStrictChooseMode(true);
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 8);
addCard(Zone.HAND, playerA, "Magma Pummeler", 1);
addCard(Zone.HAND, playerA, "Shock", 1);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Magma Pummeler");
setChoice(playerA, "X=5");
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Shock", "Magma Pummeler");
addTarget(playerA, playerB);
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
assertPermanentCount(playerA, "Magma Pummeler", 1);
assertPowerToughness(playerA, "Magma Pummeler", 3, 3); // 2 counters removed
assertLife(playerB, 18); // 2 damage dealt
}
@Test
public void testProteanHydraBoosted() {
setStrictChooseMode(true);
addCard(Zone.BATTLEFIELD, playerA, "Forest", 2);
addCard(Zone.BATTLEFIELD, playerA, "Glorious Anthem", 1);
addCard(Zone.HAND, playerA, "Protean Hydra", 1);
addCard(Zone.HAND, playerA, "Hornet Sting", 1);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Protean Hydra");
setChoice(playerA, "X=0");
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Hornet Sting", "Protean Hydra");
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
assertPermanentCount(playerA, "Protean Hydra", 1);
assertPowerToughness(playerA, "Protean Hydra", 1, 1);
}
}

View file

@ -12,17 +12,23 @@ import mage.game.permanent.Permanent;
*/
public class PreventDamageAndRemoveCountersEffect extends PreventionEffectImpl {
private final boolean thatMany;
private final boolean whileHasCounter;
public PreventDamageAndRemoveCountersEffect(boolean thatMany) {
public PreventDamageAndRemoveCountersEffect(boolean thatMany, boolean whileHasCounter, boolean textFromIt) {
super(Duration.WhileOnBattlefield, Integer.MAX_VALUE, false, false);
this.thatMany = thatMany;
staticText = "If damage would be dealt to {this} while it has a +1/+1 counter on it, " +
"prevent that damage and remove " + (thatMany ? "that many +1/+1 counters" : "a +1/+1 counter") + " from it";
this.whileHasCounter = whileHasCounter;
staticText = "If damage would be dealt to {this}" +
(whileHasCounter ? " while it has a +1/+1 counter on it" : "") +
", prevent that damage and remove " +
(thatMany ? "that many +1/+1 counters" : "a +1/+1 counter") +
" from " + (textFromIt ? "it" : "{this}");
}
private PreventDamageAndRemoveCountersEffect(final PreventDamageAndRemoveCountersEffect effect) {
protected PreventDamageAndRemoveCountersEffect(final PreventDamageAndRemoveCountersEffect effect) {
super(effect);
this.thatMany = effect.thatMany;
this.whileHasCounter = effect.whileHasCounter;
}
@Override
@ -56,6 +62,6 @@ public class PreventDamageAndRemoveCountersEffect extends PreventionEffectImpl {
return super.applies(event, source, game)
&& permanent != null
&& event.getTargetId().equals(source.getSourceId())
&& permanent.getCounters(game).containsKey(CounterType.P1P1);
&& (!whileHasCounter || permanent.getCounters(game).containsKey(CounterType.P1P1));
}
}