diff --git a/Mage.Sets/src/mage/cards/e/EtherealValkyrie.java b/Mage.Sets/src/mage/cards/e/EtherealValkyrie.java index 14d8a6d4ae..22746cbf8c 100644 --- a/Mage.Sets/src/mage/cards/e/EtherealValkyrie.java +++ b/Mage.Sets/src/mage/cards/e/EtherealValkyrie.java @@ -136,7 +136,7 @@ class EtherealValkyrieEffect extends OneShotEffect { foretellAbility = new ForetellAbility(exileCard, leftHalfCost, rightHalfCost); } else if (exileCard instanceof ModalDoubleFacesCard) { ModalDoubleFacesCardHalf leftHalfCard = ((ModalDoubleFacesCard) exileCard).getLeftHalfCard(); - if (!leftHalfCard.isLand(game)) { + if (!leftHalfCard.isLand(game)) { // Only MDFC cards with a left side a land have a land on the right side too String leftHalfCost = CardUtil.reduceCost(leftHalfCard.getManaCost(), 2).getText(); game.getState().setValue(exileCard.getMainCard().getId().toString() + "Foretell Cost", leftHalfCost); ModalDoubleFacesCardHalf rightHalfCard = ((ModalDoubleFacesCard) exileCard).getRightHalfCard(); @@ -154,14 +154,21 @@ class EtherealValkyrieEffect extends OneShotEffect { game.getState().setValue(exileCard.getMainCard().getId().toString() + "Foretell Cost", creatureCost); game.getState().setValue(exileCard.getMainCard().getId().toString() + "Foretell Split Cost", spellCost); foretellAbility = new ForetellAbility(exileCard, creatureCost, spellCost); - } else { + } else if (!exileCard.isLand()){ // normal card String costText = CardUtil.reduceCost(exileCard.getManaCost(), 2).getText(); game.getState().setValue(exileCard.getId().toString() + "Foretell Cost", costText); foretellAbility = new ForetellAbility(exileCard, costText); } + + // All card types (including lands) must be exiled + UUID exileId = CardUtil.getExileZoneId(exileCard.getMainCard().getId().toString() + "foretellAbility", game); + controller.moveCardsToExile(exileCard, source, game, true, exileId, " Foretell Turn Number: " + game.getTurnNum()); + exileCard.setFaceDown(true, game); + // all done pre-processing so stick the foretell cost effect onto the main card // note that the card is not foretell'd into exile, it is put into exile and made foretold + // If the card is a non-land, it will not be exiled. if (foretellAbility != null) { // copy source and use it for the foretold effect on the exiled card // bug #8673 @@ -169,9 +176,6 @@ class EtherealValkyrieEffect extends OneShotEffect { copiedSource.newId(); copiedSource.setSourceId(exileCard.getId()); game.getState().setValue(exileCard.getMainCard().getId().toString() + "Foretell Turn Number", game.getTurnNum()); - UUID exileId = CardUtil.getExileZoneId(exileCard.getMainCard().getId().toString() + "foretellAbility", game); - controller.moveCardsToExile(exileCard, source, game, true, exileId, " Foretell Turn Number: " + game.getTurnNum()); - exileCard.setFaceDown(true, game); foretellAbility.setSourceId(exileCard.getId()); foretellAbility.setControllerId(exileCard.getOwnerId()); game.getState().addOtherAbility(exileCard, foretellAbility); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/khc/EtherealValkyrieTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/khc/EtherealValkyrieTest.java new file mode 100644 index 0000000000..b86c6c8243 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/khc/EtherealValkyrieTest.java @@ -0,0 +1,219 @@ +package org.mage.test.cards.single.khc; + +import mage.abilities.keyword.ForecastAbility; +import mage.cards.basiclands.Mountain; +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * {@link mage.cards.e.EtherealValkyrie Ethereal Valkyrie} + *

+ * Whenever Ethereal Valkyrie enters the battlefield or attacks, draw a card, then exile a card from your hand face down. + * It becomes foretold. + * Its foretell cost is its mana cost reduced by {2}. + * (On a later turn, you may cast it for its foretell cost, even if this creature has left the battlefield.) + * + * @author Alex-Vasile + */ +public class EtherealValkyrieTest extends CardTestPlayerBase { + + // {4}{W}{U} + private static final String etherealValkyrie = "Ethereal Valkyrie"; + // Suspend 4—{U} + // Target player draws three cards. + private static final String ancestralVision = "Ancestral Vision"; + // {5}{R} Creature-Land MDFC + private static final String akoumWarrior = "Akoum Warrior"; + private static final String akoumTeeth = "Akoum Teeth"; + // {3}{U}{U}-{1}{U} Creature-Creature MDFC + private static final String alrund = "Alrund, God of the Cosmos"; + private static final String hakka = "Hakka, Whispering Raven"; + // MDFC with where both sides are lands + private static final String blightclimbPathway = "Brightclimb Pathway"; + private static final String grimclimbPathway = "Grimclimb Pathway"; + // {3} + // {T}: Add one mana of any color + private static final String alloyMyr = "Alloy Myr"; + // Land + private static final String exoticOrchard = "Exotic Orchard"; + + /** + * Test that a regular card is playable. + */ + @Test + public void testRegularCard() { + addCard(Zone.BATTLEFIELD, playerA, "Plains", 1); + addCard(Zone.BATTLEFIELD, playerA, "Island", 1); + addCard(Zone.BATTLEFIELD, playerA, "Island", 4); + addCard(Zone.HAND, playerA, etherealValkyrie); + addCard(Zone.HAND, playerA, alloyMyr); // The one to exile with ETB ability + + setStrictChooseMode(true); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, etherealValkyrie); + addTarget(playerA, alloyMyr); + + setStopAt(1, PhaseStep.PRECOMBAT_MAIN); + execute(); + assertAllCommandsUsed(); + assertExileCount(playerA, alloyMyr, 1); + + setStopAt(3, PhaseStep.PRECOMBAT_MAIN); + activateAbility(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Foretell"); + execute(); + assertAllCommandsUsed(); + assertExileCount(playerA, alloyMyr, 0); + assertPermanentCount(playerA, alloyMyr, 1); + } + + /** + * Reported Bug: When you only have lands in hand the game enters a permanent rollback state. + * https://github.com/magefree/mage/issues/9361 + */ + @Test + public void testLand() { + addCard(Zone.BATTLEFIELD, playerA, "Plains", 1); + addCard(Zone.BATTLEFIELD, playerA, "Island", 1); + addCard(Zone.BATTLEFIELD, playerA, "Island", 4); + addCard(Zone.HAND, playerA, etherealValkyrie); + addCard(Zone.HAND, playerA, exoticOrchard); + + setStrictChooseMode(true); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, etherealValkyrie); + addTarget(playerA, exoticOrchard); + + setStopAt(1, PhaseStep.PRECOMBAT_MAIN); + execute(); + assertAllCommandsUsed(); + assertExileCount(playerA, exoticOrchard, 1); + + checkPlayableAbility("Can't fortell land", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Foretell", false); + + setStopAt(3, PhaseStep.PRECOMBAT_MAIN); + execute(); + assertAllCommandsUsed(); + } + + /** + * MDFC cards where both sides are lands should be exiled, but not fortell-able. + */ + @Test + public void testMDFCDualLand() { + addCard(Zone.BATTLEFIELD, playerA, "Plains", 1); + addCard(Zone.BATTLEFIELD, playerA, "Island", 1); + addCard(Zone.BATTLEFIELD, playerA, "Island", 4); + addCard(Zone.HAND, playerA, etherealValkyrie); + + addCard(Zone.HAND, playerA, blightclimbPathway); + + + setStrictChooseMode(true); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, etherealValkyrie); + addTarget(playerA, blightclimbPathway); + + setStopAt(1, PhaseStep.PRECOMBAT_MAIN); + execute(); + assertAllCommandsUsed(); + assertExileCount(playerA, blightclimbPathway, 1); + checkPlayableAbility("Can't fortell land", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Foretell", false); + } + + /** + * MDFC cards where only one side is a land should let you fortell its non-land side. + */ + @Test + public void testMDFCNonLandLand() { + addCard(Zone.BATTLEFIELD, playerA, "Plains", 1); + addCard(Zone.BATTLEFIELD, playerA, "Island", 1); + addCard(Zone.BATTLEFIELD, playerA, "Island", 4); + addCard(Zone.HAND, playerA, etherealValkyrie, 1); + + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 4); + addCard(Zone.HAND, playerA, akoumWarrior); + + setStrictChooseMode(true); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, etherealValkyrie); + addTarget(playerA, akoumWarrior); + + setStopAt(1, PhaseStep.PRECOMBAT_MAIN); + execute(); + assertAllCommandsUsed(); + assertExileCount(playerA, akoumWarrior, 1); + + // TODO: Add functionality to test for this programmatically by changing assertAbilityCount + showAvailableAbilities("Should only be 1 Foretell ability", 3, PhaseStep.PRECOMBAT_MAIN, playerA); + activateAbility(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Foretell {3}{R}"); + + setStopAt(3, PhaseStep.PRECOMBAT_MAIN); + execute(); + assertAllCommandsUsed(); + assertExileCount(playerA, akoumWarrior, 0); + assertPermanentCount(playerA, akoumWarrior, 1); + } + + /** + * MDFC cards where only one side is a land should let you fortell its non-land side. + */ + @Test + public void testMDFCCreatureCreature() { + addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); + addCard(Zone.BATTLEFIELD, playerA, "Island", 2); + addCard(Zone.BATTLEFIELD, playerA, "Island", 8); + addCard(Zone.HAND, playerA, etherealValkyrie, 2); + + addCard(Zone.BATTLEFIELD, playerA, "Island", 3); + addCard(Zone.HAND, playerA, alrund); + + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3); + addCard(Zone.HAND, playerA, alrund); + + setStrictChooseMode(true); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, etherealValkyrie); + addTarget(playerA, alrund); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, etherealValkyrie); + addTarget(playerA, alrund); + + setStopAt(1, PhaseStep.PRECOMBAT_MAIN); + execute(); + assertAllCommandsUsed(); + assertExileCount(playerA, alrund, 2); + + activateAbility(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Foretell {1}"); + activateAbility(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Foretell {U}"); + + setStopAt(3, PhaseStep.PRECOMBAT_MAIN); + execute(); + assertAllCommandsUsed(); + assertExileCount(playerA, alrund, 0); + assertPermanentCount(playerA, alrund, 1); + assertPermanentCount(playerA, hakka, 1); + } + + /** + * Test a Suspend card, which should not be playable from exile with foretell. + */ + @Test + public void testSuspendCard() { + addCard(Zone.BATTLEFIELD, playerA, "Plains", 1); + addCard(Zone.BATTLEFIELD, playerA, "Island", 1); + addCard(Zone.BATTLEFIELD, playerA, "Island", 4); + addCard(Zone.HAND, playerA, etherealValkyrie); + addCard(Zone.HAND, playerA, ancestralVision); + + setStrictChooseMode(true); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, etherealValkyrie); + addTarget(playerA, ancestralVision); + + setStopAt(1, PhaseStep.PRECOMBAT_MAIN); + execute(); + assertAllCommandsUsed(); + assertExileCount(playerA, ancestralVision, 1); + + checkPlayableAbility("Can't fortell suspend-only card", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Foretell", false); + + setStopAt(3, PhaseStep.PRECOMBAT_MAIN); + execute(); + assertAllCommandsUsed(); + } +}