refactor "counter [spell] unless its controller pays [...]. If they do, [effect]"

closes #13861
This commit is contained in:
Susucre 2025-07-19 17:42:04 +02:00
parent 8bb1f40bf7
commit 35e0ca2561
5 changed files with 119 additions and 135 deletions

View file

@ -1,18 +1,13 @@
package mage.cards.a;
import mage.abilities.Ability;
import mage.abilities.costs.Cost;
import mage.abilities.costs.mana.GenericManaCost;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.CounterUnlessPaysEffect;
import mage.abilities.effects.keyword.IncubateEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.filter.FilterSpell;
import mage.filter.predicate.Predicates;
import mage.game.Game;
import mage.players.Player;
import mage.target.TargetSpell;
import java.util.UUID;
@ -35,7 +30,10 @@ public final class AssimilateEssence extends CardImpl {
super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{U}");
// Counter target creature or battle spell unless its controller pays {4}. If they do, you incubate 2.
this.getSpellAbility().addEffect(new AssimilateEssenceEffect());
this.getSpellAbility().addEffect(
new CounterUnlessPaysEffect(new GenericManaCost(4))
.withIfTheyDo(new IncubateEffect(2).setText("you incubate 2"))
);
this.getSpellAbility().addTarget(new TargetSpell(filter));
}
@ -48,35 +46,3 @@ public final class AssimilateEssence extends CardImpl {
return new AssimilateEssence(this);
}
}
class AssimilateEssenceEffect extends OneShotEffect {
AssimilateEssenceEffect() {
super(Outcome.Benefit);
staticText = "counter target creature or battle spell unless its controller pays {4}. If they do, you incubate 2";
}
private AssimilateEssenceEffect(final AssimilateEssenceEffect effect) {
super(effect);
}
@Override
public AssimilateEssenceEffect copy() {
return new AssimilateEssenceEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
UUID targetId = getTargetPointer().getFirst(game, source);
Player player = game.getPlayer(game.getControllerId(targetId));
Cost cost = new GenericManaCost(4);
if (player == null
|| !cost.canPay(source, source, player.getId(), game)
|| !player.chooseUse(outcome, "Pay {4}?", source, game)
|| !cost.pay(source, game, source, player.getId(), false)) {
game.getStack().counter(targetId, source, game);
return true;
}
return IncubateEffect.doIncubate(2, source.getControllerId(), game, source);
}
}

View file

@ -1,18 +1,12 @@
package mage.cards.d;
import mage.abilities.Ability;
import mage.abilities.costs.Cost;
import mage.abilities.costs.mana.GenericManaCost;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.CounterUnlessPaysEffect;
import mage.abilities.effects.common.CreateTokenEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.game.Game;
import mage.game.permanent.token.LanderToken;
import mage.game.stack.Spell;
import mage.players.Player;
import mage.target.TargetSpell;
import java.util.UUID;
@ -26,7 +20,10 @@ public final class DivertDisaster extends CardImpl {
super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{U}");
// Counter target spell unless its controller pays {2}. If they do, you create a Lander token.
this.getSpellAbility().addEffect(new DivertDisasterEffect());
this.getSpellAbility().addEffect(
new CounterUnlessPaysEffect(new GenericManaCost(2))
.withIfTheyDo(new CreateTokenEffect(new LanderToken()).setText("you create a Lander token"))
);
this.getSpellAbility().addTarget(new TargetSpell());
}
@ -39,40 +36,3 @@ public final class DivertDisaster extends CardImpl {
return new DivertDisaster(this);
}
}
class DivertDisasterEffect extends OneShotEffect {
DivertDisasterEffect() {
super(Outcome.Benefit);
staticText = "counter target spell unless its controller pays {2}. If they do, you create a Lander token.";
}
private DivertDisasterEffect(final DivertDisasterEffect effect) {
super(effect);
}
@Override
public DivertDisasterEffect copy() {
return new DivertDisasterEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Spell spell = game.getSpell(getTargetPointer().getFirst(game, source));
if (spell == null) {
return false;
}
Player player = game.getPlayer(spell.getControllerId());
if (player == null) {
return game.getStack().counter(spell.getId(), source, game);
}
Cost cost = new GenericManaCost(2);
if (!cost.canPay(source, source, player.getId(), game)
|| !player.chooseUse(outcome, "Pay {2}?", source, game)
|| !cost.pay(source, game, source, player.getId(), false)) {
return game.getStack().counter(spell.getId(), source, game);
}
new CreateTokenEffect(new LanderToken()).apply(game, source);
return true;
}
}

View file

@ -1,19 +1,13 @@
package mage.cards.d;
import mage.abilities.Ability;
import mage.abilities.costs.Cost;
import mage.abilities.costs.mana.GenericManaCost;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.CounterUnlessPaysEffect;
import mage.abilities.effects.keyword.SurveilEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.game.Game;
import mage.game.stack.Spell;
import mage.players.Player;
import mage.target.TargetSpell;
import java.util.Optional;
import java.util.UUID;
/**
@ -25,7 +19,10 @@ public final class DontMakeASound extends CardImpl {
super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{U}");
// Counter target spell unless its controller pays {2}. If they do, surveil 2.
this.getSpellAbility().addEffect(new DontMakeASoundEffect());
this.getSpellAbility().addEffect(
new CounterUnlessPaysEffect(new GenericManaCost(2))
.withIfTheyDo(new SurveilEffect(2))
);
this.getSpellAbility().addTarget(new TargetSpell());
}
@ -38,41 +35,3 @@ public final class DontMakeASound extends CardImpl {
return new DontMakeASound(this);
}
}
class DontMakeASoundEffect extends OneShotEffect {
DontMakeASoundEffect() {
super(Outcome.Benefit);
staticText = "counter target spell unless its controller pays {2}. If they do, surveil 2";
}
private DontMakeASoundEffect(final DontMakeASoundEffect effect) {
super(effect);
}
@Override
public DontMakeASoundEffect copy() {
return new DontMakeASoundEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Spell spell = game.getSpell(getTargetPointer().getFirst(game, source));
if (spell == null) {
return false;
}
Player player = game.getPlayer(spell.getControllerId());
if (player == null) {
return game.getStack().counter(spell.getId(), source, game);
}
Cost cost = new GenericManaCost(2);
if (!cost.canPay(source, source, player.getId(), game)
|| !player.chooseUse(outcome, "Pay {2}?", source, game)
|| !cost.pay(source, game, source, player.getId(), false)) {
return game.getStack().counter(spell.getId(), source, game);
}
Optional.ofNullable(game.getPlayer(source.getControllerId()))
.ifPresent(p -> p.surveil(2, source, game));
return true;
}
}

View file

@ -0,0 +1,79 @@
package org.mage.test.cards.single.eoe;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
* @author Susucr
*/
public class DivertDisasterTest extends CardTestPlayerBase {
/**
* {@link mage.cards.d.DivertDisaster Divert Disaster} {1}{U}
* Instant
* Counter target spell unless its controller pays {2}. If they do, you create a Lander token.
*/
private static final String disaster = "Divert Disaster";
@Test
public void test_Pay() {
addCard(Zone.HAND, playerA, disaster);
addCard(Zone.HAND, playerB, "Elite Vanguard");
addCard(Zone.BATTLEFIELD, playerA, "Island", 2);
addCard(Zone.BATTLEFIELD, playerB, "Plains", 3);
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Elite Vanguard");
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerA, disaster, "Elite Vanguard", "Elite Vanguard");
setChoice(playerB, true); // yes to "pays {2}"
setStopAt(2, PhaseStep.BEGIN_COMBAT);
setStrictChooseMode(true);
execute();
assertTappedCount("Plains", true, 3);
assertPermanentCount(playerA, "Lander Token", 1);
assertPermanentCount(playerB, "Elite Vanguard", 1);
}
@Test
public void test_NoPay() {
addCard(Zone.HAND, playerA, disaster);
addCard(Zone.HAND, playerB, "Elite Vanguard");
addCard(Zone.BATTLEFIELD, playerA, "Island", 2);
addCard(Zone.BATTLEFIELD, playerB, "Plains", 3);
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Elite Vanguard");
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerA, disaster, "Elite Vanguard", "Elite Vanguard");
setChoice(playerB, false); // yes to "pays {2}"
setStopAt(2, PhaseStep.BEGIN_COMBAT);
setStrictChooseMode(true);
execute();
assertTappedCount("Plains", true, 1);
assertPermanentCount(playerA, "Lander Token", 0);
assertGraveyardCount(playerB, "Elite Vanguard", 1);
}
@Test
public void test_CantPay() {
addCard(Zone.HAND, playerA, disaster);
addCard(Zone.HAND, playerB, "Elite Vanguard");
addCard(Zone.BATTLEFIELD, playerA, "Island", 2);
addCard(Zone.BATTLEFIELD, playerB, "Plains", 1);
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Elite Vanguard");
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerA, disaster, "Elite Vanguard", "Elite Vanguard");
setChoice(playerB, false); // yes to "pays {2}"
setStopAt(2, PhaseStep.BEGIN_COMBAT);
setStrictChooseMode(true);
execute();
assertTappedCount("Plains", true, 1);
assertPermanentCount(playerA, "Lander Token", 0);
assertGraveyardCount(playerB, "Elite Vanguard", 1);
}
}

View file

@ -5,6 +5,7 @@ import mage.abilities.Mode;
import mage.abilities.costs.Cost;
import mage.abilities.costs.mana.ManaCost;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.effects.Effect;
import mage.abilities.effects.OneShotEffect;
import mage.constants.Outcome;
import mage.constants.PutCards;
@ -14,13 +15,14 @@ import mage.players.Player;
import mage.util.ManaUtil;
/**
* @author BetaSteward_at_googlemail.com
* @author BetaSteward_at_googlemail.com, Susucr
*/
public class CounterUnlessPaysEffect extends OneShotEffect {
protected Cost cost;
protected DynamicValue genericMana;
private final boolean exile;
private Effect effectIfTheyDo = null; // optional "If they do, [...]" effect
public CounterUnlessPaysEffect(Cost cost) {
this(cost, false);
@ -51,6 +53,17 @@ public class CounterUnlessPaysEffect extends OneShotEffect {
this.genericMana = effect.genericMana.copy();
}
this.exile = effect.exile;
if (effect.effectIfTheyDo != null) {
this.effectIfTheyDo = effect.effectIfTheyDo.copy();
}
}
public CounterUnlessPaysEffect withIfTheyDo(Effect effect) {
if (effectIfTheyDo != null) {
throw new IllegalStateException("Wrong code usage: only a single 'if they do' effect is expected.");
}
effectIfTheyDo = effect.copy();
return this;
}
@Override
@ -91,6 +104,9 @@ public class CounterUnlessPaysEffect extends OneShotEffect {
game.getStack().counter(spell.getId(), source, game, exile ? PutCards.EXILED : PutCards.GRAVEYARD);
} else {
game.informPlayers(player.getLogName() + " chooses to pay " + costValueMessage + " to prevent the counter effect");
if (effectIfTheyDo != null) {
effectIfTheyDo.apply(game, source);
}
}
return true;
}
@ -115,6 +131,10 @@ public class CounterUnlessPaysEffect extends OneShotEffect {
if (exile) {
sb.append(". If that spell is countered this way, exile it instead of putting it into its owner's graveyard");
}
if (effectIfTheyDo != null) {
sb.append(". If they do, ");
sb.append(effectIfTheyDo.getText(mode));
}
return sb.toString();
}
}