* 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);

This commit is contained in:
Oleg Agafonov 2020-06-22 20:20:33 +04:00
parent cb7c787c37
commit 8667d2a923
4 changed files with 98 additions and 48 deletions

View file

@ -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());

View file

@ -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);
}
}

View file

@ -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<Ability> 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<ActivatedAbility> output) {
if (fromZone == null || card == null) {
private void getPlayableFromObjectAll(Game game, Zone fromZone, MageObject object, ManaOptions availableMana, List<ActivatedAbility> 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<Ability> candidateAbilities, ManaOptions availableMana, List<ActivatedAbility> output) {
private void getPlayableFromObjectSingle(Game game, Zone fromZone, MageObject object, Abilities<Ability> candidateAbilities, ManaOptions availableMana, List<ActivatedAbility> 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<ActivatedAbility> battlePlayable = new ArrayList<>();
getPlayableFromCardAll(game, Zone.BATTLEFIELD, permanent, availableMana, battlePlayable);
for (ActivatedAbility ability : battlePlayable) {
List<ActivatedAbility> 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<ActivatedAbility> 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<ActivatedAbility> currentPlayable = new ArrayList<>();
getPlayableFromObjectAll(game, Zone.COMMAND, commandObject, availableMana, currentPlayable);
for (ActivatedAbility ability : currentPlayable) {
activatedUnique.put(ability.toString(), ability);
activatedAll.add(ability);
}
}
}

View file

@ -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<Ability> getAbilities(MageObject object, Game game) {
if (object instanceof Card) {
return ((Card) object).getAbilities(game);
} else {
return object.getAbilities();
}
}
}