diff --git a/Mage.Sets/src/mage/cards/c/Conflagrate.java b/Mage.Sets/src/mage/cards/c/Conflagrate.java index 11e5cf6b274..70289727353 100644 --- a/Mage.Sets/src/mage/cards/c/Conflagrate.java +++ b/Mage.Sets/src/mage/cards/c/Conflagrate.java @@ -30,7 +30,6 @@ package mage.cards.c; import java.util.UUID; import mage.abilities.Ability; import mage.abilities.costs.Cost; -import mage.abilities.costs.common.DiscardTargetCost; import mage.abilities.costs.common.DiscardXTargetCost; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.dynamicvalue.DynamicValue; @@ -82,8 +81,8 @@ class ConflagrateVariableValue implements DynamicValue { public int calculate(Game game, Ability sourceAbility, Effect effect) { int xValue = sourceAbility.getManaCostsToPay().getX(); for (Cost cost : sourceAbility.getCosts()) { - if (cost instanceof DiscardTargetCost) { - xValue = ((DiscardTargetCost) cost).getCards().size(); + if (cost instanceof DiscardXTargetCost) { + xValue = ((DiscardXTargetCost) cost).getAmount(); } } return xValue; 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 30f0d24fb93..02bf64326b3 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 @@ -27,9 +27,9 @@ */ package org.mage.test.cards.abilities.keywords; +import mage.abilities.keyword.TrampleAbility; import mage.constants.PhaseStep; import mage.constants.Zone; -import org.junit.Ignore; import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; @@ -39,6 +39,27 @@ import org.mage.test.serverside.base.CardTestPlayerBase; */ public class FlashbackTest extends CardTestPlayerBase { + @Test + public void testNormalWildHunger() { + + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 4); + + // Target creature gets +3/+1 and gains trample until end of turn. + // Flashback {3}{R} + addCard(Zone.GRAVEYARD, playerA, "Wild Hunger"); + + addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion", 1); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Flashback", "Silvercoat Lion"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPowerToughness(playerA, "Silvercoat Lion", 5, 3); + assertAbility(playerA, "Silvercoat Lion", TrampleAbility.getInstance(), true); + assertExileCount("Wild Hunger", 1); + } + /** * Fracturing Gust is bugged. In a match against Affinity, it worked * properly when cast from hand. When I cast it from graveyard c/o @@ -219,7 +240,7 @@ public class FlashbackTest extends CardTestPlayerBase { // Conflagrate deals X damage divided as you choose among any number of target creatures and/or players. // Flashback-{R}{R}, Discard X cards. - addCard(Zone.HAND, playerA, "Conflagrate", 1); + addCard(Zone.HAND, playerA, "Conflagrate", 1); // Sorcery {X}{X}{R} addCard(Zone.HAND, playerA, "Forest", 4); @@ -307,20 +328,26 @@ public class FlashbackTest extends CardTestPlayerBase { public void testAltarsReap() { addCard(Zone.LIBRARY, playerA, "Island", 2); - addCard(Zone.GRAVEYARD, playerA, "Altar's Reap", 1); + // As an additional cost to cast Altar's Reap, sacrifice a creature. + // Draw two cards. + addCard(Zone.GRAVEYARD, playerA, "Altar's Reap", 1); // Instant {1}{B} addCard(Zone.BATTLEFIELD, playerA, "Underground Sea", 4); addCard(Zone.HAND, playerA, "Snapcaster Mage", 1); + // 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. castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Snapcaster Mage"); setChoice(playerA, "Altar's Reap"); - activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Flashback {1}{B}"); + activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Flashback"); setChoice(playerA, "Snapcaster Mage"); setStopAt(1, PhaseStep.END_TURN); execute(); assertGraveyardCount(playerA, "Snapcaster Mage", 1); + assertExileCount(playerA, "Altar's Reap", 1); } /** @@ -520,7 +547,6 @@ public class FlashbackTest extends CardTestPlayerBase { * to a spell, and the flashback cost is already an alternative cost. */ @Test - @Ignore public void testSnapcasterMageSpellWithAlternateCost() { addCard(Zone.BATTLEFIELD, playerA, "Island", 1); addCard(Zone.BATTLEFIELD, playerA, "Forest", 1); diff --git a/Mage/src/main/java/mage/abilities/AbilityImpl.java b/Mage/src/main/java/mage/abilities/AbilityImpl.java index 8caf2db48c3..23f81762129 100644 --- a/Mage/src/main/java/mage/abilities/AbilityImpl.java +++ b/Mage/src/main/java/mage/abilities/AbilityImpl.java @@ -44,7 +44,6 @@ import mage.abilities.effects.Effects; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.DynamicManaEffect; import mage.abilities.effects.common.ManaEffect; -import mage.abilities.keyword.FlashbackAbility; import mage.abilities.mana.ActivatedManaAbilityImpl; import mage.cards.Card; import mage.cards.SplitCard; @@ -337,9 +336,7 @@ public abstract class AbilityImpl implements Ability { if (sourceObject != null && this.getAbilityType() != AbilityType.TRIGGERED) { // triggered abilities check this already in playerImpl.triggerAbility sourceObject.adjustTargets(this, game); } - // Flashback abilities haven't made the choices the underlying spell might need for targeting. - if (!(this instanceof FlashbackAbility) - && !getTargets().isEmpty()) { + if (!getTargets().isEmpty()) { Outcome outcome = getEffects().isEmpty() ? Outcome.Detriment : getEffects().get(0).getOutcome(); if (getTargets().chooseTargets(outcome, this.controllerId, this, noMana, game) == false) { if ((variableManaCost != null || announceString != null)) { @@ -445,8 +442,15 @@ public abstract class AbilityImpl implements Ability { @Override public boolean activateAlternateOrAdditionalCosts(MageObject sourceObject, boolean noMana, Player controller, Game game) { + 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; + } + } boolean alternativeCostisUsed = false; - if (sourceObject != null && !(sourceObject instanceof Permanent) && !(this instanceof FlashbackAbility)) { + if (sourceObject != null && !(sourceObject instanceof Permanent)) { Abilities abilities = null; if (sourceObject instanceof Card) { abilities = ((Card) sourceObject).getAbilities(game); diff --git a/Mage/src/main/java/mage/abilities/SpellAbility.java b/Mage/src/main/java/mage/abilities/SpellAbility.java index 2cd291a957a..289fa05592e 100644 --- a/Mage/src/main/java/mage/abilities/SpellAbility.java +++ b/Mage/src/main/java/mage/abilities/SpellAbility.java @@ -217,7 +217,7 @@ public class SpellAbility extends ActivatedAbilityImpl { this.name = "Cast fused " + cardName; break; default: - this.name = "Cast " + cardName + (this.spellAbilityCastMode != SpellAbilityCastMode.NORMAL ? " by " + spellAbilityCastMode.toString() : ""); + this.name = "Cast " + cardName + (this.spellAbilityCastMode != SpellAbilityCastMode.NORMAL ? " using " + spellAbilityCastMode.toString() : ""); } } @@ -230,4 +230,11 @@ public class SpellAbility extends ActivatedAbilityImpl { setSpellName(); } + public SpellAbility getSpellAbilityToResolve(Game game) { + return this; + } + + public void setId(UUID idToUse) { + this.id = idToUse; + } } diff --git a/Mage/src/main/java/mage/abilities/costs/VariableCostImpl.java b/Mage/src/main/java/mage/abilities/costs/VariableCostImpl.java index aefaf6f5269..6b96444bd20 100644 --- a/Mage/src/main/java/mage/abilities/costs/VariableCostImpl.java +++ b/Mage/src/main/java/mage/abilities/costs/VariableCostImpl.java @@ -29,7 +29,6 @@ package mage.abilities.costs; import java.util.UUID; import mage.abilities.Ability; -import mage.abilities.keyword.FlashbackAbility; import mage.abilities.mana.ManaAbility; import mage.game.Game; import mage.game.stack.StackObject; @@ -173,7 +172,6 @@ public abstract class VariableCostImpl implements Cost, VariableCost { StackObject stackObject = game.getStack().getStackObject(source.getId()); if (controller != null && (source instanceof ManaAbility - || source instanceof FlashbackAbility || stackObject != null)) { xValue = controller.announceXCost(getMinValue(source, game), getMaxValue(source, game), "Announce the number of " + actionText, game, source, this); diff --git a/Mage/src/main/java/mage/abilities/keyword/FlashbackAbility.java b/Mage/src/main/java/mage/abilities/keyword/FlashbackAbility.java index c05f20118f5..aa457aea70a 100644 --- a/Mage/src/main/java/mage/abilities/keyword/FlashbackAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/FlashbackAbility.java @@ -32,14 +32,13 @@ import mage.abilities.Ability; import mage.abilities.SpellAbility; import mage.abilities.costs.Cost; import mage.abilities.costs.Costs; -import mage.abilities.costs.mana.ManaCost; import mage.abilities.effects.ContinuousEffect; -import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.ReplacementEffectImpl; import mage.cards.Card; import mage.cards.SplitCard; import mage.constants.Duration; import mage.constants.Outcome; +import mage.constants.SpellAbilityCastMode; import mage.constants.SpellAbilityType; import mage.constants.TimingRule; import mage.constants.Zone; @@ -66,23 +65,21 @@ import mage.target.targetpointer.FixedTarget; public class FlashbackAbility extends SpellAbility { private String abilityName; + private SpellAbility spellAbilityToResolve; public FlashbackAbility(Cost cost, TimingRule timingRule) { - super(null, "", Zone.GRAVEYARD); + super(null, "", Zone.GRAVEYARD, SpellAbilityType.BASE_ALTERNATE, SpellAbilityCastMode.FLASHBACK); this.setAdditionalCostsRuleVisible(false); this.name = "Flashback " + cost.getText(); - this.addEffect(new FlashbackEffect()); this.addCost(cost); this.timing = timingRule; - this.usesStack = false; - this.spellAbilityType = SpellAbilityType.BASE_ALTERNATE; - setCostModificationActive(false); } public FlashbackAbility(final FlashbackAbility ability) { super(ability); this.spellAbilityType = ability.spellAbilityType; this.abilityName = ability.abilityName; + this.spellAbilityToResolve = ability.spellAbilityToResolve; } @Override @@ -108,6 +105,47 @@ public class FlashbackAbility extends SpellAbility { return false; } + @Override + public SpellAbility getSpellAbilityToResolve(Game game) { + Card card = game.getCard(getSourceId()); + if (card != null) { + if (spellAbilityToResolve == null) { + SpellAbility spellAbilityCopy = null; + if (card.isSplitCard()) { + if (((SplitCard) card).getLeftHalfCard().getName().equals(abilityName)) { + spellAbilityCopy = ((SplitCard) card).getLeftHalfCard().getSpellAbility().copy(); + } else if (((SplitCard) card).getRightHalfCard().getName().equals(abilityName)) { + spellAbilityCopy = ((SplitCard) card).getRightHalfCard().getSpellAbility().copy(); + } + } else { + spellAbilityCopy = card.getSpellAbility().copy(); + } + if (spellAbilityCopy == null) { + return null; + } + spellAbilityCopy.setId(this.getId()); + spellAbilityCopy.getManaCosts().clear(); + spellAbilityCopy.getManaCostsToPay().clear(); + spellAbilityCopy.getCosts().addAll(this.getCosts()); + spellAbilityCopy.addCost(this.getManaCosts()); + spellAbilityCopy.setSpellAbilityCastMode(this.getSpellAbilityCastMode()); + spellAbilityToResolve = spellAbilityCopy; + ContinuousEffect effect = new FlashbackReplacementEffect(); + effect.setTargetPointer(new FixedTarget(getSourceId(), game.getState().getZoneChangeCounter(getSourceId()))); + game.addEffect(effect, this); + } + } + return spellAbilityToResolve; + } + + @Override + public Costs getCosts() { + if (spellAbilityToResolve == null) { + return super.getCosts(); + } + return spellAbilityToResolve.getCosts(); + } + @Override public FlashbackAbility copy() { return new FlashbackAbility(this); @@ -144,102 +182,18 @@ public class FlashbackAbility extends SpellAbility { return sbRule.toString(); } - @Override - public void setSpellAbilityType(SpellAbilityType spellAbilityType) { - this.spellAbilityType = spellAbilityType; - } - - @Override - public SpellAbilityType getSpellAbilityType() { - return this.spellAbilityType; - } - + /** + * Used for split card sin PlayerImpl method: + * getOtherUseableActivatedAbilities + * + * @param abilityName + */ public void setAbilityName(String abilityName) { this.abilityName = abilityName; } } -class FlashbackEffect extends OneShotEffect { - - public FlashbackEffect() { - super(Outcome.Benefit); - staticText = ""; - } - - public FlashbackEffect(final FlashbackEffect effect) { - super(effect); - } - - @Override - public FlashbackEffect copy() { - return new FlashbackEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Card card = (Card) game.getObject(source.getSourceId()); - if (card != null) { - Player controller = game.getPlayer(source.getControllerId()); - if (controller != null) { - SpellAbility spellAbility; - switch (((FlashbackAbility) source).getSpellAbilityType()) { - case SPLIT_LEFT: - spellAbility = ((SplitCard) card).getLeftHalfCard().getSpellAbility().copy(); - break; - case SPLIT_RIGHT: - spellAbility = ((SplitCard) card).getRightHalfCard().getSpellAbility().copy(); - break; - default: - spellAbility = card.getSpellAbility().copy(); - } - - spellAbility.clear(); - // 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.getManaCosts()); - spellAbility.getManaCosts().clear(); - spellAbility.getManaCosts().addAll(source.getManaCosts()); - // needed to get e.g. paid costs from Conflagrate - - for (Cost cost : source.getCosts()) { - if (cost instanceof Costs) { - Costs listOfCosts = (Costs) cost; - for (Cost singleCost : listOfCosts) { - if (singleCost instanceof ManaCost) { - singleCost.clearPaid(); - spellAbility.getManaCosts().add((ManaCost) singleCost); - spellAbility.getManaCostsToPay().add((ManaCost) singleCost); - } else { - spellAbility.getCosts().add(singleCost); - } - } - - } else { - if (cost instanceof ManaCost) { - spellAbility.getManaCosts().add((ManaCost) cost); - spellAbility.getManaCostsToPay().add((ManaCost) cost); - } else { - spellAbility.getCosts().add(cost); - } - } - } - if (!game.isSimulation()) { - game.informPlayers(controller.getLogName() + " flashbacks " + card.getLogName()); - } - if (controller.cast(spellAbility, game, false)) { - ContinuousEffect effect = new FlashbackReplacementEffect(); - effect.setTargetPointer(new FixedTarget(source.getSourceId(), game.getState().getZoneChangeCounter(source.getSourceId()))); - game.addEffect(effect, source); - } - return true; - } - } - return false; - } - -} - class FlashbackReplacementEffect extends ReplacementEffectImpl { public FlashbackReplacementEffect() { @@ -287,7 +241,7 @@ class FlashbackReplacementEffect extends ReplacementEffectImpl { && ((ZoneChangeEvent) event).getToZone() != Zone.EXILED) { int zcc = game.getState().getZoneChangeCounter(source.getSourceId()); - if (((FixedTarget) getTargetPointer()).getZoneChangeCounter() == zcc) { + if (((FixedTarget) getTargetPointer()).getZoneChangeCounter() + 1 == zcc) { return true; } diff --git a/Mage/src/main/java/mage/cards/CardImpl.java b/Mage/src/main/java/mage/cards/CardImpl.java index f82868e628b..4fc6a220e94 100644 --- a/Mage/src/main/java/mage/cards/CardImpl.java +++ b/Mage/src/main/java/mage/cards/CardImpl.java @@ -516,7 +516,7 @@ public abstract class CardImpl extends MageObjectImpl implements Card { Card mainCard = getMainCard(); ZoneChangeEvent event = new ZoneChangeEvent(mainCard.getId(), ability.getId(), controllerId, fromZone, Zone.STACK); ZoneChangeInfo.Stack info - = new ZoneChangeInfo.Stack(event, new Spell(this, ability.copy(), controllerId, event.getFromZone())); + = new ZoneChangeInfo.Stack(event, new Spell(this, ability.getSpellAbilityToResolve(game), controllerId, event.getFromZone())); return ZonesHandler.cast(info, game); } diff --git a/Mage/src/main/java/mage/constants/SpellAbilityCastMode.java b/Mage/src/main/java/mage/constants/SpellAbilityCastMode.java index 663c8c8f1bf..71aecf37ed5 100644 --- a/Mage/src/main/java/mage/constants/SpellAbilityCastMode.java +++ b/Mage/src/main/java/mage/constants/SpellAbilityCastMode.java @@ -33,7 +33,8 @@ package mage.constants; */ public enum SpellAbilityCastMode { NORMAL("Normal"), - MADNESS("Madness"); + MADNESS("Madness"), + FLASHBACK("Flashback"); private final String text; diff --git a/Mage/src/main/java/mage/players/PlayerImpl.java b/Mage/src/main/java/mage/players/PlayerImpl.java index ceb43d787f0..a61e8023f3b 100644 --- a/Mage/src/main/java/mage/players/PlayerImpl.java +++ b/Mage/src/main/java/mage/players/PlayerImpl.java @@ -1158,7 +1158,7 @@ public abstract class PlayerImpl implements Player, Serializable { } } else { int bookmark = game.bookmarkState(); - if (ability.activate(game, ability instanceof FlashbackAbility)) { + if (ability.activate(game, false)) { ability.resolve(game); game.removeBookmark(bookmark); resetStoredBookmark(game); @@ -1219,11 +1219,7 @@ public abstract class PlayerImpl implements Player, Serializable { result = playManaAbility((ActivatedManaAbilityImpl) ability.copy(), game); break; case SPELL: - if (ability instanceof FlashbackAbility) { - result = playAbility(ability.copy(), game); - } else { - result = cast((SpellAbility) ability, game, false); - } + result = cast((SpellAbility) ability.copy(), game, false); break; default: result = playAbility(ability.copy(), game);