/* (non-Javadoc)
   * @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, forge.card.spellability.SpellAbility)
   */
  @Override
  protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
    String logic = sa.getParam("AILogic");
    Game game = aiPlayer.getGame();

    if ("ZeroToughness".equals(logic)) {
      // If Creature has Zero Toughness, make sure some static ability is in play
      // That will grant a toughness bonus

      final List<Card> list = aiPlayer.getCardsIn(ZoneType.Battlefield);
      if (!Iterables.any(
          list,
          Predicates.or(
              CardPredicates.nameEquals("Glorious Anthem"),
              CardPredicates.nameEquals("Gaea's Anthem")))) {
        return false;
      }

      // TODO See if card ETB will survive after Static Effects
      /*
      List<Card> cards = game.getCardsIn(ZoneType.Battlefield);

      for(Card c : cards) {
          ArrayList<StaticAbility> statics = c.getStaticAbilities();
          for(StaticAbility s : statics) {
              final Map<String, String> stabMap = s.parseParams();

              if (!stabMap.get("Mode").equals("Continuous")) {
                  continue;
              }

              final String affected = stabMap.get("Affected");

              if (affected == null) {
                  continue;
              }
          }
      }
      */
    }

    // Wait for Main2 if possible
    if (game.getPhaseHandler().is(PhaseType.MAIN1)
        && !ComputerUtil.castPermanentInMain1(aiPlayer, sa)) {
      return false;
    }

    // AI shouldn't be retricted all that much for Creatures for now
    return true;
  }
 private void passPriority(final Runnable runnable) {
   if (FModel.getPreferences().getPrefBoolean(FPref.UI_MANA_LOST_PROMPT)) {
     // if gui player has mana floating that will be lost if phase ended right now, prompt before
     // passing priority
     final Game game = player.getGame();
     if (game.getStack().isEmpty()) { // phase can't end right now if stack isn't empty
       Player player = game.getPhaseHandler().getPriorityPlayer();
       if (player != null
           && player.getManaPool().willManaBeLostAtEndOfPhase()
           && player.getLobbyPlayer() == GamePlayerUtil.getGuiPlayer()) {
         ThreadUtil.invokeInGameThread(
             new Runnable() { // must invoke in game thread so dialog can be shown on mobile game
               @Override
               public void run() {
                 String message =
                     "You have mana floating in your mana pool that could be lost if you pass priority now.";
                 if (FModel.getPreferences().getPrefBoolean(FPref.UI_MANABURN)) {
                   message +=
                       " You will take mana burn damage equal to the amount of floating mana lost this way.";
                 }
                 if (SOptionPane.showOptionDialog(
                         message,
                         "Mana Floating",
                         SOptionPane.WARNING_ICON,
                         new String[] {"OK", "Cancel"})
                     == 0) {
                   runnable.run();
                 }
               }
             });
         return;
       }
     }
   }
   runnable.run(); // just pass priority immediately if no mana floating that would be lost
 }
Beispiel #3
0
  /* (non-Javadoc)
   * @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
   */
  @Override
  protected boolean canPlayAI(Player ai, SpellAbility sa) {
    final Cost cost = sa.getPayCosts();
    final Game game = ai.getGame();
    final PhaseHandler ph = game.getPhaseHandler();
    final List<String> keywords =
        sa.hasParam("KW") ? Arrays.asList(sa.getParam("KW").split(" & ")) : new ArrayList<String>();
    final String numDefense = sa.hasParam("NumDef") ? sa.getParam("NumDef") : "";
    final String numAttack = sa.hasParam("NumAtt") ? sa.getParam("NumAtt") : "";

    if (!ComputerUtilCost.checkLifeCost(ai, cost, sa.getHostCard(), 4, null)) {
      return false;
    }

    if (!ComputerUtilCost.checkDiscardCost(ai, cost, sa.getHostCard())) {
      return false;
    }

    if (!ComputerUtilCost.checkCreatureSacrificeCost(ai, cost, sa.getHostCard())) {
      return false;
    }

    if (!ComputerUtilCost.checkRemoveCounterCost(cost, sa.getHostCard())) {
      return false;
    }

    if (!ComputerUtilCost.checkTapTypeCost(ai, cost, sa.getHostCard())) {
      return false;
    }

    if (game.getStack().isEmpty() && hasTapCost(cost, sa.getHostCard())) {
      if (ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS) && ph.isPlayerTurn(ai)) {
        return false;
      }
      if (ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)
          && ph.isPlayerTurn(ai.getOpponent())) {
        return false;
      }
    }
    if (ComputerUtil.preventRunAwayActivations(sa)) {
      return false;
    }

    // Phase Restrictions
    if (game.getStack().isEmpty() && ph.getPhase().isBefore(PhaseType.COMBAT_BEGIN)) {
      // Instant-speed pumps should not be cast outside of combat when the
      // stack is empty
      if (!sa.isCurse() && !SpellAbilityAi.isSorcerySpeed(sa)) {
        return false;
      }
    } else if (!game.getStack().isEmpty() && !sa.isCurse()) {
      return pumpAgainstRemoval(ai, sa);
    }

    if (sa.hasParam("ActivationNumberSacrifice")) {
      final SpellAbilityRestriction restrict = sa.getRestrictions();
      final int sacActivations =
          Integer.parseInt(sa.getParam("ActivationNumberSacrifice").substring(2));
      final int activations = restrict.getNumberTurnActivations();
      // don't risk sacrificing a creature just to pump it
      if (activations >= sacActivations - 1) {
        return false;
      }
    }

    final Card source = sa.getHostCard();
    if (source.getSVar("X").equals("Count$xPaid")) {
      source.setSVar("PayX", "");
    }

    int defense;
    if (numDefense.contains("X") && source.getSVar("X").equals("Count$xPaid")) {
      // Set PayX here to maximum value.
      final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
      source.setSVar("PayX", Integer.toString(xPay));
      defense = xPay;
      if (numDefense.equals("-X")) {
        defense = -xPay;
      }
    } else {
      defense = AbilityUtils.calculateAmount(sa.getHostCard(), numDefense, sa);
    }

    int attack;
    if (numAttack.contains("X") && source.getSVar("X").equals("Count$xPaid")) {
      // Set PayX here to maximum value.
      final String toPay = source.getSVar("PayX");

      if (toPay.equals("")) {
        final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
        source.setSVar("PayX", Integer.toString(xPay));
        attack = xPay;
      } else {
        attack = Integer.parseInt(toPay);
      }
    } else {
      attack = AbilityUtils.calculateAmount(sa.getHostCard(), numAttack, sa);
    }

    if ((numDefense.contains("X") && defense == 0) || (numAttack.contains("X") && attack == 0)) {
      return false;
    }

    // Untargeted
    if ((sa.getTargetRestrictions() == null) || !sa.getTargetRestrictions().doesTarget()) {
      final List<Card> cards =
          AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa);

      if (cards.size() == 0) {
        return false;
      }

      // when this happens we need to expand AI to consider if its ok for
      // everything?
      for (final Card card : cards) {
        if (sa.isCurse()) {
          if (!card.getController().isOpponentOf(ai)) {
            return false;
          }

          if (!containsUsefulKeyword(ai, keywords, card, sa, attack)) {
            continue;
          }

          return true;
        }
        if (!card.getController().isOpponentOf(ai)
            && shouldPumpCard(ai, sa, card, defense, attack, keywords)) {
          return true;
        }
      }
      return false;
    }
    // Targeted
    if (!this.pumpTgtAI(ai, sa, defense, attack, false)) {
      return false;
    }

    return true;
  } // pumpPlayAI()
Beispiel #4
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()
Beispiel #5
0
  @Override
  protected boolean canPlayAI(Player ai, SpellAbility sa) {
    final TargetRestrictions tgt = sa.getTargetRestrictions();
    final Card source = sa.getHostCard();
    final Game game = source.getGame();

    boolean useAbility = true;

    //        if (card.getController().isComputer()) {
    //            final List<Card> creatures = AllZoneUtil.getCreaturesInPlay();
    //            if (!creatures.isEmpty()) {
    //                cardToCopy = CardFactoryUtil.getBestCreatureAI(creatures);
    //            }
    //        }

    // TODO - add some kind of check to answer
    // "Am I going to attack with this?"
    // TODO - add some kind of check for during human turn to answer
    // "Can I use this to block something?"

    PhaseHandler phase = game.getPhaseHandler();
    // don't use instant speed clone abilities outside computers
    // Combat_Begin step
    if (!phase.is(PhaseType.COMBAT_BEGIN)
        && phase.isPlayerTurn(ai)
        && !SpellAbilityAi.isSorcerySpeed(sa)
        && !sa.hasParam("ActivationPhases")
        && !sa.hasParam("Permanent")) {
      return false;
    }

    // don't use instant speed clone abilities outside humans
    // Combat_Declare_Attackers_InstantAbility step
    if (!phase.is(PhaseType.COMBAT_DECLARE_ATTACKERS)
        || phase.isPlayerTurn(ai)
        || game.getCombat().getAttackers().isEmpty()) {
      return false;
    }

    // don't activate during main2 unless this effect is permanent
    if (phase.is(PhaseType.MAIN2) && !sa.hasParam("Permanent")) {
      return false;
    }

    if (null == tgt) {
      final List<Card> defined = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa);

      boolean bFlag = false;
      for (final Card c : defined) {
        bFlag |= (!c.isCreature() && !c.isTapped() && !(c.getTurnInZone() == phase.getTurn()));

        // for creatures that could be improved (like Figure of Destiny)
        if (c.isCreature() && (sa.hasParam("Permanent") || (!c.isTapped() && !c.isSick()))) {
          int power = -5;
          if (sa.hasParam("Power")) {
            power = AbilityUtils.calculateAmount(source, sa.getParam("Power"), sa);
          }
          int toughness = -5;
          if (sa.hasParam("Toughness")) {
            toughness = AbilityUtils.calculateAmount(source, sa.getParam("Toughness"), sa);
          }
          if ((power + toughness) > (c.getCurrentPower() + c.getCurrentToughness())) {
            bFlag = true;
          }
        }
      }

      if (!bFlag) { // All of the defined stuff is cloned, not very
        // useful
        return false;
      }
    } else {
      sa.resetTargets();
      useAbility &= cloneTgtAI(sa);
    }

    return useAbility;
  } // end cloneCanPlayAI()
  /* (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;
  }
  /* (non-Javadoc)
   * @see forge.card.ability.SpellAbilityEffect#resolve(forge.card.spellability.SpellAbility)
   */
  @Override
  public void resolve(final SpellAbility sa) {
    final Card hostCard = sa.getHostCard();
    final Game game = hostCard.getGame();
    final List<String> keywords = new ArrayList<String>();
    final List<String> types = new ArrayList<String>();
    final List<String> svars = new ArrayList<String>();
    final List<String> triggers = new ArrayList<String>();
    if (sa.hasParam("Optional")) {
      if (!sa.getActivatingPlayer()
          .getController()
          .confirmAction(sa, null, "Copy this permanent?")) {
        return;
      }
    }
    if (sa.hasParam("Keywords")) {
      keywords.addAll(Arrays.asList(sa.getParam("Keywords").split(" & ")));
    }
    if (sa.hasParam("AddTypes")) {
      types.addAll(Arrays.asList(sa.getParam("AddTypes").split(" & ")));
    }
    if (sa.hasParam("AddSVars")) {
      svars.addAll(Arrays.asList(sa.getParam("AddSVars").split(" & ")));
    }
    if (sa.hasParam("Triggers")) {
      triggers.addAll(Arrays.asList(sa.getParam("Triggers").split(" & ")));
    }
    final int numCopies =
        sa.hasParam("NumCopies")
            ? AbilityUtils.calculateAmount(hostCard, sa.getParam("NumCopies"), sa)
            : 1;

    Player controller = null;
    if (sa.hasParam("Controller")) {
      final FCollectionView<Player> defined =
          AbilityUtils.getDefinedPlayers(hostCard, sa.getParam("Controller"), sa);
      if (!defined.isEmpty()) {
        controller = defined.getFirst();
      }
    }
    if (controller == null) {
      controller = sa.getActivatingPlayer();
    }

    List<Card> tgtCards = getTargetCards(sa);
    final TargetRestrictions tgt = sa.getTargetRestrictions();

    if (sa.hasParam("ValidSupportedCopy")) {
      List<PaperCard> cards =
          Lists.newArrayList(StaticData.instance().getCommonCards().getUniqueCards());
      String valid = sa.getParam("ValidSupportedCopy");
      if (valid.contains("X")) {
        valid =
            valid.replace("X", Integer.toString(AbilityUtils.calculateAmount(hostCard, "X", sa)));
      }
      if (StringUtils.containsIgnoreCase(valid, "creature")) {
        Predicate<PaperCard> cpp =
            Predicates.compose(CardRulesPredicates.Presets.IS_CREATURE, PaperCard.FN_GET_RULES);
        cards = Lists.newArrayList(Iterables.filter(cards, cpp));
      }
      if (StringUtils.containsIgnoreCase(valid, "equipment")) {
        Predicate<PaperCard> cpp =
            Predicates.compose(CardRulesPredicates.Presets.IS_EQUIPMENT, PaperCard.FN_GET_RULES);
        cards = Lists.newArrayList(Iterables.filter(cards, cpp));
      }
      if (sa.hasParam("RandomCopied")) {
        List<PaperCard> copysource = new ArrayList<PaperCard>(cards);
        List<Card> choice = new ArrayList<Card>();
        final String num = sa.hasParam("RandomNum") ? sa.getParam("RandomNum") : "1";
        int ncopied = AbilityUtils.calculateAmount(hostCard, num, sa);
        while (ncopied > 0) {
          final PaperCard cp = Aggregates.random(copysource);
          Card possibleCard =
              Card.fromPaperCard(
                  cp,
                  sa.getActivatingPlayer()); // Need to temporarily set the Owner so the Game is set

          if (possibleCard.isValid(valid, hostCard.getController(), hostCard)) {
            choice.add(possibleCard);
            copysource.remove(cp);
            ncopied -= 1;
          }
        }
        tgtCards = choice;
      } else if (sa.hasParam("DefinedName")) {
        String name = sa.getParam("DefinedName");
        if (name.equals("NamedCard")) {
          if (!hostCard.getNamedCard().isEmpty()) {
            name = hostCard.getNamedCard();
          }
        }

        Predicate<PaperCard> cpp =
            Predicates.compose(
                CardRulesPredicates.name(StringOp.EQUALS, name), PaperCard.FN_GET_RULES);
        cards = Lists.newArrayList(Iterables.filter(cards, cpp));

        tgtCards.clear();
        if (!cards.isEmpty()) {
          tgtCards.add(Card.fromPaperCard(cards.get(0), controller));
        }
      }
    }
    hostCard.clearClones();

    for (final Card c : tgtCards) {
      if ((tgt == null) || c.canBeTargetedBy(sa)) {

        int multiplier = numCopies * hostCard.getController().getTokenDoublersMagnitude();
        final List<Card> crds = new ArrayList<Card>(multiplier);

        for (int i = 0; i < multiplier; i++) {
          final Card copy = CardFactory.copyCopiableCharacteristics(c, sa.getActivatingPlayer());
          copy.setToken(true);
          copy.setCopiedPermanent(c);
          CardFactory.copyCopiableAbilities(c, copy);
          // add keywords from sa
          for (final String kw : keywords) {
            copy.addIntrinsicKeyword(kw);
          }
          for (final String type : types) {
            copy.addType(type);
          }
          for (final String svar : svars) {
            String actualsVar = hostCard.getSVar(svar);
            String name = svar;
            if (actualsVar.startsWith("SVar:")) {
              actualsVar = actualsVar.split("SVar:")[1];
              name = actualsVar.split(":")[0];
              actualsVar = actualsVar.split(":")[1];
            }
            copy.setSVar(name, actualsVar);
          }
          for (final String s : triggers) {
            final String actualTrigger = hostCard.getSVar(s);
            final Trigger parsedTrigger = TriggerHandler.parseTrigger(actualTrigger, copy, true);
            copy.addTrigger(parsedTrigger);
          }

          // Temporarily register triggers of an object created with CopyPermanent
          // game.getTriggerHandler().registerActiveTrigger(copy, false);
          final Card copyInPlay = game.getAction().moveToPlay(copy);

          // when copying something stolen:
          copyInPlay.setController(controller, 0);
          copyInPlay.setSetCode(c.getSetCode());

          copyInPlay.setCloneOrigin(hostCard);
          sa.getHostCard().addClone(copyInPlay);
          crds.add(copyInPlay);
          if (sa.hasParam("RememberCopied")) {
            hostCard.addRemembered(copyInPlay);
          }
          if (sa.hasParam("Tapped")) {
            copyInPlay.setTapped(true);
          }
          if (sa.hasParam("CopyAttacking") && game.getPhaseHandler().inCombat()) {
            final String attacked = sa.getParam("CopyAttacking");
            GameEntity defender;
            if ("True".equals(attacked)) {
              FCollectionView<GameEntity> defs = game.getCombat().getDefenders();
              defender =
                  c.getController()
                      .getController()
                      .chooseSingleEntityForEffect(
                          defs, sa, "Choose which defender to attack with " + c, false);
            } else {
              defender =
                  AbilityUtils.getDefinedPlayers(hostCard, sa.getParam("CopyAttacking"), sa).get(0);
            }
            game.getCombat().addAttacker(copyInPlay, defender);
            game.fireEvent(new GameEventCombatChanged());
          }

          if (sa.hasParam("AttachedTo")) {
            CardCollectionView list =
                AbilityUtils.getDefinedCards(hostCard, sa.getParam("AttachedTo"), sa);
            if (list.isEmpty()) {
              list = copyInPlay.getController().getGame().getCardsIn(ZoneType.Battlefield);
              list =
                  CardLists.getValidCards(
                      list, sa.getParam("AttachedTo"), copyInPlay.getController(), copyInPlay);
            }
            if (!list.isEmpty()) {
              Card attachedTo =
                  sa.getActivatingPlayer()
                      .getController()
                      .chooseSingleEntityForEffect(
                          list, sa, copyInPlay + " - Select a card to attach to.");
              if (copyInPlay.isAura()) {
                if (attachedTo.canBeEnchantedBy(copyInPlay)) {
                  copyInPlay.enchantEntity(attachedTo);
                } else { // can't enchant
                  continue;
                }
              } else if (copyInPlay.isEquipment()) { // Equipment
                if (attachedTo.canBeEquippedBy(copyInPlay)) {
                  copyInPlay.equipCard(attachedTo);
                } else {
                  continue;
                }
              } else { // Fortification
                copyInPlay.fortifyCard(attachedTo);
              }
            } else {
              continue;
            }
          }
        }

        if (sa.hasParam("AtEOT")) {
          final String location = sa.getParam("AtEOT");
          registerDelayedTrigger(sa, location, crds);
        }
      } // end canBeTargetedBy
    } // end foreach Card
  } // end resolve