mirror of
https://github.com/magefree/mage.git
synced 2026-01-10 04:42:07 -08:00
Reworked asThough effects:
* Game: improved asThough effects processing and combo with different cards/abilities (e.g. adventure cards, play from non own hand, etc); * AI: computer can see and play non hand cards with dynamic effects in all zones (not only graveyard); * AI: computer can see and play "as though" mana and alternative costs; * UI: added non hand cards highlights of available abilities/cards;
This commit is contained in:
parent
6791aea98e
commit
d271feb0cb
6 changed files with 258 additions and 70 deletions
|
|
@ -0,0 +1,128 @@
|
||||||
|
package org.mage.test.cards.asthough;
|
||||||
|
|
||||||
|
import mage.constants.PhaseStep;
|
||||||
|
import mage.constants.Zone;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.mage.test.serverside.base.CardTestPlayerBase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author JayDi85
|
||||||
|
*/
|
||||||
|
public class PlayTopCardFromLibraryTest extends CardTestPlayerBase {
|
||||||
|
|
||||||
|
/*
|
||||||
|
Bolas's Citadel
|
||||||
|
{3}{B}{B}{B}
|
||||||
|
You may look at the top card of your library any time.
|
||||||
|
You may play the top card of your library. If you cast a spell this way, pay life equal to its converted mana cost rather than pay its mana cost.
|
||||||
|
{T}, Sacrifice ten nonland permanents: Each opponent loses 10 life.
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_CreaturePlay() {
|
||||||
|
removeAllCardsFromLibrary(playerA);
|
||||||
|
addCard(Zone.LIBRARY, playerA, "Balduvian Bears", 1);
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Bolas's Citadel", 1);
|
||||||
|
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Balduvian Bears"); // 2 CMC
|
||||||
|
|
||||||
|
setStrictChooseMode(true);
|
||||||
|
setStopAt(1, PhaseStep.END_TURN);
|
||||||
|
execute();
|
||||||
|
assertAllCommandsUsed();
|
||||||
|
|
||||||
|
assertPermanentCount(playerA, "Balduvian Bears", 1);
|
||||||
|
assertLife(playerA, 20 - 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_CreaturePlay2() {
|
||||||
|
removeAllCardsFromLibrary(playerA);
|
||||||
|
addCard(Zone.LIBRARY, playerA, "Balduvian Bears", 1);
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Forest", 2);
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Vizier of the Menagerie", 1);
|
||||||
|
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Balduvian Bears");
|
||||||
|
|
||||||
|
setStrictChooseMode(true);
|
||||||
|
setStopAt(1, PhaseStep.END_TURN);
|
||||||
|
execute();
|
||||||
|
assertAllCommandsUsed();
|
||||||
|
|
||||||
|
assertPermanentCount(playerA, "Balduvian Bears", 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_ManaCostmodifications() {
|
||||||
|
//
|
||||||
|
// {5}{B}{B}
|
||||||
|
// You may cast Scourge of Nel Toth from your graveyard by paying {B}{B} and sacrificing two creatures rather than paying its mana cost.
|
||||||
|
addCard(Zone.GRAVEYARD, playerA, "Scourge of Nel Toth", 1);
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Kitesail Corsair", 2);
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2);
|
||||||
|
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Scourge of Nel Toth");
|
||||||
|
setChoice(playerA, "Kitesail Corsair");
|
||||||
|
setChoice(playerA, "Kitesail Corsair");
|
||||||
|
|
||||||
|
setStrictChooseMode(true);
|
||||||
|
setStopAt(1, PhaseStep.END_TURN);
|
||||||
|
execute();
|
||||||
|
assertAllCommandsUsed();
|
||||||
|
|
||||||
|
assertPermanentCount(playerA, "Scourge of Nel Toth", 1);
|
||||||
|
assertLife(playerA, 20);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_SplitRightPlay() {
|
||||||
|
// https://github.com/magefree/mage/issues/5912
|
||||||
|
// Bolas's citadel requires you to pay mana instead of life for a split card on top of library.
|
||||||
|
//
|
||||||
|
// Steps to reproduce:
|
||||||
|
//
|
||||||
|
// Bolas's Citadel in play, Revival//Revenge on top of library.
|
||||||
|
// Cast Revenge, choose target
|
||||||
|
// receive prompt to pay 4WB.
|
||||||
|
//
|
||||||
|
// Expected outcome
|
||||||
|
//
|
||||||
|
// No prompt for mana payment, payment of six life instead.
|
||||||
|
|
||||||
|
removeAllCardsFromLibrary(playerA);
|
||||||
|
addCard(Zone.LIBRARY, playerA, "Revival // Revenge", 1);
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Bolas's Citadel", 1);
|
||||||
|
|
||||||
|
// Double your life total. Target opponent loses half their life, rounded up.
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Revenge", playerB); // {4}{W}{B} = 6 life
|
||||||
|
|
||||||
|
setStrictChooseMode(true);
|
||||||
|
setStopAt(1, PhaseStep.END_TURN);
|
||||||
|
execute();
|
||||||
|
assertAllCommandsUsed();
|
||||||
|
|
||||||
|
assertLife(playerA, (20 - 6) * 2);
|
||||||
|
assertLife(playerB, 20 / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_SplitLeftPlay() {
|
||||||
|
removeAllCardsFromLibrary(playerA);
|
||||||
|
addCard(Zone.LIBRARY, playerA, "Revival // Revenge", 1);
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Bolas's Citadel", 1);
|
||||||
|
addCard(Zone.GRAVEYARD, playerA, "Balduvian Bears", 1);
|
||||||
|
|
||||||
|
// Return target creature card with converted mana cost 3 or less from your graveyard to the battlefield.
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Revival", "Balduvian Bears"); // {W/B}{W/B} = 2 life
|
||||||
|
|
||||||
|
setStrictChooseMode(true);
|
||||||
|
setStopAt(1, PhaseStep.END_TURN);
|
||||||
|
execute();
|
||||||
|
assertAllCommandsUsed();
|
||||||
|
|
||||||
|
assertLife(playerA, 20 - 2);
|
||||||
|
assertLife(playerB, 20);
|
||||||
|
assertGraveyardCount(playerA, "Balduvian Bears", 0);
|
||||||
|
assertPermanentCount(playerA, "Balduvian Bears", 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
package mage.abilities.effects;
|
package mage.abilities.effects;
|
||||||
|
|
||||||
import java.util.UUID;
|
|
||||||
import mage.abilities.Ability;
|
import mage.abilities.Ability;
|
||||||
import mage.constants.AsThoughEffectType;
|
import mage.constants.AsThoughEffectType;
|
||||||
import mage.constants.Duration;
|
import mage.constants.Duration;
|
||||||
|
|
@ -8,8 +7,9 @@ import mage.constants.EffectType;
|
||||||
import mage.constants.Outcome;
|
import mage.constants.Outcome;
|
||||||
import mage.game.Game;
|
import mage.game.Game;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
* @author BetaSteward_at_googlemail.com
|
* @author BetaSteward_at_googlemail.com
|
||||||
*/
|
*/
|
||||||
public abstract class AsThoughEffectImpl extends ContinuousEffectImpl implements AsThoughEffect {
|
public abstract class AsThoughEffectImpl extends ContinuousEffectImpl implements AsThoughEffect {
|
||||||
|
|
@ -29,10 +29,11 @@ public abstract class AsThoughEffectImpl extends ContinuousEffectImpl implements
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean applies(UUID objectId, Ability affectedAbility, Ability source, Game game, UUID playerId) {
|
public boolean applies(UUID objectId, Ability affectedAbility, Ability source, Game game, UUID playerId) {
|
||||||
|
// affectedControllerId = player to check
|
||||||
if (getAsThoughEffectType().equals(AsThoughEffectType.LOOK_AT_FACE_DOWN)) {
|
if (getAsThoughEffectType().equals(AsThoughEffectType.LOOK_AT_FACE_DOWN)) {
|
||||||
return applies(objectId, source, playerId, game);
|
return applies(objectId, source, playerId, game);
|
||||||
} else {
|
} else {
|
||||||
return applies(objectId, source, affectedAbility.getControllerId(), game);
|
return applies(objectId, source, playerId, game);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -505,12 +505,19 @@ public class ContinuousEffects implements Serializable {
|
||||||
UUID idToCheck;
|
UUID idToCheck;
|
||||||
if (affectedAbility != null && affectedAbility.getSourceObject(game) instanceof SplitCardHalf) {
|
if (affectedAbility != null && affectedAbility.getSourceObject(game) instanceof SplitCardHalf) {
|
||||||
idToCheck = ((SplitCardHalf) affectedAbility.getSourceObject(game)).getParentCard().getId();
|
idToCheck = ((SplitCardHalf) affectedAbility.getSourceObject(game)).getParentCard().getId();
|
||||||
|
} else if (affectedAbility != null && affectedAbility.getSourceObject(game) instanceof AdventureCardSpell
|
||||||
|
&& type != AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE
|
||||||
|
&& type != AsThoughEffectType.CAST_AS_INSTANT) {
|
||||||
|
// adventure spell uses alternative characteristics for spell/stack
|
||||||
|
idToCheck = ((AdventureCardSpell) affectedAbility.getSourceObject(game)).getParentCard().getId();
|
||||||
} else {
|
} else {
|
||||||
Card card = game.getCard(objectId);
|
Card card = game.getCard(objectId);
|
||||||
if (card != null && card instanceof SplitCardHalf) {
|
if (card instanceof SplitCardHalf) {
|
||||||
idToCheck = ((SplitCardHalf) card).getParentCard().getId();
|
idToCheck = ((SplitCardHalf) card).getParentCard().getId();
|
||||||
} else if (card != null && type == AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE
|
} else if (card instanceof AdventureCardSpell
|
||||||
&& card instanceof AdventureCardSpell) {
|
&& type != AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE
|
||||||
|
&& type != AsThoughEffectType.CAST_AS_INSTANT) {
|
||||||
|
// adventure spell uses alternative characteristics for spell/stack
|
||||||
idToCheck = ((AdventureCardSpell) card).getParentCard().getId();
|
idToCheck = ((AdventureCardSpell) card).getParentCard().getId();
|
||||||
} else {
|
} else {
|
||||||
idToCheck = objectId;
|
idToCheck = objectId;
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,8 @@ import mage.abilities.Abilities;
|
||||||
import mage.abilities.AbilitiesImpl;
|
import mage.abilities.AbilitiesImpl;
|
||||||
import mage.abilities.Ability;
|
import mage.abilities.Ability;
|
||||||
import mage.abilities.SpellAbility;
|
import mage.abilities.SpellAbility;
|
||||||
import mage.constants.*;
|
import mage.constants.CardType;
|
||||||
|
import mage.constants.Zone;
|
||||||
import mage.game.Game;
|
import mage.game.Game;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
@ -26,7 +27,7 @@ public abstract class AdventureCard extends CardImpl {
|
||||||
public AdventureCard(AdventureCard card) {
|
public AdventureCard(AdventureCard card) {
|
||||||
super(card);
|
super(card);
|
||||||
this.spellCard = card.getSpellCard().copy();
|
this.spellCard = card.getSpellCard().copy();
|
||||||
((AdventureCardSpell)this.spellCard).setParentCard(this);
|
((AdventureCardSpell) this.spellCard).setParentCard(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Card getSpellCard() {
|
public Card getSpellCard() {
|
||||||
|
|
@ -91,6 +92,11 @@ public abstract class AdventureCard extends CardImpl {
|
||||||
return allAbilities;
|
return allAbilities;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Abilities<Ability> getSharedAbilities() {
|
||||||
|
// abilities without spellcard
|
||||||
|
return super.getAbilities();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setOwnerId(UUID ownerId) {
|
public void setOwnerId(UUID ownerId) {
|
||||||
super.setOwnerId(ownerId);
|
super.setOwnerId(ownerId);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package mage.constants;
|
package mage.constants;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
* @author North
|
* @author North
|
||||||
*/
|
*/
|
||||||
public enum AsThoughEffectType {
|
public enum AsThoughEffectType {
|
||||||
|
|
@ -19,15 +18,29 @@ public enum AsThoughEffectType {
|
||||||
BLOCK_FORESTWALK,
|
BLOCK_FORESTWALK,
|
||||||
DAMAGE_NOT_BLOCKED,
|
DAMAGE_NOT_BLOCKED,
|
||||||
BE_BLOCKED,
|
BE_BLOCKED,
|
||||||
PLAY_FROM_NOT_OWN_HAND_ZONE, // do not use dialogs in "applies" method for that type of effect (it calls multiple times)
|
|
||||||
|
// PLAY_FROM_NOT_OWN_HAND_ZONE + CAST_AS_INSTANT:
|
||||||
|
// 1. Do not use dialogs in "applies" method for that type of effect (it calls multiple times and will freeze the game)
|
||||||
|
// 2. All effects in "applies" must checks affectedControllerId.equals(source.getControllerId()) (if not then all players will be able to play it)
|
||||||
|
// 3. Target points to mainCard, but card's characteristics from objectId (split, adventure)
|
||||||
|
// TODO: search all PLAY_FROM_NOT_OWN_HAND_ZONE and CAST_AS_INSTANT effects and add support of mainCard and objectId
|
||||||
|
PLAY_FROM_NOT_OWN_HAND_ZONE,
|
||||||
CAST_AS_INSTANT,
|
CAST_AS_INSTANT,
|
||||||
|
|
||||||
ACTIVATE_AS_INSTANT,
|
ACTIVATE_AS_INSTANT,
|
||||||
DAMAGE,
|
DAMAGE,
|
||||||
SHROUD,
|
SHROUD,
|
||||||
HEXPROOF,
|
HEXPROOF,
|
||||||
PAY_0_ECHO,
|
PAY_0_ECHO,
|
||||||
LOOK_AT_FACE_DOWN,
|
LOOK_AT_FACE_DOWN,
|
||||||
|
|
||||||
|
// SPEND_OTHER_MANA:
|
||||||
|
// 1. It's uses for mana calcs at any zone, not stack only
|
||||||
|
// 2. Compare zone change counter as "objectZCC <= targetZCC + 1"
|
||||||
|
// 3. Compare zone with original (like exiled) and stack, not stack only
|
||||||
|
// TODO: search all SPEND_ONLY_MANA effects and improve counters compare as SPEND_OTHER_MANA
|
||||||
SPEND_OTHER_MANA,
|
SPEND_OTHER_MANA,
|
||||||
|
|
||||||
SPEND_ONLY_MANA,
|
SPEND_ONLY_MANA,
|
||||||
TARGET
|
TARGET
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3046,7 +3046,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
||||||
game.getContinuousEffects().costModification(copy, game);
|
game.getContinuousEffects().costModification(copy, game);
|
||||||
}
|
}
|
||||||
|
|
||||||
Card card = game.getCard(ability.getSourceId());
|
Card card = game.getCard(copy.getSourceId());
|
||||||
if (card != null) {
|
if (card != null) {
|
||||||
for (Ability ability0 : card.getAbilities()) {
|
for (Ability ability0 : card.getAbilities()) {
|
||||||
if (ability0 instanceof AdjustingSourceCosts) {
|
if (ability0 instanceof AdjustingSourceCosts) {
|
||||||
|
|
@ -3072,10 +3072,16 @@ public abstract class PlayerImpl implements Player, Serializable {
|
||||||
if (available == null) {
|
if (available == null) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
MageObjectReference permittingObject = game.getContinuousEffects().asThough(ability.getSourceId(),
|
MageObjectReference permittingObject = game.getContinuousEffects().asThough(copy.getSourceId(),
|
||||||
AsThoughEffectType.SPEND_OTHER_MANA, ability, ability.getControllerId(), game);
|
AsThoughEffectType.SPEND_OTHER_MANA, copy, copy.getControllerId(), game);
|
||||||
for (Mana mana : abilityOptions) {
|
for (Mana mana : abilityOptions) {
|
||||||
for (Mana avail : available) {
|
for (Mana avail : available) {
|
||||||
|
// TODO: SPEND_OTHER_MANA effects with getAsThoughManaType can change mana type to pay,
|
||||||
|
// but that code processing it as any color, need to test and fix another use cases
|
||||||
|
// (example: Sunglasses of Urza - may spend white mana as though it were red mana)
|
||||||
|
|
||||||
|
//
|
||||||
|
// add tests for non any color like Sunglasses of Urza
|
||||||
if (permittingObject != null && mana.count() <= avail.count()) {
|
if (permittingObject != null && mana.count() <= avail.count()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -3089,7 +3095,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
||||||
|
|
||||||
for (Ability objectAbility : sourceObject.getAbilities()) {
|
for (Ability objectAbility : sourceObject.getAbilities()) {
|
||||||
if (objectAbility instanceof AlternativeCostSourceAbility) {
|
if (objectAbility instanceof AlternativeCostSourceAbility) {
|
||||||
if (objectAbility.getCosts().canPay(ability, ability.getSourceId(), playerId, game)) {
|
if (objectAbility.getCosts().canPay(copy, copy.getSourceId(), playerId, game)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3215,35 +3221,85 @@ public abstract class PlayerImpl implements Player, Serializable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Ability> cardPlayableAbilities(Game game, Card card, boolean setControllerId) {
|
private void getPlayableFromNonHandCardAll(Game game, Zone fromZone, Card card, ManaOptions availableMana, List<Ability> output) {
|
||||||
List<Ability> playable = new ArrayList();
|
if (fromZone == null) {
|
||||||
if (card != null) {
|
return;
|
||||||
for (ActivatedAbility ability : card.getAbilities().getActivatedAbilities(Zone.HAND)) {
|
}
|
||||||
if (!ability.canActivate(playerId, game).canActivate()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
UUID savedControllerId = null;
|
// BASIC abilities
|
||||||
if (setControllerId) {
|
if (card instanceof SplitCard) {
|
||||||
// For when owner != caster, e.g. with Psychic Intrusion and similar effects.
|
SplitCard splitCard = (SplitCard) card;
|
||||||
savedControllerId = getId();
|
getPlayableFromNonHandCardSingle(game, fromZone, splitCard.getLeftHalfCard(), splitCard.getLeftHalfCard().getAbilities(), availableMana, output);
|
||||||
ability.setControllerId(getId());
|
getPlayableFromNonHandCardSingle(game, fromZone, splitCard.getRightHalfCard(), splitCard.getRightHalfCard().getAbilities(), availableMana, output);
|
||||||
}
|
getPlayableFromNonHandCardSingle(game, fromZone, splitCard, splitCard.getSharedAbilities(), availableMana, output);
|
||||||
if (ability instanceof SpellAbility
|
} else if (card instanceof AdventureCard) {
|
||||||
&& null != game.getContinuousEffects().asThough(card.getId(),
|
// adventure must use different card characteristics for different spells (main or adventure)
|
||||||
AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, ability, getId(), game)) {
|
AdventureCard adventureCard = (AdventureCard) card;
|
||||||
playable.add(ability);
|
getPlayableFromNonHandCardSingle(game, fromZone, adventureCard.getSpellCard(), adventureCard.getSpellCard().getAbilities(), availableMana, output);
|
||||||
} else if (ability instanceof PlayLandAbility
|
getPlayableFromNonHandCardSingle(game, fromZone, adventureCard, adventureCard.getSharedAbilities(), availableMana, output);
|
||||||
&& null != game.getContinuousEffects().asThough(card.getId(),
|
} else {
|
||||||
AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, card.getSpellAbility(), getId(), game)) {
|
getPlayableFromNonHandCardSingle(game, fromZone, card, card.getAbilities(), availableMana, output);
|
||||||
playable.add(ability);
|
}
|
||||||
}
|
|
||||||
if (setControllerId) {
|
// DYNAMIC ADDED abilities
|
||||||
ability.setControllerId(savedControllerId);
|
if (fromZone != Zone.ALL) { // TODO: test revealed cards with dynamic added abilities
|
||||||
}
|
// Other activated abilities (added dynamic by effects)
|
||||||
|
LinkedHashMap<UUID, ActivatedAbility> useable;
|
||||||
|
if (card instanceof AdventureCard) {
|
||||||
|
// adventure cards (contains two different cards: main and adventure spell)
|
||||||
|
useable = new LinkedHashMap<>();
|
||||||
|
getOtherUseableActivatedAbilities(((AdventureCard) card).getSpellCard(), fromZone, game, useable);
|
||||||
|
output.addAll(useable.values());
|
||||||
|
|
||||||
|
useable = new LinkedHashMap<>();
|
||||||
|
getOtherUseableActivatedAbilities(card, fromZone, game, useable);
|
||||||
|
output.addAll(useable.values());
|
||||||
|
} else {
|
||||||
|
// all other cards (TODO: check split cards with dynamic added abilities)
|
||||||
|
useable = new LinkedHashMap<>();
|
||||||
|
getOtherUseableActivatedAbilities(card, fromZone, game, useable);
|
||||||
|
output.addAll(useable.values());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void getPlayableFromNonHandCardSingle(Game game, Zone fromZone, Card card, Abilities<Ability> candidateAbilities, ManaOptions availableMana, List<Ability> output) {
|
||||||
|
|
||||||
|
// check "can play from hand" condition as original controller (effects checks affected controller with source controller)
|
||||||
|
// TODO: remove card.getSpellAbility() ?
|
||||||
|
MageObjectReference permittingObject = game.getContinuousEffects().asThough(card.getId(),
|
||||||
|
AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, card.getSpellAbility(), this.getId(), game);
|
||||||
|
boolean canActivateAsHandZone = permittingObject != null
|
||||||
|
|| (fromZone == Zone.GRAVEYARD && canPlayCardsFromGraveyard());
|
||||||
|
|
||||||
|
// check "can play" condition as affected controller
|
||||||
|
for (ActivatedAbility ability : candidateAbilities.getActivatedAbilities(Zone.ALL)) {
|
||||||
|
UUID savedControllerId = ability.getControllerId();
|
||||||
|
ability.setControllerId(this.getId());
|
||||||
|
try {
|
||||||
|
boolean possibleToPlay = false;
|
||||||
|
|
||||||
|
// spell/hand abilities (play from all zones)
|
||||||
|
// need permitingObject or canPlayCardsFromGraveyard
|
||||||
|
if (canActivateAsHandZone
|
||||||
|
&& ability.getZone().match(Zone.HAND)
|
||||||
|
&& (ability instanceof SpellAbility || ability instanceof PlayLandAbility)) {
|
||||||
|
possibleToPlay = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// zone's abilities (play from specific zone)
|
||||||
|
// no need in permitingObject
|
||||||
|
if (fromZone != Zone.ALL && ability.getZone().match(fromZone)) {
|
||||||
|
possibleToPlay = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (possibleToPlay && canPlay(ability, availableMana, card, game)) {
|
||||||
|
output.add(ability);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
ability.setControllerId(savedControllerId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return playable;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -3262,11 +3318,9 @@ public abstract class PlayerImpl implements Player, Serializable {
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean fromAll = fromZone.equals(Zone.ALL);
|
boolean fromAll = fromZone.equals(Zone.ALL);
|
||||||
Collection<Card> cards;
|
|
||||||
|
|
||||||
if (hidden && (fromAll || fromZone == Zone.HAND)) {
|
if (hidden && (fromAll || fromZone == Zone.HAND)) {
|
||||||
cards = hideDuplicatedAbilities ? hand.getUniqueCards(game) : hand.getCards(game);
|
for (Card card : hand.getCards(game)) {
|
||||||
for (Card card : cards) {
|
|
||||||
for (Ability ability : card.getAbilities(game)) { // gets this activated ability from hand? (Morph?)
|
for (Ability ability : card.getAbilities(game)) { // gets this activated ability from hand? (Morph?)
|
||||||
if (ability.getZone().match(Zone.HAND)) {
|
if (ability.getZone().match(Zone.HAND)) {
|
||||||
if (ability instanceof ActivatedAbility) {
|
if (ability instanceof ActivatedAbility) {
|
||||||
|
|
@ -3297,37 +3351,15 @@ public abstract class PlayerImpl implements Player, Serializable {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fromAll || fromZone == Zone.GRAVEYARD) {
|
if (fromAll || fromZone == Zone.GRAVEYARD) {
|
||||||
cards = hideDuplicatedAbilities ? graveyard.getUniqueCards(game) : graveyard.getCards(game);
|
for (Card card : graveyard.getCards(game)) {
|
||||||
for (Card card : cards) {
|
getPlayableFromNonHandCardAll(game, Zone.GRAVEYARD, card, availableMana, playable);
|
||||||
// Handle split cards in graveyard to support Aftermath
|
|
||||||
if (card instanceof SplitCard) {
|
|
||||||
SplitCard splitCard = (SplitCard) card;
|
|
||||||
getPlayableFromGraveyardCard(game, splitCard.getLeftHalfCard(),
|
|
||||||
splitCard.getLeftHalfCard().getAbilities(), availableMana, playable);
|
|
||||||
getPlayableFromGraveyardCard(game, splitCard.getRightHalfCard(),
|
|
||||||
splitCard.getRightHalfCard().getAbilities(), availableMana, playable);
|
|
||||||
getPlayableFromGraveyardCard(game, splitCard, splitCard.getSharedAbilities(),
|
|
||||||
availableMana, playable);
|
|
||||||
} else if (card instanceof AdventureCard) {
|
|
||||||
AdventureCard adventureCard = (AdventureCard) card;
|
|
||||||
getPlayableFromGraveyardCard(game, adventureCard.getSpellCard(),
|
|
||||||
adventureCard.getSpellCard().getAbilities(), availableMana, playable);
|
|
||||||
getPlayableFromGraveyardCard(game, adventureCard, adventureCard.getAbilities(), availableMana, playable);
|
|
||||||
} else {
|
|
||||||
getPlayableFromGraveyardCard(game, card, card.getAbilities(), availableMana, playable);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Other activated abilities
|
|
||||||
LinkedHashMap<UUID, ActivatedAbility> useable = new LinkedHashMap<>();
|
|
||||||
getOtherUseableActivatedAbilities(card, Zone.GRAVEYARD, game, useable);
|
|
||||||
playable.addAll(useable.values());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fromAll || fromZone == Zone.EXILED) {
|
if (fromAll || fromZone == Zone.EXILED) {
|
||||||
for (ExileZone exile : game.getExile().getExileZones()) {
|
for (ExileZone exile : game.getExile().getExileZones()) {
|
||||||
for (Card card : exile.getCards(game)) {
|
for (Card card : exile.getCards(game)) {
|
||||||
playable.addAll(cardPlayableAbilities(game, card, true));
|
getPlayableFromNonHandCardAll(game, Zone.EXILED, card, availableMana, playable);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3336,7 +3368,8 @@ public abstract class PlayerImpl implements Player, Serializable {
|
||||||
if (fromAll) {
|
if (fromAll) {
|
||||||
for (Cards revealedCards : game.getState().getRevealed().values()) {
|
for (Cards revealedCards : game.getState().getRevealed().values()) {
|
||||||
for (Card card : revealedCards.getCards(game)) {
|
for (Card card : revealedCards.getCards(game)) {
|
||||||
playable.addAll(cardPlayableAbilities(game, card, false));
|
// revealed cards can be from any zones
|
||||||
|
getPlayableFromNonHandCardAll(game, game.getState().getZone(card.getId()), card, availableMana, playable);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3348,7 +3381,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
||||||
if (player != null) {
|
if (player != null) {
|
||||||
if (/*player.isTopCardRevealed() &&*/player.getLibrary().hasCards()) {
|
if (/*player.isTopCardRevealed() &&*/player.getLibrary().hasCards()) {
|
||||||
Card card = player.getLibrary().getFromTop(game);
|
Card card = player.getLibrary().getFromTop(game);
|
||||||
playable.addAll(cardPlayableAbilities(game, card, false));
|
getPlayableFromNonHandCardAll(game, Zone.LIBRARY, card, availableMana, playable);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue