/* * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of * conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list * of conditions and the following disclaimer in the documentation and/or other materials * provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * The views and conclusions contained in the software and documentation are those of the * authors and should not be interpreted as representing official policies, either expressed * or implied, of BetaSteward_at_googlemail.com. */ package mage.abilities.keyword; import java.util.Iterator; import mage.MageObject; import mage.ObjectColor; import mage.abilities.Ability; import mage.abilities.StaticAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.costs.AlternativeCost2Impl; import mage.abilities.costs.AlternativeSourceCosts; import mage.abilities.costs.Cost; import mage.abilities.costs.Costs; import mage.abilities.costs.CostsImpl; import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.costs.mana.ManaCost; import mage.abilities.costs.mana.ManaCosts; import mage.abilities.effects.common.continuous.BecomesFaceDownCreatureEffect; import mage.abilities.effects.common.continuous.BecomesFaceDownCreatureEffect.FaceDownType; import mage.cards.Card; import mage.constants.AbilityType; import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.Rarity; import mage.constants.Zone; import mage.game.Game; import mage.game.permanent.Permanent; import mage.game.stack.Spell; import mage.players.Player; /** * 702.36. Morph * * 702.36a Morph is a static ability that functions in any zone from which you * could play the card it’s on, and the morph effect works any time the card is * face down. "Morph [cost]" means "You may cast this card as a 2/2 face-down * creature, with no text, no name, no subtypes, and no mana cost by paying {3} * rather than paying its mana cost." (See rule 707, "Face-Down Spells and * Permanents.") * * 702.36b To cast a card using its morph ability, turn it face down. It becomes * a 2/2 face-down creature card, with no text, no name, no subtypes, and no * mana cost. Any effects or prohibitions that would apply to casting a card * with these characteristics (and not the face-up card’s characteristics) are * applied to casting this card. These values are the copiable values of that * object’s characteristics. (See rule 613, "Interaction of Continuous Effects," * and rule 706, "Copying Objects.") Put it onto the stack (as a face-down spell * with the same characteristics), and pay {3} rather than pay its mana cost. * This follows the rules for paying alternative costs. You can use morph to * cast a card from any zone from which you could normally play it. When the * spell resolves, it enters the battlefield with the same characteristics the * spell had. The morph effect applies to the face-down object wherever it is, * and it ends when the permanent is turned face up. # * * 702.36c You can’t cast a card face down if it doesn’t have morph. * * 702.36d If you have priority, you may turn a face-down permanent you control * face up. This is a special action; it doesn’t use the stack (see rule 115). * To do this, show all players what the permanent’s morph cost would be if it * were face up, pay that cost, then turn the permanent face up. (If the * permanent wouldn’t have a morph cost if it were face up, it can’t be turned * face up this way.) The morph effect on it ends, and it regains its normal * characteristics. Any abilities relating to the permanent entering the * battlefield don’t trigger when it’s turned face up and don’t have any effect, * because the permanent has already entered the battlefield. * * 702.36e See rule 707, "Face-Down Spells and Permanents," for more information * on how to cast cards with morph. * * @author LevelX2 */ public class MorphAbility extends StaticAbility implements AlternativeSourceCosts { protected static final String ABILITY_KEYWORD = "Morph"; protected static final String ABILITY_KEYWORD_MEGA = "Megamorph"; protected static final String REMINDER_TEXT = "<i>(You may cast this card face down as a 2/2 creature for {3}. Turn it face up any time for its morph cost.)</i>"; protected static final String REMINDER_TEXT_MEGA = "<i>(You may cast this card face down as a 2/2 creature for {3}. Turn it face up any time for its megamorph cost and put a +1/+1 counter on it.)</i>"; protected String ruleText; protected AlternativeCost2Impl alternateCosts = new AlternativeCost2Impl(ABILITY_KEYWORD, REMINDER_TEXT, new GenericManaCost(3)); protected Costs<Cost> morphCosts; // needed to check activation status, if card changes zone after casting it private int zoneChangeCounter = 0; private boolean megamorph; public MorphAbility(Card card, Cost morphCost) { this(card, createCosts(morphCost)); } public MorphAbility(Card card, Cost morphCost, boolean megamorph) { this(card, createCosts(morphCost), megamorph); } public MorphAbility(Card card, Costs<Cost> morphCosts) { this(card, morphCosts, false); } public MorphAbility(Card card, Costs<Cost> morphCosts, boolean megamorph) { super(Zone.HAND, null); this.morphCosts = morphCosts; this.megamorph = megamorph; this.setWorksFaceDown(true); StringBuilder sb = new StringBuilder(); if (megamorph) { sb.append(ABILITY_KEYWORD_MEGA).append(' '); } else { sb.append(ABILITY_KEYWORD).append(' '); } name = ABILITY_KEYWORD; for (Cost cost : morphCosts) { if (!(cost instanceof ManaCosts)) { sb.append("- "); break; } } sb.append(morphCosts.getText()).append(' '); if (megamorph) { sb.append(REMINDER_TEXT_MEGA); } else { sb.append(REMINDER_TEXT); } ruleText = sb.toString(); Ability ability = new SimpleStaticAbility(Zone.BATTLEFIELD, new BecomesFaceDownCreatureEffect(morphCosts, (megamorph ? FaceDownType.MEGAMORPHED : FaceDownType.MORPHED))); ability.setWorksFaceDown(true); ability.setRuleVisible(false); addSubAbility(ability); } public MorphAbility(final MorphAbility ability) { super(ability); this.zoneChangeCounter = ability.zoneChangeCounter; this.ruleText = ability.ruleText; this.alternateCosts = ability.alternateCosts.copy(); this.morphCosts = ability.morphCosts; // can't be changed this.megamorph = ability.megamorph; } private static Costs<Cost> createCosts(Cost cost) { Costs<Cost> costs = new CostsImpl<>(); costs.add(cost); return costs; } @Override public MorphAbility copy() { return new MorphAbility(this); } public void resetMorph() { alternateCosts.reset(); zoneChangeCounter = 0; } public Costs<Cost> getMorphCosts() { return morphCosts; } @Override public boolean isActivated(Ability ability, Game game) { Card card = game.getCard(sourceId); if (card != null && card.getZoneChangeCounter(game) <= zoneChangeCounter + 1) { return alternateCosts.isActivated(game); } return false; } @Override public boolean isAvailable(Ability source, Game game) { return game.isMainPhase() && game.getActivePlayerId().equals(source.getControllerId()) && (game.getStack().isEmpty() || (game.getStack().size() == 1 && game.getStack().getFirst().getSourceId().equals(source.getSourceId()))); } @Override public boolean askToActivateAlternativeCosts(Ability ability, Game game) { if (ability.getAbilityType() == AbilityType.SPELL) { Player player = game.getPlayer(controllerId); Spell spell = game.getStack().getSpell(ability.getId()); if (player != null && spell != null) { this.resetMorph(); spell.setFaceDown(true, game); // so only the back is visible if (alternateCosts.canPay(ability, sourceId, controllerId, game)) { if (player.chooseUse(Outcome.Benefit, "Cast this card as a 2/2 face-down creature for " + getCosts().getText() + " ?", ability, game)) { activateMorph(game); // change mana costs ability.getManaCostsToPay().clear(); ability.getCosts().clear(); for (Iterator it = this.alternateCosts.iterator(); it.hasNext();) { Cost cost = (Cost) it.next(); if (cost instanceof ManaCost) { ability.getManaCostsToPay().add((ManaCost) cost.copy()); } else { ability.getCosts().add(cost.copy()); } } // change spell colors ObjectColor spellColor = spell.getColor(game); spellColor.setBlack(false); spellColor.setRed(false); spellColor.setGreen(false); spellColor.setWhite(false); spellColor.setBlue(false); } else { spell.setFaceDown(false, game); } } } } if (ability.getAbilityType() == AbilityType.PLAY_LAND) { Player player = game.getPlayer(controllerId); if (player != null) { this.resetMorph(); if (alternateCosts.canPay(ability, sourceId, controllerId, game)) { if (player.chooseUse(Outcome.Benefit, "Cast this card as a 2/2 face-down creature for " + getCosts().getText() + " ?", ability, game)) { activateMorph(game); // change mana costs ability.getManaCostsToPay().clear(); ability.getCosts().clear(); for (Iterator it = this.alternateCosts.iterator(); it.hasNext();) { Cost cost = (Cost) it.next(); if (cost instanceof ManaCost) { ability.getManaCostsToPay().add((ManaCost) cost.copy()); } else { ability.getCosts().add(cost.copy()); } } } } } } return isActivated(ability, game); } private void activateMorph(Game game) { alternateCosts.activate(); // remember zone change counter if (zoneChangeCounter == 0) { Card card = game.getCard(getSourceId()); if (card != null) { zoneChangeCounter = card.getZoneChangeCounter(game); } else { throw new IllegalArgumentException("Morph source card not found"); } } } @Override public String getRule(boolean all) { return getRule(); } @Override public String getRule() { return ruleText; } @Override public String getCastMessageSuffix(Game game) { return alternateCosts.getCastSuffixMessage(0); } @Override @SuppressWarnings({"unchecked"}) public Costs<Cost> getCosts() { return alternateCosts; } public static void setPermanentToFaceDownCreature(MageObject mageObject) { mageObject.getPower().modifyBaseValue(2); mageObject.getToughness().modifyBaseValue(2); mageObject.getAbilities().clear(); mageObject.getColor(null).setColor(new ObjectColor()); mageObject.setName(""); mageObject.getCardType().clear(); mageObject.addCardType(CardType.CREATURE); mageObject.getSubtype(null).clear(); mageObject.getSuperType().clear(); mageObject.getManaCost().clear(); if (mageObject instanceof Permanent) { ((Permanent) mageObject).setExpansionSetCode(""); ((Permanent) mageObject).setRarity(Rarity.NA); } } }