From 606bf4d6e0b296d21cd5ecd6bd5de828706fdc17 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Tue, 20 Oct 2015 00:44:32 +0200 Subject: [PATCH] * Fixed that it was not possible to play spells without costs with alternative costs (e.g. Ancestral Visions with Omniscience). Fixed that playing spells with alternate costs did also remove additional costs (e.g. card with entwine cast with Omniscience). --- .../src/mage/sets/onslaught/FutureSight.java | 4 +- .../keywords/SpliceOnArcaneTest.java | 2 +- .../test/cards/single/OmniscienceTest.java | 100 ++++++++++++++++++ Mage/src/mage/abilities/AbilityImpl.java | 5 +- Mage/src/mage/abilities/SpellAbility.java | 4 - .../costs/AlternativeCostSourceAbility.java | 39 ++++--- .../abilities/keyword/EntwineAbility.java | 2 +- Mage/src/mage/players/PlayerImpl.java | 26 +++-- 8 files changed, 150 insertions(+), 32 deletions(-) diff --git a/Mage.Sets/src/mage/sets/onslaught/FutureSight.java b/Mage.Sets/src/mage/sets/onslaught/FutureSight.java index 0c8dff1f1c6..929246b111d 100644 --- a/Mage.Sets/src/mage/sets/onslaught/FutureSight.java +++ b/Mage.Sets/src/mage/sets/onslaught/FutureSight.java @@ -35,7 +35,6 @@ import mage.cards.CardImpl; import mage.constants.CardType; import mage.constants.Rarity; import mage.constants.Zone; -import mage.filter.FilterCard; /** * @@ -47,11 +46,10 @@ public class FutureSight extends CardImpl { super(ownerId, 84, "Future Sight", Rarity.RARE, new CardType[]{CardType.ENCHANTMENT}, "{2}{U}{U}{U}"); this.expansionSetCode = "ONS"; - // Play with the top card of your library revealed. this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new PlayWithTheTopCardRevealedEffect())); // You may play the top card of your library. - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new PlayTheTopCardEffect(new FilterCard()))); + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new PlayTheTopCardEffect())); } public FutureSight(final FutureSight card) { diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/SpliceOnArcaneTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/SpliceOnArcaneTest.java index 0cd925879a7..f67350c1d85 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/SpliceOnArcaneTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/SpliceOnArcaneTest.java @@ -115,7 +115,7 @@ public class SpliceOnArcaneTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerA, "Mountain", 4); // You may exile a green card with converted mana cost X from your hand rather than pay Nourishing Shoal's mana cost. // You gain X life. - addCard(Zone.HAND, playerA, "Nourishing Shoal", 1); + addCard(Zone.HAND, playerA, "Nourishing Shoal", 1); // {X}{G}{G} addCard(Zone.HAND, playerA, "Giant Growth", 1); // You may put a creature card from your hand onto the battlefield. That creature gains haste. Sacrifice that creature at the beginning of the next end step. // Splice onto Arcane {2}{R}{R} (As you cast an Arcane spell, you may reveal this card from your hand and pay its splice cost. If you do, add this card's effects to that spell.) diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/OmniscienceTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/OmniscienceTest.java index ad4f7febe71..fe2d8ea9fb3 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/OmniscienceTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/OmniscienceTest.java @@ -192,4 +192,104 @@ public class OmniscienceTest extends CardTestPlayerBase { assertGraveyardCount(playerB, "Pillarfield Ox", 1); } + /** + * If another effect (e.g. Future Sight) allows you to cast nonland cards + * from zones other than your hand, Xmage incorrectly lets you cast those + * cards without paying their mana costs. Omniscience only lets you cast + * spells from your hand without paying their mana costs. + */ + @Test + public void testCastingWithFutureSight() { + // You may cast nonland cards from your hand without paying their mana costs. + addCard(Zone.BATTLEFIELD, playerA, "Omniscience"); + // Play with the top card of your library revealed. + // You may play the top card of your library. + addCard(Zone.BATTLEFIELD, playerA, "Future Sight", 1); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); + + addCard(Zone.LIBRARY, playerA, "Silvercoat Lion", 1); + skipInitShuffling(); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Silvercoat Lion"); + setChoice(playerA, "Yes"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertLife(playerA, 20); + assertLife(playerB, 20); + + assertPermanentCount(playerA, "Silvercoat Lion", 1); + assertTapped("Plains", true); // plains have to be tapped because {2} have to be paid + } + + /** + * If a spell has an additional cost (optional or mandatory, e.g. Entwine), + * Omniscience incorrectly allows you cast the spell as if that cost had + * been paid without paying that spell's mana cost. 117.9d If an alternative + * cost is being paid to cast a spell, any additional costs, cost increases, + * and cost reductions that affect that spell are applied to that + * alternative cost. (See rule 601.2f.) + */ + @Test + public void testCastingWithCyclonicRiftWithOverload() { + // You may cast nonland cards from your hand without paying their mana costs. + addCard(Zone.BATTLEFIELD, playerA, "Omniscience"); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); + + // Choose one - Barbed Lightning deals 3 damage to target creature; or Barbed Lightning deals 3 damage to target player. + // Entwine {2} (Choose both if you pay the entwine cost.) + addCard(Zone.HAND, playerA, "Barbed Lightning", 1); + + // Creature - 3/3 Swampwalk + addCard(Zone.BATTLEFIELD, playerB, "Bog Wraith", 1); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Barbed Lightning", "Bog Wraith"); + addTarget(playerA, playerB); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertGraveyardCount(playerA, "Barbed Lightning", 1); + assertGraveyardCount(playerB, "Bog Wraith", 1); + + assertLife(playerA, 20); + assertLife(playerB, 17); + + assertTapped("Plains", true); // plains have to be tapped because {2} from Entwine have to be paid + } + + /** + * If a spell has an unpayable cost (e.g. Ancestral Vision, which has no + * mana cost), Omniscience should allow you to cast that spell without + * paying its mana cost. In the case of Ancestral Vision, for example, Xmage + * only gives you the option to suspend Ancestral Vision. 117.6a If an + * unpayable cost is increased by an effect or an additional cost is + * imposed, the cost is still unpayable. If an alternative cost is applied + * to an unpayable cost, including an effect that allows a player to cast a + * spell without paying its mana cost, the alternative cost may be paid. + */ + @Test + public void testCastingUnpayableCost() { + // You may cast nonland cards from your hand without paying their mana costs. + addCard(Zone.BATTLEFIELD, playerA, "Omniscience"); + + // Suspend 4-{U} + // Target player draws three cards. + addCard(Zone.HAND, playerA, "Ancestral Vision", 1); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Ancestral Vision", playerA); + addTarget(playerA, playerB); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertGraveyardCount(playerA, "Ancestral Vision", 1); + + assertHandCount(playerA, 3); + assertLife(playerA, 20); + assertLife(playerB, 20); + + } + } diff --git a/Mage/src/mage/abilities/AbilityImpl.java b/Mage/src/mage/abilities/AbilityImpl.java index d7b581515ba..659d4987fb1 100644 --- a/Mage/src/mage/abilities/AbilityImpl.java +++ b/Mage/src/mage/abilities/AbilityImpl.java @@ -471,7 +471,10 @@ public abstract class AbilityImpl implements Ability { } // controller specific alternate spell costs if (!noMana && !alternativeCostisUsed) { - if (this.getAbilityType().equals(AbilityType.SPELL)) { + if (this.getAbilityType().equals(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 + && !((SpellAbility) this).getSpellAbilityType().equals(SpellAbilityType.BASE_ALTERNATE)) { for (AlternativeSourceCosts alternativeSourceCosts : controller.getAlternativeSourceCosts()) { if (alternativeSourceCosts.isAvailable(this, game)) { if (alternativeSourceCosts.askToActivateAlternativeCosts(this, game)) { diff --git a/Mage/src/mage/abilities/SpellAbility.java b/Mage/src/mage/abilities/SpellAbility.java index 1dedf4b81e7..5106b8dc61c 100644 --- a/Mage/src/mage/abilities/SpellAbility.java +++ b/Mage/src/mage/abilities/SpellAbility.java @@ -99,10 +99,6 @@ public class SpellAbility extends ActivatedAbilityImpl { && !controllerId.equals(playerId)) { return false; } - // Check if spell has no costs (not {0} mana costs), than it's not castable. E.g. for spells like Living End, that only can be cast by Suspend Ability. - if (this.getManaCosts().isEmpty() && this.getCosts().isEmpty()) { - return false; - } // Check if rule modifying events prevent to cast the spell in check playable mode if (this.isCheckPlayableMode()) { if (game.getContinuousEffects().preventedByRuleModification( diff --git a/Mage/src/mage/abilities/costs/AlternativeCostSourceAbility.java b/Mage/src/mage/abilities/costs/AlternativeCostSourceAbility.java index 5fb7fd20f90..be4893b1b45 100644 --- a/Mage/src/mage/abilities/costs/AlternativeCostSourceAbility.java +++ b/Mage/src/mage/abilities/costs/AlternativeCostSourceAbility.java @@ -29,6 +29,7 @@ package mage.abilities.costs; import java.util.Iterator; import mage.abilities.Ability; +import mage.abilities.SpellAbility; import mage.abilities.StaticAbility; import mage.abilities.condition.Condition; import mage.abilities.costs.mana.ManaCost; @@ -39,6 +40,7 @@ import mage.constants.Zone; import mage.filter.FilterCard; import mage.game.Game; import mage.players.Player; +import mage.util.CardUtil; /** * @@ -145,28 +147,39 @@ public class AlternativeCostSourceAbility extends StaticAbility implements Alter } Player player = game.getPlayer(ability.getControllerId()); if (player != null) { - Costs alternativeCosts; + Costs alternativeCostsToCheck; if (dynamicCost != null) { - alternativeCosts = new CostsImpl<>(); - alternativeCosts.add(convertToAlternativeCost(dynamicCost.getCost(ability, game))); + alternativeCostsToCheck = new CostsImpl<>(); + alternativeCostsToCheck.add(convertToAlternativeCost(dynamicCost.getCost(ability, game))); } else { - alternativeCosts = this.alternateCosts; + alternativeCostsToCheck = this.alternateCosts; } String costChoiceText; if (dynamicCost != null) { costChoiceText = dynamicCost.getText(ability, game); } else { - costChoiceText = alternativeCosts.isEmpty() ? "Cast without paying its mana cost?" : "Pay alternative costs? (" + alternativeCosts.getText() + ")"; + costChoiceText = alternativeCostsToCheck.isEmpty() ? "Cast without paying its mana cost?" : "Pay alternative costs? (" + alternativeCostsToCheck.getText() + ")"; } - if (alternativeCosts.canPay(ability, ability.getSourceId(), ability.getControllerId(), game) + if (alternativeCostsToCheck.canPay(ability, ability.getSourceId(), ability.getControllerId(), game) && player.chooseUse(Outcome.Benefit, costChoiceText, this, game)) { - ability.getManaCostsToPay().clear(); + if (ability instanceof SpellAbility) { + for (Iterator iterator = ability.getManaCostsToPay().iterator(); iterator.hasNext();) { + ManaCost manaCost = iterator.next(); + if (manaCost instanceof VariableCost) { + iterator.remove(); + } + } + CardUtil.reduceCost((SpellAbility) ability, ability.getManaCosts()); + + } else { + ability.getManaCostsToPay().clear(); + } if (!onlyMana) { ability.getCosts().clear(); } - for (Cost cost : alternativeCosts) { + for (Cost cost : alternativeCostsToCheck) { AlternativeCost2 alternateCost = (AlternativeCost2) cost; alternateCost.activate(); for (Iterator it = ((Costs) alternateCost).iterator(); it.hasNext();) { @@ -190,14 +203,14 @@ public class AlternativeCostSourceAbility extends StaticAbility implements Alter @Override public boolean isActivated(Ability source, Game game) { - Costs alternativeCosts; + Costs alternativeCostsToCheck; if (dynamicCost != null) { - alternativeCosts = new CostsImpl<>(); - alternativeCosts.add(convertToAlternativeCost(dynamicCost.getCost(source, game))); + alternativeCostsToCheck = new CostsImpl<>(); + alternativeCostsToCheck.add(convertToAlternativeCost(dynamicCost.getCost(source, game))); } else { - alternativeCosts = this.alternateCosts; + alternativeCostsToCheck = this.alternateCosts; } - for (AlternativeCost2 cost : alternativeCosts) { + for (AlternativeCost2 cost : alternativeCostsToCheck) { if (cost.isActivated(game)) { return true; } diff --git a/Mage/src/mage/abilities/keyword/EntwineAbility.java b/Mage/src/mage/abilities/keyword/EntwineAbility.java index c81d1cbd955..0604445c2a4 100644 --- a/Mage/src/mage/abilities/keyword/EntwineAbility.java +++ b/Mage/src/mage/abilities/keyword/EntwineAbility.java @@ -111,7 +111,7 @@ public class EntwineAbility extends StaticAbility implements OptionalAdditionalM if (player != null) { this.resetCosts(); if (additionalCost != null) { - if (player.chooseUse(Outcome.Benefit, new StringBuilder("Pay ").append(additionalCost.getText(false)).append(" ?").toString(), ability, game)) { + if (player.chooseUse(Outcome.Benefit, "Pay " + additionalCost.getText(false) + " ?", ability, game)) { additionalCost.activate(); for (Iterator it = ((Costs) additionalCost).iterator(); it.hasNext();) { Cost cost = (Cost) it.next(); diff --git a/Mage/src/mage/players/PlayerImpl.java b/Mage/src/mage/players/PlayerImpl.java index 8949f270970..1719603aa72 100644 --- a/Mage/src/mage/players/PlayerImpl.java +++ b/Mage/src/mage/players/PlayerImpl.java @@ -2317,15 +2317,23 @@ public abstract class PlayerImpl implements Player, Serializable { } } } - - ManaOptions abilityOptions = copy.getManaCostsToPay().getOptions(); - if (abilityOptions.size() == 0) { - return true; - } else { - for (Mana mana : abilityOptions) { - for (Mana avail : available) { - if (mana.enough(avail)) { - return true; + boolean canBeCastRegularly = true; + if (copy instanceof SpellAbility && copy.getManaCosts().isEmpty() && copy.getCosts().isEmpty()) { + // 117.6. Some mana costs contain no mana symbols. This represents an unpayable cost... + // 117.6a (...) If an alternative cost is applied to an unpayable cost, + // including an effect that allows a player to cast a spell without paying its mana cost, the alternative cost may be paid. + canBeCastRegularly = false; + } + if (canBeCastRegularly) { + ManaOptions abilityOptions = copy.getManaCostsToPay().getOptions(); + if (abilityOptions.size() == 0) { + return true; + } else { + for (Mana mana : abilityOptions) { + for (Mana avail : available) { + if (mana.enough(avail)) { + return true; + } } } }