diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/SuspendTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/SuspendTest.java index 5a8536f4b70..7cd178aa7e6 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/SuspendTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/SuspendTest.java @@ -29,6 +29,9 @@ public class SuspendTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Epochrasite"); castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Lightning Bolt", "Epochrasite"); + setChoice(playerA, true); // choose yes to cast + + setStrictChooseMode(true); setStopAt(7, PhaseStep.PRECOMBAT_MAIN); execute(); @@ -55,6 +58,9 @@ public class SuspendTest extends CardTestPlayerBase { activateAbility(3, PhaseStep.PRECOMBAT_MAIN, playerA, "{2}, Exile a nonland card from your hand: Put four time counters on the exiled card. If it doesn't have suspend, it gains suspend"); setChoice(playerA, "Silvercoat Lion"); + setChoice(playerA, true); // choose yes to cast + + setStrictChooseMode(true); setStopAt(11, PhaseStep.PRECOMBAT_MAIN); execute(); @@ -87,6 +93,9 @@ public class SuspendTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Silvercoat Lion"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Delay", "Silvercoat Lion"); + setChoice(playerA, true); // choose yes to cast + + setStrictChooseMode(true); setStopAt(7, PhaseStep.BEGIN_COMBAT); execute(); @@ -109,6 +118,7 @@ public class SuspendTest extends CardTestPlayerBase { activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Suspend"); castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Lightning Bolt", playerA); + setStrictChooseMode(true); setStopAt(1, PhaseStep.END_TURN); execute(); @@ -128,6 +138,7 @@ public class SuspendTest extends CardTestPlayerBase { checkPlayableAbility("Can't cast directly", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Ancestral", false); // castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Ancestral Vision", playerA); + setStrictChooseMode(true); setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); @@ -150,6 +161,7 @@ public class SuspendTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerB, "Suppression Field", 1); activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Suspend"); + setStrictChooseMode(true); setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); @@ -183,8 +195,10 @@ public class SuspendTest extends CardTestPlayerBase { castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Knowledge Pool"); + setChoice(playerA, true); // choose yes to cast addTarget(playerA, playerB); + setStrictChooseMode(true); setStopAt(3, PhaseStep.BEGIN_COMBAT); execute(); @@ -226,6 +240,7 @@ public class SuspendTest extends CardTestPlayerBase { checkExileCount("after counter", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", 1); // 3 time counters removes on upkeep (3, 5, 7) and cast again + setChoice(playerA, true); // choose yes to cast addTarget(playerA, playerB); checkLife("after suspend", 7, PhaseStep.PRECOMBAT_MAIN, playerB, 20 - 3); checkGraveyardCount("after suspend", 7, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", 1); @@ -259,6 +274,7 @@ public class SuspendTest extends CardTestPlayerBase { checkExileCount("after counter", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Wear // Tear", 1); // 3 time counters removes on upkeep (3, 5, 7) and cast again + setChoice(playerA, true); // choose yes to cast addTarget(playerA, "Bident of Thassa"); checkPermanentCount("after suspend", 7, PhaseStep.PRECOMBAT_MAIN, playerB, "Bident of Thassa", 0); checkPermanentCount("after suspend", 7, PhaseStep.PRECOMBAT_MAIN, playerB, "Bow of Nylea", 1); @@ -300,6 +316,7 @@ public class SuspendTest extends CardTestPlayerBase { checkExileCount("after counter", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Wear // Tear", 1); // 3 time counters removes on upkeep (3, 5, 7) and cast again (fused cards can't be played from exile zone, so select split spell only) + setChoice(playerA, true); // choose yes to cast setChoice(playerA, "Cast Wear"); addTarget(playerA, "Bident of Thassa"); checkPermanentCount("after suspend", 7, PhaseStep.PRECOMBAT_MAIN, playerB, "Bident of Thassa", 0); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/ncc/SinisterConciergeTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/ncc/SinisterConciergeTest.java index c63d0be9ec7..1aef884b088 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/ncc/SinisterConciergeTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/ncc/SinisterConciergeTest.java @@ -1,9 +1,6 @@ package org.mage.test.cards.single.ncc; -import mage.abilities.costs.mana.ManaCostsImpl; -import mage.abilities.keyword.SuspendAbility; import mage.constants.PhaseStep; -import mage.constants.TimingRule; import mage.constants.Zone; import mage.counters.CounterType; import org.junit.Test; @@ -29,6 +26,7 @@ public class SinisterConciergeTest extends CardTestPlayerBase { */ @Test public void testWorking() { + // TODO: remove multiple calls to execute() addCard(Zone.HAND, playerA, lightningBolt); addCard(Zone.BATTLEFIELD, playerA, sinisterConcierge); addCard(Zone.BATTLEFIELD, playerA, "Mountain"); @@ -59,12 +57,14 @@ public class SinisterConciergeTest extends CardTestPlayerBase { assertExileCount(playerB, bondedConstruct, 1); assertCounterOnExiledCardCount(bondedConstruct, CounterType.TIME, 1); + setChoice(playerB, true); // yes to cast setStopAt(6, PhaseStep.PRECOMBAT_MAIN); execute(); assertExileCount(playerA, sinisterConcierge, 1); assertExileCount(playerB, bondedConstruct, 0); assertPermanentCount(playerB, bondedConstruct, 1); + setChoice(playerA, true); // yes to cast setStopAt(7, PhaseStep.PRECOMBAT_MAIN); execute(); assertExileCount(playerA, sinisterConcierge, 0); diff --git a/Mage/src/main/java/mage/abilities/keyword/SuspendAbility.java b/Mage/src/main/java/mage/abilities/keyword/SuspendAbility.java index afaaa3baec5..ed6e2691fa3 100644 --- a/Mage/src/main/java/mage/abilities/keyword/SuspendAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/SuspendAbility.java @@ -30,25 +30,25 @@ import java.util.List; import java.util.UUID; /** - * 502.59. Suspend + * 702.62. Suspend *
- * 502.59a Suspend is a keyword that represents three abilities. The first is a - * static ability that functions while the card with suspend is in a player's - * hand. The second and third are triggered abilities that function in the - * removed-from-the-game zone. "Suspend N--[cost]" means "If you could play this - * card from your hand, you may pay [cost] and remove it from the game with N - * time counters on it. This is a special action that doesn't use the stack," - * and "At the beginning of your upkeep, if this card is suspended, remove a - * time counter from it," and "When the last time counter is removed from this - * card, if it's removed from the game, play it without paying its mana cost if - * able. If you can't, it remains removed from the game. If you play it this way - * and it's a creature, it gains haste until you lose control of it." + * 702.62a. Suspend is a keyword that represents three abilities. + * The first is a static ability that functions while the card with suspend is in a player's hand. + * The second and third are triggered abilities that function in the exile zone. + * "Suspend N--[cost]" means "If you could begin to cast this card by putting it onto the stack from your hand, + * you may pay [cost] and exile it with N time counters on it. This action doesn't use the stack," + * and "At the beginning of your upkeep, if this card is suspended, remove a time counter from it," + * and "When the last time counter is removed from this card, if it's exiled, + * you may play it without paying its mana cost if able. If you don't, it remains exiled. + * If you cast a creature spell this way, it gains haste until you lose control of the spell or the permanent it becomes." *
- * 502.59b A card is "suspended" if it's in the removed-from-the-game zone, has - * suspend, and has a time counter on it. + * 702.62b. A card is "suspended" if it's in the exile zone, has suspend, and has a time counter on it. *
- * 502.59c Playing a spell as an effect of its suspend ability follows the rules - * for paying alternative costs in rules 409.1b and 409.1f-h. + * 702.62c. While determining if you could begin to cast a card with suspend, + * take into consideration any effects that would prohibit that card from being cast. + *
+ * 702.62d. Casting a spell as an effect of its suspend ability follows the rules + * for paying alternative costs in rules 601.2b and 601.2f-h. *
* The phrase "if you could play this card from your hand" checks only for
* timing restrictions and permissions. This includes both what's inherent in
@@ -124,7 +124,7 @@ public class SuspendAbility extends SpecialAction {
this(suspend, cost, card, false);
}
- public SuspendAbility(int suspend, ManaCost cost, Card card, boolean shortRule) {
+ public SuspendAbility(int suspend, ManaCost cost, Card card, boolean hideReminderText) {
super(Zone.HAND);
this.addCost(cost);
this.addEffect(new SuspendExileEffect(suspend));
@@ -135,39 +135,42 @@ public class SuspendAbility extends SpecialAction {
this.addCost(xCosts);
cost = new ManaCostsImpl<>("{X}" + cost.getText());
}
- StringBuilder sb = new StringBuilder("Suspend ");
if (cost != null) {
- sb.append(suspend == Integer.MAX_VALUE ? "X" : suspend).append("—")
- .append(cost.getText()).append(suspend
- == Integer.MAX_VALUE ? ". X can't be 0." : "");
- if (!shortRule) {
- sb.append(" (Rather than cast this card from your hand, you may pay ")
- .append(cost.getText())
- .append(" and exile it with ")
- .append((suspend == 1 ? "a time counter" : (suspend == Integer.MAX_VALUE
- ? "X time counters" : CardUtil.numberToText(suspend) + " time counters")))
- .append(" on it.")
- .append(" At the beginning of your upkeep, remove a time counter. "
- + "When the last is removed, cast it without paying its mana cost.")
- .append(card.isCreature() ? " It has haste." : "")
- .append(")");
- }
+ ruleText = "Suspend " + (suspend == Integer.MAX_VALUE ? "X" : suspend) + "—"
+ + cost.getText() + (suspend == Integer.MAX_VALUE ? ". X can't be 0." : "")
+ + (hideReminderText ? "" : makeReminderText(suspend, cost.getText(), card.isCreature()));
if (card.getManaCost().isEmpty()) {
setRuleAtTheTop(true);
}
addSubAbility(new SuspendBeginningOfUpkeepInterveningIfTriggeredAbility());
addSubAbility(new SuspendPlayCardAbility());
+ } else {
+ ruleText = "Suspend";
}
- ruleText = sb.toString();
+ }
+
+ private String makeReminderText(int suspend, String costText, boolean isCreature) {
+ String counterText;
+ switch (suspend) {
+ case 1:
+ counterText = "a time counter";
+ break;
+ case Integer.MAX_VALUE:
+ counterText = "X time counters";
+ break;
+ default:
+ counterText = CardUtil.numberToText(suspend) + " time counters";
+ }
+ return " (Rather than cast this card from your hand, you may pay " + costText
+ + " and exile it with " + counterText + " on it. "
+ + "At the beginning of your upkeep, remove a time counter. "
+ + "When the last is removed, you may cast it without paying its mana cost."
+ + (isCreature ? " It has haste." : "") + ")";
}
/**
* Adds suspend to a card that does not have it regularly e.g. Epochrasite
* or added by Jhoira of the Ghitu
- *
- * @param card
- * @param source
- * @param game
*/
public static void addSuspendTemporaryToCard(Card card, Ability source, Game game) {
SuspendAbility ability = new SuspendAbility(0, null, card, false);
@@ -196,9 +199,9 @@ public class SuspendAbility extends SpecialAction {
return exileId;
}
- public SuspendAbility(SuspendAbility ability) {
+ private SuspendAbility(final SuspendAbility ability) {
super(ability);
- this.ruleText = ability.getRule();
+ this.ruleText = ability.ruleText;
this.gainedTemporary = ability.gainedTemporary;
}
@@ -239,14 +242,13 @@ class SuspendExileEffect extends OneShotEffect {
private int suspend;
- public SuspendExileEffect(int suspend) {
+ SuspendExileEffect(int suspend) {
super(Outcome.PutCardInPlay);
- this.staticText = new StringBuilder("Suspend ").append(suspend
- == Integer.MAX_VALUE ? "X" : suspend).toString();
+ this.staticText = "Suspend " + (suspend == Integer.MAX_VALUE ? "X" : suspend);
this.suspend = suspend;
}
- protected SuspendExileEffect(final SuspendExileEffect effect) {
+ private SuspendExileEffect(final SuspendExileEffect effect) {
super(effect);
this.suspend = effect.suspend;
}
@@ -260,33 +262,31 @@ class SuspendExileEffect extends OneShotEffect {
public boolean apply(Game game, Ability source) {
Card card = game.getCard(source.getSourceId());
Player controller = game.getPlayer(source.getControllerId());
- if (card != null && controller != null) {
- UUID exileId = SuspendAbility.getSuspendExileId(controller.getId(), game);
- if (controller.moveCardToExileWithInfo(card, exileId, "Suspended cards of "
- + controller.getName(), source, game, Zone.HAND, true)) {
- if (suspend == Integer.MAX_VALUE) {
- suspend = source.getManaCostsToPay().getX();
- }
- card.addCounters(CounterType.TIME.createInstance(suspend), source.getControllerId(), source, game);
- if (!game.isSimulation()) {
- game.informPlayers(controller.getLogName()
- + " suspends (" + suspend + ") " + card.getLogName());
- }
- return true;
- }
+ if (card == null || controller == null) {
+ return false;
}
- return false;
+ UUID exileId = SuspendAbility.getSuspendExileId(controller.getId(), game);
+ if (controller.moveCardToExileWithInfo(card, exileId, "Suspended cards of "
+ + controller.getName(), source, game, Zone.HAND, true)) {
+ if (suspend == Integer.MAX_VALUE) {
+ suspend = source.getManaCostsToPay().getX();
+ }
+ card.addCounters(CounterType.TIME.createInstance(suspend), source.getControllerId(), source, game);
+ game.informPlayers(controller.getLogName()
+ + " suspends (" + suspend + ") " + card.getLogName());
+ }
+ return true;
}
}
class SuspendPlayCardAbility extends TriggeredAbilityImpl {
- public SuspendPlayCardAbility() {
+ SuspendPlayCardAbility() {
super(Zone.EXILED, new SuspendPlayCardEffect());
setRuleVisible(false);
}
- public SuspendPlayCardAbility(SuspendPlayCardAbility ability) {
+ private SuspendPlayCardAbility(final SuspendPlayCardAbility ability) {
super(ability);
}
@@ -308,8 +308,7 @@ class SuspendPlayCardAbility extends TriggeredAbilityImpl {
@Override
public String getRule() {
- return "When the last time counter is removed from this card ({this}), "
- + "if it's removed from the game, ";
+ return "When the last time counter is removed from {this}, if it's exiled, ";
}
@Override
@@ -320,10 +319,9 @@ class SuspendPlayCardAbility extends TriggeredAbilityImpl {
class SuspendPlayCardEffect extends OneShotEffect {
- public SuspendPlayCardEffect() {
+ SuspendPlayCardEffect() {
super(Outcome.PlayForFree);
- this.staticText = "play it without paying its mana cost if able. "
- + "If you can't, it remains removed from the game";
+ staticText = "you may play it without paying its mana cost if able. If you don't, it remains exiled";
}
protected SuspendPlayCardEffect(final SuspendPlayCardEffect effect) {
@@ -339,49 +337,48 @@ class SuspendPlayCardEffect extends OneShotEffect {
public boolean apply(Game game, Ability source) {
Player player = game.getPlayer(source.getControllerId());
Card card = game.getCard(source.getSourceId());
- if (player != null && card != null) {
- // remove temporary suspend ability (used e.g. for Epochrasite)
- // TODO: isGainedTemporary is not set or use in other places, so it can be deleted?!
- List