From 692c55e3e1b641fd94f233e8832239146f2d950d Mon Sep 17 00:00:00 2001 From: PurpleCrowbar <26198472+PurpleCrowbar@users.noreply.github.com> Date: Wed, 6 Sep 2023 19:12:03 +0100 Subject: [PATCH] Do this only once each turn - fixed wrong triggers after optional usage (example: Ondu Spiritdancer, see #11106) (#11107) * Fix Ondu Spiritdancer. Closes #11106 * Add tests for "Do this only once each turn" * Add test for Ondu Spiritdancer --- .../rules/DoThisOnlyOnceEachTurnTest.java | 127 ++++++++++++++++++ .../mage/abilities/TriggeredAbilityImpl.java | 8 +- 2 files changed, 131 insertions(+), 4 deletions(-) create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/rules/DoThisOnlyOnceEachTurnTest.java diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/rules/DoThisOnlyOnceEachTurnTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/rules/DoThisOnlyOnceEachTurnTest.java new file mode 100644 index 00000000000..462169c4e42 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/rules/DoThisOnlyOnceEachTurnTest.java @@ -0,0 +1,127 @@ +package org.mage.test.cards.rules; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author PurpleCrowbar + */ +public class DoThisOnlyOnceEachTurnTest extends CardTestPlayerBase { + + /** + * Whitesun's Passage restores 5 life + *
+ * Nykthos Paragon {4}{W}{W} + * Enchantment Creature - Human Soldier + * 4/6 + * Whenever you gain life, you may put that many +1/+1 counters on each creature you control. Do this only once each turn. + */ + @Test + public void testAbilityCantTriggerSameTurn() { + addCard(Zone.BATTLEFIELD, playerA, "Plains", 4); + addCard(Zone.BATTLEFIELD, playerA, "Nykthos Paragon"); + addCard(Zone.HAND, playerA, "Whitesun's Passage", 2); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Whitesun's Passage"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, true); + setChoice(playerA, true); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Whitesun's Passage"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA, true); + checkStackSize("empty stack", 1, PhaseStep.PRECOMBAT_MAIN, playerA, 0); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPowerToughness(playerA, "Nykthos Paragon", 9, 11); + } + + /** + * With two instances of a "Do this only once each turn" ability on the stack, + * if the first to resolve is used the second one should fizzle. + */ + @Test + public void testAbilityFizzlesSameTurn() { + addCard(Zone.BATTLEFIELD, playerA, "Plains", 4); + addCard(Zone.BATTLEFIELD, playerA, "Nykthos Paragon"); + addCard(Zone.HAND, playerA, "Whitesun's Passage", 2); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Whitesun's Passage"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA, true); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Whitesun's Passage"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA, true); + checkLife("both heals resolved", 1, PhaseStep.PRECOMBAT_MAIN, playerA, 30); + checkStackSize("both triggers on stack", 1, PhaseStep.PRECOMBAT_MAIN, playerA, 2); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPowerToughness(playerA, "Nykthos Paragon", 9, 11); // would be 14/16 if ability used twice + } + + /** + * Ability should only be prevented for the turn it is used, and should work fine on future turns. + */ + @Test + public void testAbilityTriggersNextTurn() { + addCard(Zone.BATTLEFIELD, playerA, "Plains", 4); + addCard(Zone.BATTLEFIELD, playerA, "Nykthos Paragon"); + addCard(Zone.HAND, playerA, "Whitesun's Passage", 2); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Whitesun's Passage"); + + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerA, "Whitesun's Passage"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA, true); + checkStackSize("second turn trigger", 2, PhaseStep.PRECOMBAT_MAIN, playerA, 1); + + setStopAt(2, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPowerToughness(playerA, "Nykthos Paragon", 14, 16); + } + + /** + * If ability is refused, ability should still be able to trigger that turn. + */ + @Test + public void testAbilityRefusedThenTriggers() { + addCard(Zone.BATTLEFIELD, playerA, "Plains", 4); + addCard(Zone.BATTLEFIELD, playerA, "Nykthos Paragon"); + addCard(Zone.HAND, playerA, "Whitesun's Passage", 2); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Whitesun's Passage"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA, true); + setChoice(playerA, false); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA); + checkPT("no counters added", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Nykthos Paragon", 4, 6); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Whitesun's Passage"); + setChoice(playerA, true); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPowerToughness(playerA, "Nykthos Paragon", 9, 11); + } + + /** + * Ability should be marked as used before effects resolve. See #11106 + */ + @Test + public void testOnduSpiritdancer() { + addCard(Zone.BATTLEFIELD, playerA, "Plains", 1); + addCard(Zone.BATTLEFIELD, playerA, "Ondu Spiritdancer"); + addCard(Zone.HAND, playerA, "Ajani's Welcome", 1); // generic enchantment + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Ajani's Welcome"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA, true); // resolve Ajani's Welcome + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA, true); // resolve initial Ondu Spiritdancer trigger + checkStackSize("no second trigger", 1, PhaseStep.PRECOMBAT_MAIN, playerA, 0); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + } +} diff --git a/Mage/src/main/java/mage/abilities/TriggeredAbilityImpl.java b/Mage/src/main/java/mage/abilities/TriggeredAbilityImpl.java index 09ea251c8eb..bea7fff3c9e 100644 --- a/Mage/src/main/java/mage/abilities/TriggeredAbilityImpl.java +++ b/Mage/src/main/java/mage/abilities/TriggeredAbilityImpl.java @@ -157,15 +157,15 @@ public abstract class TriggeredAbilityImpl extends AbilityImpl implements Trigge return false; } } - //20091005 - 603.4 - if (!super.resolve(game)) { - return false; - } if (doOnlyOnceEachTurn) { game.getState().setValue(CardUtil.getCardZoneString( "lastTurnUsed" + originalId, sourceId, game ), game.getTurnNum()); } + //20091005 - 603.4 + if (!super.resolve(game)) { + return false; + } return true; }