forge.game.spellability.SpellAbility.java Source code

Java tutorial

Introduction

Here is the source code for forge.game.spellability.SpellAbility.java

Source

/*
 * Forge: Play Magic: the Gathering.
 * Copyright (C) 2011  Forge Team
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package forge.game.spellability;

import com.google.common.collect.Iterables;

import forge.card.mana.ManaCost;
import forge.game.CardTraitBase;
import forge.game.Game;
import forge.game.GameEntity;
import forge.game.GameObject;
import forge.game.IIdentifiable;
import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
import forge.game.cost.Cost;
import forge.game.cost.CostPartMana;
import forge.game.mana.Mana;
import forge.game.player.Player;
import forge.game.trigger.TriggerType;
import forge.game.trigger.WrappedAbility;
import forge.util.Expressions;
import forge.util.TextUtil;

import org.apache.commons.lang3.StringUtils;

import java.util.*;

//only SpellAbility can go on the stack
//override any methods as needed
/**
 * <p>
 * Abstract SpellAbility class.
 * </p>
 *
 * @author Forge
 * @version $Id: SpellAbility.java 30088 2015-09-23 08:20:25Z Agetian $
 */
public abstract class SpellAbility extends CardTraitBase implements ISpellAbility, IIdentifiable {
    private static int maxId = 0;

    private static int nextId() {
        return ++maxId;
    }

    public static class EmptySa extends SpellAbility {
        public EmptySa(Card sourceCard) {
            super(sourceCard, Cost.Zero);
            setActivatingPlayer(sourceCard.getController());
        }

        public EmptySa(ApiType api0, Card sourceCard) {
            super(sourceCard, Cost.Zero);
            setActivatingPlayer(sourceCard.getController());
            api = api0;
        }

        public EmptySa(Card sourceCard, Player activator) {
            super(sourceCard, Cost.Zero);
            setActivatingPlayer(activator);
        }

        public EmptySa(ApiType api0, Card sourceCard, Player activator) {
            super(sourceCard, Cost.Zero);
            setActivatingPlayer(activator);
            api = api0;
        }

        @Override
        public void resolve() {
        }

        @Override
        public boolean canPlay() {
            return false;
        }
    }

    private int id;

    // choices for constructor isPermanent argument
    private String originalDescription = "", description = "";
    private String originalStackDescription = "", stackDescription = "";
    private ManaCost multiKickerManaCost = null;
    private Player activatingPlayer = null;
    private Player targetingPlayer = null;

    private boolean basicLandAbility; // granted by basic land type

    private Card grantorCard = null; // card which grants the ability (equipment or owner of static ability that gave this one)

    private CardCollection splicedCards = null;

    private boolean basicSpell = true;
    private boolean trigger = false;
    private boolean optionalTrigger = false;
    private boolean replacementAbility = false;
    private int sourceTrigger = -1;
    private List<Object> triggerRemembered = new ArrayList<Object>();

    private boolean flashBackAbility = false;
    private boolean cycling = false;
    private boolean delve = false;
    private boolean dash = false;
    private boolean offering = false;
    private boolean morphup = false;
    private boolean manifestUp = false;
    private boolean cumulativeupkeep = false;
    private boolean outlast = false;
    private SplitSide splitSide = null;

    enum SplitSide {
        LEFT, RIGHT
    };

    private int totalManaSpent = 0;

    /** The pay costs. */
    private Cost payCosts;
    private SpellAbilityRestriction restrictions = new SpellAbilityRestriction();
    private SpellAbilityCondition conditions = new SpellAbilityCondition();
    private AbilitySub subAbility = null;

    protected ApiType api = null;

    private final List<Mana> payingMana = new ArrayList<Mana>();
    private final List<SpellAbility> paidAbilities = new ArrayList<SpellAbility>();

    private HashMap<String, CardCollection> paidLists = new HashMap<String, CardCollection>();

    private HashMap<String, Object> triggeringObjects = new HashMap<String, Object>();

    private HashMap<String, Object> replacingObjects = new HashMap<String, Object>();

    private List<AbilitySub> chosenList = null;
    private CardCollection tappedForConvoke = new CardCollection();
    private Card sacrificedAsOffering = null;
    private int conspireInstances = 0;

    private HashMap<String, String> sVars = new HashMap<String, String>();

    private AbilityManaPart manaPart = null;

    private boolean undoable;

    private boolean isCopied = false;

    private EnumSet<OptionalCost> optionalCosts = EnumSet.noneOf(OptionalCost.class);
    private TargetRestrictions targetRestrictions = null;
    private TargetChoices targetChosen = new TargetChoices();

    private SpellAbilityView view;

    protected SpellAbility(final Card iSourceCard, final Cost toPay) {
        this(iSourceCard, toPay, null);
    }

    protected SpellAbility(final Card iSourceCard, final Cost toPay, SpellAbilityView view0) {
        id = nextId();
        hostCard = iSourceCard;
        payCosts = toPay;
        if (view0 == null) {
            view0 = new SpellAbilityView(this);
        }
        view = view0;
        if (hostCard != null && hostCard.getGame() != null) {
            hostCard.getGame().addSpellAbility(id, this);
        }
    }

    @Override
    public int getId() {
        return id;
    }

    @Override
    public int hashCode() {
        return getId();
    }

    @Override
    public boolean equals(final Object obj) {
        return obj instanceof SpellAbility && this.id == ((SpellAbility) obj).id;
    };

    @Override
    public void setHostCard(final Card c) {
        if (hostCard == c) {
            return;
        }
        super.setHostCard(c);
        view.updateHostCard(this);
        view.updateDescription(this); //description can change if host card does
    }

    public final AbilityManaPart getManaPart() {
        return manaPart;
    }

    public final AbilityManaPart getManaPartRecursive() {
        SpellAbility tail = this;
        while (tail != null) {
            if (tail.manaPart != null) {
                return tail.manaPart;
            }
            tail = tail.getSubAbility();
        }
        return null;
    }

    public final boolean isManaAbility() {
        // Check whether spell or ability first
        if (isSpell()) {
            return false;
        }
        // without a target
        if (usesTargeting()) {
            return false;
        }
        if (restrictions != null && restrictions.isPwAbility()) {
            return false; //Loyalty ability, not a mana ability.
        }
        if (isWrapper() && ((WrappedAbility) this).getTrigger().getMode() != TriggerType.TapsForMana) {
            return false;
        }

        return getManaPartRecursive() != null;
    }

    protected final void setManaPart(AbilityManaPart manaPart0) {
        manaPart = manaPart0;
    }

    public final String getSVar(final String name) {
        String var = sVars.get(name);
        if (var == null) {
            var = "";
        }
        return var;
    }

    public final Integer getSVarInt(final String name) {
        String var = sVars.get(name);
        if (var != null) {
            try {
                return Integer.parseInt(var);
            } catch (Exception e) {
            }
        }
        return null;
    }

    public final void setSVar(final String name, final String value) {
        sVars.put(name, value);
    }

    public Set<String> getSVars() {
        return sVars.keySet();
    }

    // Spell, and Ability, and other Ability objects override this method
    public abstract boolean canPlay();

    public boolean isPossible() {
        return canPlay(); //by default, ability is only possible if it can be played
    }

    public boolean promptIfOnlyPossibleAbility() {
        return false; //by default, don't prompt user if ability is only possible ability
    }

    // all Spell's and Abilities must override this method
    public abstract void resolve();

    public ManaCost getMultiKickerManaCost() {
        return multiKickerManaCost;
    }

    public void setMultiKickerManaCost(final ManaCost cost) {
        multiKickerManaCost = cost;
    }

    public Player getActivatingPlayer() {
        return activatingPlayer;
    }

    public void setActivatingPlayer(final Player player) {
        // trickle down activating player
        activatingPlayer = player;
        if (subAbility != null) {
            subAbility.setActivatingPlayer(player);
        }
        view.updateCanPlay(this);
    }

    public Player getTargetingPlayer() {
        return targetingPlayer;
    }

    public void setTargetingPlayer(Player targetingPlayer0) {
        targetingPlayer = targetingPlayer0;
    }

    public boolean isSpell() {
        return false;
    }

    public boolean isAbility() {
        return true;
    }

    public boolean isMorphUp() {
        return morphup;
    }

    public final void setIsMorphUp(final boolean b) {
        morphup = b;
    }

    public boolean isManifestUp() {
        return manifestUp;
    }

    public final void setIsManifestUp(final boolean b) {
        manifestUp = b;
    }

    public boolean isCycling() {
        return cycling;
    }

    public final void setIsCycling(final boolean b) {
        cycling = b;
    }

    public Card getOriginalHost() {
        return grantorCard;
    }

    public void setOriginalHost(final Card c) {
        grantorCard = c;
    }

    public String getParamOrDefault(String key, String defaultValue) {
        return mapParams.containsKey(key) ? mapParams.get(key) : defaultValue;
    }

    public String getParam(String key) {
        return mapParams.get(key);
    }

    public boolean hasParam(String key) {
        return mapParams.containsKey(key);
    }

    public void copyParamsToMap(Map<String, String> mapParam) {
        mapParam.putAll(mapParams);
    }

    // If this is not null, then ability was made in a factory
    public ApiType getApi() {
        return api;
    }

    public void setApi(ApiType apiType) {
        api = apiType;
    }

    public final boolean isCurse() {
        return hasParam("IsCurse");
    }

    // begin - Input methods

    public Cost getPayCosts() {
        return payCosts;
    }

    public void setPayCosts(final Cost abCost) {
        payCosts = abCost;
    }

    public SpellAbilityRestriction getRestrictions() {
        return restrictions;
    }

    public void setRestrictions(final SpellAbilityRestriction restrict) {
        restrictions = restrict;
    }

    /**
     * Shortcut to see how many activations there were this turn.
     */
    public int getActivationsThisTurn() {
        return restrictions.getNumberTurnActivations();
    }

    public SpellAbilityCondition getConditions() {
        return conditions;
    }

    public final void setConditions(final SpellAbilityCondition condition) {
        conditions = condition;
    }

    public List<Mana> getPayingMana() {
        return payingMana;
    }

    public final void clearManaPaid() {
        payingMana.clear();
    }

    public List<SpellAbility> getPayingManaAbilities() {
        return paidAbilities;
    }

    // Combined PaidLists
    public HashMap<String, CardCollection> getPaidHash() {
        return paidLists;
    }

    public void setPaidHash(final HashMap<String, CardCollection> hash) {
        paidLists = hash;
    }

    public CardCollection getPaidList(final String str) {
        return paidLists.get(str);
    }

    public void addCostToHashList(final Card c, final String str) {
        if (!paidLists.containsKey(str)) {
            paidLists.put(str, new CardCollection());
        }
        paidLists.get(str).add(c);
    }

    public void resetPaidHash() {
        paidLists = new HashMap<String, CardCollection>();
    }

    public Iterable<OptionalCost> getOptionalCosts() {
        return optionalCosts;
    }

    public final void addOptionalCost(OptionalCost cost) {
        // Optional costs are added to swallow copies of original SAs,
        // Thus, to protect the original's set from changes, we make a copy right here.
        optionalCosts = EnumSet.copyOf(optionalCosts);
        optionalCosts.add(cost);
    }

    public boolean isBuyBackAbility() {
        return isOptionalCostPaid(OptionalCost.Buyback);
    }

    public boolean isKicked() {
        return isOptionalCostPaid(OptionalCost.Kicker1) || isOptionalCostPaid(OptionalCost.Kicker2);
    }

    public boolean isOptionalCostPaid(OptionalCost cost) {
        SpellAbility saRoot = getRootAbility();
        return saRoot.optionalCosts.contains(cost);
    }

    public HashMap<String, Object> getTriggeringObjects() {
        return triggeringObjects;
    }

    public void setTriggeringObjects(final HashMap<String, Object> triggeredObjects) {
        triggeringObjects = triggeredObjects;
    }

    public Object getTriggeringObject(final String type) {
        return triggeringObjects.get(type);
    }

    public void setTriggeringObject(final String type, final Object o) {
        triggeringObjects.put(type, o);
    }

    public boolean hasTriggeringObject(final String type) {
        return triggeringObjects.containsKey(type);
    }

    public void resetTriggeringObjects() {
        triggeringObjects = new HashMap<String, Object>();
    }

    public List<Object> getTriggerRemembered() {
        return triggerRemembered;
    }

    public void setTriggerRemembered(List<Object> list) {
        triggerRemembered = list;
    }

    public void resetTriggerRemembered() {
        triggerRemembered = new ArrayList<Object>();
    }

    public HashMap<String, Object> getReplacingObjects() {
        return replacingObjects;
    }

    public Object getReplacingObject(final String type) {
        final Object res = replacingObjects.get(type);
        return res;
    }

    public void setReplacingObject(final String type, final Object o) {
        replacingObjects.put(type, o);
    }

    public void resetOnceResolved() {
        resetPaidHash();
        resetTargets();
        resetTriggeringObjects();
        resetTriggerRemembered();

        // Clear SVars
        for (final String store : Card.getStorableSVars()) {
            final String value = hostCard.getSVar(store);
            if (value.length() > 0) {
                hostCard.setSVar(store, "");
            }
        }
    }

    public String getStackDescription() {
        String text = getHostCard().getView().getText();
        if (stackDescription.equals(text)) {
            return getHostCard().getName() + " - " + text;
        }
        return stackDescription.replaceAll("CARDNAME", getHostCard().getName());
    }

    public void setStackDescription(final String s) {
        originalStackDescription = s;
        stackDescription = originalStackDescription;
        if (StringUtils.isEmpty(description) && StringUtils.isEmpty(hostCard.getView().getText())) {
            setDescription(s);
        }
    }

    // setDescription() includes mana cost and everything like
    // "G, tap: put target creature from your hand onto the battlefield"
    public String getDescription() {
        return description;
    }

    public void setDescription(final String s) {
        originalDescription = s;
        description = originalDescription;
    }

    /** {@inheritDoc} */
    @Override
    public final String toString() {
        if (isSuppressed()) {
            return "";
        }
        return toUnsuppressedString();
    }

    public String toUnsuppressedString() {
        final StringBuilder sb = new StringBuilder();
        SpellAbility node = this;

        while (node != null) {
            if (node != this) {
                sb.append(" ");
            }
            if (node.getHostCard() != null) {
                sb.append(node.getDescription().replace("CARDNAME", node.getHostCard().getName()));
            }
            node = node.getSubAbility();
        }
        return sb.toString();
    }

    public AbilitySub getSubAbility() {
        return subAbility;
    }

    public void setSubAbility(final AbilitySub subAbility0) {
        if (subAbility == subAbility0) {
            return;
        }
        subAbility = subAbility0;
        if (subAbility != null) {
            subAbility.setParent(this);
        }
        view.updateDescription(this); //description changes when sub-abilities change
    }

    public void appendSubAbility(final AbilitySub toAdd) {
        SpellAbility tailend = this;
        while (tailend.getSubAbility() != null) {
            tailend = tailend.getSubAbility();
        }
        tailend.setSubAbility(toAdd);
    }

    public boolean isBasicSpell() {
        return basicSpell && !isFlashBackAbility() && !isBuyBackAbility();
    }

    public void setBasicSpell(final boolean basicSpell0) {
        basicSpell = basicSpell0;
    }

    public void setFlashBackAbility(final boolean flashBackAbility0) {
        flashBackAbility = flashBackAbility0;
    }

    public boolean isFlashBackAbility() {
        return flashBackAbility;
    }

    public boolean isOutlast() {
        return outlast;
    }

    public void setOutlast(boolean outlast0) {
        outlast = outlast0;
    }

    public boolean isLeftSplit() {
        return splitSide == SplitSide.LEFT;
    }

    public boolean isRightSplit() {
        return splitSide == SplitSide.RIGHT;
    }

    public void setNoSplit() {
        splitSide = null;
    }

    public void setLeftSplit() {
        splitSide = SplitSide.LEFT;
    }

    public void setRightSplit() {
        splitSide = SplitSide.RIGHT;
    }

    public SpellAbility copy() {
        SpellAbility clone = null;
        try {
            clone = (SpellAbility) clone();
            clone.id = nextId();
            clone.view = new SpellAbilityView(clone);
            if (clone.hostCard != null && clone.hostCard.getGame() != null) {
                clone.hostCard.getGame().addSpellAbility(clone.id, clone);
            }
        } catch (final CloneNotSupportedException e) {
            System.err.println(e);
        }
        return clone;
    }

    public SpellAbility copyWithNoManaCost() {
        final SpellAbility newSA = copy();
        newSA.setPayCosts(newSA.getPayCosts().copyWithNoMana());
        newSA.setDescription(newSA.getDescription() + " (without paying its mana cost)");
        return newSA;
    }

    public SpellAbility copyWithDefinedCost(Cost abCost) {
        final SpellAbility newSA = copy();
        newSA.setPayCosts(abCost);
        return newSA;
    }

    public boolean isTrigger() {
        return trigger;
    }

    public void setTrigger(final boolean trigger0) {
        trigger = trigger0;
    }

    public boolean isOptionalTrigger() {
        return optionalTrigger;
    }

    public void setOptionalTrigger(final boolean optrigger) {
        optionalTrigger = optrigger;
    }

    public int getSourceTrigger() {
        return sourceTrigger;
    }

    public void setSourceTrigger(final int id) {
        sourceTrigger = id;
    }

    public boolean isReplacementAbility() {
        return replacementAbility;
    }

    public void setReplacementAbility(boolean replacement) {
        replacementAbility = replacement;
    }

    public boolean isMandatory() {
        return false;
    }

    public final boolean canTarget(final GameObject entity) {
        final TargetRestrictions tr = getTargetRestrictions();

        // Restriction related to this ability
        if (tr != null) {
            if (tr.isUniqueTargets() && getUniqueTargets().contains(entity))
                return false;

            // If the cards must have a specific controller
            if (hasParam("TargetsWithDefinedController") && entity instanceof Card) {
                final Card c = (Card) entity;
                List<Player> pl = AbilityUtils.getDefinedPlayers(getHostCard(),
                        getParam("TargetsWithDefinedController"), this);
                if (pl == null || !pl.contains(c.getController())) {
                    return false;
                }
            }
            if (hasParam("TargetsWithSharedCardType") && entity instanceof Card) {
                final Card c = (Card) entity;
                CardCollection pl = AbilityUtils.getDefinedCards(getHostCard(),
                        getParam("TargetsWithSharedCardType"), this);
                for (final Card crd : pl) {
                    if (!c.sharesCardTypeWith(crd)) {
                        return false;
                    }
                }
            }
            if (hasParam("TargetsWithSharedTypes") && entity instanceof Card) {
                final Card c = (Card) entity;
                final SpellAbility parent = getParentTargetingCard();
                final Card parentTargeted = parent != null ? parent.getTargetCard() : null;
                if (parentTargeted == null) {
                    return false;
                }
                boolean flag = false;
                for (final String type : getParam("TargetsWithSharedTypes").split(",")) {
                    if (c.getType().hasStringType(type) && parentTargeted.getType().hasStringType(type)) {
                        flag = true;
                        break;
                    }
                }
                if (!flag) {
                    return false;
                }
            }
            if (hasParam("TargetsWithRelatedProperty") && entity instanceof Card) {
                final String related = getParam("TargetsWithRelatedProperty");
                final Card c = (Card) entity;
                Card parentTarget = null;
                for (GameObject o : getUniqueTargets()) {
                    if (o instanceof Card) {
                        parentTarget = (Card) o;
                        break;
                    }
                }
                if (parentTarget == null) {
                    return false;
                }
                switch (related) {
                case "LEPower":
                    return c.getNetPower() <= parentTarget.getNetPower();
                case "LECMC":
                    return c.getCMC() <= parentTarget.getCMC();
                }
            }

            if (hasParam("TargetingPlayerControls") && entity instanceof Card) {
                final Card c = (Card) entity;
                if (!c.getController().equals(targetingPlayer)) {
                    return false;
                }
            }

            if (hasParam("MaxTotalTargetCMC") && entity instanceof Card) {
                final Card c = (Card) entity;
                if (c.getCMC() > tr.getMaxTotalCMC(c, this)) {
                    return false;
                }
            }

            String[] validTgt = tr.getValidTgts();
            if (entity instanceof GameEntity
                    && !((GameEntity) entity).isValid(validTgt, getActivatingPlayer(), getHostCard())) {
                return false;
            }
        }

        // Restrictions coming from target
        return entity.canBeTargetedBy(this);
    }

    // is this a wrapping ability (used by trigger abilities)
    public boolean isWrapper() {
        return false;
    }

    public final boolean isDelve() {
        return delve;
    }

    public final void setDelve(final boolean isDelve0) {
        delve = isDelve0;
    }

    public final boolean isDash() {
        return dash;
    }

    public final void setDash(final boolean isDash) {
        dash = isDash;
    }

    public CardCollection getTappedForConvoke() {
        return tappedForConvoke;
    }

    public void addTappedForConvoke(final Card c) {
        if (tappedForConvoke == null) {
            tappedForConvoke = new CardCollection();
        }
        tappedForConvoke.add(c);
    }

    public void clearTappedForConvoke() {
        if (tappedForConvoke != null) {
            tappedForConvoke.clear();
        }
    }

    public boolean isOffering() {
        return offering;
    }

    public void setIsOffering(final boolean bOffering) {
        offering = bOffering;
    }

    public Card getSacrificedAsOffering() { //for Patron offering
        return sacrificedAsOffering;
    }

    public void setSacrificedAsOffering(final Card c) {
        sacrificedAsOffering = c;
    }

    public void resetSacrificedAsOffering() {
        sacrificedAsOffering = null;
    }

    public CardCollection getSplicedCards() {
        return splicedCards;
    }

    public void setSplicedCards(CardCollection splicedCards0) {
        splicedCards = splicedCards0;
    }

    public void addSplicedCards(Card splicedCard) {
        if (splicedCards == null) {
            splicedCards = new CardCollection();
        }
        splicedCards.add(splicedCard);
    }

    public CardCollection knownDetermineDefined(final String defined) {
        final CardCollection ret = new CardCollection();
        final CardCollection list = AbilityUtils.getDefinedCards(getHostCard(), defined, this);
        final Game game = getActivatingPlayer().getGame();

        for (final Card c : list) {
            final Card actualCard = game.getCardState(c);
            ret.add(actualCard);
        }
        return ret;
    }

    public SpellAbility getRootAbility() {
        SpellAbility parent = this;
        while (null != parent.getParent()) {
            parent = parent.getParent();
        }
        return parent;
    }

    public SpellAbility getParent() {
        return null;
    }

    public boolean isUndoable() {
        return undoable && payCosts.isUndoable() && getHostCard().isInPlay();
    }

    public boolean undo() {
        if (isUndoable() && getActivatingPlayer().getManaPool().accountFor(getManaPart())) {
            payCosts.refundPaidCost(hostCard);
        }
        return false;
    }

    public void setUndoable(boolean b) {
        undoable = b;
    }

    public boolean isCopied() {
        return isCopied;
    }

    public void setCopied(boolean isCopied0) {
        isCopied = isCopied0;
    }

    /**
     * Returns whether variable was present in the announce list.
     */
    public boolean isAnnouncing(String variable) {
        String announce = getParam("Announce");
        if (StringUtils.isBlank(announce)) {
            return false;
        }

        String[] announcedOnes = TextUtil.split(announce, ',');
        for (String a : announcedOnes) {
            if (a.trim().equalsIgnoreCase(variable)) {
                return true;
            }
        }
        return false;
    }

    public void addAnnounceVar(String variable) {
        String announce = getParam("Announce");
        if (StringUtils.isBlank(announce)) {
            mapParams.put("Announce", variable);
            return;
        }
        String[] announcedOnes = TextUtil.split(announce, ',');
        for (String a : announcedOnes) {
            if (a.trim().equalsIgnoreCase(variable)) {
                return; //don't add announce variable that already exists
            }
        }
        mapParams.put("Announce", announce + ";" + variable);
    }

    public boolean isXCost() {
        CostPartMana cm = payCosts != null ? getPayCosts().getCostMana() : null;
        return cm != null && cm.getAmountOfX() > 0;
    }

    public boolean isBasicLandAbility() {
        return basicLandAbility;
    }

    public void setBasicLandAbility(boolean basicLandAbility0) {
        basicLandAbility = basicLandAbility0;
    }

    @Override
    public boolean canBeTargetedBy(SpellAbility sa) {
        return sa.canTargetSpellAbility(this);
    }

    public boolean usesTargeting() {
        return targetRestrictions != null;
    }

    public TargetRestrictions getTargetRestrictions() {
        return targetRestrictions;
    }

    /////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    //
    // THE CODE BELOW IS RELATED TO TARGETING. It might be extracted to other class from here
    //
    /////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    public void setTargetRestrictions(final TargetRestrictions tgt) {
        targetRestrictions = tgt;
    }

    /**
     * Gets the chosen target.
     *
     * @return the chosenTarget
     */
    public TargetChoices getTargets() {
        return targetChosen;
    }

    public void setTargets(TargetChoices targets) {
        targetChosen = targets;
    }

    public void resetTargets() {
        targetChosen = new TargetChoices();
    }

    /**
     * Reset the first target.
     *
     */
    public void resetFirstTarget(GameObject c, SpellAbility originalSA) {
        SpellAbility sa = this;
        while (sa != null) {
            if (sa.targetRestrictions != null) {
                sa.targetChosen = new TargetChoices();
                sa.targetChosen.add(c);
                if (!originalSA.targetRestrictions.getDividedMap().isEmpty()) {
                    sa.targetRestrictions.addDividedAllocation(c,
                            Iterables.getFirst(originalSA.targetRestrictions.getDividedMap().values(), null));
                }
                break;
            }
            sa = sa.subAbility;
        }
    }

    /**
     * <p>
     * getAllTargetChoices.
     * </p>
     *
     * @return a {@link java.util.ArrayList} object.
     * @since 1.0.15
     */
    public final List<TargetChoices> getAllTargetChoices() {
        final List<TargetChoices> res = new ArrayList<TargetChoices>();

        SpellAbility sa = getRootAbility();
        if (sa.getTargetRestrictions() != null) {
            res.add(sa.getTargets());
        }
        while (sa.getSubAbility() != null) {
            sa = sa.getSubAbility();

            if (sa.getTargetRestrictions() != null) {
                res.add(sa.getTargets());
            }
        }

        return res;
    }

    public Card getTargetCard() {
        return targetChosen.getFirstTargetedCard();
    }

    /**
     * <p>
     * Setter for the field <code>targetCard</code>.
     * </p>
     *
     * @param card
     *            a {@link forge.game.card.Card} object.
     */
    public void setTargetCard(final Card card) {
        if (card == null) {
            System.out.println(getHostCard() + " - SpellAbility.setTargetCard() called with null for target card.");
            return;
        }

        resetTargets();
        targetChosen.add(card);
        setStackDescription(getHostCard().getName() + " - targeting " + card);
    }

    /**
     * <p>
     * findTargetCards.
     * </p>
     *
     * @return a {@link forge.game.spellability.SpellAbility} object.
     */
    public CardCollectionView findTargetedCards() {
        // First search for targeted cards associated with current ability
        if (targetChosen.isTargetingAnyCard()) {
            return targetChosen.getTargetCards();
        }

        // Next search for source cards of targeted SAs associated with current ability
        if (targetChosen.isTargetingAnySpell()) {
            CardCollection res = new CardCollection();
            for (final SpellAbility ability : targetChosen.getTargetSpells()) {
                res.add(ability.getHostCard());
            }
            return res;
        }

        // Lastly Search parent SAs that targets a card
        SpellAbility parent = getParentTargetingCard();
        if (null != parent) {
            return parent.findTargetedCards();
        }

        // Lastly Search parent SAs that targets an SA
        parent = getParentTargetingSA();
        if (null != parent) {
            return parent.findTargetedCards();
        }

        return CardCollection.EMPTY;
    }

    public SpellAbility getSATargetingCard() {
        return targetChosen.isTargetingAnyCard() ? this : getParentTargetingCard();
    }

    public SpellAbility getParentTargetingCard() {
        SpellAbility parent = getParent();
        if (parent instanceof WrappedAbility) {
            parent = ((WrappedAbility) parent).getWrappedAbility();
        }
        while (parent != null) {
            if (parent.targetChosen.isTargetingAnyCard()) {
                return parent;
            }
            parent = parent.getParent();
        }
        return null;
    }

    public SpellAbility getSATargetingSA() {
        return targetChosen.isTargetingAnySpell() ? this : getParentTargetingSA();
    }

    public SpellAbility getParentTargetingSA() {
        SpellAbility parent = getParent();
        while (parent != null) {
            if (parent.targetChosen.isTargetingAnySpell())
                return parent;
            parent = parent.getParent();
        }
        return null;
    }

    public SpellAbility getSATargetingPlayer() {
        return targetChosen.isTargetingAnyPlayer() ? this : getParentTargetingPlayer();
    }

    public SpellAbility getParentTargetingPlayer() {
        SpellAbility parent = getParent();
        while (parent != null) {
            if (parent.getTargets().isTargetingAnyPlayer())
                return parent;
            parent = parent.getParent();
        }
        return null;
    }

    public final List<GameObject> getUniqueTargets() {
        final List<GameObject> targets = new ArrayList<GameObject>();
        SpellAbility child = getParent();
        while (child != null) {
            if (child.getTargetRestrictions() != null) {
                Iterables.addAll(targets, child.getTargets().getTargets());
            }
            child = child.getParent();
        }
        return targets;
    }

    public boolean canTargetSpellAbility(final SpellAbility topSA) {
        final TargetRestrictions tgt = getTargetRestrictions();

        if (hasParam("TargetType")
                && !topSA.isValid(getParam("TargetType").split(","), getActivatingPlayer(), getHostCard())) {
            return false;
        }

        final String splitTargetRestrictions = tgt.getSAValidTargeting();
        if (splitTargetRestrictions != null) {
            // TODO Ensure that spells with subabilities are processed correctly

            TargetChoices matchTgt = topSA.getTargets();

            if (matchTgt == null) {
                return false;
            }

            boolean result = false;

            for (final GameObject o : matchTgt.getTargets()) {
                if (o.isValid(splitTargetRestrictions.split(","), getActivatingPlayer(), getHostCard())) {
                    result = true;
                    break;
                }
            }

            // spells with subabilities
            if (!result) {
                AbilitySub subAb = topSA.getSubAbility();
                while (subAb != null) {
                    if (subAb.getTargetRestrictions() != null) {
                        matchTgt = subAb.getTargets();
                        if (matchTgt == null) {
                            continue;
                        }
                        for (final GameObject o : matchTgt.getTargets()) {
                            if (o.isValid(splitTargetRestrictions.split(","), getActivatingPlayer(),
                                    getHostCard())) {
                                result = true;
                                break;
                            }
                        }
                    }
                    subAb = subAb.getSubAbility();
                }
            }

            if (!result) {
                return false;
            }
        }

        if (tgt.isSingleTarget()) {
            Set<GameObject> targets = new HashSet<>();
            for (TargetChoices tc : topSA.getAllTargetChoices()) {
                targets.addAll(tc.getTargets());
                if (targets.size() > 1) {
                    // As soon as we get more than one, bail out
                    return false;
                }
            }
            if (targets.size() != 1) {
                // Make sure that there actually is one target
                return false;
            }
        }

        return topSA.getHostCard().isValid(tgt.getValidTgts(), getActivatingPlayer(), getHostCard());
    }

    // Takes one argument like Permanent.Blue+withFlying
    @Override
    public final boolean isValid(final String restriction, final Player sourceController, final Card source) {
        // Inclusive restrictions are Card types
        final String[] incR = restriction.split("\\.", 2);

        if (incR[0].equals("Spell")) {
            if (!isSpell()) {
                return false;
            }
        } else if (incR[0].equals("Triggered")) {
            if (!isTrigger()) {
                return false;
            }
        } else if (incR[0].equals("Activated")) {
            if (!(this instanceof AbilityActivated)) {
                return false;
            }
        } else { //not a spell/ability type
            return false;
        }

        if (incR.length > 1) {
            final String excR = incR[1];
            final String[] exR = excR.split("\\+"); // Exclusive Restrictions are ...
            for (int j = 0; j < exR.length; j++) {
                if (!hasProperty(exR[j], sourceController, source)) {
                    return false;
                }
            }
        }
        return true;
    }

    // Takes arguments like Blue or withFlying
    @Override
    public boolean hasProperty(final String property, final Player sourceController, final Card source) {
        return true;
    }

    // Methods enabling multiple instances of conspire
    public void addConspireInstance() {
        conspireInstances++;
    }

    public void subtractConspireInstance() {
        conspireInstances--;
    }

    public int getConspireInstances() {
        return conspireInstances;
    } // End of Conspire methods

    public boolean isCumulativeupkeep() {
        return cumulativeupkeep;
    }

    public void setCumulativeupkeep(boolean cumulativeupkeep0) {
        cumulativeupkeep = cumulativeupkeep0;
    }

    // Return whether this spell tracks what color mana is spent to cast it for the sake of the effect
    public boolean tracksManaSpent() {
        if (hostCard == null || hostCard.getRules() == null) {
            return false;
        }

        if (hostCard.hasKeyword("Sunburst")) {
            return true;
        }
        String text = hostCard.getRules().getOracleText();
        if (isSpell() && text.contains("was spent to cast")) {
            return true;
        }
        if (isAbility() && text.contains("mana spent to pay")) {
            return true;
        }
        return false;
    }

    public void checkActivationResloveSubs() {
        if (hasParam("ActivationNumberSacrifice")) {
            String comp = getParam("ActivationNumberSacrifice");
            int right = Integer.parseInt(comp.substring(2));
            int activationNum = getRestrictions().getNumberTurnActivations();
            if (Expressions.compare(activationNum, comp, right)) {
                SpellAbility deltrig = AbilityFactory.getAbility(hostCard.getSVar(getParam("ActivationResolveSub")),
                        hostCard);
                deltrig.setActivatingPlayer(activatingPlayer);
                AbilityUtils.resolve(deltrig);
            }
        }
    }

    public void setTotalManaSpent(int totManaSpent) {
        totalManaSpent = totManaSpent;
    }

    public int getTotalManaSpent() {
        return totalManaSpent;
    }

    public List<AbilitySub> getChosenList() {
        return chosenList;
    }

    public void setChosenList(List<AbilitySub> choices) {
        chosenList = choices;
    }

    @Override
    public void changeText() {
        super.changeText();

        if (targetRestrictions != null) {
            targetRestrictions.applyTargetTextChanges(this);
        }

        if (getPayCosts() != null) {
            getPayCosts().applyTextChangeEffects(this);
        }

        stackDescription = AbilityUtils.applyDescriptionTextChangeEffects(originalStackDescription, this);
        description = AbilityUtils.applyDescriptionTextChangeEffects(originalDescription, this);

        if (subAbility != null) {
            subAbility.changeText();
        }
    }

    @Override
    public void setIntrinsic(boolean i) {
        super.setIntrinsic(i);
        if (subAbility != null) {
            subAbility.setIntrinsic(i);
        }
    }

    public SpellAbilityView getView() {
        view.updateHostCard(this);
        view.updateDescription(this);
        view.updateCanPlay(this);
        view.updatePromptIfOnlyPossibleAbility(this);
        return view;
    }
}