From 35e0ca25610bf73154608867e060d6e23d953d01 Mon Sep 17 00:00:00 2001 From: Susucre <34709007+Susucre@users.noreply.github.com> Date: Sat, 19 Jul 2025 17:42:04 +0200 Subject: [PATCH] refactor "counter [spell] unless its controller pays [...]. If they do, [effect]" closes #13861 --- .../src/mage/cards/a/AssimilateEssence.java | 46 ++--------- .../src/mage/cards/d/DivertDisaster.java | 52 ++---------- .../src/mage/cards/d/DontMakeASound.java | 55 ++----------- .../cards/single/eoe/DivertDisasterTest.java | 79 +++++++++++++++++++ .../common/CounterUnlessPaysEffect.java | 22 +++++- 5 files changed, 119 insertions(+), 135 deletions(-) create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/single/eoe/DivertDisasterTest.java diff --git a/Mage.Sets/src/mage/cards/a/AssimilateEssence.java b/Mage.Sets/src/mage/cards/a/AssimilateEssence.java index 1e3852eee1b..29d2cff7dc6 100644 --- a/Mage.Sets/src/mage/cards/a/AssimilateEssence.java +++ b/Mage.Sets/src/mage/cards/a/AssimilateEssence.java @@ -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)); } @@ -47,36 +45,4 @@ public final class AssimilateEssence extends CardImpl { public AssimilateEssence copy() { 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); - } -} +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/d/DivertDisaster.java b/Mage.Sets/src/mage/cards/d/DivertDisaster.java index 64e92821e0f..d8cc9a6be37 100644 --- a/Mage.Sets/src/mage/cards/d/DivertDisaster.java +++ b/Mage.Sets/src/mage/cards/d/DivertDisaster.java @@ -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()); } @@ -38,41 +35,4 @@ public final class DivertDisaster extends CardImpl { public DivertDisaster copy() { 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; - } -} +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/d/DontMakeASound.java b/Mage.Sets/src/mage/cards/d/DontMakeASound.java index 4a40f281698..7934615b8b4 100644 --- a/Mage.Sets/src/mage/cards/d/DontMakeASound.java +++ b/Mage.Sets/src/mage/cards/d/DontMakeASound.java @@ -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()); } @@ -37,42 +34,4 @@ public final class DontMakeASound extends CardImpl { public DontMakeASound copy() { 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; - } -} +} \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/eoe/DivertDisasterTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/eoe/DivertDisasterTest.java new file mode 100644 index 00000000000..335189673a0 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/eoe/DivertDisasterTest.java @@ -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); + } +} diff --git a/Mage/src/main/java/mage/abilities/effects/common/CounterUnlessPaysEffect.java b/Mage/src/main/java/mage/abilities/effects/common/CounterUnlessPaysEffect.java index fa5b1363f3a..d36beca17a9 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/CounterUnlessPaysEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/CounterUnlessPaysEffect.java @@ -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(); } }