From fe28c9c7d9fd43fb9f304e29310b1b788a181270 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Mon, 17 Jun 2019 15:03:37 +0400 Subject: [PATCH] * Flashback ability -- added support of additional cost like flashback with kicker combo (#5389); --- .../keywords/KickerWithFlashbackTest.java | 95 +++++++++++++++++++ .../java/org/mage/test/player/TestPlayer.java | 2 +- .../main/java/mage/abilities/AbilityImpl.java | 34 +++++-- 3 files changed, 123 insertions(+), 8 deletions(-) create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/KickerWithFlashbackTest.java diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/KickerWithFlashbackTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/KickerWithFlashbackTest.java new file mode 100644 index 00000000000..304e2f324cb --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/KickerWithFlashbackTest.java @@ -0,0 +1,95 @@ +package org.mage.test.cards.abilities.keywords; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author JayDi85 + */ +public class KickerWithFlashbackTest extends CardTestPlayerBase { + + // https://github.com/magefree/mage/issues/5389 + + + // Burst Lightning {R} + // Kicker {4} (You may pay an additional {4} as you cast this spell.) + // Burst Lightning deals 2 damage to any target. If this spell was kicked, it deals 4 damage to that permanent or player instead. + + // Snapcaster Mage {1}{U} + // Flash + // When Snapcaster Mage enters the battlefield, target instant or sorcery card in your graveyard gains flashback until end of turn. + // The flashback cost is equal to its mana cost. (You may cast that card from your graveyard for its flashback cost. Then exile it.) + + @Test + public void test_SimpleKicker() { + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 5); + addCard(Zone.HAND, playerA, "Burst Lightning"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Burst Lightning", playerB); + setChoice(playerA, "Yes"); // use kicker + + checkLife("after", 1, PhaseStep.END_TURN, playerB, 20 - 4); + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + assertAllCommandsUsed(); + } + + @Test + public void test_SimpleFlashback() { + addCard(Zone.BATTLEFIELD, playerA, "Island", 2); + addCard(Zone.HAND, playerA, "Snapcaster Mage"); + // + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1); + addCard(Zone.GRAVEYARD, playerA, "Lightning Bolt", 1); + + // add flashback to bolt + activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {U}"); + activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {U}"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Snapcaster Mage"); + addTarget(playerA, "Lightning Bolt"); + + // cast bolt by flashback + activateAbility(1, PhaseStep.BEGIN_COMBAT, playerA, "Flashback"); + addTarget(playerA, playerB); + + checkLife("after", 1, PhaseStep.END_TURN, playerB, 20 - 3); + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + assertAllCommandsUsed(); + } + + @Test + public void test_KickerWithFlashback() { + addCard(Zone.BATTLEFIELD, playerA, "Island", 2); + addCard(Zone.HAND, playerA, "Snapcaster Mage"); + // + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1 + 4); + addCard(Zone.GRAVEYARD, playerA, "Burst Lightning", 1); + + // add flashback to burst + activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {U}"); + activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {U}"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Snapcaster Mage"); + addTarget(playerA, "Burst Lightning"); + + // cast burst by flashback + showAvaileableAbilities("after", 1, PhaseStep.BEGIN_COMBAT, playerA); + activateAbility(1, PhaseStep.BEGIN_COMBAT, playerA, "Flashback"); + setChoice(playerA, "Yes"); // use kicker + addTarget(playerA, playerB); + + checkLife("after", 1, PhaseStep.END_TURN, playerB, 20 - 4); + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + assertAllCommandsUsed(); + } + +} diff --git a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java index b11671fa0b8..f5db6d66195 100644 --- a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java +++ b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java @@ -835,7 +835,7 @@ public class TestPlayer implements Player { return perm; } } - Assert.assertNotNull(action.getActionName() + " - can''t find permanent to check PT: " + cardName, founded); + Assert.assertNotNull(action.getActionName() + " - can''t find permanent to check: " + cardName, founded); return null; } diff --git a/Mage/src/main/java/mage/abilities/AbilityImpl.java b/Mage/src/main/java/mage/abilities/AbilityImpl.java index e5bc20104f8..58c9e51b53f 100644 --- a/Mage/src/main/java/mage/abilities/AbilityImpl.java +++ b/Mage/src/main/java/mage/abilities/AbilityImpl.java @@ -432,13 +432,30 @@ public abstract class AbilityImpl implements Ability { @Override public boolean activateAlternateOrAdditionalCosts(MageObject sourceObject, boolean noMana, Player controller, Game game) { + boolean canUseAlternativeCost = true; + boolean canUseAdditionalCost = true; + if (this instanceof SpellAbility) { - if (((SpellAbility) this).getSpellAbilityCastMode() != SpellAbilityCastMode.NORMAL) { - // A player can't apply two alternative methods of casting or two alternative costs to a single spell. - // So can only use alternate costs if the spell is cast in normal mode - return false; + // A player can't apply two alternative methods of casting or two alternative costs to a single spell. + switch (((SpellAbility) this).getSpellAbilityCastMode()) { + case NORMAL: + case MADNESS: + default: + canUseAlternativeCost = true; + canUseAdditionalCost = true; + break; + case FLASHBACK: + // from Snapcaster Mage: + // If you cast a spell from a graveyard using its flashback ability, you can’t pay other alternative costs + // (such as that of Foil). (2018-12-07) + canUseAlternativeCost = false; + // You may pay any optional additional costs the spell has, such as kicker costs. You must pay any + // mandatory additional costs the spell has, such as that of Tormenting Voice. (2018-12-07) + canUseAdditionalCost = true; + break; } } + boolean alternativeCostisUsed = false; if (sourceObject != null && !(sourceObject instanceof Permanent)) { Abilities abilities = null; @@ -447,10 +464,11 @@ public abstract class AbilityImpl implements Ability { } else { sourceObject.getAbilities(); } + if (abilities != null) { for (Ability ability : abilities) { // if cast for noMana no Alternative costs are allowed - if (!noMana && ability instanceof AlternativeSourceCosts) { + if (canUseAlternativeCost && !noMana && ability instanceof AlternativeSourceCosts) { AlternativeSourceCosts alternativeSpellCosts = (AlternativeSourceCosts) ability; if (alternativeSpellCosts.isAvailable(this, game)) { if (alternativeSpellCosts.askToActivateAlternativeCosts(this, game)) { @@ -460,13 +478,14 @@ public abstract class AbilityImpl implements Ability { } } } - if (ability instanceof OptionalAdditionalSourceCosts) { + if (canUseAdditionalCost && ability instanceof OptionalAdditionalSourceCosts) { ((OptionalAdditionalSourceCosts) ability).addOptionalAdditionalCosts(this, game); } } } + // controller specific alternate spell costs - if (!noMana && !alternativeCostisUsed) { + if (canUseAlternativeCost && !noMana && !alternativeCostisUsed) { if (this.getAbilityType() == AbilityType.SPELL // 117.9a Only one alternative cost can be applied to any one spell as it's being cast. // So an alternate spell ability can't be paid with Omniscience @@ -483,6 +502,7 @@ public abstract class AbilityImpl implements Ability { } } } + return alternativeCostisUsed; }