diff --git a/Mage.Sets/src/mage/cards/a/Aluren.java b/Mage.Sets/src/mage/cards/a/Aluren.java index a0c485b682c..6578cdad713 100644 --- a/Mage.Sets/src/mage/cards/a/Aluren.java +++ b/Mage.Sets/src/mage/cards/a/Aluren.java @@ -70,7 +70,7 @@ class AlurenRuleEffect extends ContinuousEffectImpl { filter.add(new ConvertedManaCostPredicate(ComparisonType.FEWER_THAN, 4)); } - private static AlternativeCostSourceAbility alternativeCastingCostAbility = new AlternativeCostSourceAbility(null, SourceIsSpellCondition.instance, null, filter, true); + private final AlternativeCostSourceAbility alternativeCastingCostAbility = new AlternativeCostSourceAbility(null, SourceIsSpellCondition.instance, null, filter, true); public AlurenRuleEffect() { super(Duration.WhileOnBattlefield, Outcome.Detriment); @@ -86,6 +86,12 @@ class AlurenRuleEffect extends ContinuousEffectImpl { return new AlurenRuleEffect(this); } + @Override + public void init(Ability source, Game game, UUID activePlayerId) { + super.init(source, game, activePlayerId); + alternativeCastingCostAbility.setSourceId(source.getSourceId()); + } + @Override public boolean apply(Layer layer, SubLayer sublayer, Ability source, Game game) { Player controller = game.getPlayer(source.getControllerId()); diff --git a/Mage.Sets/src/mage/cards/a/AsForetold.java b/Mage.Sets/src/mage/cards/a/AsForetold.java index ddbf71d3105..5dda9d51dd5 100644 --- a/Mage.Sets/src/mage/cards/a/AsForetold.java +++ b/Mage.Sets/src/mage/cards/a/AsForetold.java @@ -83,17 +83,14 @@ class SpellWithManaCostLessThanOrEqualToCondition implements Condition { */ class AsForetoldAlternativeCost extends AlternativeCostSourceAbility { - private UUID sourceAsForetold; private boolean wasActivated; - AsForetoldAlternativeCost(UUID sourceAsForetold, int timeCounters) { + AsForetoldAlternativeCost(int timeCounters) { super(new ManaCostsImpl("{0}"), new SpellWithManaCostLessThanOrEqualToCondition(timeCounters)); - this.sourceAsForetold = sourceAsForetold; } private AsForetoldAlternativeCost(final AsForetoldAlternativeCost ability) { super(ability); - this.sourceAsForetold = ability.sourceAsForetold; this.wasActivated = ability.wasActivated; } @@ -105,7 +102,7 @@ class AsForetoldAlternativeCost extends AlternativeCostSourceAbility { @Override public boolean askToActivateAlternativeCosts(Ability ability, Game game) { Player controller = game.getPlayer(ability.getControllerId()); - Permanent asForetold = game.getPermanent(sourceAsForetold); + Permanent asForetold = game.getPermanent(getSourceId()); if (controller != null && asForetold != null) { if (controller.chooseUse(Outcome.Neutral, "Do you wish to use " @@ -156,8 +153,9 @@ class AsForetoldAddAltCostEffect extends ContinuousEffectImpl { // If we haven't used it yet this turn, give the option of using the zero alternative cost if (wasItUsed == null) { int timeCounters = sourcePermanent.getCounters(game).getCount("time"); - controller.getAlternativeSourceCosts().add( - new AsForetoldAlternativeCost(sourcePermanent.getId(), timeCounters)); + AsForetoldAlternativeCost alternateCostAbility = new AsForetoldAlternativeCost(timeCounters); + alternateCostAbility.setSourceId(source.getSourceId()); + controller.getAlternativeSourceCosts().add(alternateCostAbility); } // Return true even if we didn't add the alt cost. We still applied the effect return true; diff --git a/Mage.Sets/src/mage/cards/d/DreamHalls.java b/Mage.Sets/src/mage/cards/d/DreamHalls.java index 38514b979de..52557183873 100644 --- a/Mage.Sets/src/mage/cards/d/DreamHalls.java +++ b/Mage.Sets/src/mage/cards/d/DreamHalls.java @@ -49,7 +49,7 @@ class DreamHallsEffect extends ContinuousEffectImpl { filter.add(new SharesColorWithSourcePredicate()); } - static AlternativeCostSourceAbility alternativeCastingCostAbility = new AlternativeCostSourceAbility(new DiscardCardCost(filter), SourceIsSpellCondition.instance); + private final AlternativeCostSourceAbility alternativeCastingCostAbility = new AlternativeCostSourceAbility(new DiscardCardCost(filter), SourceIsSpellCondition.instance); public DreamHallsEffect() { super(Duration.WhileOnBattlefield, Outcome.Detriment); @@ -64,7 +64,13 @@ class DreamHallsEffect extends ContinuousEffectImpl { public DreamHallsEffect copy() { return new DreamHallsEffect(this); } - + + @Override + public void init(Ability source, Game game, UUID activePlayerId) { + super.init(source, game, activePlayerId); + alternativeCastingCostAbility.setSourceId(source.getSourceId()); + } + @Override public boolean apply(Layer layer, SubLayer sublayer, Ability source, Game game) { Player controller = game.getPlayer(source.getControllerId()); diff --git a/Mage.Sets/src/mage/cards/k/KentaroTheSmilingCat.java b/Mage.Sets/src/mage/cards/k/KentaroTheSmilingCat.java index fe292620f6f..0850535a9ce 100644 --- a/Mage.Sets/src/mage/cards/k/KentaroTheSmilingCat.java +++ b/Mage.Sets/src/mage/cards/k/KentaroTheSmilingCat.java @@ -60,7 +60,7 @@ class KentaroTheSmilingCatCastingEffect extends ContinuousEffectImpl { filterSamurai.add(SubType.SAMURAI.getPredicate()); } - static final AlternativeCostSourceAbility alternativeCastingCostAbility = new AlternativeCostSourceAbility( + private final AlternativeCostSourceAbility alternativeCastingCostAbility = new AlternativeCostSourceAbility( SourceIsSpellCondition.instance, null, filterSamurai, true, new ColorlessConvertedManaCost()); public KentaroTheSmilingCatCastingEffect() { @@ -77,6 +77,12 @@ class KentaroTheSmilingCatCastingEffect extends ContinuousEffectImpl { return new KentaroTheSmilingCatCastingEffect(this); } + @Override + public void init(Ability source, Game game, UUID activePlayerId) { + super.init(source, game, activePlayerId); + alternativeCastingCostAbility.setSourceId(source.getSourceId()); + } + @Override public boolean apply(Layer layer, SubLayer sublayer, Ability source, Game game) { Player controller = game.getPlayer(source.getControllerId()); diff --git a/Mage.Sets/src/mage/cards/r/RooftopStorm.java b/Mage.Sets/src/mage/cards/r/RooftopStorm.java index 0b82329db9e..6083db62303 100644 --- a/Mage.Sets/src/mage/cards/r/RooftopStorm.java +++ b/Mage.Sets/src/mage/cards/r/RooftopStorm.java @@ -48,7 +48,7 @@ class RooftopStormRuleEffect extends ContinuousEffectImpl { filter.add(CardType.CREATURE.getPredicate()); } - static AlternativeCostSourceAbility alternativeCastingCostAbility + private final AlternativeCostSourceAbility alternativeCastingCostAbility = new AlternativeCostSourceAbility(new ManaCostsImpl("{0}"), SourceIsSpellCondition.instance, null, filter, true); public RooftopStormRuleEffect() { @@ -65,6 +65,12 @@ class RooftopStormRuleEffect extends ContinuousEffectImpl { return new RooftopStormRuleEffect(this); } + @Override + public void init(Ability source, Game game, UUID activePlayerId) { + super.init(source, game, activePlayerId); + alternativeCastingCostAbility.setSourceId(source.getSourceId()); + } + @Override public boolean apply(Layer layer, SubLayer sublayer, Ability source, Game game) { Player controller = game.getPlayer(source.getControllerId()); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/cost/alternate/CastFromHandWithoutPayingManaCostTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/cost/alternate/CastFromHandWithoutPayingManaCostTest.java index a9ddf58c339..34acb039a4e 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/cost/alternate/CastFromHandWithoutPayingManaCostTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/cost/alternate/CastFromHandWithoutPayingManaCostTest.java @@ -192,16 +192,15 @@ public class CastFromHandWithoutPayingManaCostTest extends CardTestPlayerBase { /** * Omniscience is not allowing me to cast spells for free. I'm playing a * Commander game against the Computer, if that helps. - *

+ * * Edit: It's not letting me cast fused spells for free. Others seems to be * working. */ @Test - @Ignore // targeting of fused/split spells not supported by testplayer public void testCastingFusedSpell() { + setStrictChooseMode(true); + addCard(Zone.BATTLEFIELD, playerA, "Omniscience"); - addCard(Zone.BATTLEFIELD, playerA, "Island", 2); - addCard(Zone.BATTLEFIELD, playerA, "Swamp", 3); addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion"); addCard(Zone.BATTLEFIELD, playerB, "Pillarfield Ox"); @@ -214,13 +213,18 @@ public class CastFromHandWithoutPayingManaCostTest extends CardTestPlayerBase { */ addCard(Zone.HAND, playerA, "Far // Away"); - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "fused Far // Away", "Silvercoat Lion^targetPlayer=PlayerB"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "fused Far // Away"); + setChoice(playerA, "Yes"); // Cast without paying its mana cost? + addTarget(playerA, "Silvercoat Lion"); + addTarget(playerA, playerB); playerB.addTarget("Pillarfield Ox"); setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); - assertHandCount(playerA, 1); + assertAllCommandsUsed(); + + assertHandCount(playerA, "Silvercoat Lion", 1); assertHandCount(playerB, 0); assertGraveyardCount(playerA, "Far // Away", 1); @@ -228,6 +232,7 @@ public class CastFromHandWithoutPayingManaCostTest extends CardTestPlayerBase { assertPermanentCount(playerB, "Pillarfield Ox", 0); assertGraveyardCount(playerB, "Pillarfield Ox", 1); } + /** * If another effect (e.g. Future Sight) allows you to cast nonland cards @@ -335,6 +340,47 @@ public class CastFromHandWithoutPayingManaCostTest extends CardTestPlayerBase { assertLife(playerB, 20); } + // Not sure what the exact interaction is, but when Omniscience is on the field with Jodah, + // if you say "no" to the Jodah cast option to get to the Omniscience option, then the game will initiate a rollback. + + @Test + public void test_OmniscienceAndJodah() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1); + addCard(Zone.BATTLEFIELD, playerA, "Island", 1); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 1); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 1); + + // Flying + // You may pay WUBRG rather than pay the mana cost for spells that you cast. + addCard(Zone.BATTLEFIELD, playerA, "Jodah, Archmage Eternal"); // Creature {1}{U}{R}{W} (4/3) + + // You may cast nonland cards from your hand without paying their mana costs. + addCard(Zone.HAND, playerA, "Omniscience"); // Enchantment {7}{U}{U}{U} + + // Creature - 3/3 Swampwalk + addCard(Zone.HAND, playerA, "Bog Wraith", 1); // Creature {3}{B} (3/3) + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Omniscience"); + setChoice(playerA, "Yes"); // Pay alternative costs? ({W}{U}{B}{R}{G}) + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Bog Wraith"); + // The order of the two alternate casting abilities is not fixed, so it's not clear which ability is asked for first + setChoice(playerA, "No"); // Pay alternative costs? ({W}{U}{B}{R}{G}) + setChoice(playerA, "Yes"); // Cast without paying its mana cost? + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertAllCommandsUsed(); + + assertPermanentCount(playerA, "Omniscience", 1); + assertPermanentCount(playerA, "Bog Wraith", 1); + + } + @Test public void testJelevaCastingSavageBeatingFromExile() { diff --git a/Mage/src/main/java/mage/abilities/costs/AlternativeCostSourceAbility.java b/Mage/src/main/java/mage/abilities/costs/AlternativeCostSourceAbility.java index 7fc082a4230..b59a93bb259 100644 --- a/Mage/src/main/java/mage/abilities/costs/AlternativeCostSourceAbility.java +++ b/Mage/src/main/java/mage/abilities/costs/AlternativeCostSourceAbility.java @@ -16,6 +16,7 @@ import mage.players.Player; import mage.util.CardUtil; import java.util.Iterator; +import mage.MageObject; /** * @author LevelX2 @@ -135,7 +136,6 @@ public class AlternativeCostSourceAbility extends StaticAbility implements Alter } else { costChoiceText = alternativeCostsToCheck.isEmpty() ? "Cast without paying its mana cost?" : "Pay alternative costs? (" + alternativeCostsToCheck.getText() + ')'; } - if (alternativeCostsToCheck.canPay(ability, ability.getSourceId(), ability.getControllerId(), game) && player.chooseUse(Outcome.Benefit, costChoiceText, this, game)) { if (ability instanceof SpellAbility) { diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/CastFromHandWithoutPayingManaCostEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/CastFromHandWithoutPayingManaCostEffect.java index c72a601922f..a220a26a761 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/CastFromHandWithoutPayingManaCostEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/CastFromHandWithoutPayingManaCostEffect.java @@ -20,9 +20,8 @@ import java.util.UUID; public class CastFromHandWithoutPayingManaCostEffect extends ContinuousEffectImpl { - private final FilterCard filter; - private final boolean fromHand; - + private final AlternativeCostSourceAbility alternativeCastingCostAbility; + public CastFromHandWithoutPayingManaCostEffect() { this(StaticFilters.FILTER_CARDS_NON_LAND, true); } @@ -33,8 +32,13 @@ public class CastFromHandWithoutPayingManaCostEffect extends ContinuousEffectImp public CastFromHandWithoutPayingManaCostEffect(FilterCard filter, boolean fromHand, Duration duration) { super(duration, Outcome.Detriment); - this.filter = filter; - this.fromHand = fromHand; + Condition condition; + if (fromHand) { + condition = new CompoundCondition(SourceIsSpellCondition.instance, IsBeingCastFromHandCondition.instance); + } else { + condition = SourceIsSpellCondition.instance; + } + this.alternativeCastingCostAbility = new AlternativeCostSourceAbility(null, condition, null, filter, true); this.staticText = "You may cast " + filter.getMessage() + (fromHand ? " from your hand" : "") + " without paying their mana costs"; @@ -42,8 +46,7 @@ public class CastFromHandWithoutPayingManaCostEffect extends ContinuousEffectImp private CastFromHandWithoutPayingManaCostEffect(final CastFromHandWithoutPayingManaCostEffect effect) { super(effect); - this.filter = effect.filter; - this.fromHand = effect.fromHand; + this.alternativeCastingCostAbility = effect.alternativeCastingCostAbility; } @Override @@ -51,21 +54,19 @@ public class CastFromHandWithoutPayingManaCostEffect extends ContinuousEffectImp return new CastFromHandWithoutPayingManaCostEffect(this); } + @Override + public void init(Ability source, Game game, UUID activePlayerId) { + super.init(source, game, activePlayerId); + alternativeCastingCostAbility.setSourceId(source.getSourceId()); + } + @Override public boolean apply(Layer layer, SubLayer sublayer, Ability source, Game game) { Player controller = game.getPlayer(source.getControllerId()); if (controller == null) { return false; } - Condition condition; - if (fromHand) { - condition = new CompoundCondition(SourceIsSpellCondition.instance, IsBeingCastFromHandCondition.instance); - } else { - condition = SourceIsSpellCondition.instance; - } - controller.getAlternativeSourceCosts().add(new AlternativeCostSourceAbility( - null, condition, null, filter, true - )); + controller.getAlternativeSourceCosts().add(alternativeCastingCostAbility); return true; } @@ -92,7 +93,7 @@ enum IsBeingCastFromHandCondition implements Condition { } if (object instanceof Spell) { // needed to check if it can be cast by alternate cost Spell spell = (Spell) object; - return spell.getFromZone() == Zone.HAND; + return Zone.HAND.equals(spell.getFromZone()); } if (object instanceof Card) { // needed for the check what's playable Card card = (Card) object; diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/WUBRGInsteadEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/WUBRGInsteadEffect.java index 8a6c39257b3..4aae2646504 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/WUBRGInsteadEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/WUBRGInsteadEffect.java @@ -1,6 +1,7 @@ package mage.abilities.effects.common.continuous; +import java.util.UUID; import mage.abilities.Ability; import mage.abilities.condition.common.SourceIsSpellCondition; import mage.abilities.costs.AlternativeCostSourceAbility; @@ -19,7 +20,7 @@ import mage.players.Player; */ public class WUBRGInsteadEffect extends ContinuousEffectImpl { - static AlternativeCostSourceAbility alternativeCastingCostAbility = new AlternativeCostSourceAbility(new ManaCostsImpl("{W}{U}{B}{R}{G}"), SourceIsSpellCondition.instance); + private final AlternativeCostSourceAbility alternativeCastingCostAbility = new AlternativeCostSourceAbility(new ManaCostsImpl("{W}{U}{B}{R}{G}"), SourceIsSpellCondition.instance); public WUBRGInsteadEffect() { super(Duration.WhileOnBattlefield, Outcome.Detriment); @@ -35,6 +36,12 @@ public class WUBRGInsteadEffect extends ContinuousEffectImpl { return new WUBRGInsteadEffect(this); } + @Override + public void init(Ability source, Game game, UUID activePlayerId) { + super.init(source, game, activePlayerId); + alternativeCastingCostAbility.setSourceId(source.getSourceId()); + } + @Override public boolean apply(Layer layer, SubLayer sublayer, Ability source, Game game) { Player controller = game.getPlayer(source.getControllerId());