diff --git a/Mage.Sets/src/mage/cards/u/UnravelTheAether.java b/Mage.Sets/src/mage/cards/u/UnravelTheAether.java index b508910aac..88cc750b08 100644 --- a/Mage.Sets/src/mage/cards/u/UnravelTheAether.java +++ b/Mage.Sets/src/mage/cards/u/UnravelTheAether.java @@ -1,6 +1,6 @@ - package mage.cards.u; +import java.util.UUID; import mage.abilities.effects.common.ShuffleIntoLibraryTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -8,8 +8,6 @@ import mage.constants.CardType; import mage.filter.StaticFilters; import mage.target.TargetPermanent; -import java.util.UUID; - /** * * @author LevelX2 @@ -17,7 +15,7 @@ import java.util.UUID; public final class UnravelTheAether extends CardImpl { public UnravelTheAether(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{1}{G}"); + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{G}"); // Choose target artifact or enchantment. Its owner shuffles it into their library. this.getSpellAbility().addEffect(new ShuffleIntoLibraryTargetEffect()); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/curses/CursesTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/curses/CursesTest.java index 5fc29b5f3d..3f74e4b399 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/curses/CursesTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/curses/CursesTest.java @@ -16,8 +16,8 @@ public class CursesTest extends CardTestPlayerBase { Enchantment - Aura Curse Enchant player At the beginning of enchanted player's upkeep, that player sacrifices a creature or planeswalker. If the player can't, they lose 5 life. - */ - private String cReality = "Cruel Reality"; + */ + private final String cReality = "Cruel Reality"; @Test public void testCurseOfBloodletting() { @@ -29,7 +29,6 @@ public class CursesTest extends CardTestPlayerBase { castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Lightning Bolt", playerB); castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Lightning Bolt", playerA); - setStopAt(1, PhaseStep.END_TURN); execute(); @@ -43,7 +42,7 @@ public class CursesTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerB, "Island", 5); // Enchant player // Whenever enchanted player casts an instant or sorcery spell, each other player may copy that - // spell and may choose new targets for the copy they control. + // spell and may choose new targets for the copy they control. addCard(Zone.HAND, playerA, "Curse of Echoes"); // Draw three cards. addCard(Zone.HAND, playerB, "Jace's Ingenuity"); @@ -71,7 +70,6 @@ public class CursesTest extends CardTestPlayerBase { castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Lightning Bolt", playerA); castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Lightning Bolt", playerA); - setStopAt(1, PhaseStep.END_TURN); execute(); @@ -90,7 +88,6 @@ public class CursesTest extends CardTestPlayerBase { castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Lightning Bolt", playerB); castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Lightning Bolt", playerB); - setStopAt(1, PhaseStep.END_TURN); execute(); @@ -102,64 +99,59 @@ public class CursesTest extends CardTestPlayerBase { * Checks if Copy Enchantment works for player auras */ @Test - public void testCurseOfExhaustion3() { + public void testCurseOfExhaustion3() { addCard(Zone.BATTLEFIELD, playerA, "Plains", 4); addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); - - addCard(Zone.BATTLEFIELD, playerB, "Island", 3); - + + addCard(Zone.BATTLEFIELD, playerB, "Island", 3); + // Enchant player // Enchanted player can't cast more than one spell each turn. addCard(Zone.HAND, playerA, "Curse of Exhaustion"); - addCard(Zone.HAND, playerA, "Lightning Bolt", 2); - + addCard(Zone.HAND, playerA, "Lightning Bolt", 2); + addCard(Zone.HAND, playerB, "Copy Enchantment", 1); - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Curse of Exhaustion", playerB); - + castSpell(4, PhaseStep.POSTCOMBAT_MAIN, playerA, "Lightning Bolt", playerB); castSpell(4, PhaseStep.PRECOMBAT_MAIN, playerB, "Copy Enchantment"); setChoice(playerB, "Yes"); setChoice(playerB, "Curse of Exhaustion"); setChoice(playerB, "targetPlayer=PlayerA"); castSpell(4, PhaseStep.POSTCOMBAT_MAIN, playerA, "Lightning Bolt", playerB); - - setStopAt(4, PhaseStep.END_TURN); execute(); assertHandCount(playerB, "Copy Enchantment", 0); assertGraveyardCount(playerB, "Copy Enchantment", 0); - - assertPermanentCount(playerA, "Curse of Exhaustion", 1); + + assertPermanentCount(playerA, "Curse of Exhaustion", 1); assertPermanentCount(playerB, "Curse of Exhaustion", 1); - + assertLife(playerA, 20); assertLife(playerB, 17); } - + // returng curse enchantment from graveyard to battlefield @Test - public void testCurseOfExhaustion4() { + public void testCurseOfExhaustion4() { addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); - + addCard(Zone.BATTLEFIELD, playerB, "Plains", 3); addCard(Zone.BATTLEFIELD, playerB, "Swamp", 2); - + addCard(Zone.HAND, playerA, "Lightning Bolt", 2); - + addCard(Zone.GRAVEYARD, playerB, "Curse of Exhaustion", 1); addCard(Zone.HAND, playerB, "Obzedat's Aid", 1); castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Obzedat's Aid", "Curse of Exhaustion"); setChoice(playerB, "PlayerA"); - - - castSpell(2, PhaseStep.POSTCOMBAT_MAIN, playerA, "Lightning Bolt", playerB); - castSpell(2, PhaseStep.POSTCOMBAT_MAIN, playerA, "Lightning Bolt", playerB); + castSpell(2, PhaseStep.POSTCOMBAT_MAIN, playerA, "Lightning Bolt", playerB); + castSpell(2, PhaseStep.POSTCOMBAT_MAIN, playerA, "Lightning Bolt", playerB); setStopAt(2, PhaseStep.END_TURN); execute(); @@ -167,13 +159,13 @@ public class CursesTest extends CardTestPlayerBase { assertHandCount(playerB, "Obzedat's Aid", 0); assertGraveyardCount(playerB, "Obzedat's Aid", 1); assertGraveyardCount(playerB, "Curse of Exhaustion", 0); - + assertPermanentCount(playerB, "Curse of Exhaustion", 1); - + assertLife(playerA, 20); assertLife(playerB, 17); - } - + } + @Test public void testCurseOfThirst1() { addCard(Zone.BATTLEFIELD, playerA, "Swamp", 5); @@ -240,10 +232,9 @@ public class CursesTest extends CardTestPlayerBase { assertLife(playerA, 20); assertLife(playerB, 20); assertPermanentCount(playerA, "Curse of Misfortunes", 1); - assertPermanentCount(playerA, "Curse of Bloodletting", 1); + assertPermanentCount(playerA, "Curse of Bloodletting", 1); } - - + @Test public void testCurseOfDeathsHold() { // Creatures enchanted player controls get -1/-1. @@ -251,7 +242,7 @@ public class CursesTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerA, "Swamp", 5); addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion", 1); - + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Curse of Death's Hold", playerB); setStopAt(1, PhaseStep.END_COMBAT); @@ -259,12 +250,12 @@ public class CursesTest extends CardTestPlayerBase { assertLife(playerA, 20); assertLife(playerB, 20); - + assertPermanentCount(playerA, "Curse of Death's Hold", 1); - + assertPowerToughness(playerB, "Silvercoat Lion", 1, 1); } - + @Test public void testCurseOfDeathsHold2() { // Creatures enchanted player controls get -1/-1. @@ -276,31 +267,29 @@ public class CursesTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion", 1); addCard(Zone.BATTLEFIELD, playerB, "Forest", 3); addCard(Zone.HAND, playerB, "Reclamation Sage"); - + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Curse of Death's Hold", playerB); castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Reclamation Sage"); addTarget(playerB, "Curse of Death's Hold"); - + // {2}{G/U}{G/U}: Put the top two cards of your library into your graveyard, then return a nonland card of an opponent's choice from your graveyard to your hand. activateAbility(3, PhaseStep.PRECOMBAT_MAIN, playerA, "{2}{G/U}{G/U}: Mill two cards"); castSpell(3, PhaseStep.POSTCOMBAT_MAIN, playerA, "Curse of Death's Hold", playerB); - + setStopAt(3, PhaseStep.END_TURN); execute(); assertLife(playerA, 20); assertLife(playerB, 20); - + assertGraveyardCount(playerB, "Reclamation Sage", 1); assertPermanentCount(playerA, "Curse of Death's Hold", 1); assertGraveyardCount(playerA, 2); - + assertPowerToughness(playerB, "Silvercoat Lion", 1, 1); } - - @Test public void cruelRealityHasBothCreatureAndPwChoosePw() { diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/oneshot/counterspell/CounterspellTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/oneshot/counterspell/CounterspellTest.java index 130ba3b9a3..8812ef5bc4 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/oneshot/counterspell/CounterspellTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/oneshot/counterspell/CounterspellTest.java @@ -2,7 +2,6 @@ package org.mage.test.cards.abilities.oneshot.counterspell; import mage.constants.PhaseStep; import mage.constants.Zone; -import org.junit.Ignore; import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; @@ -99,4 +98,76 @@ public class CounterspellTest extends CardTestPlayerBase { assertLife(playerA, 20); assertLife(playerB, 17); } + + @Test + public void testCopyCounterToCounterGraveyard() { + // Lightning Bolt deals 3 damage to any target. + addCard(Zone.HAND, playerA, "Lightning Bolt"); + // Copy target instant or sorcery spell. You may choose new targets for the copy. + addCard(Zone.HAND, playerA, "Twincast"); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1); + addCard(Zone.BATTLEFIELD, playerA, "Island", 2); + + addCard(Zone.BATTLEFIELD, playerB, "Island", 2); + // Counter target spell + addCard(Zone.HAND, playerB, "Counterspell"); // Instant {1}{U} + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", playerB); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Counterspell", "Lightning Bolt"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Twincast", "Counterspell"); + + setChoice(playerA, "Yes"); // change the target + addTarget(playerA, "Counterspell"); + + setStrictChooseMode(true); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertAllCommandsUsed(); + + assertGraveyardCount(playerA, "Lightning Bolt", 1); + assertGraveyardCount(playerA, "Twincast", 1); + + assertGraveyardCount(playerB, "Counterspell", 1); + + assertLife(playerA, 20); + assertLife(playerB, 17); + } + + @Test + public void testCopyCounterToCounterExile() { + // Lightning Bolt deals 3 damage to any target. + addCard(Zone.HAND, playerA, "Lightning Bolt"); + // Copy target instant or sorcery spell. You may choose new targets for the copy. + addCard(Zone.HAND, playerA, "Twincast"); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1); + addCard(Zone.BATTLEFIELD, playerA, "Island", 2); + + addCard(Zone.BATTLEFIELD, playerB, "Island", 3); + // CCounter target spell. If that spell is countered this way, exile it instead of putting it into its owner's graveyard. + addCard(Zone.HAND, playerB, "Dissipate"); // Instant {1}{U} + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", playerB); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Dissipate", "Lightning Bolt"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Twincast", "Dissipate"); + + setChoice(playerA, "Yes"); // change the target + addTarget(playerA, "Dissipate"); + + setStrictChooseMode(true); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertAllCommandsUsed(); + + assertGraveyardCount(playerA, "Lightning Bolt", 1); + assertGraveyardCount(playerA, "Twincast", 1); + + assertExileCount(playerB, "Dissipate", 1); + + assertLife(playerA, 20); + assertLife(playerB, 17); + } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/enchantments/AnimateDeadTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/enchantments/AnimateDeadTest.java index bbe7b8c8ea..54b1f44a9f 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/enchantments/AnimateDeadTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/enchantments/AnimateDeadTest.java @@ -1,4 +1,3 @@ - package org.mage.test.cards.enchantments; import mage.constants.PhaseStep; @@ -119,7 +118,7 @@ public class AnimateDeadTest extends CardTestPlayerBase { castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Cruel Edict", playerA); - setStopAt(2, PhaseStep.BEGIN_COMBAT); + setStopAt(2, PhaseStep.END_TURN); execute(); assertGraveyardCount(playerB, "Cruel Edict", 1); @@ -168,6 +167,8 @@ public class AnimateDeadTest extends CardTestPlayerBase { */ @Test public void testAnimateAndDragonlordAtarkaWithNoTargets() { + setStrictChooseMode(true); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2); // Enchant creature card in a graveyard @@ -188,6 +189,11 @@ public class AnimateDeadTest extends CardTestPlayerBase { setStopAt(2, PhaseStep.BEGIN_COMBAT); execute(); + assertAllCommandsUsed(); + + assertLife(playerA, 20); + assertLife(playerB, 20); + assertPermanentCount(playerA, "Animate Dead", 1); assertPermanentCount(playerA, "Dragonlord Atarka", 1); } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/WorldgorgerDragonTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/WorldgorgerDragonTest.java index 798f0b88a6..8550b9c081 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/WorldgorgerDragonTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/WorldgorgerDragonTest.java @@ -67,6 +67,8 @@ public class WorldgorgerDragonTest extends CardTestPlayerBase { */ @Test public void testWithAnimateDead() { + setStrictChooseMode(true); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2); // When Worldgorger Dragon enters the battlefield, exile all other permanents you control. @@ -79,6 +81,7 @@ public class WorldgorgerDragonTest extends CardTestPlayerBase { // Enchanted creature gets -1/-0. addCard(Zone.HAND, playerA, "Animate Dead"); addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3); + // Instant {X}{R}{R} // Volcanic Geyser deals X damage to any target. addCard(Zone.HAND, playerA, "Volcanic Geyser", 1); @@ -86,44 +89,61 @@ public class WorldgorgerDragonTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerA, "Staunch Defenders", 1); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Animate Dead", "Worldgorger Dragon"); + setChoice(playerA, "Worldgorger Dragon"); + setChoice(playerA, "When {this} enters the battlefield, if it's"); + activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}"); + activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}"); + activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}"); + + setChoice(playerA, "Worldgorger Dragon"); + setChoice(playerA, "When {this} enters the battlefield, if it's"); + activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}"); + activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}"); + activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}"); + + setChoice(playerA, "Worldgorger Dragon"); + setChoice(playerA, "When {this} enters the battlefield, if it's"); + activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}"); + activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}"); + activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}"); + + setChoice(playerA, "Worldgorger Dragon"); + setChoice(playerA, "When {this} enters the battlefield, if it's"); + setChoice(playerA, "No"); + activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}"); + activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}"); + activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}"); + + setChoice(playerA, "Worldgorger Dragon"); setChoice(playerA, "When {this} enters the battlefield, if it's"); activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}"); activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}"); activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}"); + + setChoice(playerA, "Worldgorger Dragon"); + setChoice(playerA, "When {this} enters the battlefield, if it's"); + activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}"); activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}"); activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}"); + + setChoice(playerA, "Worldgorger Dragon"); + setChoice(playerA, "When {this} enters the battlefield, if it's"); activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}"); activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}"); activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}"); - activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}"); - activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}"); - activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}"); - activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}"); - activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}"); - activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}"); - activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}"); - activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}"); - activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}"); - activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}"); - activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}"); + activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}"); activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Volcanic Geyser", playerB, 22); setChoice(playerA, "X=20"); - // not an infinite loop resulting in a draw - for (int i = 0; i < 6; ++i) - { - setChoice(playerA, "No"); - setChoice(playerB, "No"); - } - setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); + assertAllCommandsUsed(); assertLife(playerA, 44); assertLife(playerB, 0); @@ -132,7 +152,7 @@ public class WorldgorgerDragonTest extends CardTestPlayerBase { } /** - * v9: Worldgorger Dragon + Animate Dead is still acting up (yey complex + * v9: Worldgorger Dragon + Animate Dead is still acting up (yet complex * rules interactions!). The first time you return Animate Dead from * Worldgorger's exile, it works like it's supposed to. You have to pick a * creature, and it brings it back. But if you pick Worldgorger Dragon @@ -149,6 +169,8 @@ public class WorldgorgerDragonTest extends CardTestPlayerBase { */ @Test public void testWithAnimateDeadDifferentTargets() { + setStrictChooseMode(true); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2); // When Worldgorger Dragon enters the battlefield, exile all other permanents you control. @@ -161,9 +183,9 @@ public class WorldgorgerDragonTest extends CardTestPlayerBase { // under your control and attach Animate Dead to it. When Animate Dead leaves the battlefield, that creature's controller sacrifices it. addCard(Zone.HAND, playerA, "Animate Dead"); addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3); - // Instant {X}{R}{R} + // Volcanic Geyser deals X damage to any target. - addCard(Zone.HAND, playerA, "Volcanic Geyser", 1); + addCard(Zone.HAND, playerA, "Volcanic Geyser", 1);// Instant {X}{R}{R} // When Staunch Defenders enters the battlefield, you gain 4 life. addCard(Zone.BATTLEFIELD, playerA, "Staunch Defenders", 1); @@ -176,13 +198,15 @@ public class WorldgorgerDragonTest extends CardTestPlayerBase { activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}"); activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}"); setChoice(playerA, "Worldgorger Dragon"); + setChoice(playerA, "When {this} enters the battlefield, if it's"); activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}"); activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}"); activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}"); - setChoice(playerA, "Silvercoat Lion"); + setChoice(playerA, "When {this} enters the battlefield, if it's"); + activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}"); activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}"); activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}"); @@ -193,6 +217,8 @@ public class WorldgorgerDragonTest extends CardTestPlayerBase { setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); + assertAllCommandsUsed(); + assertGraveyardCount(playerA, "Volcanic Geyser", 1); assertGraveyardCount(playerA, "Worldgorger Dragon", 1); assertPermanentCount(playerA, "Silvercoat Lion", 1); diff --git a/Mage.Tests/src/test/java/org/mage/test/turnmod/ExtraTurnsTest.java b/Mage.Tests/src/test/java/org/mage/test/turnmod/ExtraTurnsTest.java index 32b27689a1..2a07f34a25 100644 --- a/Mage.Tests/src/test/java/org/mage/test/turnmod/ExtraTurnsTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/turnmod/ExtraTurnsTest.java @@ -1,4 +1,3 @@ - package org.mage.test.turnmod; import mage.constants.PhaseStep; @@ -97,4 +96,50 @@ public class ExtraTurnsTest extends CardTestPlayerBase { Assert.assertEquals("For turn " + currentGame.getTurnNum() + ", playerB has to be the active player but active player is: " + currentGame.getPlayer(currentGame.getActivePlayerId()).getName(), currentGame.getActivePlayerId(), playerB.getId()); } + + /** + * https://github.com/magefree/mage/issues/6824 + * + * When you cast miracled Temporal Mastery with God-Eternal Kefnet on the + * battlefield and copy it with it's ability you get only 1 extra turn. It + * should be 2, since you cast Temporal Mastery with it's miracle ability + + * you get a copy from Kefnet's ability. Still after first extra turn game + * proceeds to next player. + */ + @Test + public void testCopyMiracledTemporalMastery4TwoExtraTurns() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerB, "Island", 7); + // Flying + // You may reveal the first card you draw each turn as you draw it. Whenever you reveal an instant or sorcery card this way, + // copy that card and you may cast the copy. That copy costs {2} less to cast. + // When God-Eternal Kefnet dies or is put into exile from the battlefield, you may put it into its owner’s library third from the top. + addCard(Zone.BATTLEFIELD, playerB, "God-Eternal Kefnet", 1); + // Take an extra turn after this one. Exile Temporal Mastery. + // Miracle {1}{U} (You may cast this card for its miracle cost when you draw it if it's the first card you drew this turn.) + addCard(Zone.LIBRARY, playerB, "Temporal Mastery", 1); // Sorcery {5}{U}{U} + skipInitShuffling(); + + setChoice(playerB, "Yes"); // Would you like to reveal first drawn card (Temporal Mastery, you can copy it and cast {2} less)? + setChoice(playerB, "Yes"); // Would you like to copy Temporal Mastery and cast it {2} less? + setChoice(playerB, "Yes"); // Reveal Temporal Mastery to be able to use Miracle? + setChoice(playerB, "Yes"); // Miracle {1}{U} (You may cast this card for its miracle cost when you draw it if it's the first card you drew this turn.) + + setChoice(playerB, "No"); // Would you like to reveal first drawn card? (Turn 3) + setChoice(playerB, "No"); // Would you like to reveal first drawn card? (Turn 4) + + // Turn 3 + 4 are extra turns + setStopAt(4, PhaseStep.PRECOMBAT_MAIN); + execute(); + + assertAllCommandsUsed(); + + assertExileCount(playerB, "Temporal Mastery", 1); + + Assert.assertTrue("Turn 4 is an extra turn ", currentGame.getState().isExtraTurn()); + Assert.assertEquals("For turn " + currentGame.getTurnNum() + ", playerB has to be the active player but active player is: " + + currentGame.getPlayer(currentGame.getActivePlayerId()).getName(), currentGame.getActivePlayerId(), playerB.getId()); + } + } diff --git a/Mage/src/main/java/mage/abilities/effects/Effects.java b/Mage/src/main/java/mage/abilities/effects/Effects.java index 094fbcc0ab..fe11c2b90d 100644 --- a/Mage/src/main/java/mage/abilities/effects/Effects.java +++ b/Mage/src/main/java/mage/abilities/effects/Effects.java @@ -1,22 +1,20 @@ package mage.abilities.effects; +import java.util.ArrayList; +import java.util.Arrays; import mage.abilities.Ability; import mage.abilities.Mode; import mage.constants.Outcome; import mage.target.targetpointer.TargetPointer; import mage.util.CardUtil; -import java.util.ArrayList; - /** * @author BetaSteward_at_googlemail.com */ public class Effects extends ArrayList { public Effects(Effect... effects) { - for (Effect effect : effects) { - this.add(effect); - } + this.addAll(Arrays.asList(effects)); } public Effects(final Effects effects) { @@ -64,15 +62,15 @@ public class Effects extends ArrayList { endString = " "; } else if (nextRule.startsWith(",") || nextRule.startsWith(" ")) { endString = ""; - // nextRule determined to be a new sentence, now check ending of lastRule + // nextRule determined to be a new sentence, now check ending of lastRule } else if (lastRule != null && lastRule.length() > 3) { //check if lastRule already has appropriate punctuation, if so, add a space. - if (lastRule.endsWith(".\"") || - lastRule.endsWith(".)") || - lastRule.endsWith(".)") || - lastRule.endsWith(".")){ + if (lastRule.endsWith(".\"") + || lastRule.endsWith(".)") + || lastRule.endsWith(".)") + || lastRule.endsWith(".")) { endString = " "; - // if lastRule does not have appropriate punctuation, add the default ". " + // if lastRule does not have appropriate punctuation, add the default ". " } else if (!lastRule.endsWith(".") && !lastRule.endsWith("
")) { endString = ". "; } diff --git a/Mage/src/main/java/mage/abilities/keyword/MiracleAbility.java b/Mage/src/main/java/mage/abilities/keyword/MiracleAbility.java index 8c5c8b1dec..99b6ce2b42 100644 --- a/Mage/src/main/java/mage/abilities/keyword/MiracleAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/MiracleAbility.java @@ -1,4 +1,3 @@ - package mage.abilities.keyword; import mage.MageObjectReference; @@ -102,9 +101,7 @@ public class MiracleAbility extends TriggeredAbilityImpl { public boolean checkTrigger(GameEvent event, Game game) { if (event.getSourceId().equals(getSourceId())) { // Refer to the card at the zone it is now (hand) - FixedTarget fixedTarget = new FixedTarget(event.getSourceId()); - fixedTarget.init(game, this); - getEffects().get(0).setTargetPointer(fixedTarget); + getEffects().setTargetPointer(new FixedTarget(game.getCard(event.getSourceId()), game)); return true; } return false; diff --git a/Mage/src/main/java/mage/cards/CardImpl.java b/Mage/src/main/java/mage/cards/CardImpl.java index 0147e84c91..bdf5a1f66e 100644 --- a/Mage/src/main/java/mage/cards/CardImpl.java +++ b/Mage/src/main/java/mage/cards/CardImpl.java @@ -1,6 +1,12 @@ package mage.cards; import com.google.common.collect.ImmutableList; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.UUID; import mage.MageObject; import mage.MageObjectImpl; import mage.Mana; @@ -27,13 +33,6 @@ import mage.util.SubTypeList; import mage.watchers.Watcher; import org.apache.log4j.Logger; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.UUID; - public abstract class CardImpl extends MageObjectImpl implements Card { private static final long serialVersionUID = 1L; @@ -137,7 +136,7 @@ public abstract class CardImpl extends MageObjectImpl implements Card { nightCard = card.nightCard; } if (card.spellAbility != null) { - spellAbility = card.getSpellAbility().copy(); + spellAbility = (SpellAbility) abilities.get(0); } else { spellAbility = null; } @@ -397,10 +396,15 @@ public abstract class CardImpl extends MageObjectImpl implements Card { } /** - * Dynamic cost modification for card (process only own abilities). - * Example: if it need stack related info (like real targets) then must check two states (game.inCheckPlayableState): - * 1. In playable state it must check all possible use cases (e.g. allow to reduce on any available target and modes) - * 2. In real cast state it must check current use case (e.g. real selected targets and modes) + * Dynamic cost modification for card (process only own abilities). Example: + * if it need stack related info (like real targets) then must check two + * states (game.inCheckPlayableState): + * + * 1. In playable state it must check all possible use cases (e.g. allow to + * reduce on any available target and modes) + * + * 2. In real cast state it must check current use case (e.g. real selected + * targets and modes) * * @param ability * @param game diff --git a/Mage/src/main/java/mage/game/permanent/PermanentCard.java b/Mage/src/main/java/mage/game/permanent/PermanentCard.java index f66b260eea..e9bd2f4d23 100644 --- a/Mage/src/main/java/mage/game/permanent/PermanentCard.java +++ b/Mage/src/main/java/mage/game/permanent/PermanentCard.java @@ -1,8 +1,12 @@ package mage.game.permanent; +import java.util.UUID; +import javax.annotation.processing.SupportedSourceVersion; +import javax.lang.model.SourceVersion; import mage.MageObject; import mage.abilities.Abilities; import mage.abilities.Ability; +import mage.abilities.SpellAbility; import mage.abilities.costs.mana.ManaCost; import mage.abilities.costs.mana.ManaCosts; import mage.abilities.keyword.TransformAbility; @@ -11,11 +15,10 @@ import mage.cards.LevelerCard; import mage.game.Game; import mage.game.events.ZoneChangeEvent; -import java.util.UUID; - /** * @author BetaSteward_at_googlemail.com */ +@SupportedSourceVersion(SourceVersion.RELEASE_8) public class PermanentCard extends PermanentImpl { protected int maxLevelCounters; @@ -86,6 +89,9 @@ public class PermanentCard extends PermanentImpl { } } else { this.abilities = card.getAbilities().copy(); + if (this.spellAbility != null) { + this.spellAbility = (SpellAbility) this.abilities.get(0); + } } // adventure cards must show adventure spell info on battlefield too /* diff --git a/Mage/src/main/java/mage/game/stack/Spell.java b/Mage/src/main/java/mage/game/stack/Spell.java index 05fbffbfa3..83aacef2db 100644 --- a/Mage/src/main/java/mage/game/stack/Spell.java +++ b/Mage/src/main/java/mage/game/stack/Spell.java @@ -1,5 +1,6 @@ package mage.game.stack; +import java.util.*; import mage.MageInt; import mage.MageObject; import mage.Mana; @@ -13,7 +14,6 @@ import mage.abilities.costs.Cost; import mage.abilities.costs.mana.ActivationManaAbilityStep; import mage.abilities.costs.mana.ManaCost; import mage.abilities.costs.mana.ManaCosts; -import mage.abilities.keyword.BestowAbility; import mage.abilities.keyword.MorphAbility; import mage.abilities.text.TextPart; import mage.cards.*; @@ -32,8 +32,6 @@ import mage.players.Player; import mage.util.GameLog; import mage.util.SubTypeList; -import java.util.*; - /** * @author BetaSteward_at_googlemail.com */ @@ -326,7 +324,7 @@ public class Spell extends StackObjImpl implements Card { */ private boolean spellAbilityCheckTargetsAndDeactivateModes(SpellAbility spellAbility, Game game) { boolean legalModes = false; - for (Iterator iterator = spellAbility.getModes().getSelectedModes().iterator(); iterator.hasNext(); ) { + for (Iterator iterator = spellAbility.getModes().getSelectedModes().iterator(); iterator.hasNext();) { UUID nextSelectedModeId = iterator.next(); Mode mode = spellAbility.getModes().get(nextSelectedModeId); if (!mode.getTargets().isEmpty()) { diff --git a/Mage/src/main/java/mage/players/PlayerImpl.java b/Mage/src/main/java/mage/players/PlayerImpl.java index be91050410..ccd31a5cc9 100644 --- a/Mage/src/main/java/mage/players/PlayerImpl.java +++ b/Mage/src/main/java/mage/players/PlayerImpl.java @@ -1,6 +1,10 @@ package mage.players; import com.google.common.collect.ImmutableMap; +import java.io.Serializable; +import java.util.*; +import java.util.Map.Entry; +import java.util.stream.Collectors; import mage.ConditionalMana; import mage.MageObject; import mage.MageObjectReference; @@ -66,11 +70,6 @@ import mage.util.GameLog; import mage.util.RandomUtil; import org.apache.log4j.Logger; -import java.io.Serializable; -import java.util.*; -import java.util.Map.Entry; -import java.util.stream.Collectors; - public abstract class PlayerImpl implements Player, Serializable { private static final Logger logger = Logger.getLogger(PlayerImpl.class); @@ -179,7 +178,7 @@ public abstract class PlayerImpl implements Player, Serializable { // Used during available mana calculation to give back possible available net mana from triggered mana abilities (No need to copy) protected final List> availableTriggeredManaList = new ArrayList<>(); - + /** * During some steps we can't play anything */ @@ -610,9 +609,9 @@ public abstract class PlayerImpl implements Player, Serializable { && this.hasOpponent(sourceControllerId, game) && game.getContinuousEffects().asThough(this.getId(), AsThoughEffectType.HEXPROOF, null, sourceControllerId, game) == null && abilities.stream() - .filter(HexproofBaseAbility.class::isInstance) - .map(HexproofBaseAbility.class::cast) - .anyMatch(ability -> ability.checkObject(source, game))) { + .filter(HexproofBaseAbility.class::isInstance) + .map(HexproofBaseAbility.class::cast) + .anyMatch(ability -> ability.checkObject(source, game))) { return false; } @@ -652,7 +651,7 @@ public abstract class PlayerImpl implements Player, Serializable { game.informPlayers(getLogName() + " discards down to " + this.maxHandSize + (this.maxHandSize == 1 - ? " hand card" : " hand cards")); + ? " hand card" : " hand cards")); } discard(hand.size() - this.maxHandSize, false, null, game); } @@ -801,7 +800,7 @@ public abstract class PlayerImpl implements Player, Serializable { } GameEvent gameEvent = GameEvent.getEvent(GameEvent.EventType.DISCARD_CARD, card.getId(), source == null - ? null : source.getSourceId(), playerId); + ? null : source.getSourceId(), playerId); gameEvent.setFlag(source != null); // event from effect or from cost (source == null) if (game.replaceEvent(gameEvent, source)) { return false; @@ -1067,12 +1066,12 @@ public abstract class PlayerImpl implements Player, Serializable { private boolean moveObjectToLibrary(UUID objectId, UUID sourceId, Game game, boolean toTop, boolean withName) { MageObject mageObject = game.getObject(objectId); if (mageObject instanceof Spell && mageObject.isCopy()) { - // Spell copies are not moved as cards, so here the no copy spell has to be selected to move - // (but because copy and original have the same objectId the wrong sepell can be selected from stack). - // So let's check if the original spell is on the stack and has to be selected. // TODO: Better handling so each spell could be selected by a unique id + // Spell copies are not moved as cards, so here the no copy spell has to be selected to move + // (but because copy and original have the same objectId the wrong sepell can be selected from stack). + // So let's check if the original spell is on the stack and has to be selected. // TODO: Better handling so each spell could be selected by a unique id Spell spellNoCopy = game.getStack().getSpell(sourceId, false); if (spellNoCopy != null) { - mageObject = spellNoCopy; + mageObject = spellNoCopy; } } if (mageObject != null) { @@ -1145,6 +1144,14 @@ public abstract class PlayerImpl implements Player, Serializable { return result; } + /** + * + * @param originalAbility + * @param game + * @param noMana cast it without paying mana costs + * @param permittingObject which object permitted the cast + * @return + */ @Override public boolean cast(SpellAbility originalAbility, Game game, boolean noMana, MageObjectReference permittingObject) { if (game == null || originalAbility == null) { @@ -1486,7 +1493,7 @@ public abstract class PlayerImpl implements Player, Serializable { return true; } } - restoreState(bookmark, triggeredAbility.getRule(), game); // why restore is needed here? (to remove the triggered ability from the stack) + restoreState(bookmark, triggeredAbility.getRule(), game); // why restore is needed here? (to remove the triggered ability from the stack because of no possible targets) return false; } @@ -2713,7 +2720,7 @@ public abstract class PlayerImpl implements Player, Serializable { * @param winnable * @param appliedEffects * @return if winnable, true if player won the toss, if not winnable, true - * for heads and false for tails + * for heads and false for tails */ @Override public boolean flipCoin(Ability source, Game game, boolean winnable, List appliedEffects) { @@ -2807,7 +2814,7 @@ public abstract class PlayerImpl implements Player, Serializable { * @param numberPlanarSides The number of chaos sides the planar die * currently has (normally 1) * @return the outcome that the player rolled. Either ChaosRoll, PlanarRoll - * or NilRoll + * or NilRoll */ @Override public PlanarDieRoll rollPlanarDie(Game game, List appliedEffects, int numberChaosSides, @@ -2861,17 +2868,18 @@ public abstract class PlayerImpl implements Player, Serializable { } /** - * Returns the mana options the player currently has. That means which combinations of - * mana are available to cast spells or activate abilities etc. - * + * Returns the mana options the player currently has. That means which + * combinations of mana are available to cast spells or activate abilities + * etc. + * * @param game - * @return + * @return */ @Override public ManaOptions getManaAvailable(Game game) { boolean oldState = game.inCheckPlayableState(); game.setCheckPlayableState(true); - + ManaOptions availableMana = new ManaOptions(); List> sourceWithoutManaCosts = new ArrayList<>(); @@ -2913,34 +2921,37 @@ public abstract class PlayerImpl implements Player, Serializable { // remove duplicated variants (see ManaOptionsTest for info - when that rises) availableMana.removeDuplicated(); - + game.setCheckPlayableState(oldState); return availableMana; } /** - * Used during calculation of available mana to gather the amount of producable triggered mana caused by using mana sources. - * So the set value is only used during the calculation of the mana produced by one source and cleared thereafter - * - * @param netManaAvailable the net mana produced by the triggered mana abaility + * Used during calculation of available mana to gather the amount of + * producable triggered mana caused by using mana sources. So the set value + * is only used during the calculation of the mana produced by one source + * and cleared thereafter + * + * @param netManaAvailable the net mana produced by the triggered mana + * abaility */ @Override public void addAvailableTriggeredMana(List netManaAvailable) { this.availableTriggeredManaList.add(netManaAvailable); - } + } /** - * Used during calculation of available mana to get the amount of producable triggered mana caused by using mana sources. - * The list is cleared as soon the value is retrieved during available mana calculation. - * - * @return + * Used during calculation of available mana to get the amount of producable + * triggered mana caused by using mana sources. The list is cleared as soon + * the value is retrieved during available mana calculation. + * + * @return */ @Override public List> getAvailableTriggeredMana() { return availableTriggeredManaList; } - - + // returns only mana producers that don't require mana payment protected List getAvailableManaProducers(Game game) { List result = new ArrayList<>(); @@ -3364,8 +3375,10 @@ public abstract class PlayerImpl implements Player, Serializable { * currently cast/activate with his available ressources * * @param game - * @param hidden also from hidden objects (e.g. turned face down cards ?) - * @param fromZone of objects from which zone (ALL = from all zones) + * @param hidden also from hidden objects (e.g. turned face + * down cards ?) + * @param fromZone of objects from which zone (ALL = from all + * zones) * @param hideDuplicatedAbilities if equal abilities exist return only the * first instance * @return @@ -3532,7 +3545,7 @@ public abstract class PlayerImpl implements Player, Serializable { * * @param game * @return A Set of cardIds that are playable and amount of playable - * abilities + * abilities */ @Override public Map getPlayableObjects(Game game, Zone zone) { @@ -4084,7 +4097,7 @@ public abstract class PlayerImpl implements Player, Serializable { // identify cards from one owner Cards cards = new CardsImpl(); UUID ownerId = null; - for (Iterator it = allCards.iterator(); it.hasNext(); ) { + for (Iterator it = allCards.iterator(); it.hasNext();) { Card card = it.next(); if (cards.isEmpty()) { ownerId = card.getOwnerId(); @@ -4265,7 +4278,7 @@ public abstract class PlayerImpl implements Player, Serializable { game.informPlayers(this.getLogName() + " moves " + (withName ? card.getLogName() + (card.isCopy() ? " (Copy)" : "") : "a card face down") + ' ' + (fromZone != null ? "from " + fromZone.toString().toLowerCase(Locale.ENGLISH) - + ' ' : "") + "to the exile zone"); + + ' ' : "") + "to the exile zone"); } result = true;