From 2153d5ccf5330c3978ad8428dabe25a1d7828bfc Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Fri, 30 Oct 2015 00:30:53 +0100 Subject: [PATCH] * Fixed that for spells cast with flashback values calculated from the paid mana (e.g. Converge) did not work correctly. --- .../abilities/keywords/FlashbackTest.java | 65 +++++++++++----- .../abilities/keyword/FlashbackAbility.java | 76 +++++++------------ 2 files changed, 72 insertions(+), 69 deletions(-) diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/FlashbackTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/FlashbackTest.java index 52dff9e173a..fe11b32bf84 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/FlashbackTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/FlashbackTest.java @@ -49,76 +49,103 @@ public class FlashbackTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerA, "Island", 2); addCard(Zone.BATTLEFIELD, playerA, "Forest", 5); addCard(Zone.HAND, playerA, "Snapcaster Mage", 1); - + // Destroy all artifacts and enchantments. You gain 2 life for each permanent destroyed this way. addCard(Zone.GRAVEYARD, playerA, "Fracturing Gust"); addCard(Zone.BATTLEFIELD, playerA, "Berserkers' Onslaught", 1); addCard(Zone.BATTLEFIELD, playerB, "Darksteel Citadel", 1); - // 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. castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Snapcaster Mage"); setChoice(playerA, "Fracturing Gust"); activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Flashback {2}{G/W}{G/W}{G/W}"); // now snapcaster mage is died so -13/-13 - setStopAt(1, PhaseStep.END_TURN); execute(); assertPermanentCount(playerA, "Snapcaster Mage", 1); assertGraveyardCount(playerA, "Berserkers' Onslaught", 1); - + assertPermanentCount(playerB, "Darksteel Citadel", 1); - + assertExileCount("Fracturing Gust", 1); } /** * My opponent put Iona on the battlefield using Unburial Rites, but my game * log didn't show me the color he has chosen. - * + * */ @Test public void testUnburialRites() { addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1); addCard(Zone.BATTLEFIELD, playerA, "Plains", 8); // Return target creature card from your graveyard to the battlefield. - // Flashback {3}{W} + // Flashback {3}{W} addCard(Zone.HAND, playerA, "Unburial Rites", 1); // Sorcery - {4}{B} - + // Flying // As Iona, Shield of Emeria enters the battlefield, choose a color. // Your opponents can't cast spells of the chosen color. addCard(Zone.GRAVEYARD, playerA, "Iona, Shield of Emeria"); - + // As Lurebound Scarecrow enters the battlefield, choose a color. - // When you control no permanents of the chosen color, sacrifice Lurebound Scarecrow. + // When you control no permanents of the chosen color, sacrifice Lurebound Scarecrow. addCard(Zone.GRAVEYARD, playerA, "Lurebound Scarecrow"); // Enchantment - {2}{U} addCard(Zone.BATTLEFIELD, playerB, "Mountain", 1); - addCard(Zone.HAND, playerB, "Lightning Bolt", 1); + addCard(Zone.HAND, playerB, "Lightning Bolt", 1); - - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Unburial Rites", "Iona, Shield of Emeria"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Unburial Rites", "Iona, Shield of Emeria"); setChoice(playerA, "Red"); - activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Flashback {3}{W}"); + activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Flashback {3}{W}"); addTarget(playerA, "Lurebound Scarecrow"); setChoice(playerA, "White"); - - castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Lightning Bolt", playerA); + + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Lightning Bolt", playerA); setStopAt(1, PhaseStep.END_TURN); execute(); assertPermanentCount(playerA, "Iona, Shield of Emeria", 1); assertPermanentCount(playerA, "Lurebound Scarecrow", 1); - + assertHandCount(playerB, "Lightning Bolt", 1); - + assertExileCount("Unburial Rites", 1); } - + + /** + * + */ + @Test + public void testFlashbackWithConverge() { + addCard(Zone.BATTLEFIELD, playerA, "Island", 1); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 1); + addCard(Zone.HAND, playerA, "Snapcaster Mage", 1); + + // Converge - Put a 1/1 white Kor Ally creature token onto the battlefield for each color of mana spent to cast Unified Front. + addCard(Zone.GRAVEYARD, playerA, "Unified Front"); // {3}{W} + + activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Add {W}"); + // 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. + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Snapcaster Mage"); + setChoice(playerA, "Unified Front"); + + activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Flashback {3}{W}"); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, "Snapcaster Mage", 1); + assertPermanentCount(playerA, "Kor Ally", 4); + assertExileCount("Unified Front", 1); + + } } diff --git a/Mage/src/mage/abilities/keyword/FlashbackAbility.java b/Mage/src/mage/abilities/keyword/FlashbackAbility.java index d1f23c4dbd1..b8631af4523 100644 --- a/Mage/src/mage/abilities/keyword/FlashbackAbility.java +++ b/Mage/src/mage/abilities/keyword/FlashbackAbility.java @@ -29,18 +29,12 @@ package mage.abilities.keyword; import java.util.UUID; import mage.abilities.Ability; -import mage.abilities.DelayedTriggeredAbility; import mage.abilities.SpellAbility; import mage.abilities.costs.Cost; -import mage.abilities.costs.VariableCost; -import mage.abilities.costs.mana.VariableManaCost; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.ReplacementEffectImpl; -import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; -import mage.abilities.effects.common.ExileSourceEffect; import mage.cards.Card; import mage.cards.SplitCard; -import mage.constants.CardType; import mage.constants.Duration; import mage.constants.Outcome; import mage.constants.SpellAbilityType; @@ -54,13 +48,14 @@ import mage.players.Player; /** * 702.32. Flashback * - * 702.32a. Flashback appears on some instants and sorceries. It represents two static abilities: - * one that functions while the card is in a player‘s graveyard and the other that functions - * while the card is on the stack. Flashback [cost] means, "You may cast this card from your - * graveyard by paying [cost] rather than paying its mana cost" and, "If the flashback cost - * was paid, exile this card instead of putting it anywhere else any time it would leave the - * stack." Casting a spell using its flashback ability follows the rules for paying alternative - * costs in rules 601.2b and 601.2e–g. + * 702.32a. Flashback appears on some instants and sorceries. It represents two + * static abilities: one that functions while the card is in a player‘s + * graveyard and the other that functions while the card is on the stack. + * Flashback [cost] means, "You may cast this card from your graveyard by paying + * [cost] rather than paying its mana cost" and, "If the flashback cost was + * paid, exile this card instead of putting it anywhere else any time it would + * leave the stack." Casting a spell using its flashback ability follows the + * rules for paying alternative costs in rules 601.2b and 601.2e–g. * * @author nantuko */ @@ -92,10 +87,10 @@ public class FlashbackAbility extends SpellAbility { if (card != null) { // Flashback can never cast a split card by Fuse, because Fuse only works from hand if (card.isSplitCard()) { - if (((SplitCard)card).getLeftHalfCard().getName().equals(abilityName)) { - return ((SplitCard)card).getLeftHalfCard().getSpellAbility().canActivate(playerId, game); - } else if (((SplitCard)card).getRightHalfCard().getName().equals(abilityName)) { - return ((SplitCard)card).getRightHalfCard().getSpellAbility().canActivate(playerId, game); + if (((SplitCard) card).getLeftHalfCard().getName().equals(abilityName)) { + return ((SplitCard) card).getLeftHalfCard().getSpellAbility().canActivate(playerId, game); + } else if (((SplitCard) card).getRightHalfCard().getName().equals(abilityName)) { + return ((SplitCard) card).getRightHalfCard().getSpellAbility().canActivate(playerId, game); } } return card.getSpellAbility().canActivate(playerId, game); @@ -108,7 +103,7 @@ public class FlashbackAbility extends SpellAbility { public FlashbackAbility copy() { return new FlashbackAbility(this); } - + @Override public String getRule(boolean all) { return this.getRule(); @@ -176,45 +171,26 @@ class FlashbackEffect extends OneShotEffect { Player controller = game.getPlayer(source.getControllerId()); if (controller != null) { SpellAbility spellAbility; - switch(((FlashbackAbility) source).getSpellAbilityType()) { + switch (((FlashbackAbility) source).getSpellAbilityType()) { case SPLIT_LEFT: - spellAbility = ((SplitCard)card).getLeftHalfCard().getSpellAbility(); + spellAbility = ((SplitCard) card).getLeftHalfCard().getSpellAbility(); break; case SPLIT_RIGHT: - spellAbility = ((SplitCard)card).getRightHalfCard().getSpellAbility(); + spellAbility = ((SplitCard) card).getRightHalfCard().getSpellAbility(); break; default: spellAbility = card.getSpellAbility(); } spellAbility.clear(); - // used if flashbacked spell has a {X} cost - int amount = source.getManaCostsToPay().getX(); - if (amount == 0) { - // add variable cost like Discard X cards to get the X value to the spell - // because there is currently no way to set the x value in anotehr way, it's set for the - // x mana value to be known by the spell - for (Cost cost:source.getCosts()) { - if (cost instanceof VariableCost && cost.isPaid()) { - amount = ((VariableCost) cost).getAmount(); - break; - } - } + // set the payed flashback costs to the spell ability so abilities like Converge or calculation of {X} values work + spellAbility.getManaCostsToPay().clear(); + spellAbility.getManaCostsToPay().addAll(source.getManaCostsToPay()); + if (!game.isSimulation()) { + game.informPlayers(controller.getLogName() + " flashbacks " + card.getLogName()); } - if (amount > 0) { - // multiplier must be taken into account because if the base spell has {X}{X} the x value would be wrongly halfed - for (VariableCost variableCost: spellAbility.getManaCostsToPay().getVariableCosts()) { - if (variableCost instanceof VariableManaCost) { - amount = amount * ((VariableManaCost)variableCost).getMultiplier(); - break; - } - } - spellAbility.getManaCostsToPay().setX(amount); - } - if (!game.isSimulation()) - game.informPlayers(new StringBuilder(controller.getLogName()).append(" flashbacks ").append(card.getName()).toString()); spellAbility.setCostModificationActive(false); // prevents to apply cost modification twice for flashbacked spells - if (controller.cast(spellAbility, game, true)) { + if (controller.cast(spellAbility, game, false)) { game.addEffect(new FlashbackReplacementEffect(), source); return true; } @@ -252,7 +228,7 @@ class FlashbackReplacementEffect extends ReplacementEffectImpl { if (controller != null) { Card card = game.getCard(event.getTargetId()); if (card != null) { - return controller.moveCardToExileWithInfo(card, null, "", source.getSourceId(), game, game.getState().getZone(card.getId()), true); + return controller.moveCards(card, Zone.EXILED, source, game); } } return false; @@ -265,8 +241,8 @@ class FlashbackReplacementEffect extends ReplacementEffectImpl { @Override public boolean applies(GameEvent event, Ability source, Game game) { - return event.getTargetId().equals(source.getSourceId()) - && ((ZoneChangeEvent)event).getFromZone() == Zone.STACK - && ((ZoneChangeEvent)event).getToZone() != Zone.EXILED; + return event.getTargetId().equals(source.getSourceId()) + && ((ZoneChangeEvent) event).getFromZone() == Zone.STACK + && ((ZoneChangeEvent) event).getToZone() != Zone.EXILED; } }