Merge pull request #11178 from ssk97/MorphRework_v2

Morph rework and check spell characteristics
This commit is contained in:
xenohedron 2023-10-02 23:01:40 -04:00 committed by GitHub
commit 9456650693
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
283 changed files with 905 additions and 649 deletions

View file

@ -281,11 +281,8 @@ public abstract class AbilityImpl implements Ability {
// as buyback, kicker, or convoke costs (see rules 117.8 and 117.9), the player announces his
// or her intentions to pay any or all of those costs (see rule 601.2e).
// A player can't apply two alternative methods of casting or two alternative costs to a single spell.
if (isMainPartAbility && !activateAlternateOrAdditionalCosts(sourceObject, noMana, controller, game)) {
if (getAbilityType() == AbilityType.SPELL
&& ((SpellAbility) this).getSpellAbilityType() == SpellAbilityType.FACE_DOWN_CREATURE) {
return false;
}
if (isMainPartAbility) {
activateAlternateOrAdditionalCosts(sourceObject, noMana, controller, game);
}
// 117.6. Some mana costs contain no mana symbols. This represents an unpayable cost. An ability can
@ -436,6 +433,7 @@ public abstract class AbilityImpl implements Ability {
case DISTURB:
case MORE_THAN_MEETS_THE_EYE:
case BESTOW:
case MORPH:
// from Snapcaster Mage:
// If you cast a spell from a graveyard using its flashback ability, you can't pay other alternative costs
// (such as that of Foil). (2018-12-07)
@ -452,6 +450,12 @@ public abstract class AbilityImpl implements Ability {
throw new IllegalArgumentException("Unknown ability cast mode: " + ((SpellAbility) this).getSpellAbilityCastMode());
}
}
if (this.getAbilityType() == AbilityType.SPELL && this instanceof SpellAbility
// 117.9a Only one alternative cost can be applied to any one spell as it's being cast.
// So an alternate spell ability can't be paid with Omniscience
&& ((SpellAbility) this).getSpellAbilityType() == SpellAbilityType.BASE_ALTERNATE) {
canUseAlternativeCost = false;
}
boolean alternativeCostUsed = false;
if (sourceObject != null && !(sourceObject instanceof Permanent)) {
@ -475,17 +479,12 @@ public abstract class AbilityImpl implements Ability {
}
// controller specific alternate spell costs
if (canUseAlternativeCost && !noMana && !alternativeCostUsed) {
if (this.getAbilityType() == AbilityType.SPELL
// 117.9a Only one alternative cost can be applied to any one spell as it's being cast.
// So an alternate spell ability can't be paid with Omniscience
&& ((SpellAbility) this).getSpellAbilityType() != SpellAbilityType.BASE_ALTERNATE) {
for (AlternativeSourceCosts alternativeSourceCosts : controller.getAlternativeSourceCosts()) {
if (alternativeSourceCosts.isAvailable(this, game)) {
if (alternativeSourceCosts.askToActivateAlternativeCosts(this, game)) {
// only one alternative costs may be activated
alternativeCostUsed = true;
break;
}
for (AlternativeSourceCosts alternativeSourceCosts : controller.getAlternativeSourceCosts()) {
if (alternativeSourceCosts.isAvailable(this, game)) {
if (alternativeSourceCosts.askToActivateAlternativeCosts(this, game)) {
// only one alternative costs may be activated
alternativeCostUsed = true;
break;
}
}
}

View file

@ -328,8 +328,17 @@ public class SpellAbility extends ActivatedAbilityImpl {
return spellCharacteristics;
}
/**
* Given a spell cast event, returns the relevant SpellAbility involved
* Currently used to get the characteristics of the spell, specifically
* for "can't cast" effects using CAST_SPELL_LATE events
*
* @param event
* @param game
* @return SpellAbility of the event
*/
public static SpellAbility getSpellAbilityFromEvent(GameEvent event, Game game) {
if (event.getType() != GameEvent.EventType.CAST_SPELL) {
if (event.getType() != GameEvent.EventType.CAST_SPELL && event.getType() != GameEvent.EventType.CAST_SPELL_LATE) {
return null;
}

View file

@ -514,14 +514,14 @@ public class ContinuousEffects implements Serializable {
// usage check: effect must apply for specific ability only, not to full object (example: PLAY_FROM_NOT_OWN_HAND_ZONE)
if (type.needAffectedAbility() && affectedAbility == null) {
throw new IllegalArgumentException("ERROR, you can't call asThough check to whole object, call it with affected ability instead: " + type);
throw new IllegalArgumentException("Wrong code usage: you can't call asThough check to whole object, call it with affected ability instead: " + type);
}
// usage check: effect must apply to full object, not specific ability (example: ATTACK_AS_HASTE)
// P.S. In theory a same AsThough effect can be applied to object or to ability, so if you really, really
// need it then disable that check or add extra param to AsThoughEffectType like needAffectedAbilityOrFullObject
if (!type.needAffectedAbility() && affectedAbility != null) {
throw new IllegalArgumentException("ERROR, you can't call AsThough check to affected ability, call it with empty affected ability instead: " + type);
throw new IllegalArgumentException("Wrong code usage: you can't call AsThough check to affected ability, call it with empty affected ability instead: " + type);
}
List<AsThoughEffect> asThoughEffectsList = getApplicableAsThoughEffects(type, game);

View file

@ -2,7 +2,9 @@ package mage.abilities.effects.common;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.SpellAbility;
import mage.abilities.effects.ContinuousRuleModifyingEffectImpl;
import mage.cards.Card;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.game.Game;
@ -45,11 +47,18 @@ public class OpponentsCantCastChosenUntilNextTurnEffect extends ContinuousRuleMo
@Override
public boolean applies(GameEvent event, Ability source, Game game) {
String cardName = (String) game.getState().getValue(source.getSourceId().toString() + ChooseACardNameEffect.INFO_KEY);
if (game.getOpponents(source.getControllerId()).contains(event.getPlayerId())) {
MageObject object = game.getObject(event.getSourceId());
return object != null && CardUtil.haveSameNames(object, cardName, game);
if (!game.getOpponents(source.getControllerId()).contains(event.getPlayerId())) {
return false;
}
return false;
SpellAbility spellAbility = SpellAbility.getSpellAbilityFromEvent(event, game);
if (spellAbility == null) {
return false;
}
Card card = spellAbility.getCharacteristics(game);
if (card == null) {
return false;
}
String cardName = (String) game.getState().getValue(source.getSourceId().toString() + ChooseACardNameEffect.INFO_KEY);
return CardUtil.haveSameNames(card, cardName, game);
}
}

View file

@ -1,6 +1,7 @@
package mage.abilities.effects.common.asthought;
import mage.abilities.Ability;
import mage.abilities.SpellAbility;
import mage.abilities.effects.AsThoughEffectImpl;
import mage.cards.Card;
import mage.constants.*;
@ -47,16 +48,23 @@ public class PlayFromNotOwnHandZoneAllEffect extends AsThoughEffectImpl {
@Override
public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) {
throw new IllegalArgumentException("Wrong code usage: can't call applies method on empty affectedAbility");
}
@Override
public boolean applies(UUID objectId, Ability affectedAbility, Ability source, Game game, UUID playerId) {
Card card = game.getCard(objectId);
if (card != null) {
if (affectedAbility instanceof SpellAbility) {
card = ((SpellAbility) affectedAbility).getCharacteristics(game);
}
switch (allowedCaster) {
case YOU:
if (affectedControllerId != source.getControllerId()) {
if (playerId != source.getControllerId()) {
return false;
}
break;
case OPPONENT:
if (!game.getOpponents(source.getControllerId()).contains(affectedControllerId)) {
if (!game.getOpponents(source.getControllerId()).contains(playerId)) {
return false;
}
break;

View file

@ -87,7 +87,7 @@ public class PlayFromNotOwnHandZoneTargetEffect extends AsThoughEffectImpl {
// PLAY_FROM_NOT_OWN_HAND_ZONE must applies to affectedAbility only
// If you see it then parent conditional effect must override both applies methods to support different
// AsThough effect types in one conditional effect
throw new IllegalArgumentException("ERROR, can't call applies method on empty affectedAbility");
throw new IllegalArgumentException("Wrong code usage: can't call applies method on empty affectedAbility");
}
// invalid targets

View file

@ -1,11 +1,10 @@
package mage.abilities.effects.common.continuous;
import mage.abilities.Ability;
import mage.abilities.SpellAbility;
import mage.abilities.effects.AsThoughEffectImpl;
import mage.abilities.keyword.MorphAbility;
import mage.cards.Card;
import mage.constants.AsThoughEffectType;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.filter.FilterCard;
@ -50,25 +49,19 @@ public class CastAsThoughItHadFlashAllEffect extends AsThoughEffectImpl {
}
@Override
public boolean applies(UUID affectedSpellId, Ability source, UUID affectedControllerId, Game game) {
if (anyPlayer || source.isControlledBy(affectedControllerId)) {
Card card = game.getCard(affectedSpellId);
public boolean applies(UUID objectId, Ability affectedAbility, Ability source, Game game, UUID playerId) {
if (affectedAbility instanceof SpellAbility && (anyPlayer||source.isControlledBy(playerId))) {
Card card = ((SpellAbility) affectedAbility).getCharacteristics(game);
if (card != null) {
//Allow lands with morph to be played at instant speed
if (card.isLand(game)) {
boolean morphAbility = card.getAbilities().stream().anyMatch(MorphAbility.class::isInstance);
if (morphAbility) {
Card cardCopy = card.copy();
cardCopy.removeAllCardTypes(game);
cardCopy.addCardType(game, CardType.CREATURE);
return filter.match(cardCopy, affectedControllerId, source, game);
}
}
return filter.match(card, affectedControllerId, source, game);
return filter.match(card, playerId, source, game);
}
}
return false;
}
@Override
public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) {
throw new IllegalArgumentException("Wrong code usage: can't call applies method on empty affectedAbility");
}
private String setText() {
StringBuilder sb = new StringBuilder(anyPlayer ? "any player" : "you");

View file

@ -1,6 +1,7 @@
package mage.abilities.effects.common.continuous;
import mage.abilities.Ability;
import mage.abilities.SpellAbility;
import mage.abilities.effects.AsThoughEffectImpl;
import mage.cards.Card;
import mage.constants.AsThoughEffectType;
@ -87,6 +88,10 @@ public class PlayTheTopCardEffect extends AsThoughEffectImpl {
@Override
public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) {
throw new IllegalArgumentException("Wrong code usage: can't call applies method on empty affectedAbility");
}
@Override
public boolean applies(UUID objectId, Ability affectedAbility, Ability source, Game game, UUID playerId) {
// main card and all parts are checks in different calls.
// two modes:
// * can play cards (must check main card and allows any parts)
@ -101,10 +106,15 @@ public class PlayTheTopCardEffect extends AsThoughEffectImpl {
if (this.canPlayCardOnly) {
// check whole card instead part
cardToCheck = cardToCheck.getMainCard();
} else if (affectedAbility instanceof SpellAbility) {
SpellAbility spell = (SpellAbility) affectedAbility;
cardToCheck = spell.getCharacteristics(game);
if (spell.getManaCosts().isEmpty()){
return false;
}
}
// must be you
if (!affectedControllerId.equals(source.getControllerId())) {
if (!playerId.equals(source.getControllerId())) {
return false;
}
@ -154,12 +164,7 @@ public class PlayTheTopCardEffect extends AsThoughEffectImpl {
}
}
// can't cast without mana cost
if (!cardToCheck.isLand(game) && cardToCheck.getManaCost().isEmpty()) {
return false;
}
// must be correct card
return filter.match(cardToCheck, affectedControllerId, source, game);
return filter.match(cardToCheck, playerId, source, game);
}
}

View file

@ -0,0 +1,33 @@
package mage.abilities.effects.common.cost;
import mage.abilities.Ability;
import mage.abilities.keyword.MorphAbility;
import mage.filter.FilterCard;
import mage.filter.common.FilterCreatureCard;
import mage.game.Game;
public class MorphSpellsCostReductionControllerEffect extends SpellsCostReductionControllerEffect{
private static final FilterCreatureCard standardFilter = new FilterCreatureCard("Face-down creature spells");
public MorphSpellsCostReductionControllerEffect(int amount) {
super(standardFilter, amount);
}
public MorphSpellsCostReductionControllerEffect(FilterCard filter, int amount) {
super(filter, amount);
}
protected MorphSpellsCostReductionControllerEffect(final MorphSpellsCostReductionControllerEffect effect) {
super(effect);
}
@Override
public MorphSpellsCostReductionControllerEffect copy() {
return new MorphSpellsCostReductionControllerEffect(this);
}
@Override
public boolean applies(Ability abilityToModify, Ability source, Game game) {
if (abilityToModify instanceof MorphAbility) {
return super.applies(abilityToModify, source, game);
}
return false;
}
}

View file

@ -1,6 +1,7 @@
package mage.abilities.effects.common.ruleModifying;
import mage.abilities.Ability;
import mage.abilities.SpellAbility;
import mage.abilities.effects.AsThoughEffectImpl;
import mage.cards.Card;
import mage.constants.AsThoughEffectType;
@ -48,6 +49,10 @@ public class PlayLandsFromGraveyardControllerEffect extends AsThoughEffectImpl {
@Override
public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) {
throw new IllegalArgumentException("Wrong code usage: can't call applies method on empty affectedAbility");
}
@Override
public boolean applies(UUID objectId, Ability affectedAbility, Ability source, Game game, UUID playerId) {
// current card's part
Card cardToCheck = game.getCard(objectId);
if (cardToCheck == null) {
@ -55,13 +60,13 @@ public class PlayLandsFromGraveyardControllerEffect extends AsThoughEffectImpl {
}
// must be you
if (!affectedControllerId.equals(source.getControllerId())) {
if (!playerId.equals(source.getControllerId())) {
return false;
}
// must be your card
Player player = game.getPlayer(cardToCheck.getOwnerId());
if (player == null || !player.getId().equals(affectedControllerId)) {
if (player == null || !player.getId().equals(playerId)) {
return false;
}
@ -75,8 +80,10 @@ public class PlayLandsFromGraveyardControllerEffect extends AsThoughEffectImpl {
if (!cardToCheck.isLand(game) && cardToCheck.getManaCost().isEmpty()) {
return false;
}
if (affectedAbility instanceof SpellAbility){
cardToCheck = ((SpellAbility) affectedAbility).getCharacteristics(game);
}
// must be correct card
return filter.match(cardToCheck, affectedControllerId, source, game);
return filter.match(cardToCheck, playerId, source, game);
}
}

View file

@ -3,8 +3,8 @@ package mage.abilities.keyword;
import mage.MageObject;
import mage.ObjectColor;
import mage.abilities.Ability;
import mage.abilities.SpellAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.costs.AlternativeSourceCostsImpl;
import mage.abilities.costs.Cost;
import mage.abilities.costs.Costs;
import mage.abilities.costs.CostsImpl;
@ -12,13 +12,12 @@ import mage.abilities.costs.mana.GenericManaCost;
import mage.abilities.costs.mana.ManaCost;
import mage.abilities.effects.common.continuous.BecomesFaceDownCreatureEffect;
import mage.abilities.effects.common.continuous.BecomesFaceDownCreatureEffect.FaceDownType;
import mage.constants.CardType;
import mage.constants.Rarity;
import mage.cards.Card;
import mage.constants.*;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.game.permanent.token.EmptyToken;
import mage.game.permanent.token.Token;
import mage.game.stack.Spell;
import mage.util.CardUtil;
/**
@ -62,8 +61,7 @@ import mage.util.CardUtil;
*
* @author LevelX2
*/
public class MorphAbility extends AlternativeSourceCostsImpl {
public class MorphAbility extends SpellAbility {
protected static final String ABILITY_KEYWORD = "Morph";
protected static final String ABILITY_KEYWORD_MEGA = "Megamorph";
protected static final String REMINDER_TEXT = "You may cast this card face down as a "
@ -75,20 +73,22 @@ public class MorphAbility extends AlternativeSourceCostsImpl {
// needed to check activation status, if card changes zone after casting it
private final boolean megamorph;
public MorphAbility(Cost morphCost) {
this(morphCost, false);
public MorphAbility(Card card, Cost morphCost) {
this(card, morphCost, false);
}
public MorphAbility(Cost morphCost, boolean megamorph) {
super(megamorph ? ABILITY_KEYWORD_MEGA : ABILITY_KEYWORD, megamorph ? REMINDER_TEXT_MEGA : REMINDER_TEXT, new GenericManaCost(3));
public MorphAbility(Card card, Cost morphCost, boolean megamorph) {
super(new GenericManaCost(3), card.getName());
this.morphCosts = new CostsImpl<>();
this.morphCosts.add(morphCost);
this.megamorph = megamorph;
this.setWorksFaceDown(true);
this.setSpellAbilityCastMode(SpellAbilityCastMode.MORPH);
this.setSpellAbilityType(SpellAbilityType.BASE_ALTERNATE);
Ability ability = new SimpleStaticAbility(new BecomesFaceDownCreatureEffect(
morphCosts, (megamorph ? FaceDownType.MEGAMORPHED : FaceDownType.MORPHED)));
ability.setWorksFaceDown(true);
ability.setRuleVisible(false);
this.timing = TimingRule.SORCERY;
addSubAbility(ability);
}
@ -103,28 +103,6 @@ public class MorphAbility extends AlternativeSourceCostsImpl {
return new MorphAbility(this);
}
@Override
public boolean askToActivateAlternativeCosts(Ability ability, Game game) {
switch (ability.getAbilityType()) {
case SPELL:
Spell spell = game.getStack().getSpell(ability.getId());
if (spell != null) {
spell.setFaceDown(true, game);
if (handleActivatingAlternativeCosts(ability, game)) {
game.getState().setValue("MorphAbility" + ability.getSourceId(), "activated");
spell.getColor(game).setColor(null);
game.getState().getCreateMageObjectAttribute(spell.getCard(), game).getSubtype().clear();
} else {
spell.setFaceDown(false, game);
}
}
break;
case PLAY_LAND:
handleActivatingAlternativeCosts(ability, game);
}
return isActivated(ability, game);
}
public Costs<Cost> getMorphCosts() {
return morphCosts;
}
@ -132,8 +110,10 @@ public class MorphAbility extends AlternativeSourceCostsImpl {
@Override
public String getRule() {
boolean isMana = morphCosts.get(0) instanceof ManaCost;
return alternativeCost.getName() + (isMana ? " " : "&mdash;") +
morphCosts.getText() + (isMana ? ' ' : ". ") + alternativeCost.getReminderText();
String name = megamorph ? ABILITY_KEYWORD_MEGA : ABILITY_KEYWORD;
String reminder = megamorph ? REMINDER_TEXT_MEGA : REMINDER_TEXT;
return name + (isMana ? " " : "&mdash;") +
morphCosts.getText() + (isMana ? ' ' : ". ") + reminder;
}
/**
@ -169,4 +149,16 @@ public class MorphAbility extends AlternativeSourceCostsImpl {
throw new IllegalArgumentException("Wrong code usage: un-supported targetObject in face down method: " + targetObject.getClass().getSimpleName());
}
}
public static void setCardToFaceDownCreature(Card targetCard) {
targetCard.getPower().setModifiedBaseValue(2);
targetCard.getToughness().setModifiedBaseValue(2);
targetCard.getAbilities().clear();
targetCard.getColor().setColor(new ObjectColor());
targetCard.setName("");
targetCard.removeAllCardTypes();
targetCard.addCardType(CardType.CREATURE);
targetCard.getSubtype().clear();
targetCard.removeAllSuperTypes();
targetCard.getManaCost().clear();
}
}

View file

@ -1,8 +1,10 @@
package mage.constants;
import mage.abilities.keyword.BestowAbility;
import mage.abilities.keyword.MorphAbility;
import mage.cards.Card;
import mage.game.Game;
import mage.game.stack.Spell;
/**
* @author LevelX2
@ -12,6 +14,7 @@ public enum SpellAbilityCastMode {
MADNESS("Madness"),
FLASHBACK("Flashback"),
BESTOW("Bestow"),
MORPH("Morph"),
TRANSFORMED("Transformed", true),
DISTURB("Disturb", true),
MORE_THAN_MEETS_THE_EYE("More than Meets the Eye", true);
@ -44,12 +47,19 @@ public enum SpellAbilityCastMode {
if (this.equals(BESTOW)) {
BestowAbility.becomeAura(cardCopy);
}
if (this.isTransformed){
if (this.isTransformed) {
Card tmp = card.getSecondCardFace();
if (tmp != null) {
cardCopy = tmp.copy();
}
}
if (this.equals(MORPH)) {
if (cardCopy instanceof Spell) {
//Spell doesn't support setName, so make a copy of the card (we're blowing it away anyway)
cardCopy = ((Spell) cardCopy).getCard().copy();
}
MorphAbility.setCardToFaceDownCreature(cardCopy);
}
return cardCopy;
}
}

View file

@ -6,7 +6,6 @@ package mage.constants;
public enum SpellAbilityType {
BASE("Basic SpellAbility"),
BASE_ALTERNATE("Basic SpellAbility Alternate"), // used for Overload, Flashback to know they must be handled as Alternate casting costs
FACE_DOWN_CREATURE("Face down creature"), // used for Lands with Morph to cast as Face Down creature
SPLIT("Split SpellAbility"),
SPLIT_AFTERMATH("AftermathSplit SpellAbility"),
SPLIT_FUSED("Split SpellAbility"),

View file

@ -1,24 +0,0 @@
package mage.filter.predicate.card;
import mage.abilities.keyword.MorphAbility;
import mage.cards.Card;
import mage.filter.predicate.Predicate;
import mage.game.Game;
/**
* @author JayDi85
*/
public enum FaceDownCastablePredicate implements Predicate<Card> {
instance;
@Override
public boolean apply(Card input, Game game) {
// is card able to cast as face down
return input.getAbilities(game).containsClass(MorphAbility.class);
}
@Override
public String toString() {
return "Face-down";
}
}

View file

@ -58,7 +58,6 @@ public class Commander extends CommandObjectImpl {
abilities.add(spellAbility.copyWithZone(Zone.COMMAND));
}
break;
case FACE_DOWN_CREATURE: // dynamic added spell for alternative cost like cast as face down
case SPLICE: // only from hand
case SPLIT_AFTERMATH: // only from graveyard
// can't use from command zone

View file

@ -50,7 +50,7 @@ public class PermanentCard extends PermanentImpl {
}
}
if (!goodForBattlefield) {
throw new IllegalArgumentException("ERROR, can't create permanent card from split or mdf: " + card.getName());
throw new IllegalArgumentException("Wrong code usage: can't create permanent card from split or mdf: " + card.getName());
}
this.card = card;

View file

@ -5,7 +5,6 @@ import mage.MageObject;
import mage.Mana;
import mage.ObjectColor;
import mage.abilities.*;
import mage.abilities.costs.AlternativeSourceCosts;
import mage.abilities.costs.mana.ActivationManaAbilityStep;
import mage.abilities.costs.mana.ManaCost;
import mage.abilities.costs.mana.ManaCosts;
@ -91,6 +90,12 @@ public class Spell extends StackObjectImpl implements Card {
this.zoneChangeCounter = affectedCard.getZoneChangeCounter(game); // sync card's ZCC with spell (copy spell settings)
this.ability = ability;
this.ability.setControllerId(controllerId);
if (ability.getSpellAbilityCastMode() == SpellAbilityCastMode.MORPH){
this.faceDown = true;
this.getColor(game).setColor(null);
game.getState().getCreateMageObjectAttribute(this.getCard(), game).getSubtype().clear();
}
if (ability.getSpellAbilityType() == SpellAbilityType.SPLIT_FUSED) {
// if this spell is going to be a copy, these abilities will be copied in copySpell
if (!isCopy) {
@ -182,11 +187,8 @@ public class Spell extends StackObjectImpl implements Card {
}
public String getSpellCastText(Game game) {
for (Ability spellAbility : getAbilities()) {
if (spellAbility instanceof MorphAbility
&& ((AlternativeSourceCosts) spellAbility).isActivated(getSpellAbility(), game)) {
return "a card face down";
}
if (this.getSpellAbility() instanceof MorphAbility) {
return "a card face down";
}
if (card instanceof AdventureCardSpell) {

View file

@ -1267,34 +1267,16 @@ public abstract class PlayerImpl implements Player, Serializable {
@Override
public boolean playLand(Card card, Game game, boolean ignoreTiming) {
// Check for alternate casting possibilities: e.g. land with Morph
if (card == null) {
return false;
}
ActivatedAbility playLandAbility = null;
boolean foundAlternative = false;
for (Ability ability : card.getAbilities(game)) {
// if cast for noMana no Alternative costs are allowed
if ((ability instanceof AlternativeSourceCosts)
|| (ability instanceof OptionalAdditionalSourceCosts)) {
foundAlternative = true;
}
if (ability instanceof PlayLandAbility) {
playLandAbility = (ActivatedAbility) ability;
}
}
// try alternative cast (face down)
if (foundAlternative) {
SpellAbility spellAbility = new SpellAbility(null, "",
game.getState().getZone(card.getId()), SpellAbilityType.FACE_DOWN_CREATURE);
spellAbility.setControllerId(this.getId());
spellAbility.setSourceId(card.getId());
if (cast(spellAbility, game, false, null)) {
return true;
}
}
if (playLandAbility == null) {
return false;
}