mirror of
https://github.com/correl/mage.git
synced 2024-11-15 03:00:16 +00:00
* 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:
parent
cb7c787c37
commit
8667d2a923
4 changed files with 98 additions and 48 deletions
|
@ -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());
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue