/* * 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.game.stack; import mage.MageInt; import mage.MageObject; import mage.Mana; import mage.ObjectColor; import mage.abilities.Abilities; import mage.abilities.Ability; import mage.abilities.Mode; import mage.abilities.SpellAbility; import mage.abilities.costs.AlternativeSourceCosts; import mage.abilities.costs.Cost; import mage.abilities.costs.mana.ManaCost; import mage.abilities.costs.mana.ManaCosts; import mage.abilities.dynamicvalue.common.StaticValue; import mage.abilities.effects.Effect; import mage.abilities.effects.PostResolveEffect; import mage.abilities.keyword.BestowAbility; import mage.abilities.keyword.MorphAbility; import mage.cards.Card; import mage.cards.SplitCard; import mage.constants.*; import mage.counters.Counter; import mage.counters.Counters; import mage.game.Game; import mage.game.events.ZoneChangeEvent; import mage.game.permanent.Permanent; import mage.game.permanent.PermanentCard; import mage.players.Player; import mage.target.Target; import mage.target.TargetAmount; import mage.watchers.Watcher; import java.util.ArrayList; import java.util.List; import java.util.UUID; /** * * @author BetaSteward_at_googlemail.com */ public class Spell implements StackObject, Card { private final List spellCards = new ArrayList<>(); private final List spellAbilities = new ArrayList<>(); private final Card card; private final ObjectColor color; private final SpellAbility ability; private final Zone fromZone; private final UUID id; private UUID controllerId; private boolean copiedSpell; private boolean faceDown; public Spell(Card card, SpellAbility ability, UUID controllerId, Zone fromZone) { this.card = card; this.color = card.getColor().copy(); id = ability.getId(); this.ability = ability; this.ability.setControllerId(controllerId); if (ability.getSpellAbilityType().equals(SpellAbilityType.SPLIT_FUSED)) { spellCards.add(((SplitCard) card).getLeftHalfCard()); spellAbilities.add(((SplitCard) card).getLeftHalfCard().getSpellAbility().copy()); spellCards.add(((SplitCard) card).getRightHalfCard()); spellAbilities.add(((SplitCard) card).getRightHalfCard().getSpellAbility().copy()); } else { spellCards.add(card); spellAbilities.add(ability); } this.controllerId = controllerId; this.fromZone = fromZone; } public Spell(final Spell spell) { this.id = spell.id; for (SpellAbility spellAbility: spell.spellAbilities) { this.spellAbilities.add(spellAbility.copy()); } for (Card spellCard: spell.spellCards) { this.spellCards.add(spellCard.copy()); } if (spell.spellAbilities.get(0).equals(spell.ability)) { this.ability = this.spellAbilities.get(0); } else { this.ability = spell.ability.copy(); } if (spell.spellCards.get(0).equals(spell.card)) { this.card = spellCards.get(0); } else { this.card = spell.card.copy(); } this.controllerId = spell.controllerId; this.fromZone = spell.fromZone; this.copiedSpell = spell.copiedSpell; this.faceDown = spell.faceDown; this.color = spell.color.copy(); } public boolean activate(Game game, boolean noMana) { if (!spellAbilities.get(0).activate(game, noMana)) { return false; } // if there are more abilities (fused split spell) or first ability added new abilities (splice), activate the additional abilities boolean ignoreAbility = true; boolean payNoMana = noMana; for (SpellAbility spellAbility: spellAbilities) { // costs for spliced abilities were added to main spellAbility, so pay no mana for spliced abilities payNoMana |= spellAbility.getSpellAbilityType().equals(SpellAbilityType.SPLICE); if (ignoreAbility) { ignoreAbility = false; } else { if (!spellAbility.activate(game, payNoMana)) { return false; } } } return true; } public String getActivatedMessage(Game game) { StringBuilder sb = new StringBuilder(); if (isCopiedSpell()) { sb.append(" copies "); } else { sb.append(" casts "); } return sb.append(ability.getGameLogMessage(game)).toString(); } public String getSpellCastText(Game game) { for (Ability spellAbility : (Abilities) getAbilities()) { if (spellAbility instanceof MorphAbility && ((AlternativeSourceCosts) spellAbility).isActivated(getSpellAbility(), game)) { return "a card face down"; } } return getSpellAbility().toString(); } @Override public boolean resolve(Game game) { boolean result; if (this.getCardType().contains(CardType.INSTANT) || this.getCardType().contains(CardType.SORCERY)) { int index = 0; result = false; boolean legalParts = false; // check for legal parts for(SpellAbility spellAbility: this.spellAbilities) { // if muliple modes are selected, and there are modes with targets, then at least one mode has to have a legal target or // When resolving a fused split spell with multiple targets, treat it as you would any spell with multiple targets. // If all targets are illegal when the spell tries to resolve, the spell is countered and none of its effects happen. // If at least one target is still legal at that time, the spell resolves, but an illegal target can’t perform any actions // or have any actions performed on it. legalParts |= spellAbilityHasLegalParts(spellAbility, game); } // resolve if legal parts if (legalParts) { for(SpellAbility spellAbility: this.spellAbilities) { if (spellAbilityHasLegalParts(spellAbility, game)) { for (UUID modeId :spellAbility.getModes().getSelectedModes()) { spellAbility.getModes().setMode(spellAbility.getModes().get(modeId)); if (spellAbility.getTargets().stillLegal(spellAbility, game)) { if (!spellAbility.getSpellAbilityType().equals(SpellAbilityType.SPLICE)) { updateOptionalCosts(index); } result |= spellAbility.resolve(game); } } index++; } } if (!copiedSpell) { for (Effect effect : ability.getEffects()) { if (effect instanceof PostResolveEffect) { if (((PostResolveEffect) effect).isActive(ability, game)) { ((PostResolveEffect) effect).postResolve(card, ability, controllerId, game); return result; } } } if (game.getState().getZone(card.getId()) == Zone.STACK) { card.moveToZone(Zone.GRAVEYARD, ability.getId(), game, false); } } return result; } //20091005 - 608.2b game.informPlayers(getName() + " has been fizzled."); counter(null, game); return false; } else if (this.getCardType().contains(CardType.ENCHANTMENT) && this.getSubtype().contains("Aura")) { if (ability.getTargets().stillLegal(ability, game)) { updateOptionalCosts(0); boolean bestow = this.getSpellAbility() instanceof BestowAbility; if (bestow) { // Must be removed first time, after that will be removed by continous effect // Otherwise effects like evolve trigger from creature comes into play event card.getCardType().remove(CardType.CREATURE); } if (card.putOntoBattlefield(game, fromZone, ability.getId(), controllerId)) { if (bestow) { // card will be copied during putOntoBattlefield, so the card of CardPermanent has to be changed // TODO: Find a better way to prevent bestow creatures from being effected by creature affecting abilities Permanent permanent = game.getPermanent(card.getId()); if (permanent != null && permanent instanceof PermanentCard) { ((PermanentCard) permanent).getCard().getCardType().add(CardType.CREATURE); } card.getCardType().add(CardType.CREATURE); } game.getState().handleSimultaneousEvent(game); return ability.resolve(game); } if (bestow) { card.getCardType().add(CardType.CREATURE); } return false; } // Aura has no legal target and its a bestow enchantment -> Add it to battlefield as creature if (this.getSpellAbility() instanceof BestowAbility) { updateOptionalCosts(0); result = card.putOntoBattlefield(game, fromZone, ability.getId(), controllerId); return result; } else { //20091005 - 608.2b game.informPlayers(getName() + " has been fizzled."); counter(null, game); return false; } } else { updateOptionalCosts(0); if (isFaceDown()) { card.setFaceDown(true); } result = card.putOntoBattlefield(game, fromZone, ability.getId(), controllerId); return result; } } private boolean spellAbilityHasLegalParts(SpellAbility spellAbility, Game game) { if (spellAbility.getModes().getSelectedModes().size() > 1) { boolean targetedMode = false; boolean legalTargetedMode = false; for (UUID modeId :spellAbility.getModes().getSelectedModes()) { spellAbility.getModes().setMode(spellAbility.getModes().get(modeId)); if (spellAbility.getTargets().size() > 0) { targetedMode = true; if (spellAbility.getTargets().stillLegal(spellAbility, game)) { legalTargetedMode = true; } } } if (targetedMode) { return legalTargetedMode; } return true; } else { return spellAbility.getTargets().stillLegal(spellAbility, game); } } /** * As we have ability in the stack, we need to update optional costs in original card. * This information will be used later by effects, e.g. to determine whether card was kicked or not. * E.g. Desolation Angel */ private void updateOptionalCosts(int index) { Ability abilityOrig = spellCards.get(index).getAbilities().get(spellAbilities.get(index).getId()); if (abilityOrig != null) { for (Object object : spellAbilities.get(index).getOptionalCosts()) { Cost cost = (Cost) object; for (Cost costOrig : abilityOrig.getOptionalCosts()) { if (cost.getId().equals(costOrig.getId())) { if (cost.isPaid()) { costOrig.setPaid(); } else { costOrig.clearPaid(); } break; } } } } } /** * Choose new targets for the spell * * @param game * @param playerId Player UUID who changes the targets. * @return */ public boolean chooseNewTargets(Game game, UUID playerId) { return chooseNewTargets(game, playerId, false, false); } /** * 114.6. Some effects allow a player to change the target(s) of a spell or * ability, and other effects allow a player to choose new targets for a * spell or ability. * * 114.6a If an effect allows a player to "change the * target(s)" of a spell or ability, each target can be changed only to * another legal target. If a target can't be changed to another legal * target, the original target is unchanged, even if the original target is * itself illegal by then. If all the targets aren't changed to other legal * targets, none of them are changed. * * 114.6b If an effect allows a player to "change a target" of a * spell or ability, the process described in rule 114.6a * is followed, except that only one of those targets may be changed * (rather than all of them or none of them). * * 114.6c If an effect allows a * player to "change any targets" of a spell or ability, the process * described in rule 114.6a is followed, except that any number of those * targets may be changed (rather than all of them or none of them). * * 114.6d If an effect allows a player to "choose new targets" for a spell or * ability, the player may leave any number of the targets unchanged, even * if those targets would be illegal. If the player chooses to change some * or all of the targets, the new targets must be legal and must not cause * any unchanged targets to become illegal. * * 114.6e When changing targets or * choosing new targets for a spell or ability, only the final set of * targets is evaluated to determine whether the change is legal. * * Example: Arc Trail is a sorcery that reads "Arc Trail deals 2 damage to * target creature or player and 1 damage to another target creature or * player." The current targets of Arc Trail are Runeclaw Bear and Llanowar * Elves, in that order. You cast Redirect, an instant that reads "You may * choose new targets for target spell," targeting Arc Trail. You can change * the first target to Llanowar Elves and change the second target to * Runeclaw Bear. * * 114.7. Modal spells and abilities may have different targeting * requirements for each mode. An effect that allows a player to change the * target(s) of a modal spell or ability, or to choose new targets for a * modal spell or ability, doesn't allow that player to change its mode. * (See rule 700.2.) * * 706.10c Some effects copy a spell or ability and state that its * controller may choose new targets for the copy. The player may leave any * number of the targets unchanged, even if those targets would be illegal. * If the player chooses to change some or all of the targets, the new * targets must be legal. Once the player has decided what the copy's * targets will be, the copy is put onto the stack with those targets. * * @param game * @param playerId - player that can/has to change the taregt of the spell * @param forceChange - does only work for targets with maximum of one * targetId * @param onlyOneTarget - 114.6b one target must be changed to another * target * @return */ public boolean chooseNewTargets(Game game, UUID playerId, boolean forceChange, boolean onlyOneTarget) { Player player = game.getPlayer(playerId); if (player != null) { StringBuilder newTargetDescription = new StringBuilder(); // Fused split spells or spells where "Splice on Arcane" was used can have more than one ability for (SpellAbility spellAbility : spellAbilities) { // Some spells can have more than one mode for (UUID modeId : spellAbility.getModes().getSelectedModes()) { Mode mode = spellAbility.getModes().get(modeId); for (Target target : mode.getTargets()) { Target newTarget = chooseNewTarget(player, spellAbility, mode, target, forceChange, game); // clear the old target and copy all targets from new target target.clearChosen(); for (UUID targetId : newTarget.getTargets()) { target.addTarget(targetId, newTarget.getTargetAmount(targetId), spellAbility, game, false); } } newTargetDescription.append(getSpellAbility().getTargetDescription(mode.getTargets(), game)); } } if (newTargetDescription.length() > 0) { game.informPlayers(this.getName() + " is now " + newTargetDescription.toString()); } return true; } return false; } /** * Handles the change of one target instance of a mode * * @param player - player that can choose the new target * @param spellAbility * @param mode * @param target * @param forceChange * @param game * @return */ private Target chooseNewTarget(Player player, SpellAbility spellAbility, Mode mode, Target target, boolean forceChange, Game game) { Target newTarget = target.copy(); newTarget.clearChosen(); for (UUID targetId : target.getTargets()) { String targetNames = getNamesOftargets(targetId, game); // change the target? if (targetNames != null && (forceChange || player.chooseUse(mode.getEffects().get(0).getOutcome(), "Change this target: " + targetNames + "?", game))) { // choose exactly one other target if (forceChange && target.possibleTargets(this.getSourceId(), getControllerId(), game).size() > 1) { // controller of spell must be used (e.g. TargetOpponent) int iteration = 0; do { if (iteration > 0) { game.informPlayer(player, "You may only select exactly one target that must be different from the origin target!"); } iteration++; newTarget.clearChosen(); // TODO: Distinction between "spell controller" and "player that can change the target" - here player is used for both newTarget.chooseTarget(mode.getEffects().get(0).getOutcome(), player.getId(), spellAbility, game); } while (player.isInGame() && (targetId.equals(newTarget.getFirstTarget()) || newTarget.getTargets().size() != 1)); // choose a new target } else { // build a target definition with exactly one possible target to select that replaces old target Target tempTarget = target.copy(); if (target instanceof TargetAmount) { ((TargetAmount)tempTarget).setAmountDefinition(new StaticValue(target.getTargetAmount(targetId))); } tempTarget.setMinNumberOfTargets(1); tempTarget.setMaxNumberOftargets(1); boolean again; do { again = false; tempTarget.clearChosen(); if (!tempTarget.chooseTarget(mode.getEffects().get(0).getOutcome(), player.getId(), spellAbility, game)) { if (player.chooseUse(Outcome.Benefit, "No target object selected. Reset to original target?", game)) { // use previous target no target was selected newTarget.addTarget(targetId, target.getTargetAmount(targetId), spellAbility, game, false); } else { again = true; } } else { // if possible add the alternate Target - it may not be included in the old definition nor in the already selected targets of the new definition if (newTarget.getTargets().contains(tempTarget.getFirstTarget()) || target.getTargets().contains(tempTarget.getFirstTarget())) { if(player.isHuman()) { game.informPlayer(player, "This target was already selected from origin spell. You can only keep this target!"); again = true; } else { newTarget.addTarget(targetId, target.getTargetAmount(targetId), spellAbility, game, false); } } else { // valid target was selected, add it to the new target definition newTarget.addTarget(tempTarget.getFirstTarget(), target.getTargetAmount(targetId), spellAbility, game, false); } } } while (again && player.isInGame()); } } // keep the target else { newTarget.addTarget(targetId, target.getTargetAmount(targetId), spellAbility, game, false); } } return newTarget; } private String getNamesOftargets(UUID targetId, Game game) { MageObject object = game.getObject(targetId); String name = null; if (object == null) { Player targetPlayer = game.getPlayer(targetId); if (targetPlayer != null) { name = targetPlayer.getName(); } } else { name = object.getName(); } return name; } @Override public void counter(UUID sourceId, Game game) { card.moveToZone(Zone.GRAVEYARD, sourceId, game, false); } @Override public UUID getSourceId() { return card.getId(); } @Override public UUID getControllerId() { return this.controllerId; } @Override public String getName() { return card.getName(); } @Override public String getLogName() { return card.getName(); } @Override public String getImageName() { return card.getImageName(); } @Override public void setName(String name) {} @Override public Rarity getRarity() { return card.getRarity(); } @Override public void setRarity(Rarity rarity) {} @Override public List getCardType() { if (this.getSpellAbility() instanceof BestowAbility) { List cardTypes = new ArrayList<>(); cardTypes.addAll(card.getCardType()); cardTypes.remove(CardType.CREATURE); return cardTypes; } return card.getCardType(); } @Override public List getSubtype() { if (this.getSpellAbility() instanceof BestowAbility) { List subtypes = new ArrayList<>(); subtypes.addAll(card.getSubtype()); subtypes.add("Aura"); return subtypes; } return card.getSubtype(); } @Override public boolean hasSubtype(String subtype) { return card.hasSubtype(subtype); } @Override public List getSupertype() { return card.getSupertype(); } public List getSpellAbilities() { return spellAbilities; } @Override public Abilities getAbilities() { return card.getAbilities(); } @Override public boolean hasAbility(UUID abilityId, Game game) { return card.hasAbility(abilityId, game); } @Override public ObjectColor getColor() { return color; } @Override public ManaCosts getManaCost() { return card.getManaCost(); } /** * 202.3b When calculating the converted mana cost of an object with an {X} in its * mana cost, X is treated as 0 while the object is not on the stack, and X is * treated as the number chosen for it while the object is on the stack. * @return */ @Override public int getConvertedManaCost() { int cmc = 0; if (this.isMorphCard() && this.isFaceDown()) { return 0; } for (Ability spellAbility: spellAbilities) { int xMultiplier = 0; for (String symbolString :spellAbility.getManaCosts().getSymbols()) { int index = symbolString.indexOf("{X}"); while (index != -1) { xMultiplier++; symbolString = symbolString.substring(index + 3); index = symbolString.indexOf("{X}"); } } cmc += spellAbility.getManaCosts().convertedManaCost() + spellAbility.getManaCostsToPay().getX() * xMultiplier; } return cmc; } @Override public MageInt getPower() { return card.getPower(); } @Override public MageInt getToughness() { return card.getToughness(); } @Override public UUID getId() { return id; } @Override public UUID getOwnerId() { return card.getOwnerId(); } public void addSpellAbility(SpellAbility spellAbility) { spellAbilities.add(spellAbility); } @Override public void addAbility(Ability ability) {} @Override public void addWatcher(Watcher watcher) {} @Override public SpellAbility getSpellAbility() { return ability; } @Override public void setControllerId(UUID controllerId) { this.ability.setControllerId(controllerId); for (SpellAbility spellAbility: spellAbilities) { spellAbility.setControllerId(controllerId); } this.controllerId = controllerId; } @Override public void setOwnerId(UUID controllerId) {} @Override public List getRules() { return card.getRules(); } @Override public List getWatchers() { return card.getWatchers(); } @Override public String getExpansionSetCode() { return card.getExpansionSetCode(); } @Override public String getTokenSetCode() { return card.getTokenSetCode(); } @Override public void setExpansionSetCode(String expansionSetCode) {} @Override public void setFaceDown(boolean value) { faceDown = value; } @Override public boolean turnFaceUp(Game game, UUID playerId) { setFaceDown(false); return true; } @Override public boolean turnFaceDown(Game game, UUID playerId) { setFaceDown(true); return true; } @Override public boolean isFaceDown() { return faceDown; } @Override public boolean isFlipCard() { return false; } @Override public String getFlipCardName() { return null; } @Override public boolean isSplitCard() { return false; } @Override public boolean canTransform() { return false; } @Override public Card getSecondCardFace() { return null; } @Override public void setSecondCardFace(Card card) { } @Override public boolean isNightCard() { return false; } @Override public void setFlipCard(boolean flipCard) { throw new UnsupportedOperationException("Not supported."); //To change body of generated methods, choose Tools | Templates. } @Override public void setFlipCardName(String flipCardName) { throw new UnsupportedOperationException("Not supported."); //To change body of generated methods, choose Tools | Templates. } @Override public Spell copy() { return new Spell(this); } public Spell copySpell() { return new Spell(this.card.copy(), this.ability.copySpell(), this.controllerId, this.fromZone); } @Override public void adjustChoices(Ability ability, Game game) { if (card != null) { card.adjustChoices(ability, game); } } @Override public void adjustCosts(Ability ability, Game game) { if (card != null) { card.adjustCosts(ability, game); } } @Override public void adjustTargets(Ability ability, Game game) { if (card != null) { card.adjustTargets(ability, game); } } @Override public boolean moveToZone(Zone zone, UUID sourceId, Game game, boolean flag) { return moveToZone(zone, sourceId, game, flag, null); } @Override public boolean moveToZone(Zone zone, UUID sourceId, Game game, boolean flag, ArrayList appliedEffects) { // 706.10a If a copy of a spell is in a zone other than the stack, it ceases to exist. // If a copy of a card is in any zone other than the stack or the battlefield, it ceases to exist. // These are state-based actions. See rule 704. if (this.isCopiedSpell() && !zone.equals(Zone.STACK)) { return true; } throw new UnsupportedOperationException("Unsupported operation"); } @Override public boolean moveToExile(UUID exileId, String name, UUID sourceId, Game game) { return moveToExile(exileId, name, sourceId, game, null); } @Override public boolean moveToExile(UUID exileId, String name, UUID sourceId, Game game, ArrayList appliedEffects) { ZoneChangeEvent event = new ZoneChangeEvent(this.getId(), sourceId, this.getOwnerId(), Zone.STACK, Zone.EXILED, appliedEffects); if (!game.replaceEvent(event)) { game.getStack().remove(this); game.rememberLKI(this.getId(), event.getFromZone(), this); if (exileId == null) { game.getExile().getPermanentExile().add(this.card); } else { game.getExile().createZone(exileId, name).add(this.card); } game.setZone(this.card.getId(), event.getToZone()); game.fireEvent(event); return event.getToZone() == Zone.EXILED; } return false; } @Override public boolean putOntoBattlefield(Game game, Zone fromZone, UUID sourceId, UUID controllerId) { throw new UnsupportedOperationException("Unsupported operation"); } @Override public boolean putOntoBattlefield(Game game, Zone fromZone, UUID sourceId, UUID controllerId, boolean tapped) { throw new UnsupportedOperationException("Not supported yet."); } @Override public boolean putOntoBattlefield(Game game, Zone fromZone, UUID sourceId, UUID controllerId, boolean tapped, ArrayList appliedEffects) { throw new UnsupportedOperationException("Not supported yet."); } @Override public int getCardNumber() { return card.getCardNumber(); } @Override public void setCardNumber(int cid) { card.setCardNumber(cid); } @Override public boolean getUsesVariousArt() { return card.getUsesVariousArt(); } @Override public void setUsesVariousArt(boolean usesVariousArt) { card.setUsesVariousArt(usesVariousArt); } @Override public List getMana() { return card.getMana(); } @Override public boolean cast(Game game, Zone fromZone, SpellAbility ability, UUID controllerId) { throw new UnsupportedOperationException("Unsupported operation"); } @Override public Ability getStackAbility() { return this.ability; } @Override public void assignNewId() { throw new UnsupportedOperationException("Unsupported operation"); } @Override public int getZoneChangeCounter() { return card.getZoneChangeCounter(); } @Override public void addInfo(String key, String value) { // do nothing } public void setCopiedSpell(boolean isCopied) { this.copiedSpell = isCopied; } public boolean isCopiedSpell() { return this.copiedSpell; } public Zone getFromZone() { return this.fromZone; } @Override public void setCopy(boolean isCopy) { setCopiedSpell(isCopy); } @Override public boolean isCopy() { return isCopiedSpell(); } @Override public void build() {} @Override public Counters getCounters() { return card.getCounters(); } @Override public void addCounters(String name, int amount, Game game) { card.addCounters(name, amount, game); } @Override public void addCounters(String name, int amount, Game game, ArrayList appliedEffects) { card.addCounters(name, amount, game, appliedEffects); } @Override public void addCounters(Counter counter, Game game) { card.addCounters(counter, game); } @Override public void addCounters(Counter counter, Game game, ArrayList appliedEffects) { card.addCounters(counter, game, appliedEffects); } @Override public void removeCounters(String name, int amount, Game game) { card.removeCounters(name, amount, game); } @Override public void removeCounters(Counter counter, Game game) { card.removeCounters(counter, game); } public Card getCard() { return card; } @Override public void setMorphCard(boolean morphCard) { throw new UnsupportedOperationException("Not supported"); } @Override public boolean isMorphCard() { return card.isMorphCard(); } }