@Override
  public void resolve(SpellAbility sa) {
    CardCollectionView tgtCards;
    final Game game = sa.getActivatingPlayer().getGame();
    final Card source = sa.getHostCard();
    final boolean phaseInOrOut = sa.hasParam("PhaseInOrOutAll");

    if (sa.hasParam("AllValid")) {
      if (phaseInOrOut) {
        tgtCards = game.getCardsIncludePhasingIn(ZoneType.Battlefield);
      } else {
        tgtCards = game.getCardsIn(ZoneType.Battlefield);
      }
      tgtCards = AbilityUtils.filterListByType(tgtCards, sa.getParam("AllValid"), sa);
    } else if (sa.hasParam("Defined")) {
      tgtCards = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa);
    } else {
      tgtCards = getTargetCards(sa);
    }
    if (phaseInOrOut) { // Time and Tide
      for (final Card tgtC : tgtCards) {
        tgtC.phase();
      }
    } else { // just phase out
      for (final Card tgtC : tgtCards) {
        if (!tgtC.isPhasedOut()) {
          tgtC.phase();
        }
      }
    }
  }
Example #2
0
  private boolean phasesUnpreferredTargeting(
      final Game game, final SpellAbility sa, final boolean mandatory) {
    final Card source = sa.getHostCard();
    final TargetRestrictions tgt = sa.getTargetRestrictions();

    CardCollectionView list = game.getCardsIn(ZoneType.Battlefield);
    list =
        CardLists.getTargetableCards(
            CardLists.getValidCards(list, tgt.getValidTgts(), source.getController(), source), sa);

    return false;
  }
Example #3
0
 // returns a List<Card> that is a subset of list with cards that share a name
 // with a permanent on the battlefield
 private static CardCollection sharesNameWithCardOnBattlefield(
     final Game game, final List<Card> list) {
   final CardCollection toReturn = new CardCollection();
   final CardCollectionView play = game.getCardsIn(ZoneType.Battlefield);
   for (final Card c : list) {
     for (final Card p : play) {
       if (p.getName().equals(c.getName()) && !toReturn.contains(c)) {
         toReturn.add(c);
       }
     }
   }
   return toReturn;
 }
Example #4
0
 public boolean checkOtherRestrictions() {
   final Card source = this.getHostCard();
   Player activator = getActivatingPlayer();
   final Game game = activator.getGame();
   // CantBeCast static abilities
   final CardCollection allp =
       new CardCollection(game.getCardsIn(ZoneType.listValueOf("Battlefield,Command")));
   allp.add(source);
   for (final Card ca : allp) {
     final FCollectionView<StaticAbility> staticAbilities = ca.getStaticAbilities();
     for (final StaticAbility stAb : staticAbilities) {
       if (stAb.applyAbility("CantBeCast", source, activator)) {
         return false;
       }
     }
   }
   return true;
 }
Example #5
0
  @Override
  public PaymentDecision visit(CostPutCardToLib cost) {
    Integer c = cost.convertAmount();
    final Game game = player.getGame();
    List<Card> chosen = new ArrayList<Card>();
    List<Card> list;

    if (cost.isSameZone()) {
      list = new ArrayList<Card>(game.getCardsIn(cost.getFrom()));
    } else {
      list = new ArrayList<Card>(player.getCardsIn(cost.getFrom()));
    }

    if (c == null) {
      final String sVar = ability.getSVar(cost.getAmount());
      // Generalize cost
      if (sVar.equals("XChoice")) {
        return null;
      }

      c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
    }

    list = CardLists.getValidCards(list, cost.getType().split(";"), player, source);

    if (cost.isSameZone()) {
      // Jotun Grunt
      // TODO: improve AI
      final List<Player> players = game.getPlayers();
      for (Player p : players) {
        List<Card> enoughType = CardLists.filter(list, CardPredicates.isOwner(p));
        if (enoughType.size() >= c) {
          chosen.addAll(enoughType);
          break;
        }
      }
      chosen = chosen.subList(0, c);
    } else {
      chosen =
          ComputerUtil.choosePutToLibraryFrom(
              player, cost.getFrom(), cost.getType(), source, ability.getTargetCard(), c);
    }
    return chosen.isEmpty() ? null : PaymentDecision.card(chosen);
  }
Example #6
0
  @Override
  public void resolve(SpellAbility sa) {
    final Card host = sa.getHostCard();
    final Player player = sa.getActivatingPlayer();
    final Game game = player.getGame();

    // make list of creatures that controller has on Battlefield
    CardCollectionView choices = game.getCardsIn(ZoneType.Battlefield);
    choices = CardLists.getValidCards(choices, "Creature.YouCtrl", host.getController(), host);

    // if no creatures on battlefield, cannot encoded
    if (choices.isEmpty()) {
      return;
    }
    // Handle choice of whether or not to encoded

    final StringBuilder sb = new StringBuilder();
    sb.append("Do you want to exile " + host + " and encode it onto a creature you control?");
    if (!player.getController().confirmAction(sa, null, sb.toString())) {
      return;
    }

    // move host card to exile
    Card movedCard = game.getAction().moveTo(ZoneType.Exile, host);

    // choose a creature
    Card choice =
        player
            .getController()
            .chooseSingleEntityForEffect(
                choices, sa, "Choose a creature you control to encode ", true);

    if (choice == null) {
      return;
    }

    StringBuilder codeLog = new StringBuilder();
    codeLog.append("Encoding ").append(host.toString()).append(" to ").append(choice.toString());
    game.getGameLog().add(GameLogEntryType.STACK_RESOLVE, codeLog.toString());

    // store hostcard in encoded array
    choice.addEncodedCard(movedCard);

    // add trigger
    final int numEncoded = choice.getEncodedCards().size();
    final StringBuilder cipherTrigger = new StringBuilder();
    cipherTrigger
        .append(
            "Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Player | Execute$ PlayEncoded")
        .append(numEncoded);
    cipherTrigger.append(" | CombatDamage$ True | OptionalDecider$ You | TriggerDescription$ ");
    cipherTrigger.append(
        "Whenever CARDNAME deals combat damage to a player, its controller may cast a copy of ");
    cipherTrigger.append(movedCard).append(" without paying its mana cost.");
    final String abName = "PlayEncoded" + numEncoded;
    final String abString =
        "AB$ Play | Cost$ 0 | Encoded$ " + numEncoded + " | WithoutManaCost$ True | CopyCard$ True";
    final Trigger parsedTrigger =
        TriggerHandler.parseTrigger(cipherTrigger.toString(), choice, false);
    choice.addTrigger(parsedTrigger);
    choice.setSVar(abName, abString);
    return;
  }
Example #7
0
  private boolean pumpMandatoryTarget(
      final Player ai, final SpellAbility sa, final boolean mandatory) {
    final Game game = ai.getGame();
    List<Card> list = game.getCardsIn(ZoneType.Battlefield);
    final TargetRestrictions tgt = sa.getTargetRestrictions();
    final Player opp = ai.getOpponent();
    list =
        CardLists.getValidCards(
            list, tgt.getValidTgts(), sa.getActivatingPlayer(), sa.getHostCard());
    list = CardLists.getTargetableCards(list, sa);

    if (list.size() < tgt.getMinTargets(sa.getHostCard(), sa)) {
      sa.resetTargets();
      return false;
    }

    // Remove anything that's already been targeted
    for (final Card c : sa.getTargets().getTargetCards()) {
      list.remove(c);
    }

    List<Card> pref;
    List<Card> forced;
    final Card source = sa.getHostCard();

    if (sa.isCurse()) {
      pref = CardLists.filterControlledBy(list, opp);
      forced = CardLists.filterControlledBy(list, ai);
    } else {
      pref = CardLists.filterControlledBy(list, ai);
      forced = CardLists.filterControlledBy(list, opp);
    }

    while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(source, sa)) {
      if (pref.isEmpty()) {
        break;
      }

      Card c;
      if (CardLists.getNotType(pref, "Creature").isEmpty()) {
        c = ComputerUtilCard.getBestCreatureAI(pref);
      } else {
        c = ComputerUtilCard.getMostExpensivePermanentAI(pref, sa, true);
      }

      pref.remove(c);

      sa.getTargets().add(c);
    }

    while (sa.getTargets().getNumTargeted() < tgt.getMinTargets(source, sa)) {
      if (forced.isEmpty()) {
        break;
      }

      Card c;
      if (CardLists.getNotType(forced, "Creature").isEmpty()) {
        c = ComputerUtilCard.getWorstCreatureAI(forced);
      } else {
        c = ComputerUtilCard.getCheapestPermanentAI(forced, sa, true);
      }

      forced.remove(c);

      sa.getTargets().add(c);
    }

    if (sa.getTargets().getNumTargeted() < tgt.getMinTargets(source, sa)) {
      sa.resetTargets();
      return false;
    }

    return true;
  } // pumpMandatoryTarget()
Example #8
0
  private boolean pumpTgtAI(
      final Player ai,
      final SpellAbility sa,
      final int defense,
      final int attack,
      final boolean mandatory) {
    final List<String> keywords =
        sa.hasParam("KW") ? Arrays.asList(sa.getParam("KW").split(" & ")) : new ArrayList<String>();
    final Game game = ai.getGame();
    final Card source = sa.getHostCard();

    if (!mandatory
        && !sa.isTrigger()
        && game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)
        && !(sa.isCurse() && defense < 0)
        && !this.containsNonCombatKeyword(keywords)
        && !sa.hasParam("UntilYourNextTurn")) {
      return false;
    }

    final Player opp = ai.getOpponent();
    final TargetRestrictions tgt = sa.getTargetRestrictions();
    sa.resetTargets();
    if (sa.hasParam("TargetingPlayer") && sa.getActivatingPlayer().equals(ai) && !sa.isTrigger()) {
      Player targetingPlayer =
          AbilityUtils.getDefinedPlayers(source, sa.getParam("TargetingPlayer"), sa).get(0);
      sa.setTargetingPlayer(targetingPlayer);
      return targetingPlayer.getController().chooseTargetsFor(sa);
    }

    List<Card> list = new ArrayList<Card>();
    if (sa.hasParam("AILogic")) {
      if (sa.getParam("AILogic").equals("HighestPower")) {
        list =
            CardLists.getValidCards(
                CardLists.filter(game.getCardsIn(ZoneType.Battlefield), Presets.CREATURES),
                tgt.getValidTgts(),
                ai,
                source);
        list = CardLists.getTargetableCards(list, sa);
        CardLists.sortByPowerDesc(list);
        if (!list.isEmpty()) {
          sa.getTargets().add(list.get(0));
          return true;
        } else {
          return false;
        }
      }
      if (sa.getParam("AILogic").equals("Fight") || sa.getParam("AILogic").equals("PowerDmg")) {
        final AbilitySub tgtFight = sa.getSubAbility();
        List<Card> aiCreatures = ai.getCreaturesInPlay();
        aiCreatures = CardLists.getTargetableCards(aiCreatures, sa);
        aiCreatures = ComputerUtil.getSafeTargets(ai, sa, aiCreatures);
        ComputerUtilCard.sortByEvaluateCreature(aiCreatures);
        // sort is suboptimal due to conflicting needs depending on game state:
        //  -deathtouch for deal damage
        //  -max power for damage to player
        //  -survivability for generic "fight"
        //  -no support for "heroic"

        List<Card> humCreatures = ai.getOpponent().getCreaturesInPlay();
        humCreatures = CardLists.getTargetableCards(humCreatures, tgtFight);
        ComputerUtilCard.sortByEvaluateCreature(humCreatures);
        if (humCreatures.isEmpty() || aiCreatures.isEmpty()) {
          return false;
        }
        int buffedAtk = attack, buffedDef = defense;
        for (Card humanCreature : humCreatures) {
          for (Card aiCreature : aiCreatures) {
            if (sa.isSpell()) { // heroic triggers adding counters
              for (Trigger t : aiCreature.getTriggers()) {
                if (t.getMode() == TriggerType.SpellCast) {
                  final Map<String, String> params = t.getMapParams();
                  if ("Card.Self".equals(params.get("TargetsValid"))
                      && "You".equals(params.get("ValidActivatingPlayer"))
                      && params.containsKey("Execute")) {
                    SpellAbility heroic =
                        AbilityFactory.getAbility(
                            aiCreature.getSVar(params.get("Execute")), aiCreature);
                    if ("Self".equals(heroic.getParam("Defined"))
                        && "P1P1".equals(heroic.getParam("CounterType"))) {
                      int amount =
                          AbilityUtils.calculateAmount(
                              aiCreature, heroic.getParam("CounterNum"), heroic);
                      buffedAtk += amount;
                      buffedDef += amount;
                    }
                    break;
                  }
                }
              }
            }
            if (sa.getParam("AILogic").equals("PowerDmg")) {
              if (FightAi.canKill(aiCreature, humanCreature, buffedAtk)) {
                sa.getTargets().add(aiCreature);
                tgtFight.getTargets().add(humanCreature);
                return true;
              }
            } else {
              if (FightAi.shouldFight(aiCreature, humanCreature, buffedAtk, buffedDef)) {
                sa.getTargets().add(aiCreature);
                tgtFight.getTargets().add(humanCreature);
                return true;
              }
            }
          }
        }
      }
      return false;
    } else if (sa.isCurse()) {
      if (sa.canTarget(opp)) {
        sa.getTargets().add(opp);
        return true;
      }
      list = this.getCurseCreatures(ai, sa, defense, attack, keywords);
    } else {
      if (!tgt.canTgtCreature()) {
        ZoneType zone = tgt.getZone().get(0);
        list = game.getCardsIn(zone);
      } else {
        list = this.getPumpCreatures(ai, sa, defense, attack, keywords);
      }
      if (sa.canTarget(ai)) {
        sa.getTargets().add(ai);
        return true;
      }
    }

    list = CardLists.getValidCards(list, tgt.getValidTgts(), ai, source);
    if (game.getStack().isEmpty()) {
      // If the cost is tapping, don't activate before declare
      // attack/block
      if (sa.getPayCosts() != null && sa.getPayCosts().hasTapCost()) {
        if (game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)
            && game.getPhaseHandler().isPlayerTurn(ai)) {
          list.remove(sa.getHostCard());
        }
        if (game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)
            && game.getPhaseHandler().isPlayerTurn(opp)) {
          list.remove(sa.getHostCard());
        }
      }
    }

    if (list.isEmpty()) {
      return mandatory && this.pumpMandatoryTarget(ai, sa, mandatory);
    }

    if (!sa.isCurse()) {
      // Don't target cards that will die.
      list = ComputerUtil.getSafeTargets(ai, sa, list);
    }

    while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(source, sa)) {
      Card t = null;
      // boolean goodt = false;

      if (list.isEmpty()) {
        if ((sa.getTargets().getNumTargeted() < tgt.getMinTargets(source, sa))
            || (sa.getTargets().getNumTargeted() == 0)) {
          if (mandatory) {
            return this.pumpMandatoryTarget(ai, sa, mandatory);
          }

          sa.resetTargets();
          return false;
        } else {
          // TODO is this good enough? for up to amounts?
          break;
        }
      }

      t = ComputerUtilCard.getBestAI(list);
      // option to hold removal instead only applies for single targeted removal
      if (!sa.isTrigger() && tgt.getMaxTargets(source, sa) == 1 && sa.isCurse()) {
        if (!ComputerUtilCard.useRemovalNow(sa, t, -defense, ZoneType.Graveyard)) {
          return false;
        }
      }
      sa.getTargets().add(t);
      list.remove(t);
    }

    return true;
  } // pumpTgtAI()
Example #9
0
  @Override
  public void resolve(final SpellAbility sa) {
    final Card host = sa.getHostCard();
    final Map<String, String> svars = host.getSVars();

    // AF specific sa
    int power = -1;
    if (sa.hasParam("Power")) {
      power = AbilityUtils.calculateAmount(host, sa.getParam("Power"), sa);
    }
    int toughness = -1;
    if (sa.hasParam("Toughness")) {
      toughness = AbilityUtils.calculateAmount(host, sa.getParam("Toughness"), sa);
    }
    final Game game = sa.getActivatingPlayer().getGame();

    // Every Animate event needs a unique time stamp
    final long timestamp = game.getNextTimestamp();

    final boolean permanent = sa.hasParam("Permanent");

    final CardType types = new CardType();
    if (sa.hasParam("Types")) {
      types.addAll(Arrays.asList(sa.getParam("Types").split(",")));
    }

    final CardType removeTypes = new CardType();
    if (sa.hasParam("RemoveTypes")) {
      removeTypes.addAll(Arrays.asList(sa.getParam("RemoveTypes").split(",")));
    }

    // allow ChosenType - overrides anything else specified
    if (types.hasSubtype("ChosenType")) {
      types.clear();
      types.add(host.getChosenType());
    }

    final List<String> keywords = new ArrayList<String>();
    if (sa.hasParam("Keywords")) {
      keywords.addAll(Arrays.asList(sa.getParam("Keywords").split(" & ")));
    }

    final List<String> removeKeywords = new ArrayList<String>();
    if (sa.hasParam("RemoveKeywords")) {
      removeKeywords.addAll(Arrays.asList(sa.getParam("RemoveKeywords").split(" & ")));
    }

    final List<String> hiddenKeywords = new ArrayList<String>();
    if (sa.hasParam("HiddenKeywords")) {
      hiddenKeywords.addAll(Arrays.asList(sa.getParam("HiddenKeywords").split(" & ")));
    }
    // allow SVar substitution for keywords
    for (int i = 0; i < keywords.size(); i++) {
      final String k = keywords.get(i);
      if (svars.containsKey(k)) {
        keywords.add(svars.get(k));
        keywords.remove(k);
      }
    }

    // colors to be added or changed to
    String tmpDesc = "";
    if (sa.hasParam("Colors")) {
      final String colors = sa.getParam("Colors");
      if (colors.equals("ChosenColor")) {
        tmpDesc = CardUtil.getShortColorsString(host.getChosenColors());
      } else {
        tmpDesc =
            CardUtil.getShortColorsString(new ArrayList<String>(Arrays.asList(colors.split(","))));
      }
    }
    final String finalDesc = tmpDesc;

    // abilities to add to the animated being
    final List<String> abilities = new ArrayList<String>();
    if (sa.hasParam("Abilities")) {
      abilities.addAll(Arrays.asList(sa.getParam("Abilities").split(",")));
    }
    // replacement effects to add to the animated being
    final List<String> replacements = new ArrayList<String>();
    if (sa.hasParam("Replacements")) {
      replacements.addAll(Arrays.asList(sa.getParam("Replacements").split(",")));
    }
    // triggers to add to the animated being
    final List<String> triggers = new ArrayList<String>();
    if (sa.hasParam("Triggers")) {
      triggers.addAll(Arrays.asList(sa.getParam("Triggers").split(",")));
    }

    // sVars to add to the animated being
    final List<String> sVars = new ArrayList<String>();
    if (sa.hasParam("sVars")) {
      sVars.addAll(Arrays.asList(sa.getParam("sVars").split(",")));
    }

    String valid = "";

    if (sa.hasParam("ValidCards")) {
      valid = sa.getParam("ValidCards");
    }

    CardCollectionView list;
    List<Player> tgtPlayers = getTargetPlayers(sa);

    if (!sa.usesTargeting() && !sa.hasParam("Defined")) {
      list = game.getCardsIn(ZoneType.Battlefield);
    } else {
      list = tgtPlayers.get(0).getCardsIn(ZoneType.Battlefield);
    }

    list = CardLists.getValidCards(list, valid.split(","), host.getController(), host);

    for (final Card c : list) {
      doAnimate(
          c,
          sa,
          power,
          toughness,
          types,
          removeTypes,
          finalDesc,
          keywords,
          removeKeywords,
          hiddenKeywords,
          timestamp);

      // give abilities
      final List<SpellAbility> addedAbilities = new ArrayList<SpellAbility>();
      if (abilities.size() > 0) {
        for (final String s : abilities) {
          final String actualAbility = host.getSVar(s);
          final SpellAbility grantedAbility = AbilityFactory.getAbility(actualAbility, c);
          addedAbilities.add(grantedAbility);
          c.addSpellAbility(grantedAbility);
        }
      }

      // remove abilities
      final List<SpellAbility> removedAbilities = new ArrayList<SpellAbility>();
      if (sa.hasParam("OverwriteAbilities") || sa.hasParam("RemoveAllAbilities")) {
        for (final SpellAbility ab : c.getSpellAbilities()) {
          if (ab.isAbility()) {
            c.removeSpellAbility(ab);
            removedAbilities.add(ab);
          }
        }
      }
      // give replacement effects
      final List<ReplacementEffect> addedReplacements = new ArrayList<ReplacementEffect>();
      if (replacements.size() > 0) {
        for (final String s : replacements) {
          final String actualReplacement = host.getSVar(s);
          final ReplacementEffect parsedReplacement =
              ReplacementHandler.parseReplacement(actualReplacement, c, false);
          addedReplacements.add(c.addReplacementEffect(parsedReplacement));
        }
      }
      // Grant triggers
      final List<Trigger> addedTriggers = new ArrayList<Trigger>();
      if (triggers.size() > 0) {
        for (final String s : triggers) {
          final String actualTrigger = host.getSVar(s);
          final Trigger parsedTrigger = TriggerHandler.parseTrigger(actualTrigger, c, false);
          addedTriggers.add(c.addTrigger(parsedTrigger));
        }
      }

      // suppress triggers from the animated card
      final List<Trigger> removedTriggers = new ArrayList<Trigger>();
      if (sa.hasParam("OverwriteTriggers") || sa.hasParam("RemoveAllAbilities")) {
        final FCollectionView<Trigger> triggersToRemove = c.getTriggers();
        for (final Trigger trigger : triggersToRemove) {
          trigger.setSuppressed(true);
          removedTriggers.add(trigger);
        }
      }

      // suppress static abilities from the animated card
      final List<StaticAbility> removedStatics = new ArrayList<StaticAbility>();
      if (sa.hasParam("OverwriteStatics") || sa.hasParam("RemoveAllAbilities")) {
        final FCollectionView<StaticAbility> staticsToRemove = c.getStaticAbilities();
        for (final StaticAbility stAb : staticsToRemove) {
          stAb.setTemporarilySuppressed(true);
          removedStatics.add(stAb);
        }
      }

      // suppress static abilities from the animated card
      final List<ReplacementEffect> removedReplacements = new ArrayList<ReplacementEffect>();
      if (sa.hasParam("OverwriteReplacements") || sa.hasParam("RemoveAllAbilities")) {
        final FCollectionView<ReplacementEffect> replacementsToRemove = c.getReplacementEffects();
        for (final ReplacementEffect re : replacementsToRemove) {
          re.setTemporarilySuppressed(true);
          removedReplacements.add(re);
        }
      }

      // give sVars
      if (sVars.size() > 0) {
        for (final String s : sVars) {
          final String actualsVar = host.getSVar(s);
          c.setSVar(s, actualsVar);
        }
      }
      game.fireEvent(new GameEventCardStatsChanged(c));

      final GameCommand unanimate =
          new GameCommand() {
            private static final long serialVersionUID = -5861759814760561373L;

            @Override
            public void run() {
              doUnanimate(
                  c,
                  sa,
                  finalDesc,
                  hiddenKeywords,
                  addedAbilities,
                  addedTriggers,
                  addedReplacements,
                  false,
                  removedAbilities,
                  timestamp);

              // give back suppressed triggers
              for (final Trigger t : removedTriggers) {
                t.setSuppressed(false);
              }

              // give back suppressed static abilities
              for (final StaticAbility s : removedStatics) {
                s.setTemporarilySuppressed(false);
              }

              // give back suppressed replacement effects
              for (final ReplacementEffect re : removedReplacements) {
                re.setTemporarilySuppressed(false);
              }
              game.fireEvent(new GameEventCardStatsChanged(c));
            }
          };

      if (!permanent) {
        if (sa.hasParam("UntilEndOfCombat")) {
          game.getEndOfCombat().addUntil(unanimate);
        } else {
          game.getEndOfTurn().addUntil(unanimate);
        }
      }
    }
  } // animateAllResolve
  /* (non-Javadoc)
   * @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
   */
  @Override
  protected boolean canPlayAI(final Player ai, SpellAbility sa) {
    // TODO: AI Support! Currently this is copied from AF ChooseCard.
    //       When implementing AI, I believe AI also needs to be made aware of the damage sources
    // chosen
    //       to be prevented (e.g. so the AI doesn't attack with a creature that will not deal any
    // damage
    //       to the player because a CoP was pre-activated on it - unless, of course, there's
    // another
    //       possible reason to attack with that creature).
    final Card host = sa.getHostCard();
    final Cost abCost = sa.getPayCosts();
    final Card source = sa.getHostCard();

    if (abCost != null) {
      // AI currently disabled for these costs
      if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, null)) {
        return false;
      }

      if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
        return false;
      }

      if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source)) {
        return false;
      }

      if (!ComputerUtilCost.checkRemoveCounterCost(abCost, source)) {
        return false;
      }
    }

    final TargetRestrictions tgt = sa.getTargetRestrictions();
    if (tgt != null) {
      sa.resetTargets();
      if (sa.canTarget(ai.getOpponent())) {
        sa.getTargets().add(ai.getOpponent());
      } else {
        return false;
      }
    }
    if (sa.hasParam("AILogic")) {
      final Game game = ai.getGame();
      if (sa.getParam("AILogic").equals("NeedsPrevention")) {
        if (!game.getStack().isEmpty()) {
          final SpellAbility topStack = game.getStack().peekAbility();
          if (sa.hasParam("Choices")
              && !topStack.getHostCard().isValid(sa.getParam("Choices"), ai, source)) {
            return false;
          }
          final ApiType threatApi = topStack.getApi();
          if (threatApi != ApiType.DealDamage && threatApi != ApiType.DamageAll) {
            return false;
          }

          final Card threatSource = topStack.getHostCard();
          List<? extends GameObject> objects = getTargets(topStack);
          if (!topStack.usesTargeting()
              && topStack.hasParam("ValidPlayers")
              && !topStack.hasParam("Defined")) {
            objects =
                AbilityUtils.getDefinedPlayers(
                    threatSource, topStack.getParam("ValidPlayers"), topStack);
          }

          if (!objects.contains(ai) || topStack.hasParam("NoPrevention")) {
            return false;
          }
          int dmg =
              AbilityUtils.calculateAmount(threatSource, topStack.getParam("NumDmg"), topStack);
          if (ComputerUtilCombat.predictDamageTo(ai, dmg, threatSource, false) <= 0) {
            return false;
          }
          return true;
        }
        if (game.getPhaseHandler().getPhase() != PhaseType.COMBAT_DECLARE_BLOCKERS) {
          return false;
        }
        CardCollectionView choices = game.getCardsIn(ZoneType.Battlefield);
        if (sa.hasParam("Choices")) {
          choices =
              CardLists.getValidCards(choices, sa.getParam("Choices"), host.getController(), host);
        }
        final Combat combat = game.getCombat();
        choices =
            CardLists.filter(
                choices,
                new Predicate<Card>() {
                  @Override
                  public boolean apply(final Card c) {
                    if (combat == null || !combat.isAttacking(c, ai) || !combat.isUnblocked(c)) {
                      return false;
                    }
                    return ComputerUtilCombat.damageIfUnblocked(c, ai, combat, true) > 0;
                  }
                });
        if (choices.isEmpty()) {
          return false;
        }
      }
    }

    return true;
  }