From 8667d2a92304445abbdcf1a3b3fa1b61bcc3c058 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Mon, 22 Jun 2020 20:20:33 +0400 Subject: [PATCH] * Special mana payments like convoke/delve - fixed that it can't be used to cast card from command zone (example: Tasigur, the Golden Fang, see #6680); --- .../mage/cards/t/TasigurTheGoldenFang.java | 2 +- .../PayDelveFromCommandZoneTest.java | 35 ++++++ .../main/java/mage/players/PlayerImpl.java | 100 ++++++++++-------- Mage/src/main/java/mage/util/CardUtil.java | 9 ++ 4 files changed, 98 insertions(+), 48 deletions(-) create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/cost/modification/PayDelveFromCommandZoneTest.java diff --git a/Mage.Sets/src/mage/cards/t/TasigurTheGoldenFang.java b/Mage.Sets/src/mage/cards/t/TasigurTheGoldenFang.java index 814b97225b..af9604a194 100644 --- a/Mage.Sets/src/mage/cards/t/TasigurTheGoldenFang.java +++ b/Mage.Sets/src/mage/cards/t/TasigurTheGoldenFang.java @@ -1,4 +1,3 @@ - package mage.cards.t; import mage.MageInt; @@ -37,6 +36,7 @@ public final class TasigurTheGoldenFang extends CardImpl { // Delve this.addAbility(new DelveAbility()); + // {2}{G/U}{G/U}: Put the top two cards of your library into your graveyard, then return a nonland card of an opponent's choice from your graveyard to your hand. Ability ability = new SimpleActivatedAbility(new PutTopCardOfLibraryIntoGraveControllerEffect(2), new ManaCostsImpl("{2}{G/U}{G/U}")); ability.addEffect(new TasigurTheGoldenFangEffect()); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/cost/modification/PayDelveFromCommandZoneTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/cost/modification/PayDelveFromCommandZoneTest.java new file mode 100644 index 0000000000..797e35260a --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/cost/modification/PayDelveFromCommandZoneTest.java @@ -0,0 +1,35 @@ +package org.mage.test.cards.cost.modification; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestCommander4Players; + +/** + * @author JayDi85 + */ +public class PayDelveFromCommandZoneTest extends CardTestCommander4Players { + + // Player order: A -> D -> C -> B + @Test + public void test_Other_CastFromCommand_Delve() { + // https://github.com/magefree/mage/issues/6698 + // Having this problem with Tasigur, the Golden Fang. I can't attempt to use delve to cast him from command zone. + + // {5}{B} creature + // Delve (Each card you exile from your graveyard while casting this spell pays for {1}.) + addCard(Zone.COMMAND, playerA, "Tasigur, the Golden Fang", 1); + addCard(Zone.GRAVEYARD, playerA, "Balduvian Bears", 5); // delve + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Tasigur, the Golden Fang"); + setChoice(playerA, "Balduvian Bears", 5); // delve pay + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + assertAllCommandsUsed(); + + assertPermanentCount(playerA, "Tasigur, the Golden Fang", 1); + } +} diff --git a/Mage/src/main/java/mage/players/PlayerImpl.java b/Mage/src/main/java/mage/players/PlayerImpl.java index 19343ddc0d..5ac203e08e 100644 --- a/Mage/src/main/java/mage/players/PlayerImpl.java +++ b/Mage/src/main/java/mage/players/PlayerImpl.java @@ -3105,12 +3105,12 @@ public abstract class PlayerImpl implements Player, Serializable { return false; } - protected ActivatedAbility findActivatedAbilityFromPlayable(Card card, ManaOptions manaAvailable, Ability ability, Game game) { + protected ActivatedAbility findActivatedAbilityFromPlayable(MageObject object, ManaOptions manaAvailable, Ability ability, Game game) { // special mana to pay spell cost ManaOptions manaFull = manaAvailable.copy(); if (ability instanceof SpellAbility) { - for (AlternateManaPaymentAbility altAbility : card.getAbilities(game).stream() + for (AlternateManaPaymentAbility altAbility : CardUtil.getAbilities(object, game).stream() .filter(a -> a instanceof AlternateManaPaymentAbility) .map(a -> (AlternateManaPaymentAbility) a) .collect(Collectors.toList())) { @@ -3127,10 +3127,10 @@ public abstract class PlayerImpl implements Player, Serializable { } } else if (ability instanceof AlternativeSourceCosts) { // alternative cost must be replaced by real play ability - return findActivatedAbilityFromAlternativeSourceCost(card, manaFull, ability, game); + return findActivatedAbilityFromAlternativeSourceCost(object, manaFull, ability, game); } else if (ability instanceof ActivatedAbility) { // all other activated ability - if (canPlay((ActivatedAbility) ability, manaFull, card, game)) { + if (canPlay((ActivatedAbility) ability, manaFull, object, game)) { return (ActivatedAbility) ability; } } @@ -3139,30 +3139,33 @@ public abstract class PlayerImpl implements Player, Serializable { return null; } - protected ActivatedAbility findActivatedAbilityFromAlternativeSourceCost(Card card, ManaOptions manaAvailable, Ability ability, Game game) { + protected ActivatedAbility findActivatedAbilityFromAlternativeSourceCost(MageObject object, ManaOptions manaAvailable, Ability ability, Game game) { // alternative cost must be replaced by real play ability if (ability instanceof AlternativeSourceCosts) { AlternativeSourceCosts altAbility = (AlternativeSourceCosts) ability; - if (card.isLand()) { + if (object.isLand()) { // land // morph ability is static, so it must be replaced with play land ability (playLand search and try to use face down first) - if (canLandPlayAlternateSourceCostsAbility(card, manaAvailable, ability, game)) { // e.g. Land with Morph - Optional landAbility = card.getAbilities(game).stream().filter(a -> a instanceof PlayLandAbility).findFirst(); - if (landAbility.isPresent()) { - return (ActivatedAbility) landAbility.get(); + if (canLandPlayAlternateSourceCostsAbility(object, manaAvailable, ability, game)) { // e.g. Land with Morph + Ability landAbility = CardUtil.getAbilities(object, game).stream().filter(a -> a instanceof PlayLandAbility).findFirst().orElse(null); + if (landAbility != null) { + return (PlayLandAbility) landAbility; } } } else { // creature and other - if (altAbility.isAvailable(card.getSpellAbility(), game)) { - return card.getSpellAbility(); + if (object instanceof Card) { + SpellAbility spellAbility = ((Card) object).getSpellAbility(); + if (altAbility.isAvailable(spellAbility, game)) { + return spellAbility; + } } } } return null; } - protected boolean canLandPlayAlternateSourceCostsAbility(Card sourceObject, ManaOptions available, Ability ability, Game game) { + protected boolean canLandPlayAlternateSourceCostsAbility(MageObject sourceObject, ManaOptions available, Ability ability, Game game) { if (sourceObject != null && !(sourceObject instanceof Permanent)) { Ability sourceAbility = sourceObject.getAbilities().stream() .filter(landAbility -> landAbility.getAbilityType() == AbilityType.PLAY_LAND) @@ -3194,30 +3197,33 @@ public abstract class PlayerImpl implements Player, Serializable { return false; } - private void getPlayableFromCardAll(Game game, Zone fromZone, Card card, ManaOptions availableMana, List output) { - if (fromZone == null || card == null) { + private void getPlayableFromObjectAll(Game game, Zone fromZone, MageObject object, ManaOptions availableMana, List output) { + if (fromZone == null || object == null) { return; } // BASIC abilities - if (card instanceof SplitCard) { - SplitCard splitCard = (SplitCard) card; - getPlayableFromCardSingle(game, fromZone, splitCard.getLeftHalfCard(), splitCard.getLeftHalfCard().getAbilities(game), availableMana, output); - getPlayableFromCardSingle(game, fromZone, splitCard.getRightHalfCard(), splitCard.getRightHalfCard().getAbilities(game), availableMana, output); - getPlayableFromCardSingle(game, fromZone, splitCard, splitCard.getSharedAbilities(game), availableMana, output); - } else if (card instanceof AdventureCard) { + if (object instanceof SplitCard) { + SplitCard splitCard = (SplitCard) object; + getPlayableFromObjectSingle(game, fromZone, splitCard.getLeftHalfCard(), splitCard.getLeftHalfCard().getAbilities(game), availableMana, output); + getPlayableFromObjectSingle(game, fromZone, splitCard.getRightHalfCard(), splitCard.getRightHalfCard().getAbilities(game), availableMana, output); + getPlayableFromObjectSingle(game, fromZone, splitCard, splitCard.getSharedAbilities(game), availableMana, output); + } else if (object instanceof AdventureCard) { // adventure must use different card characteristics for different spells (main or adventure) - AdventureCard adventureCard = (AdventureCard) card; - getPlayableFromCardSingle(game, fromZone, adventureCard.getSpellCard(), adventureCard.getSpellCard().getAbilities(game), availableMana, output); - getPlayableFromCardSingle(game, fromZone, adventureCard, adventureCard.getSharedAbilities(game), availableMana, output); + AdventureCard adventureCard = (AdventureCard) object; + getPlayableFromObjectSingle(game, fromZone, adventureCard.getSpellCard(), adventureCard.getSpellCard().getAbilities(game), availableMana, output); + getPlayableFromObjectSingle(game, fromZone, adventureCard, adventureCard.getSharedAbilities(game), availableMana, output); + } else if (object instanceof Card) { + getPlayableFromObjectSingle(game, fromZone, object, ((Card) object).getAbilities(game), availableMana, output); } else { - getPlayableFromCardSingle(game, fromZone, card, card.getAbilities(game), availableMana, output); + // other things like StackObject or CommandObject + getPlayableFromObjectSingle(game, fromZone, object, object.getAbilities(), availableMana, output); } // DYNAMIC ADDED abilities are adds in getAbilities(game) } - private void getPlayableFromCardSingle(Game game, Zone fromZone, Card card, Abilities candidateAbilities, ManaOptions availableMana, List output) { + private void getPlayableFromObjectSingle(Game game, Zone fromZone, MageObject object, Abilities candidateAbilities, ManaOptions availableMana, List output) { // check "can play" condition as affected controller (BUT play from not own hand zone must be checked as original controller) // must check all abilities, not activated only for (Ability ability : candidateAbilities) { @@ -3247,7 +3253,7 @@ public abstract class PlayerImpl implements Player, Serializable { MageObjectReference permittingObject; if (isPlaySpell || isPlayLand) { // play hand from non hand zone - permittingObject = game.getContinuousEffects().asThough(card.getId(), + permittingObject = game.getContinuousEffects().asThough(object.getId(), AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, ability, this.getId(), game); } else { // other abilities from direct zones @@ -3277,7 +3283,7 @@ public abstract class PlayerImpl implements Player, Serializable { } // direct mode (with original controller) - ActivatedAbility playAbility = findActivatedAbilityFromPlayable(card, availableMana, ability, game); + ActivatedAbility playAbility = findActivatedAbilityFromPlayable(object, availableMana, ability, game); if (playAbility != null && !output.contains(playAbility)) { output.add(playAbility); continue; @@ -3288,7 +3294,7 @@ public abstract class PlayerImpl implements Player, Serializable { UUID savedControllerId = ability.getControllerId(); ability.setControllerId(this.getId()); try { - playAbility = findActivatedAbilityFromPlayable(card, availableMana, ability, game); + playAbility = findActivatedAbilityFromPlayable(object, availableMana, ability, game); if (playAbility != null && !output.contains(playAbility)) { output.add(playAbility); } @@ -3359,14 +3365,14 @@ public abstract class PlayerImpl implements Player, Serializable { if (fromAll || fromZone == Zone.GRAVEYARD) { for (Card card : graveyard.getCards(game)) { - getPlayableFromCardAll(game, Zone.GRAVEYARD, card, availableMana, playable); + getPlayableFromObjectAll(game, Zone.GRAVEYARD, card, availableMana, playable); } } if (fromAll || fromZone == Zone.EXILED) { for (ExileZone exile : game.getExile().getExileZones()) { for (Card card : exile.getCards(game)) { - getPlayableFromCardAll(game, Zone.EXILED, card, availableMana, playable); + getPlayableFromObjectAll(game, Zone.EXILED, card, availableMana, playable); } } } @@ -3376,7 +3382,7 @@ public abstract class PlayerImpl implements Player, Serializable { for (Cards revealedCards : game.getState().getRevealed().values()) { for (Card card : revealedCards.getCards(game)) { // revealed cards can be from any zones - getPlayableFromCardAll(game, game.getState().getZone(card.getId()), card, availableMana, playable); + getPlayableFromObjectAll(game, game.getState().getZone(card.getId()), card, availableMana, playable); } } } @@ -3385,7 +3391,7 @@ public abstract class PlayerImpl implements Player, Serializable { if (fromAll || fromZone == Zone.OUTSIDE) { for (Cards companionCards : game.getState().getCompanion().values()) { for (Card card : companionCards.getCards(game)) { - getPlayableFromCardAll(game, Zone.OUTSIDE, card, availableMana, playable); + getPlayableFromObjectAll(game, Zone.OUTSIDE, card, availableMana, playable); } } } @@ -3397,7 +3403,7 @@ public abstract class PlayerImpl implements Player, Serializable { if (player != null && player.getLibrary().hasCards()) { Card card = player.getLibrary().getFromTop(game); if (card != null) { - getPlayableFromCardAll(game, Zone.LIBRARY, card, availableMana, playable); + getPlayableFromObjectAll(game, Zone.LIBRARY, card, availableMana, playable); } } } @@ -3411,9 +3417,9 @@ public abstract class PlayerImpl implements Player, Serializable { if (fromAll || fromZone == Zone.BATTLEFIELD) { for (Permanent permanent : game.getBattlefield().getAllActivePermanents()) { boolean canUseActivated = permanent.canUseActivatedAbilities(game); - List battlePlayable = new ArrayList<>(); - getPlayableFromCardAll(game, Zone.BATTLEFIELD, permanent, availableMana, battlePlayable); - for (ActivatedAbility ability : battlePlayable) { + List currentPlayable = new ArrayList<>(); + getPlayableFromObjectAll(game, Zone.BATTLEFIELD, permanent, availableMana, currentPlayable); + for (ActivatedAbility ability : currentPlayable) { if (ability instanceof SpecialAction || canUseActivated) { activatedUnique.putIfAbsent(ability.toString(), ability); activatedAll.add(ability); @@ -3425,11 +3431,11 @@ public abstract class PlayerImpl implements Player, Serializable { // activated abilities from stack objects if (fromAll || fromZone == Zone.STACK) { for (StackObject stackObject : game.getState().getStack()) { - for (ActivatedAbility ability : stackObject.getAbilities().getActivatedAbilities(Zone.STACK)) { - if (canPlay(ability, availableMana, game.getObject(ability.getSourceId()), game)) { - activatedUnique.put(ability.toString(), ability); - activatedAll.add(ability); - } + List currentPlayable = new ArrayList<>(); + getPlayableFromObjectAll(game, Zone.STACK, stackObject, availableMana, currentPlayable); + for (ActivatedAbility ability : currentPlayable) { + activatedUnique.put(ability.toString(), ability); + activatedAll.add(ability); } } } @@ -3437,11 +3443,11 @@ public abstract class PlayerImpl implements Player, Serializable { // activated abilities from objects in the command zone (emblems or commanders) if (fromAll || fromZone == Zone.COMMAND) { for (CommandObject commandObject : game.getState().getCommand()) { - for (ActivatedAbility ability : commandObject.getAbilities().getActivatedAbilities(Zone.COMMAND)) { - if (canPlay(ability, availableMana, game.getObject(ability.getSourceId()), game)) { - activatedUnique.put(ability.toString(), ability); - activatedAll.add(ability); - } + List currentPlayable = new ArrayList<>(); + getPlayableFromObjectAll(game, Zone.COMMAND, commandObject, availableMana, currentPlayable); + for (ActivatedAbility ability : currentPlayable) { + activatedUnique.put(ability.toString(), ability); + activatedAll.add(ability); } } } diff --git a/Mage/src/main/java/mage/util/CardUtil.java b/Mage/src/main/java/mage/util/CardUtil.java index 1d1b72f079..6836eab716 100644 --- a/Mage/src/main/java/mage/util/CardUtil.java +++ b/Mage/src/main/java/mage/util/CardUtil.java @@ -2,6 +2,7 @@ package mage.util; import mage.MageObject; import mage.Mana; +import mage.abilities.Abilities; import mage.abilities.Ability; import mage.abilities.SpellAbility; import mage.abilities.costs.VariableCost; @@ -791,4 +792,12 @@ public final class CardUtil { } return false; } + + public static Abilities getAbilities(MageObject object, Game game) { + if (object instanceof Card) { + return ((Card) object).getAbilities(game); + } else { + return object.getAbilities(); + } + } }