diff --git a/Mage.Sets/src/mage/cards/d/DiluvianPrimordial.java b/Mage.Sets/src/mage/cards/d/DiluvianPrimordial.java index 61f8053ed07..236567bbfe8 100644 --- a/Mage.Sets/src/mage/cards/d/DiluvianPrimordial.java +++ b/Mage.Sets/src/mage/cards/d/DiluvianPrimordial.java @@ -40,10 +40,7 @@ public final class DiluvianPrimordial extends CardImpl { // Flying this.addAbility(FlyingAbility.getInstance()); - // When Diluvian Primordial enters the battlefield, for each opponent, - // you may cast up to one target instant or sorcery card from that - // player's graveyard without paying its mana cost. If a card cast this way - // would be put into a graveyard this turn, exile it instead. + // When Diluvian Primordial enters the battlefield, for each opponent, you may cast up to one target instant or sorcery card from that player's graveyard without paying its mana cost. If a card cast this way would be put into a graveyard this turn, exile it instead. Ability ability = new EntersBattlefieldTriggeredAbility(new DiluvianPrimordialEffect(), false); ability.setTargetAdjuster(DiluvianPrimordialAdjuster.instance); this.addAbility(ability); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/cost/splitcards/SplitCardsReboundTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/cost/splitcards/SplitCardsReboundTest.java new file mode 100644 index 00000000000..bc77b662890 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/cost/splitcards/SplitCardsReboundTest.java @@ -0,0 +1,42 @@ +package org.mage.test.cards.cost.splitcards; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Ignore; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class SplitCardsReboundTest extends CardTestPlayerBase { + + @Ignore // Cast Through Time is broken on Split Cards. + @Test + public void test_FireIce() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, "Cast Through Time"); // Instant and sorcery spells you control have rebound. + addCard(Zone.BATTLEFIELD, playerA, "Mountain"); + addCard(Zone.BATTLEFIELD, playerA, "Swamp"); + addCard(Zone.HAND, playerA, "Fire // Ice"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Fire"); + addTargetAmount(playerA, playerB, 2); + + checkExileCount("in exile", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Fire // Ice", 1); + + setChoice(playerA, true); // yes to Rebound trigger + setChoice(playerA, "Cast Ice"); // choose the side to cast + addTarget(playerA, "Mountain"); + + setStopAt(3, PhaseStep.PRECOMBAT_MAIN); + execute(); + + assertLife(playerB, 20 - 2); + assertGraveyardCount(playerA, "Fire // Ice", 1); + assertTapped("Mountain", true); + assertTapped("Swamp", false); + } + +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/gtc/DiluvianPrimordialTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/gtc/DiluvianPrimordialTest.java new file mode 100644 index 00000000000..8d8353cf3a1 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/gtc/DiluvianPrimordialTest.java @@ -0,0 +1,83 @@ +package org.mage.test.cards.single.gtc; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.player.TestPlayer; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class DiluvianPrimordialTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.d.DiluvianPrimordial Diluvian Primordial} {5}{U}{U} + * Creature — Avatar + * Flying + * When Diluvian Primordial enters the battlefield, for each opponent, you may cast up to one target instant or sorcery card from that player’s graveyard without paying its mana cost. If a spell cast this way would be put into a graveyard, exile it instead. + * 5/5 + */ + private static final String primordial = "Diluvian Primordial"; + + // Bug: NPE on casting Valakut Awakening + @Test + public void test_MDFC() { + setStrictChooseMode(true); + + addCard(Zone.HAND, playerA, primordial); + addCard(Zone.BATTLEFIELD, playerA, "Island", 7); + addCard(Zone.GRAVEYARD, playerB, "Valakut Awakening"); // MDFC Instant / Land + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, primordial); + addTarget(playerA, "Valakut Awakening"); + setChoice(playerA, true); // Yes to "You may" + setChoice(playerA, TestPlayer.CHOICE_SKIP); // No choice for Awakening's effect + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertExileCount(playerB, "Valakut Awakening", 1); + } + + @Test + public void test_Split() { + setStrictChooseMode(true); + + addCard(Zone.HAND, playerA, primordial); + addCard(Zone.BATTLEFIELD, playerA, "Island", 7); + addCard(Zone.GRAVEYARD, playerB, "Fire // Ice"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, primordial); + addTarget(playerA, "Fire // Ice"); + setChoice(playerA, true); // Yes to "You may" + setChoice(playerA, "Cast Ice"); // Choose what part of the card to cast + addTarget(playerA, primordial); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertExileCount(playerB, "Fire // Ice", 1); + assertTapped(primordial, true); + } + + @Test + public void test_Adventure() { + setStrictChooseMode(true); + + addCard(Zone.HAND, playerA, primordial); + addCard(Zone.BATTLEFIELD, playerA, "Island", 7); + addCard(Zone.GRAVEYARD, playerB, "Twice Upon a Time"); // Adventure with Sorcery on main face + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, primordial); + addTarget(playerA, "Twice Upon a Time"); + setChoice(playerA, true); // Yes to "You may" + setChoice(playerA, "Cast Unlikely Meeting"); // Cast Adventure side + addTarget(playerA, TestPlayer.TARGET_SKIP); // not searching for a Doctor + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertExileCount(playerB, "Twice Upon a Time", 1); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/ReturnToHandEffectsTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/ReturnToHandEffectsTest.java index 52e4d237895..2fc31b9d548 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/ReturnToHandEffectsTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/ReturnToHandEffectsTest.java @@ -1,13 +1,21 @@ package org.mage.test.cards.triggers; +import mage.cards.Card; +import mage.cards.ModalDoubleFacedCard; import mage.constants.PhaseStep; import mage.constants.Zone; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import org.junit.Assert; import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; +import java.util.Objects; +import java.util.Optional; + /** - * * @author LevelX2 */ public class ReturnToHandEffectsTest extends CardTestPlayerBase { @@ -80,6 +88,91 @@ public class ReturnToHandEffectsTest extends CardTestPlayerBase { } + /** + * Assumes there is exactly 1 permanent with the cardName, and checks its permanent zcc and card zcc + */ + private void checkZCCPermanent(String info, Player player, Game game, String cardName, int permanentZCC, int cardZCC, boolean checkMDFC, int leftZCC, int rightZCC) { + Optional optPermanent = game + .getBattlefield() + .getAllActivePermanents() + .stream() + .filter(Objects::nonNull) + .filter(p -> p.getName().equals(cardName)) + .findFirst(); + if (!optPermanent.isPresent()) { + Assert.fail(info + " — no permanent named \"" + cardName + "\" found on battlefield"); + } + Permanent permanent = optPermanent.get(); + Assert.assertEquals(info + " — permanent zcc for \"" + cardName + "\"", permanentZCC, permanent.getZoneChangeCounter(game)); + Card card = game.getCard(permanent.getId()).getMainCard(); + if (card == null) { + Assert.fail(info + " — missing card for permanent \"" + cardName + "\""); + } + Assert.assertEquals(info + " — card zcc for \"" + cardName + "\"", cardZCC, card.getZoneChangeCounter(game)); + if (checkMDFC) { + Assert.assertEquals(info + " — left card zcc for \"" + cardName + "\"", leftZCC, ((ModalDoubleFacedCard) card).getLeftHalfCard().getZoneChangeCounter(game)); + Assert.assertEquals(info + " — right card zcc for \"" + cardName + "\"", rightZCC, ((ModalDoubleFacedCard) card).getRightHalfCard().getZoneChangeCounter(game)); + } + } + + private void checkZCCNormalPermanent(String info, Player player, Game game, String cardName, int permanentZCC, int cardZCC) { + checkZCCPermanent(info, player, game, cardName, permanentZCC, cardZCC, false, cardZCC, cardZCC); + } + + private void checkZCCMDFCPermanent(String info, Player player, Game game, String cardName, int permanentZCC, int mainCardZCC, int leftCardZCC, int rightCardZCC) { + checkZCCPermanent(info, player, game, cardName, permanentZCC, mainCardZCC, true, leftCardZCC, rightCardZCC); + } + + /** + * Assumes there is exactly 1 card in player's graveyard with the cardName, and checks its card zcc + */ + private void checkZCCCardInGraveyard(String info, Player player, Game game, String cardName, int cardZCC) { + Optional optCard = game + .getPlayer(player.getId()) + .getGraveyard() + .getCards(game) + .stream() + .filter(Objects::nonNull) + .filter(p -> p.getName().equals(cardName)) + .findFirst(); + if (!optCard.isPresent()) { + Assert.fail(info + " — no card named \"" + cardName + "\" found in graveyard"); + } + Card card = optCard.get(); + Assert.assertEquals(info + " — card zcc for \"" + cardName + "\"", cardZCC, card.getZoneChangeCounter(game)); + } + + /** + * Assumes there is exactly 1 card in player's hand with the cardName, and checks its card zcc + */ + private void checkZCCCardInHand(String info, Player player, Game game, String cardName, int cardZCC, boolean checkMDFC, int leftZCC, int rightZCC) { + Optional optCard = game + .getPlayer(player.getId()) + .getHand() + .getCards(game) + .stream() + .filter(Objects::nonNull) + .filter(p -> p.getName().equals(cardName)) + .findFirst(); + if (!optCard.isPresent()) { + Assert.fail(info + " — no card named \"" + cardName + "\" found in hand"); + } + Card card = optCard.get(); + Assert.assertEquals(info + " — card zcc for \"" + cardName + "\"", cardZCC, card.getZoneChangeCounter(game)); + if (checkMDFC) { + Assert.assertEquals(info + " — left card zcc for \"" + cardName + "\"", leftZCC, ((ModalDoubleFacedCard) card).getLeftHalfCard().getZoneChangeCounter(game)); + Assert.assertEquals(info + " — right card zcc for \"" + cardName + "\"", rightZCC, ((ModalDoubleFacedCard) card).getRightHalfCard().getZoneChangeCounter(game)); + } + } + + private void checkZCCNormalCardInHand(String info, Player player, Game game, String cardName, int cardZCC) { + checkZCCCardInHand(info, player, game, cardName, cardZCC, false, cardZCC, cardZCC); + } + + private void checkZCCMDFCCardInHand(String info, Player player, Game game, String cardName, int mainZCC, int leftZCC, int rightZCC) { + checkZCCCardInHand(info, player, game, cardName, mainZCC, true, leftZCC, rightZCC); + } + @Test public void testZendikon() { addCard(Zone.BATTLEFIELD, playerA, "Island", 1); @@ -90,12 +183,17 @@ public class ReturnToHandEffectsTest extends CardTestPlayerBase { playLand(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Mountain"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Wind Zendikon", "Mountain"); + + runCode("1: check zcc permanent", 1, PhaseStep.BEGIN_COMBAT, playerA, + (String info, Player player, Game game) -> checkZCCNormalPermanent(info, player, game, "Mountain", 2, 2)); castSpell(1, PhaseStep.BEGIN_COMBAT, playerA, "Disfigure", "Mountain"); + runCode("2: check zcc card", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, + (String info, Player player, Game game) -> checkZCCNormalCardInHand(info, player, game, "Mountain", 4)); + setStrictChooseMode(true); setStopAt(1, PhaseStep.END_TURN); execute(); - // ZCC is 2 for perm, 3 for card assertGraveyardCount(playerA, "Disfigure", 1); assertGraveyardCount(playerA, "Wind Zendikon", 1); @@ -114,12 +212,19 @@ public class ReturnToHandEffectsTest extends CardTestPlayerBase { playLand(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Tangled Vale"); waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Wind Zendikon", "Tangled Vale"); + + // TODO: investigate why MDFC zcc moves separatedly. + runCode("1: check zcc", 1, PhaseStep.BEGIN_COMBAT, playerA, + (String info, Player player, Game game) -> checkZCCMDFCPermanent(info, player, game, "Tangled Vale", 2, 1, 1, 2)); castSpell(1, PhaseStep.BEGIN_COMBAT, playerA, "Disfigure", "Tangled Vale"); + // TODO: investigate why MDFC zcc moves separatedly. + runCode("2: check zcc card", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, + (String info, Player player, Game game) -> checkZCCMDFCCardInHand(info, player, game, "Tangled Florahedron", 2, 2, 4)); + setStrictChooseMode(true); setStopAt(1, PhaseStep.END_TURN); execute(); - // ZCC is 2 for perm, 1 for card (?!) assertGraveyardCount(playerA, "Disfigure", 1); assertGraveyardCount(playerA, "Wind Zendikon", 1); @@ -138,12 +243,19 @@ public class ReturnToHandEffectsTest extends CardTestPlayerBase { playLand(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Riverglide Pathway"); waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Wind Zendikon", "Riverglide Pathway"); + + // TODO: investigate why MDFC zcc moves separatedly. + runCode("1: check zcc pre disfigure", 1, PhaseStep.BEGIN_COMBAT, playerA, + (String info, Player player, Game game) -> checkZCCMDFCPermanent(info, player, game, "Riverglide Pathway", 2, 1, 2, 1)); castSpell(1, PhaseStep.BEGIN_COMBAT, playerA, "Disfigure", "Riverglide Pathway"); + // TODO: investigate why MDFC zcc moves separatedly. + runCode("2: check zcc post disfigure", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, + (String info, Player player, Game game) -> checkZCCMDFCCardInHand(info, player, game, "Riverglide Pathway", 2, 4, 2)); + setStrictChooseMode(true); setStopAt(1, PhaseStep.END_TURN); execute(); - // ZCC is 2 for perm, 1 for card (?!) assertGraveyardCount(playerA, "Disfigure", 1); assertGraveyardCount(playerA, "Wind Zendikon", 1); @@ -162,12 +274,19 @@ public class ReturnToHandEffectsTest extends CardTestPlayerBase { playLand(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lavaglide Pathway"); waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Wind Zendikon", "Lavaglide Pathway"); + + // TODO: investigate why MDFC zcc moves separatedly. + runCode("1: check zcc", 1, PhaseStep.BEGIN_COMBAT, playerA, + (String info, Player player, Game game) -> checkZCCMDFCPermanent(info, player, game, "Lavaglide Pathway", 2, 1, 1, 2)); castSpell(1, PhaseStep.BEGIN_COMBAT, playerA, "Disfigure", "Lavaglide Pathway"); + // TODO: investigate why MDFC zcc moves separatedly. + runCode("2: check zcc card", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, + (String info, Player player, Game game) -> checkZCCMDFCCardInHand(info, player, game, "Riverglide Pathway", 2, 2, 4)); + setStrictChooseMode(true); setStopAt(1, PhaseStep.END_TURN); execute(); - // ZCC is 2 for perm, 1 for card (?!) assertGraveyardCount(playerA, "Disfigure", 1); assertGraveyardCount(playerA, "Wind Zendikon", 1); @@ -186,12 +305,17 @@ public class ReturnToHandEffectsTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Dryad Sophisticate"); waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Demonic Vigor", "Dryad Sophisticate"); + + runCode("1: check zcc", 1, PhaseStep.BEGIN_COMBAT, playerA, + (String info, Player player, Game game) -> checkZCCNormalPermanent(info, player, game, "Dryad Sophisticate", 3, 3)); castSpell(1, PhaseStep.BEGIN_COMBAT, playerA, "Disfigure", "Dryad Sophisticate"); + runCode("2: check zcc card", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, + (String info, Player player, Game game) -> checkZCCNormalCardInHand(info, player, game, "Dryad Sophisticate", 5)); + setStrictChooseMode(true); setStopAt(1, PhaseStep.END_TURN); execute(); - // ZCC is 3 for perm, 4 for card assertGraveyardCount(playerA, "Disfigure", 1); assertGraveyardCount(playerA, "Demonic Vigor", 1); @@ -210,12 +334,19 @@ public class ReturnToHandEffectsTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Tangled Florahedron"); waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Demonic Vigor", "Tangled Florahedron"); + + // TODO: investigate why MDFC zcc moves separatedly. + runCode("1: check zcc", 1, PhaseStep.BEGIN_COMBAT, playerA, + (String info, Player player, Game game) -> checkZCCMDFCPermanent(info, player, game, "Tangled Florahedron", 3, 2, 3, 2)); castSpell(1, PhaseStep.BEGIN_COMBAT, playerA, "Disfigure", "Tangled Florahedron"); + // TODO: investigate why MDFC zcc moves separatedly. + runCode("2: check zcc card", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, + (String info, Player player, Game game) -> checkZCCMDFCCardInHand(info, player, game, "Tangled Florahedron", 3, 5, 3)); + setStrictChooseMode(true); setStopAt(1, PhaseStep.END_TURN); execute(); - // ZCC is 3 for perm, 2 for card (?!) assertGraveyardCount(playerA, "Disfigure", 1); assertGraveyardCount(playerA, "Demonic Vigor", 1); @@ -234,18 +365,26 @@ public class ReturnToHandEffectsTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Dryad Sophisticate"); waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Demonic Vigor", "Dryad Sophisticate"); - castSpell(1, PhaseStep.BEGIN_COMBAT, playerA, "Disfigure", "Dryad Sophisticate"); + + runCode("1: check zcc", 1, PhaseStep.BEGIN_COMBAT, playerA, + (String info, Player player, Game game) -> checkZCCNormalPermanent(info, player, game, "Dryad Sophisticate", 3, 3)); + castSpell(1, PhaseStep.BEGIN_COMBAT, playerA, "Disfigure", "Dryad Sophisticate", true); + runCode("2: check zcc card", 1, PhaseStep.BEGIN_COMBAT, playerA, + (String info, Player player, Game game) -> checkZCCNormalCardInHand(info, player, game, "Dryad Sophisticate", 5)); castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Dryad Sophisticate"); waitStackResolved(1, PhaseStep.POSTCOMBAT_MAIN); castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Demonic Vigor", "Dryad Sophisticate"); waitStackResolved(1, PhaseStep.POSTCOMBAT_MAIN); - castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Disfigure", "Dryad Sophisticate"); + runCode("3: check zcc", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, + (String info, Player player, Game game) -> checkZCCNormalPermanent(info, player, game, "Dryad Sophisticate", 7, 7)); + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Disfigure", "Dryad Sophisticate", true); + runCode("4: check zcc card", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, + (String info, Player player, Game game) -> checkZCCNormalCardInHand(info, player, game, "Dryad Sophisticate", 9)); setStrictChooseMode(true); setStopAt(1, PhaseStep.END_TURN); execute(); - // ZCC is 7 for perm, 8 for card assertGraveyardCount(playerA, "Disfigure", 2); assertGraveyardCount(playerA, "Demonic Vigor", 2); @@ -264,18 +403,29 @@ public class ReturnToHandEffectsTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Tangled Florahedron"); waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Demonic Vigor", "Tangled Florahedron"); - castSpell(1, PhaseStep.BEGIN_COMBAT, playerA, "Disfigure", "Tangled Florahedron"); + // TODO: investigate why MDFC zcc moves separatedly. + runCode("1: check zcc", 1, PhaseStep.BEGIN_COMBAT, playerA, + (String info, Player player, Game game) -> checkZCCMDFCPermanent(info, player, game, "Tangled Florahedron", 3, 2, 3, 2)); + castSpell(1, PhaseStep.BEGIN_COMBAT, playerA, "Disfigure", "Tangled Florahedron", true); + // TODO: investigate why MDFC zcc moves separatedly. + runCode("2: check zcc card", 1, PhaseStep.BEGIN_COMBAT, playerA, + (String info, Player player, Game game) -> checkZCCMDFCCardInHand(info, player, game, "Tangled Florahedron", 3, 5, 3)); castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Tangled Florahedron"); waitStackResolved(1, PhaseStep.POSTCOMBAT_MAIN); - castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Demonic Vigor", "Tangled Florahedron"); + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Demonic Vigor", "Tangled Florahedron", true); + // TODO: investigate why MDFC zcc moves separatedly. + runCode("3: check zcc", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, + (String info, Player player, Game game) -> checkZCCMDFCPermanent(info, player, game, "Tangled Florahedron", 7, 4, 7, 4)); waitStackResolved(1, PhaseStep.POSTCOMBAT_MAIN); - castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Disfigure", "Tangled Florahedron"); + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Disfigure", "Tangled Florahedron", true); + // TODO: investigate why MDFC zcc moves separatedly. + runCode("4: check zcc card", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, + (String info, Player player, Game game) -> checkZCCMDFCCardInHand(info, player, game, "Tangled Florahedron", 5, 9, 5)); setStrictChooseMode(true); setStopAt(1, PhaseStep.END_TURN); execute(); - // ZCC is 7 for perm, 4 for card (?!) assertGraveyardCount(playerA, "Disfigure", 2); assertGraveyardCount(playerA, "Demonic Vigor", 2); @@ -291,22 +441,30 @@ public class ReturnToHandEffectsTest extends CardTestPlayerBase { addCard(Zone.HAND, playerA, "Demonic Vigor"); addCard(Zone.HAND, playerA, "Disfigure"); addCard(Zone.HAND, playerA, "Makeshift Mannequin"); - addCard(Zone.HAND, playerA, "Coat with Venom"); + addCard(Zone.HAND, playerA, "Coat with Venom"); // target for triggering the Makeshift Mannequin's sacrifice setStrictChooseMode(true); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Carrion Feeder"); waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Demonic Vigor", "Carrion Feeder"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Demonic Vigor", "Carrion Feeder", true); + runCode("1: check zcc", 1, PhaseStep.PRECOMBAT_MAIN, playerA, + (String info, Player player, Game game) -> checkZCCNormalPermanent(info, player, game, "Carrion Feeder", 3, 3)); castSpell(1, PhaseStep.BEGIN_COMBAT, playerA, "Disfigure", "Carrion Feeder"); waitStackResolved(1, PhaseStep.BEGIN_COMBAT, 1); + runCode("2: check graveyard zcc", 1, PhaseStep.BEGIN_COMBAT, playerA, + (String info, Player player, Game game) -> checkZCCCardInGraveyard(info, player, game, "Carrion Feeder", 4)); castSpell(1, PhaseStep.BEGIN_COMBAT, playerA, "Makeshift Mannequin", "Carrion Feeder"); waitStackResolved(1, PhaseStep.BEGIN_COMBAT, 1); - castSpell(1, PhaseStep.BEGIN_COMBAT, playerA, "Coat with Venom", "Carrion Feeder"); + runCode("3: check zcc", 1, PhaseStep.BEGIN_COMBAT, playerA, + (String info, Player player, Game game) -> checkZCCNormalPermanent(info, player, game, "Carrion Feeder", 5, 5)); + castSpell(1, PhaseStep.BEGIN_COMBAT, playerA, "Coat with Venom", "Carrion Feeder", true); + runCode("4: check graveyard zcc", 1, PhaseStep.BEGIN_COMBAT, playerA, + (String info, Player player, Game game) -> checkZCCCardInGraveyard(info, player, game, "Carrion Feeder", 6)); setStrictChooseMode(true); setStopAt(1, PhaseStep.END_TURN); execute(); - // ZCC is 3 for perm, 6 for card, so should not return + // Vigor tries to return the Carrion Feeder card with zcc 4, so 6 doesn't return. assertGraveyardCount(playerA, "Disfigure", 1); assertGraveyardCount(playerA, "Demonic Vigor", 1); diff --git a/Mage/src/main/java/mage/abilities/effects/common/MayCastTargetCardEffect.java b/Mage/src/main/java/mage/abilities/effects/common/MayCastTargetCardEffect.java index 9d19306419a..0953892768b 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/MayCastTargetCardEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/MayCastTargetCardEffect.java @@ -1,6 +1,5 @@ package mage.abilities.effects.common; -import mage.ApprovingObject; import mage.abilities.Ability; import mage.abilities.Mode; import mage.abilities.effects.ContinuousEffect; @@ -115,8 +114,7 @@ public class MayCastTargetCardEffect extends OneShotEffect { game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), Boolean.TRUE); boolean noMana = manaAdjustment == CastManaAdjustment.WITHOUT_PAYING_MANA_COST; - controller.cast(controller.chooseAbilityForCast(card, game, noMana), - game, noMana, new ApprovingObject(source, game)); + CardUtil.castSingle(controller, source, game, card, noMana, null); game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), null); } else { // TODO: support (and add tests!) for the non-NONE manaAdjustment diff --git a/Mage/src/main/java/mage/abilities/effects/common/replacement/ThatSpellGraveyardExileReplacementEffect.java b/Mage/src/main/java/mage/abilities/effects/common/replacement/ThatSpellGraveyardExileReplacementEffect.java index e79795eefc4..705468b7f16 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/replacement/ThatSpellGraveyardExileReplacementEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/replacement/ThatSpellGraveyardExileReplacementEffect.java @@ -2,6 +2,7 @@ package mage.abilities.effects.common.replacement; import mage.abilities.Ability; import mage.abilities.effects.ReplacementEffectImpl; +import mage.cards.Card; import mage.constants.Duration; import mage.constants.Outcome; import mage.constants.Zone; @@ -50,9 +51,21 @@ public class ThatSpellGraveyardExileReplacementEffect extends ReplacementEffectI @Override public boolean applies(GameEvent event, Ability source, Game game) { ZoneChangeEvent zEvent = (ZoneChangeEvent) event; - return zEvent.getToZone() == Zone.GRAVEYARD - && zEvent.getTargetId().equals(((FixedTarget) getTargetPointer()).getTarget()) - && ((FixedTarget) getTargetPointer()).getZoneChangeCounter() + 1 - == game.getState().getZoneChangeCounter(zEvent.getTargetId()); + if (zEvent.getToZone() != Zone.GRAVEYARD) { + return false; + } + Card cardMoving = game.getCard(zEvent.getTargetId()); + Card cardTarget = game.getCard(((FixedTarget) getTargetPointer()).getTarget()); + if (cardMoving == null || cardTarget == null) { + return false; + } + // for MDFC. + Card mainCardMoving = cardMoving.getMainCard(); + Card mainCardTarget = cardTarget.getMainCard(); + return mainCardMoving != null + && mainCardTarget != null + && mainCardMoving.getId().equals(mainCardTarget.getId()) + && ((FixedTarget) getTargetPointer()).getZoneChangeCounter() + 1 + == game.getState().getZoneChangeCounter(mainCardMoving.getId()); } } diff --git a/Mage/src/main/java/mage/cards/AdventureCard.java b/Mage/src/main/java/mage/cards/AdventureCard.java index 080ab85c040..70cc1f4e7e0 100644 --- a/Mage/src/main/java/mage/cards/AdventureCard.java +++ b/Mage/src/main/java/mage/cards/AdventureCard.java @@ -56,7 +56,8 @@ public abstract class AdventureCard extends CardImpl { @Override public boolean moveToZone(Zone toZone, Ability source, Game game, boolean flag, List appliedEffects) { if (super.moveToZone(toZone, source, game, flag, appliedEffects)) { - game.getState().setZone(getSpellCard().getId(), toZone); + Zone currentZone = game.getState().getZone(getId()); + game.getState().setZone(getSpellCard().getId(), currentZone); return true; } return false; diff --git a/Mage/src/main/java/mage/cards/ModalDoubleFacedCard.java b/Mage/src/main/java/mage/cards/ModalDoubleFacedCard.java index 7e637f5635a..6a71064c7ba 100644 --- a/Mage/src/main/java/mage/cards/ModalDoubleFacedCard.java +++ b/Mage/src/main/java/mage/cards/ModalDoubleFacedCard.java @@ -116,7 +116,8 @@ public abstract class ModalDoubleFacedCard extends CardImpl implements CardWithH @Override public boolean moveToZone(Zone toZone, Ability source, Game game, boolean flag, List appliedEffects) { if (super.moveToZone(toZone, source, game, flag, appliedEffects)) { - setSideZones(toZone, game); + Zone currentZone = game.getState().getZone(getId()); + setSideZones(currentZone, game); return true; } return false; @@ -131,7 +132,8 @@ public abstract class ModalDoubleFacedCard extends CardImpl implements CardWithH @Override public boolean moveToExile(UUID exileId, String name, Ability source, Game game, List appliedEffects) { if (super.moveToExile(exileId, name, source, game, appliedEffects)) { - setSideZones(Zone.EXILED, game); + Zone currentZone = game.getState().getZone(getId()); + setSideZones(currentZone, game); return true; } return false; @@ -233,10 +235,12 @@ public abstract class ModalDoubleFacedCard extends CardImpl implements CardWithH case MODAL_RIGHT: return this.rightHalfCard.cast(game, fromZone, ability, controllerId); default: - if (this.leftHalfCard.getSpellAbility() != null) + if (this.leftHalfCard.getSpellAbility() != null) { this.leftHalfCard.getSpellAbility().setControllerId(controllerId); - if (this.rightHalfCard.getSpellAbility() != null) + } + if (this.rightHalfCard.getSpellAbility() != null) { this.rightHalfCard.getSpellAbility().setControllerId(controllerId); + } return super.cast(game, fromZone, ability, controllerId); } } diff --git a/Mage/src/main/java/mage/cards/SplitCard.java b/Mage/src/main/java/mage/cards/SplitCard.java index ae691f0d584..ce2b387d7c0 100644 --- a/Mage/src/main/java/mage/cards/SplitCard.java +++ b/Mage/src/main/java/mage/cards/SplitCard.java @@ -77,8 +77,9 @@ public abstract class SplitCard extends CardImpl implements CardWithHalves { @Override public boolean moveToZone(Zone toZone, Ability source, Game game, boolean flag, List appliedEffects) { if (super.moveToZone(toZone, source, game, flag, appliedEffects)) { - game.getState().setZone(getLeftHalfCard().getId(), toZone); - game.getState().setZone(getRightHalfCard().getId(), toZone); + Zone currentZone = game.getState().getZone(getId()); + game.getState().setZone(getLeftHalfCard().getId(), currentZone); + game.getState().setZone(getRightHalfCard().getId(), currentZone); return true; } return false; diff --git a/Mage/src/main/java/mage/util/CardUtil.java b/Mage/src/main/java/mage/util/CardUtil.java index 8a2e9827675..8d29374b85c 100644 --- a/Mage/src/main/java/mage/util/CardUtil.java +++ b/Mage/src/main/java/mage/util/CardUtil.java @@ -1494,6 +1494,10 @@ public final class CardUtil { } public static void castSingle(Player player, Ability source, Game game, Card card, ManaCostsImpl manaCost) { + castSingle(player, source, game, card, false, manaCost); + } + + public static void castSingle(Player player, Ability source, Game game, Card card, boolean noMana, ManaCostsImpl manaCost) { // handle split-cards if (card instanceof SplitCard) { SplitCardHalf leftHalfCard = ((SplitCard) card).getLeftHalfCard(); @@ -1560,8 +1564,8 @@ public final class CardUtil { } // cast it - player.cast(player.chooseAbilityForCast(card.getMainCard(), game, false), - game, false, new ApprovingObject(source, game)); + player.cast(player.chooseAbilityForCast(card.getMainCard(), game, noMana), + game, noMana, new ApprovingObject(source, game)); // turn off effect after cast on every possible card-face if (card instanceof SplitCard) {