From 2de7c136eabd84428771f95786d4a3ae1b3bf60b Mon Sep 17 00:00:00 2001 From: jeffwadsworth Date: Sun, 1 Dec 2019 21:53:01 -0600 Subject: [PATCH] - Fixed #6056. Please test when you can. Now you will see other abilities/spellAbilities from cards presented during the cast from exile. Overload, Emerge, Surge, etc. --- .../src/mage/player/human/HumanPlayer.java | 59 +++++++++++---- .../mage/cards/c/ChandraTorchOfDefiance.java | 13 +++- .../java/org/mage/test/player/TestPlayer.java | 5 ++ .../java/org/mage/test/stub/PlayerStub.java | 5 ++ .../java/mage/abilities/SpellAbility.java | 9 ++- .../mage/abilities/keyword/EmergeAbility.java | 3 +- .../abilities/keyword/FlashbackAbility.java | 9 ++- .../abilities/keyword/SpectacleAbility.java | 5 +- .../mage/abilities/keyword/SurgeAbility.java | 5 +- Mage/src/main/java/mage/players/Player.java | 75 ++++++++++--------- .../main/java/mage/players/PlayerImpl.java | 13 +++- 11 files changed, 135 insertions(+), 66 deletions(-) diff --git a/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java b/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java index d2c615b80b9..25e018a02ea 100644 --- a/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java +++ b/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java @@ -710,9 +710,9 @@ public class HumanPlayer extends PlayerImpl { if (!isExecutingMacro()) { String selectedNames = target.getTargetedName(game); game.fireSelectTargetEvent(playerId, new MessageToClient(target.getMessage() - + "
Amount remaining: " + target.getAmountRemaining() - + (selectedNames.isEmpty() ? "" : ", selected: " + selectedNames), - getRelatedObjectName(source, game)), + + "
Amount remaining: " + target.getAmountRemaining() + + (selectedNames.isEmpty() ? "" : ", selected: " + selectedNames), + getRelatedObjectName(source, game)), target.possibleTargets(source == null ? null : source.getSourceId(), playerId, game), target.isRequired(source), getOptions(target, null)); @@ -725,7 +725,7 @@ public class HumanPlayer extends PlayerImpl { boolean removeMode = target.getTargets().contains(targetId) && chooseUse(outcome, "What do you want to do with " + (targetObject != null ? targetObject.getLogName() : "target") + "?", "", - "Remove from selected", "Add extra amount", source, game); + "Remove from selected", "Add extra amount", source, game); if (removeMode) { target.remove(targetId); @@ -862,9 +862,9 @@ public class HumanPlayer extends PlayerImpl { if (!skippedAtLeastOnce || (playerId.equals(game.getActivePlayerId()) && !controllingPlayer - .getUserData() - .getUserSkipPrioritySteps() - .isStopOnAllEndPhases())) { + .getUserData() + .getUserSkipPrioritySteps() + .isStopOnAllEndPhases())) { skippedAtLeastOnce = true; if (passWithManaPoolCheck(game)) { return false; @@ -896,9 +896,9 @@ public class HumanPlayer extends PlayerImpl { if (haveNewObjectsOnStack && (playerId.equals(game.getActivePlayerId()) && controllingPlayer - .getUserData() - .getUserSkipPrioritySteps() - .isStopOnStackNewObjects())) { + .getUserData() + .getUserSkipPrioritySteps() + .isStopOnStackNewObjects())) { // new objects on stack -- disable "pass until stack resolved" passedUntilStackResolved = false; } else { @@ -975,7 +975,9 @@ public class HumanPlayer extends PlayerImpl { } } return result; - } else return response.getManaType() == null; + } else { + return response.getManaType() == null; + } return true; } return false; @@ -1233,8 +1235,8 @@ public class HumanPlayer extends PlayerImpl { if (passedAllTurns || passedUntilEndStepBeforeMyTurn || (!getControllingPlayersUserData(game) - .getUserSkipPrioritySteps() - .isStopOnDeclareAttackers() + .getUserSkipPrioritySteps() + .isStopOnDeclareAttackers() && (passedTurn || passedTurnSkipStack || passedUntilEndOfTurn @@ -1255,8 +1257,7 @@ public class HumanPlayer extends PlayerImpl { return; } } - */ - + */ Map options = new HashMap<>(); options.put(Constants.Option.POSSIBLE_ATTACKERS, (Serializable) possibleAttackers); if (!possibleAttackers.isEmpty()) { @@ -1418,7 +1419,7 @@ public class HumanPlayer extends PlayerImpl { /** * Selects a defender for an attacker and adds the attacker to combat * - * @param defenders - list of possible defender + * @param defenders - list of possible defender * @param attackerId - UUID of attacker * @param game * @return @@ -1782,6 +1783,31 @@ public class HumanPlayer extends PlayerImpl { } } + @Override + public SpellAbility chooseAbilityForCast(Card card, Game game, boolean nonMana) { + MageObject object = game.getObject(card.getId()); + if (object != null) { + LinkedHashMap useableAbilities = getSpellAbilities(object, game.getState().getZone(object.getId()), game); + if (useableAbilities != null + && useableAbilities.size() == 1) { + return (SpellAbility) useableAbilities.values().iterator().next(); + } else if (useableAbilities != null + && !useableAbilities.isEmpty()) { + prepareForResponse(game); + if (!isExecutingMacro()) { + game.fireGetChoiceEvent(playerId, name, object, new ArrayList<>(useableAbilities.values())); + } + waitForResponse(game); + if (response.getUUID() != null) { + if (useableAbilities.containsKey(response.getUUID())) { + return (SpellAbility) useableAbilities.get(response.getUUID()); + } + } + } + } + return card.getSpellAbility(); + } + @Override public Mode chooseMode(Modes modes, Ability source, Game game) { // choose mode to activate @@ -2115,4 +2141,5 @@ public class HumanPlayer extends PlayerImpl { public String getHistory() { return "no available"; } + } diff --git a/Mage.Sets/src/mage/cards/c/ChandraTorchOfDefiance.java b/Mage.Sets/src/mage/cards/c/ChandraTorchOfDefiance.java index 95fd6740e5c..1ecb741f4b3 100644 --- a/Mage.Sets/src/mage/cards/c/ChandraTorchOfDefiance.java +++ b/Mage.Sets/src/mage/cards/c/ChandraTorchOfDefiance.java @@ -21,6 +21,7 @@ import mage.constants.Outcome; import mage.constants.SubType; import mage.constants.SuperType; import mage.constants.TargetController; +import mage.constants.Zone; import mage.game.Game; import mage.game.command.emblems.ChandraTorchOfDefianceEmblem; import mage.players.Library; @@ -91,15 +92,19 @@ class ChandraTorchOfDefianceEffect extends OneShotEffect { if (card != null) { boolean exiledCardWasCast = false; controller.moveCardsToExile(card, source, game, true, source.getSourceId(), sourceObject.getIdName()); - if (!card.getManaCost().isEmpty() && !card.isLand()) { - if (controller.chooseUse(Outcome.Benefit, "Cast " + card.getName() + "? (You still pay the costs)", source, game)) { - exiledCardWasCast = controller.cast(card.getSpellAbility(), game, false, new MageObjectReference(sourceObject, game)); + if (!card.getManaCost().isEmpty() + || !card.isLand()) { + if (controller.chooseUse(Outcome.Benefit, "Cast " + card.getName() + "? (You still pay the costs)", source, game) + && (game.getState().getZone(card.getId()) == Zone.EXILED)) { // card must be in the exile zone + game.getState().setValue("CastFromExileEnabled" + card.getId(), Boolean.TRUE); // enable the card to be cast from the exile zone + exiledCardWasCast = controller.cast(controller.chooseAbilityForCast(card, game, false), + game, false, new MageObjectReference(sourceObject, game)); + game.getState().setValue("CastFromExileEnabled" + card.getId(), Boolean.FALSE); // reset to false } } if (!exiledCardWasCast) { new DamagePlayersEffect(Outcome.Damage, new StaticValue(2), TargetController.OPPONENT).apply(game, source); } - } return true; } diff --git a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java index 5c3bd9efab7..5c0e75f1414 100644 --- a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java +++ b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java @@ -3478,4 +3478,9 @@ public class TestPlayer implements Player { public FilterMana getPhyrexianColors() { return computerPlayer.getPhyrexianColors(); } + + @Override + public SpellAbility chooseAbilityForCast(Card card, Game game, boolean noMana) { + return card.getSpellAbility(); + } } diff --git a/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java b/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java index 34e6f698323..97465f17704 100644 --- a/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java +++ b/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java @@ -1386,5 +1386,10 @@ public class PlayerStub implements Player { public FilterMana getPhyrexianColors() { return (new FilterMana()); } + + @Override + public SpellAbility chooseAbilityForCast(Card card, Game game, boolean noMana) { + return card.getSpellAbility(); + } } diff --git a/Mage/src/main/java/mage/abilities/SpellAbility.java b/Mage/src/main/java/mage/abilities/SpellAbility.java index 2256c0c82f8..0a76898c13f 100644 --- a/Mage/src/main/java/mage/abilities/SpellAbility.java +++ b/Mage/src/main/java/mage/abilities/SpellAbility.java @@ -63,6 +63,9 @@ public class SpellAbility extends ActivatedAbilityImpl { */ public boolean spellCanBeActivatedRegularlyNow(UUID playerId, Game game) { MageObject object = game.getObject(sourceId); + if ((Boolean) game.getState().getValue("CastFromExileEnabled" + object.getId()) != null) { + return (Boolean) game.getState().getValue("CastFromExileEnabled" + object.getId()); // card like Chandra, Torch of Defiance +1 loyal ability) + } return null != game.getContinuousEffects().asThough(sourceId, AsThoughEffectType.CAST_AS_INSTANT, this, playerId, game) // check this first to allow Offering in main phase || timing == TimingRule.INSTANT || object.hasAbility(FlashAbility.getInstance().getId(), game) @@ -72,7 +75,8 @@ public class SpellAbility extends ActivatedAbilityImpl { @Override public ActivationStatus canActivate(UUID playerId, Game game) { if (this.spellCanBeActivatedRegularlyNow(playerId, game)) { - if (spellAbilityType == SpellAbilityType.SPLIT || spellAbilityType == SpellAbilityType.SPLIT_AFTERMATH) { + if (spellAbilityType == SpellAbilityType.SPLIT + || spellAbilityType == SpellAbilityType.SPLIT_AFTERMATH) { return ActivationStatus.getFalse(); } // fix for Gitaxian Probe and casting opponent's spells @@ -93,7 +97,8 @@ public class SpellAbility extends ActivatedAbilityImpl { // Alternate spell abilities (Flashback, Overload) can't be cast with no mana to pay option if (getSpellAbilityType() == SpellAbilityType.BASE_ALTERNATE) { Player player = game.getPlayer(playerId); - if (player != null && getSourceId().equals(player.getCastSourceIdWithAlternateMana())) { + if (player != null + && getSourceId().equals(player.getCastSourceIdWithAlternateMana())) { return ActivationStatus.getFalse(); } } diff --git a/Mage/src/main/java/mage/abilities/keyword/EmergeAbility.java b/Mage/src/main/java/mage/abilities/keyword/EmergeAbility.java index 24ade344220..c11ec9da556 100644 --- a/Mage/src/main/java/mage/abilities/keyword/EmergeAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/EmergeAbility.java @@ -53,7 +53,8 @@ public class EmergeAbility extends SpellAbility { if (super.canActivate(playerId, game).canActivate()) { Player controller = game.getPlayer(this.getControllerId()); if (controller != null) { - for (Permanent creature : game.getBattlefield().getActivePermanents(new FilterControlledCreaturePermanent(), this.getControllerId(), this.getSourceId(), game)) { + for (Permanent creature : game.getBattlefield().getActivePermanents( + new FilterControlledCreaturePermanent(), this.getControllerId(), this.getSourceId(), game)) { ManaCost costToPay = CardUtil.reduceCost(emergeCost.copy(), creature.getConvertedManaCost()); if (costToPay.canPay(this, this.getSourceId(), this.getControllerId(), game)) { return ActivationStatus.getTrue(); diff --git a/Mage/src/main/java/mage/abilities/keyword/FlashbackAbility.java b/Mage/src/main/java/mage/abilities/keyword/FlashbackAbility.java index ba192daadf0..145257ae4b1 100644 --- a/Mage/src/main/java/mage/abilities/keyword/FlashbackAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/FlashbackAbility.java @@ -57,10 +57,13 @@ public class FlashbackAbility extends SpellAbility { @Override public ActivationStatus canActivate(UUID playerId, Game game) { - ActivationStatus activationStatus = super.canActivate(playerId, game); - if (activationStatus.canActivate()) { + if (super.canActivate(playerId, game).canActivate()) { Card card = game.getCard(getSourceId()); if (card != null) { + // Card must be in the graveyard zone + if (game.getState().getZone(card.getId()) != Zone.GRAVEYARD) { + return ActivationStatus.getFalse(); + } // Cards with no Mana Costs cant't be flashbacked (e.g. Ancestral Vision) if (card.getManaCost().isEmpty()) { return ActivationStatus.getFalse(); @@ -76,7 +79,7 @@ public class FlashbackAbility extends SpellAbility { return card.getSpellAbility().canActivate(playerId, game); } } - return activationStatus; + return ActivationStatus.getFalse(); } @Override diff --git a/Mage/src/main/java/mage/abilities/keyword/SpectacleAbility.java b/Mage/src/main/java/mage/abilities/keyword/SpectacleAbility.java index 393da2251ee..531f3b6304a 100644 --- a/Mage/src/main/java/mage/abilities/keyword/SpectacleAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/SpectacleAbility.java @@ -45,8 +45,9 @@ public class SpectacleAbility extends SpellAbility { @Override public ActivationStatus canActivate(UUID playerId, Game game) { - if (OpponentsLostLifeCount.instance.calculate(game, playerId) > 0) { - return super.canActivate(playerId, game); + if (OpponentsLostLifeCount.instance.calculate(game, playerId) > 0 + && super.canActivate(playerId, game).canActivate()) { + return ActivationStatus.getTrue(); } return ActivationStatus.getFalse(); } diff --git a/Mage/src/main/java/mage/abilities/keyword/SurgeAbility.java b/Mage/src/main/java/mage/abilities/keyword/SurgeAbility.java index f1a335bf8ec..4d3a17be182 100644 --- a/Mage/src/main/java/mage/abilities/keyword/SurgeAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/SurgeAbility.java @@ -51,8 +51,9 @@ public class SurgeAbility extends SpellAbility { if (player != null) { for (UUID playerToCheckId : game.getState().getPlayersInRange(playerId, game)) { if (!player.hasOpponent(playerToCheckId, game)) { - if (watcher.getAmountOfSpellsPlayerCastOnCurrentTurn(playerToCheckId) > 0) { - return super.canActivate(playerId, game); + if (watcher.getAmountOfSpellsPlayerCastOnCurrentTurn(playerToCheckId) > 0 + && super.canActivate(playerId, game).canActivate()) { + return ActivationStatus.getTrue(); } } } diff --git a/Mage/src/main/java/mage/players/Player.java b/Mage/src/main/java/mage/players/Player.java index 9220f50835b..1e8ddbb897f 100644 --- a/Mage/src/main/java/mage/players/Player.java +++ b/Mage/src/main/java/mage/players/Player.java @@ -75,7 +75,7 @@ public interface Player extends MageItem, Copyable { void setLife(int life, Game game, UUID sourceId); /** - * @param amount amount of life loss + * @param amount amount of life loss * @param game * @param atCombat was the source combat damage * @return @@ -311,7 +311,7 @@ public interface Player extends MageItem, Copyable { void useDeck(Deck deck, Game game); /** - * Called before each applyEffects, to rest all what can be applyed by + * Called before each applyEffects, to rest all what can be applied by * continuous effects */ void reset(); @@ -326,6 +326,8 @@ public interface Player extends MageItem, Copyable { SpellAbility chooseSpellAbilityForCast(SpellAbility ability, Game game, boolean noMana); + SpellAbility chooseAbilityForCast(Card card, Game game, boolean noMana); + boolean putInHand(Card card, Game game); boolean removeFromHand(Card card, Game game); @@ -349,14 +351,15 @@ public interface Player extends MageItem, Copyable { * @param source * @param game * @param targetPlayerId player whose library will be searched - * @param triggerEvents whether searching will trigger any game events + * @param triggerEvents whether searching will trigger any game events * @return true if search was successful */ boolean searchLibrary(TargetCardInLibrary target, Ability source, Game game, UUID targetPlayerId, boolean triggerEvents); /** - * Reveals all players' libraries. Useful for abilities like Jace, Architect of Thought's -8 - * that have effects that require information from all libraries. + * Reveals all players' libraries. Useful for abilities like Jace, Architect + * of Thought's -8 that have effects that require information from all + * libraries. * * @param source * @param game @@ -369,23 +372,23 @@ public interface Player extends MageItem, Copyable { /** * Plays a card if possible * - * @param card the card that can be cast + * @param card the card that can be cast * @param game - * @param noMana if it's a spell i can be cast without paying mana + * @param noMana if it's a spell i can be cast without paying mana * @param ignoreTiming if it's cast during the resolution of another spell - * no sorcery or play land timing restriction are checked. For a land it has - * to be the turn of the player playing that card. - * @param reference mage object that allows to play the card + * no sorcery or play land timing restriction are checked. For a land it has + * to be the turn of the player playing that card. + * @param reference mage object that allows to play the card * @return */ boolean playCard(Card card, Game game, boolean noMana, boolean ignoreTiming, MageObjectReference reference); /** - * @param card the land card to play + * @param card the land card to play * @param game * @param ignoreTiming false - it won't be checked if the stack is empty and - * you are able to play a Sorcery. It's still checked, if you are able to - * play a land concerning the number of lands you already played. + * you are able to play a Sorcery. It's still checked, if you are able to + * play a land concerning the number of lands you already played. * @return */ boolean playLand(Card card, Game game, boolean ignoreTiming); @@ -531,11 +534,11 @@ public interface Player extends MageItem, Copyable { /** * Moves the cards from cards to the bottom of the players library. * - * @param cards - list of cards that have to be moved - * @param game - game + * @param cards - list of cards that have to be moved + * @param game - game * @param anyOrder - true if player can determine the order of the cards - * else random order - * @param source - source ability + * else random order + * @param source - source ability * @return */ boolean putCardsOnBottomOfLibrary(Cards cards, Game game, Ability source, boolean anyOrder); @@ -556,10 +559,10 @@ public interface Player extends MageItem, Copyable { /** * Moves the cards from cards to the top of players library. * - * @param cards - list of cards that have to be moved - * @param game - game + * @param cards - list of cards that have to be moved + * @param game - game * @param anyOrder - true if player can determine the order of the cards - * @param source - source ability + * @param source - source ability * @return */ boolean putCardsOnTopOfLibrary(Cards cards, Game game, Ability source, boolean anyOrder); @@ -590,8 +593,8 @@ public interface Player extends MageItem, Copyable { /** * Choose the order in which blockers get damage assigned to * - * @param blockers list of blockers where to choose the next one from - * @param combatGroup the concerning combat group + * @param blockers list of blockers where to choose the next one from + * @param combatGroup the concerning combat group * @param blockerOrder the already set order of blockers * @param game * @return blocker next to add to the blocker order @@ -660,7 +663,8 @@ public interface Player extends MageItem, Copyable { * * @param card * @param game - * @param abilitiesToActivate extra info about abilities that can be activated on NO option + * @param abilitiesToActivate extra info about abilities that can be + * activated on NO option * @return player looked at the card */ boolean lookAtFaceDownCard(Card card, Game game, int abilitiesToActivate); @@ -700,8 +704,8 @@ public interface Player extends MageItem, Copyable { void addCommanderId(UUID commanderId); /** - * Get the commanderIds of the player - * Deprecated, use game.getCommandersIds(xxx) instead + * Get the commanderIds of the player Deprecated, use + * game.getCommandersIds(xxx) instead * * @return */ @@ -733,11 +737,11 @@ public interface Player extends MageItem, Copyable { * @param toZone * @param source * @param game - * @param tapped the cards are tapped on the battlefield - * @param faceDown the cards are face down in the to zone - * @param byOwner the card is moved (or put onto battlefield) by the owner - * of the card and if target zone is battlefield controls the permanent - * (instead of the controller of the source) + * @param tapped the cards are tapped on the battlefield + * @param faceDown the cards are face down in the to zone + * @param byOwner the card is moved (or put onto battlefield) by the owner + * of the card and if target zone is battlefield controls the permanent + * (instead of the controller of the source) * @param appliedEffects * @return */ @@ -773,7 +777,7 @@ public interface Player extends MageItem, Copyable { * list of applied effects is not saved * * @param card - * @param exileId exile zone id (optional) + * @param exileId exile zone id (optional) * @param exileName name of exile zone (optional) * @param sourceId * @param game @@ -815,7 +819,7 @@ public interface Player extends MageItem, Copyable { * @param sourceId * @param game * @param fromZone if null, this info isn't postet - * @param toTop to the top of the library else to the bottom + * @param toTop to the top of the library else to the bottom * @param withName show the card name in the log * @return */ @@ -840,10 +844,10 @@ public interface Player extends MageItem, Copyable { * without mana (null) or the mana set to manaCosts instead of its normal * mana costs. * - * @param sourceId the source that can be cast without mana + * @param sourceId the source that can be cast without mana * @param manaCosts alternate ManaCost, null if it can be cast without mana - * cost - * @param costs alternate other costs you need to pay + * cost + * @param costs alternate other costs you need to pay */ void setCastSourceIdWithAlternateMana(UUID sourceId, ManaCosts manaCosts, Costs costs); @@ -900,4 +904,5 @@ public interface Player extends MageItem, Copyable { void removePhyrexianFromColors(FilterMana colors); FilterMana getPhyrexianColors(); + } diff --git a/Mage/src/main/java/mage/players/PlayerImpl.java b/Mage/src/main/java/mage/players/PlayerImpl.java index 05a7aa386fc..b34fe4f9013 100644 --- a/Mage/src/main/java/mage/players/PlayerImpl.java +++ b/Mage/src/main/java/mage/players/PlayerImpl.java @@ -1462,6 +1462,12 @@ public abstract class PlayerImpl implements Player, Serializable { for (Ability ability : object.getAbilities()) { if (ability instanceof SpellAbility) { switch (((SpellAbility) ability).getSpellAbilityType()) { + case BASE_ALTERNATE: + ActivationStatus as = ((SpellAbility) ability).canActivate(playerId, game); + if (as.canActivate()) { + useable.put(ability.getId(), (SpellAbility) ability); // example: Chandra, Torch of Defiance +1 loyal ability + } + return useable; case SPLIT_FUSED: if (zone == Zone.HAND) { if (ability.canChooseTarget(game)) { @@ -1502,7 +1508,7 @@ public abstract class PlayerImpl implements Player, Serializable { // Get the usable activated abilities for a *single card object*, that is, either a card or half of a split card. // Also called on the whole split card but only passing the fuse ability and other whole-split-card shared abilities // as candidates. - private void getUseableActivatedAbilitiesHalfImpl(MageObject object, Zone zone, Game game, Abilities candidateAbilites, + private void getUseableActivatedAbilitiesHalfImpl(MageObject object, Zone zone, Game game, Abilities candidateAbilites, LinkedHashMap output) { boolean canUse = !(object instanceof Permanent) || ((Permanent) object).canUseActivatedAbilities(game); ManaOptions availableMana = null; @@ -4345,4 +4351,9 @@ public abstract class PlayerImpl implements Player, Serializable { public FilterMana getPhyrexianColors() { return this.phyrexianColors; } + + @Override + public SpellAbility chooseAbilityForCast(Card card, Game game, boolean noMana) { + return card.getSpellAbility(); + } }