diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/cost/modaldoublefaced/ModalDoubleFacedCardsTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/cost/modaldoublefaced/ModalDoubleFacedCardsTest.java index 107bed4dd55a..de371ae2803b 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/cost/modaldoublefaced/ModalDoubleFacedCardsTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/cost/modaldoublefaced/ModalDoubleFacedCardsTest.java @@ -10,6 +10,7 @@ import mage.constants.Zone; import mage.game.permanent.PermanentCard; import mage.game.permanent.PermanentToken; +import mage.game.stack.Spell; import mage.util.CardUtil; import mage.util.ManaUtil; import mage.view.GameView; @@ -1286,4 +1287,265 @@ public void test_Graveyard_MustApplyCardTypes() { assertPermanentCount(playerA, "Akoum Warrior", 1); assertType("Akoum Warrior", CardType.ARTIFACT, true); } + + /** + * Rule 712.8f: While a modal double-faced permanent is on the battlefield, + * it has only the characteristics of the face that's up. + * So mana value must reflect the face currently on the battlefield. + * + * Esika, God of the Tree {1}{G}{G} - MV 3 (front face) + * The Prismatic Bridge {W}{U}{B}{R}{G} - MV 5 (back face) + */ + @Test + public void test_ManaValue_MDFC_FrontFace() { + // Cast front face (Esika, God of the Tree) - MV must be 3 + addCard(Zone.HAND, playerA, "Esika, God of the Tree"); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 3); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Esika, God of the Tree"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerA, "Esika, God of the Tree", 1); + Assert.assertEquals("MDFC front face on battlefield must have its own mana value", + 3, getPermanent("Esika, God of the Tree", playerA).getManaValue()); + } + + @Test + public void test_ManaValue_MDFC_BackFace() { + // Cast back face (The Prismatic Bridge) - MV must be 5, not Esika's 3 + addCard(Zone.HAND, playerA, "Esika, God of the Tree"); + addCard(Zone.BATTLEFIELD, playerA, "Plains"); + addCard(Zone.BATTLEFIELD, playerA, "Island"); + addCard(Zone.BATTLEFIELD, playerA, "Swamp"); + addCard(Zone.BATTLEFIELD, playerA, "Mountain"); + addCard(Zone.BATTLEFIELD, playerA, "Forest"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "The Prismatic Bridge"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerA, "The Prismatic Bridge", 1); + Assert.assertEquals("MDFC back face on battlefield must have back face mana value (712.8f)", + 5, getPermanent("The Prismatic Bridge", playerA).getManaValue()); + } + + /** + * Verify that spells filtering by mana value correctly target MDFC permanents + * based on whichever face is currently up (712.8f). + * + * Eliminate: Destroy target creature or planeswalker with mana value 3 or less. + * - Esika (MV 3) should be targetable + * - The Prismatic Bridge (MV 5) should NOT be targetable + */ + @Test + public void test_Targeting_MDFC_FrontFace_CanBeTargetedByManaValueFilter() { + // Esika MV 3 - Eliminate can target it (MV <= 3) + addCard(Zone.HAND, playerA, "Esika, God of the Tree"); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 3); + addCard(Zone.HAND, playerB, "Eliminate"); // {1}{B} - destroy creature/planeswalker with MV <= 3 + addCard(Zone.BATTLEFIELD, playerB, "Swamp", 2); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Esika, God of the Tree"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Eliminate", "Esika, God of the Tree"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerA, "Esika, God of the Tree", 0); + assertGraveyardCount(playerA, "Esika, God of the Tree", 1); + } + + /** + * Rule 712.8f: While a modal double-faced spell is on the stack, it has only the + * characteristics of the face that's up. So mana value on the stack must match + * whichever face was chosen to cast. + * + * Esika, God of the Tree {1}{G}{G} - MV 3 (front face) + * The Prismatic Bridge {W}{U}{B}{R}{G} - MV 5 (back face) + */ + @Test + public void test_ManaValue_MDFC_FrontFace_OnStack() { + addCard(Zone.HAND, playerA, "Esika, God of the Tree"); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 3); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Esika, God of the Tree"); + checkStackObject("on stack", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Esika, God of the Tree", 1); + runCode("check mana value on stack", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> { + Spell spell = (Spell) game.getStack().getFirstOrNull(); + Assert.assertNotNull(spell); + Assert.assertEquals("Esika, God of the Tree", spell.getName()); + Assert.assertEquals("MDFC front face on stack must have its own mana value (712.8f)", + 3, spell.getManaValue()); + }); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerA, "Esika, God of the Tree", 1); + } + + @Test + public void test_ManaValue_MDFC_BackFace_OnStack() { + addCard(Zone.HAND, playerA, "Esika, God of the Tree"); + addCard(Zone.BATTLEFIELD, playerA, "Plains"); + addCard(Zone.BATTLEFIELD, playerA, "Island"); + addCard(Zone.BATTLEFIELD, playerA, "Swamp"); + addCard(Zone.BATTLEFIELD, playerA, "Mountain"); + addCard(Zone.BATTLEFIELD, playerA, "Forest"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "The Prismatic Bridge"); + checkStackObject("on stack", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast The Prismatic Bridge", 1); + runCode("check mana value on stack", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> { + Spell spell = (Spell) game.getStack().getFirstOrNull(); + Assert.assertNotNull(spell); + Assert.assertEquals("The Prismatic Bridge", spell.getName()); + Assert.assertEquals("MDFC back face on stack must have back face mana value (712.8f)", + 5, spell.getManaValue()); + }); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerA, "The Prismatic Bridge", 1); + } + + @Test + public void test_Targeting_MDFC_BackFace_CannotBeTargetedByManaValueFilter() { + // The Prismatic Bridge MV 5 - Eliminate cannot target it (MV > 3) + addCard(Zone.HAND, playerA, "Esika, God of the Tree"); + addCard(Zone.BATTLEFIELD, playerA, "Plains"); + addCard(Zone.BATTLEFIELD, playerA, "Island"); + addCard(Zone.BATTLEFIELD, playerA, "Swamp"); + addCard(Zone.BATTLEFIELD, playerA, "Mountain"); + addCard(Zone.BATTLEFIELD, playerA, "Forest"); + addCard(Zone.HAND, playerB, "Eliminate"); // {1}{B} - destroy creature/planeswalker with MV <= 3 + addCard(Zone.BATTLEFIELD, playerB, "Swamp", 2); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "The Prismatic Bridge"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + + checkPlayableAbility("Eliminate must not be castable (no valid target)", 1, PhaseStep.PRECOMBAT_MAIN, playerB, "Cast Eliminate", false); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerA, "The Prismatic Bridge", 1); + } + + @Test + public void test_Moonmist_MustTransformAllDFC() { + // With Opalescence + Arcane Adaptation, The Prismatic Bridge becomes a Human creature, so Moonmist will transform it + + addCard(Zone.HAND, playerA, "Esika, God of the Tree"); + addCard(Zone.HAND, playerA, "Moonmist"); + addCard(Zone.HAND, playerA, "Arcane Adaptation"); + addCard(Zone.BATTLEFIELD, playerA, "Opalescence"); + addCard(Zone.BATTLEFIELD, playerA, "Reckless Waif"); + + addCard(Zone.BATTLEFIELD, playerA, "Plains", 3); + addCard(Zone.BATTLEFIELD, playerA, "Island", 3); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 3); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 3); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "The Prismatic Bridge"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Arcane Adaptation"); + setChoice(playerA, "Human"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + + checkPermanentCount("must resolve as back side", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "The Prismatic Bridge", 1); + checkType("bridge must be creature from Opalescence", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "The Prismatic Bridge", CardType.CREATURE, true); + checkSubType("bridge must be Human from Arcane Adaptation", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "The Prismatic Bridge", SubType.HUMAN, true); + checkPT("bridge must be 5/5 from Opalescence", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "The Prismatic Bridge", 5, 5); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Moonmist"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + + // transform + checkPermanentCount("moonmist must transform TDFC", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Merciless Predator", 1); + checkPermanentCount("front side must be gone", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Reckless Waif", 0); + + checkPermanentCount("moonmist must transform MDFC since spiderman", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Esika, God of the Tree", 1); + checkPermanentCount("mmoonmist must transform MDFC since spiderman", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "The Prismatic Bridge", 0); + + checkPT("Now Esika must be 1/4 again", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Esika, God of the Tree", 1, 4); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + } + + /** + * In all zones other than stack and battlefield, an MDFC uses only the characteristics + * of its front face (712.8g). Mana value must always be the front face's MV, + * regardless of which face was on the battlefield before it moved to that zone. + * + * Esika, God of the Tree {1}{G}{G} - MV 3 (front face) + * The Prismatic Bridge {W}{U}{B}{R}{G} - MV 5 (back face) + */ + @Test + public void test_ManaValue_MDFC_OtherZones() { + // In hand (never cast): must be front face MV 3 + addCard(Zone.HAND, playerA, "Esika, God of the Tree"); + addCard(Zone.BATTLEFIELD, playerA, "Plains"); + addCard(Zone.BATTLEFIELD, playerA, "Island"); + addCard(Zone.BATTLEFIELD, playerA, "Swamp"); + addCard(Zone.BATTLEFIELD, playerA, "Mountain"); + addCard(Zone.BATTLEFIELD, playerA, "Forest"); + // Naturalize {1}{G}: destroy target artifact or enchantment + addCard(Zone.HAND, playerB, "Naturalize"); + addCard(Zone.BATTLEFIELD, playerB, "Forest", 2); + + // Cast as back face (The Prismatic Bridge, MV 5 on battlefield) + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "The Prismatic Bridge"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkPermanentCount("back face on battlefield", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "The Prismatic Bridge", 1); + + // Destroy it — card goes to graveyard + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Naturalize", "The Prismatic Bridge"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + // Graveyard: must use front face MV 3, not back face MV 5 + assertGraveyardCount(playerA, "Esika, God of the Tree", 1); + Card graveyardCard = playerA.getGraveyard().getCards(currentGame) + .stream() + .filter(c -> CardUtil.haveSameNames(c, "Esika, God of the Tree", currentGame)) + .findFirst().orElse(null); + Assert.assertNotNull("Card must be in graveyard", graveyardCard); + Assert.assertEquals("MDFC in graveyard must use front face mana value (712.8g)", + 3, graveyardCard.getManaValue()); + } + + @Test + public void test_ManaValue_MDFC_InHand() { + // In hand, MDFC must use front face MV 3 (never the back face MV 5) + addCard(Zone.HAND, playerA, "Esika, God of the Tree"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.PRECOMBAT_MAIN); + execute(); + + Card handCard = getHandCards(playerA).stream() + .filter(c -> CardUtil.haveSameNames(c, "Esika, God of the Tree", currentGame)) + .findFirst().orElse(null); + Assert.assertNotNull("Card must be in hand", handCard); + Assert.assertEquals("MDFC in hand must use front face mana value (712.8g)", + 3, handCard.getManaValue()); + } } diff --git a/Mage/src/main/java/mage/game/permanent/PermanentCard.java b/Mage/src/main/java/mage/game/permanent/PermanentCard.java index 9f0b6e11cc61..b88d648a66a6 100644 --- a/Mage/src/main/java/mage/game/permanent/PermanentCard.java +++ b/Mage/src/main/java/mage/game/permanent/PermanentCard.java @@ -270,17 +270,25 @@ public ManaCosts getManaCost() { @Override public int getManaValue() { if (isTransformed()) { - // 711.4b While a double-faced permanent's back face is up, it has only the characteristics of its back face. - // However, its converted mana cost is calculated using the mana cost of its front face. This is a change from previous rules. - // If a permanent is copying the back face of a double-faced card (even if the card representing that copy - // is itself a double-faced card), the converted mana cost of that permanent is 0. + // Note: this.card is ALWAYS the front/left side; secondSideCard is the back/right side when transformed. + // 712.8f While a modal double-faced spell is on the stack or a modal double-faced permanent is on + // the battlefield, it has only the characteristics of the face that's up. + // So for MDFC: return the back face's mana value. + if (getCard() instanceof ModalDoubleFacedCardHalf) { + return secondSideCard.getManaValue(); + } + + // 712.8e While a nonmodal double-faced permanent has its back face up, it has only the characteristics + // of its back face. However, its mana value is calculated using the mana cost of its front face. + // If a permanent is copying the back face of a nonmodal double-faced permanent (even if the object + // representing that copy is itself a double-faced permanent), the mana value of that permanent is 0. + // See rule 202.3b. return getCard().getManaValue(); } if (faceDown) { // game not neccessary return getManaCost().manaValue(); } return super.getManaValue(); - } @Override