mirror of
https://github.com/magefree/mage.git
synced 2025-12-22 03:22:00 -08:00
* Flashback ability -- added support of additional cost like flashback with kicker combo (#5389);
This commit is contained in:
parent
e39dc1124b
commit
fe28c9c7d9
3 changed files with 123 additions and 8 deletions
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -835,7 +835,7 @@ public class TestPlayer implements Player {
|
||||||
return perm;
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -432,13 +432,30 @@ public abstract class AbilityImpl implements Ability {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean activateAlternateOrAdditionalCosts(MageObject sourceObject, boolean noMana, Player controller, Game game) {
|
public boolean activateAlternateOrAdditionalCosts(MageObject sourceObject, boolean noMana, Player controller, Game game) {
|
||||||
|
boolean canUseAlternativeCost = true;
|
||||||
|
boolean canUseAdditionalCost = true;
|
||||||
|
|
||||||
if (this instanceof SpellAbility) {
|
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.
|
// 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
|
switch (((SpellAbility) this).getSpellAbilityCastMode()) {
|
||||||
return false;
|
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;
|
boolean alternativeCostisUsed = false;
|
||||||
if (sourceObject != null && !(sourceObject instanceof Permanent)) {
|
if (sourceObject != null && !(sourceObject instanceof Permanent)) {
|
||||||
Abilities<Ability> abilities = null;
|
Abilities<Ability> abilities = null;
|
||||||
|
|
@ -447,10 +464,11 @@ public abstract class AbilityImpl implements Ability {
|
||||||
} else {
|
} else {
|
||||||
sourceObject.getAbilities();
|
sourceObject.getAbilities();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (abilities != null) {
|
if (abilities != null) {
|
||||||
for (Ability ability : abilities) {
|
for (Ability ability : abilities) {
|
||||||
// if cast for noMana no Alternative costs are allowed
|
// if cast for noMana no Alternative costs are allowed
|
||||||
if (!noMana && ability instanceof AlternativeSourceCosts) {
|
if (canUseAlternativeCost && !noMana && ability instanceof AlternativeSourceCosts) {
|
||||||
AlternativeSourceCosts alternativeSpellCosts = (AlternativeSourceCosts) ability;
|
AlternativeSourceCosts alternativeSpellCosts = (AlternativeSourceCosts) ability;
|
||||||
if (alternativeSpellCosts.isAvailable(this, game)) {
|
if (alternativeSpellCosts.isAvailable(this, game)) {
|
||||||
if (alternativeSpellCosts.askToActivateAlternativeCosts(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);
|
((OptionalAdditionalSourceCosts) ability).addOptionalAdditionalCosts(this, game);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// controller specific alternate spell costs
|
// controller specific alternate spell costs
|
||||||
if (!noMana && !alternativeCostisUsed) {
|
if (canUseAlternativeCost && !noMana && !alternativeCostisUsed) {
|
||||||
if (this.getAbilityType() == AbilityType.SPELL
|
if (this.getAbilityType() == AbilityType.SPELL
|
||||||
// 117.9a Only one alternative cost can be applied to any one spell as it's being cast.
|
// 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
|
// So an alternate spell ability can't be paid with Omniscience
|
||||||
|
|
@ -483,6 +502,7 @@ public abstract class AbilityImpl implements Ability {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return alternativeCostisUsed;
|
return alternativeCostisUsed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue