@Override protected String getStackDescription(SpellAbility sa) { final Card host = sa.getHostCard(); final StringBuilder sb = new StringBuilder(); final int numToDig = AbilityUtils.calculateAmount(host, sa.getParam("DigNum"), sa); final List<Player> tgtPlayers = getTargetPlayers(sa); sb.append(host.getController()).append(" looks at the top "); sb.append(Lang.nounWithAmount(numToDig, "card")).append(" of "); if (tgtPlayers.contains(host.getController())) { sb.append("his or her "); } else { for (final Player p : tgtPlayers) { sb.append(Lang.getPossesive(p.getName())).append(" "); } } sb.append("library."); return sb.toString(); }
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; }
/** {@inheritDoc} */ @Override public boolean canPlay() { final Card card = this.getHostCard(); Player activator = this.getActivatingPlayer(); if (activator == null) { activator = card.getController(); if (activator == null) { return false; } } final Game game = activator.getGame(); if (game.getStack().isSplitSecondOnStack()) { return false; } if (!(card.isInstant() || activator.canCastSorcery() || card.hasKeyword("Flash") || this.getRestrictions().isInstantSpeed() || activator.hasKeyword("You may cast nonland cards as though they had flash.") || card.hasStartOfKeyword("You may cast CARDNAME as though it had flash."))) { return false; } if (!this.getRestrictions().canPlay(card, this)) { return false; } // for uncastables like lotus bloom, check if manaCost is blank (except for morph spells) if (!isCastFaceDown() && isBasicSpell() && card.getState(card.isFaceDown() ? CardStateName.Original : card.getCurrentStateName()) .getManaCost() .isNoCost()) { return false; } if (this.getPayCosts() != null) { if (!CostPayment.canPayAdditionalCosts(this.getPayCosts(), this)) { return false; } } return checkOtherRestrictions(); } // canPlay()
public static boolean isValidBand(List<Card> band, boolean shareDamage) { if (band.isEmpty()) { // An empty band is not a valid band return false; } int bandingCreatures = CardLists.getKeyword(band, "Banding").size(); int neededBandingCreatures = shareDamage ? 1 : band.size() - 1; if (neededBandingCreatures <= bandingCreatures) { // For starting a band, only one can be non-Banding // For sharing damage, only one needs to be Banding return true; } // Legends lands, Master of the Hunt, Old Fogey (just in case) // Since Bands With Other is a dead keyword, no major reason to make this more generic // But if someone is super motivated, feel free to do it. Just make sure you update Tolaria and // Shelkie Brownie String[] bandsWithString = { "Bands with Other Legendary Creatures", "Bands with Other Creatures named Wolves of the Hunt", "Bands with Other Dinosaurs" }; String[] validString = {"Legendary.Creature", "Creature.namedWolves of the Hunt", "Dinosaur"}; Card source = band.get(0); for (int i = 0; i < bandsWithString.length; i++) { String keyword = bandsWithString[i]; String valid = validString[i]; // Check if a bands with other keyword exists in band, and each creature in the band fits the // valid quality if (!CardLists.getKeyword(band, keyword).isEmpty() && CardLists.getValidCards(band, valid, source.getController(), source).size() == band.size()) { return true; } } return false; }
@Override public PaymentDecision visit(CostExileFromStack cost) { Integer c = cost.convertAmount(); 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<SpellAbility> chosen = new ArrayList<SpellAbility>(); for (SpellAbilityStackInstance si : source.getGame().getStack()) { SpellAbility sp = si.getSpellAbility().getRootAbility(); if (si.getSourceCard().isValid(cost.getType().split(";"), source.getController(), source)) { chosen.add(sp); } } return chosen.isEmpty() ? null : PaymentDecision.spellabilities(chosen); }
@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; }
/* (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()
@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
@Override public void resolve(SpellAbility sa) { final Card host = sa.getHostCard(); final Player player = sa.getActivatingPlayer(); final Game game = player.getGame(); Player chooser = player; int numToDig = AbilityUtils.calculateAmount(host, sa.getParam("DigNum"), sa); final ZoneType srcZone = sa.hasParam("SourceZone") ? ZoneType.smartValueOf(sa.getParam("SourceZone")) : ZoneType.Library; final ZoneType destZone1 = sa.hasParam("DestinationZone") ? ZoneType.smartValueOf(sa.getParam("DestinationZone")) : ZoneType.Hand; final ZoneType destZone2 = sa.hasParam("DestinationZone2") ? ZoneType.smartValueOf(sa.getParam("DestinationZone2")) : ZoneType.Library; int libraryPosition = sa.hasParam("LibraryPosition") ? Integer.parseInt(sa.getParam("LibraryPosition")) : -1; int destZone1ChangeNum = 1; final boolean mitosis = sa.hasParam("Mitosis"); String changeValid = sa.hasParam("ChangeValid") ? sa.getParam("ChangeValid") : ""; // andOrValid is for cards with "creature card and/or a land card" String andOrValid = sa.hasParam("AndOrValid") ? sa.getParam("AndOrValid") : ""; final boolean anyNumber = sa.hasParam("AnyNumber"); final int libraryPosition2 = sa.hasParam("LibraryPosition2") ? Integer.parseInt(sa.getParam("LibraryPosition2")) : -1; final boolean optional = sa.hasParam("Optional"); final boolean noMove = sa.hasParam("NoMove"); final boolean skipReorder = sa.hasParam("SkipReorder"); // A hack for cards like Explorer's Scope that need to ensure that a card is revealed to the // player activating the ability final boolean forceRevealToController = sa.hasParam("ForceRevealToController"); // These parameters are used to indicate that a dialog box must be show to the player asking if // the player wants to proceed // with an optional ability, otherwise the optional ability is skipped. final boolean mayBeSkipped = sa.hasParam("PromptToSkipOptionalAbility"); final String optionalAbilityPrompt = sa.hasParam("OptionalAbilityPrompt") ? sa.getParam("OptionalAbilityPrompt") : ""; boolean changeAll = false; boolean allButOne = false; final List<String> keywords = new ArrayList<String>(); if (sa.hasParam("Keywords")) { keywords.addAll(Arrays.asList(sa.getParam("Keywords").split(" & "))); } if (sa.hasParam("ChangeNum")) { if (sa.getParam("ChangeNum").equalsIgnoreCase("All")) { changeAll = true; } else if (sa.getParam("ChangeNum").equalsIgnoreCase("AllButOne")) { allButOne = true; } else { destZone1ChangeNum = AbilityUtils.calculateAmount(host, sa.getParam("ChangeNum"), sa); } } final TargetRestrictions tgt = sa.getTargetRestrictions(); final List<Player> tgtPlayers = getTargetPlayers(sa); if (sa.hasParam("Choser")) { final List<Player> choosers = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("Choser"), sa); if (!choosers.isEmpty()) { chooser = choosers.get(0); } } for (final Player p : tgtPlayers) { if (tgt != null && !p.canBeTargetedBy(sa)) { continue; } final CardCollection top = new CardCollection(); final CardCollection rest = new CardCollection(); final PlayerZone sourceZone = p.getZone(srcZone); numToDig = Math.min(numToDig, sourceZone.size()); for (int i = 0; i < numToDig; i++) { top.add(sourceZone.get(i)); } if (!top.isEmpty()) { DelayedReveal delayedReveal = null; boolean hasRevealed = true; if (sa.hasParam("Reveal")) { game.getAction().reveal(top, p, false); } else if (sa.hasParam("RevealOptional")) { String question = "Reveal: " + Lang.joinHomogenous(top) + "?"; hasRevealed = p.getController().confirmAction(sa, null, question); if (hasRevealed) { game.getAction().reveal(top, p); } } else if (sa.hasParam("RevealValid")) { final String revealValid = sa.getParam("RevealValid"); final CardCollection toReveal = CardLists.getValidCards(top, revealValid, host.getController(), host); if (!toReveal.isEmpty()) { game.getAction().reveal(toReveal, host.getController()); if (sa.hasParam("RememberRevealed")) { for (final Card one : toReveal) { host.addRemembered(one); } } } // Singletons.getModel().getGameAction().revealToCopmuter(top.toArray()); // - for when it exists } else if (!sa.hasParam("NoLooking")) { // show the user the revealed cards delayedReveal = new DelayedReveal(top, srcZone, PlayerView.get(p)); if (noMove) { // Let the activating player see the cards even if they're not moved game.getAction().revealTo(top, player); } } if (sa.hasParam("RememberRevealed") && !sa.hasParam("RevealValid") && hasRevealed) { for (final Card one : top) { host.addRemembered(one); } } if (!noMove) { CardCollection movedCards; CardCollection andOrCards; for (final Card c : top) { rest.add(c); } CardCollection valid; if (mitosis) { valid = sharesNameWithCardOnBattlefield(game, top); andOrCards = new CardCollection(); } else if (!changeValid.isEmpty()) { if (changeValid.contains("ChosenType")) { changeValid = changeValid.replace("ChosenType", host.getChosenType()); } valid = CardLists.getValidCards(top, changeValid.split(","), host.getController(), host); if (!andOrValid.equals("")) { andOrCards = CardLists.getValidCards(top, andOrValid.split(","), host.getController(), host); andOrCards.removeAll((Collection<?>) valid); valid.addAll(andOrCards); } else { andOrCards = new CardCollection(); } } else { valid = top; andOrCards = new CardCollection(); } if (forceRevealToController) { // Force revealing the card to the player activating the ability (e.g. Explorer's Scope) game.getAction().revealTo(top, player); } // Optional abilities that use a dialog box to prompt the user to skip the ability (e.g. // Explorer's Scope, Quest for Ula's Temple) if (optional && mayBeSkipped && !valid.isEmpty()) { String prompt = !optionalAbilityPrompt.isEmpty() ? optionalAbilityPrompt : "Would you like to proceed with the optional ability for " + sa.getHostCard() + "?\n\n(" + sa.getDescription() + ")"; if (!p.getController() .confirmAction(sa, null, prompt.replace("CARDNAME", sa.getHostCard().getName()))) { return; } } if (changeAll) { movedCards = new CardCollection(valid); } else if (sa.hasParam("RandomChange")) { int numChanging = Math.min(destZone1ChangeNum, valid.size()); movedCards = CardLists.getRandomSubList(valid, numChanging); } else if (allButOne) { movedCards = new CardCollection(valid); String prompt; if (destZone2.equals(ZoneType.Library) && libraryPosition2 == 0) { prompt = "Choose a card to leave on top of {player's} library"; } else { prompt = "Choose a card to leave in {player's} " + destZone2.name(); } Card chosen = chooser .getController() .chooseSingleEntityForEffect(valid, delayedReveal, sa, prompt, false, p); movedCards.remove(chosen); if (sa.hasParam("RandomOrder")) { CardLists.shuffle(movedCards); } } else { int j = 0; String prompt; if (sa.hasParam("PrimaryPrompt")) { prompt = sa.getParam("PrimaryPrompt"); } else { prompt = "Choose a card to put into " + destZone1.name(); if (destZone1.equals(ZoneType.Library)) { if (libraryPosition == -1) { prompt = "Choose a card to put on the bottom of {player's} library"; } else if (libraryPosition == 0) { prompt = "Choose a card to put on top of {player's} library"; } } } movedCards = new CardCollection(); while (j < destZone1ChangeNum || (anyNumber && j < numToDig)) { // let user get choice Card chosen = null; if (!valid.isEmpty()) { chosen = chooser .getController() .chooseSingleEntityForEffect( valid, delayedReveal, sa, prompt, anyNumber || optional, p); } else { chooser.getController().notifyOfValue(sa, null, "No valid cards"); } if (chosen == null) { break; } movedCards.add(chosen); valid.remove(chosen); if (!andOrValid.equals("")) { andOrCards.remove(chosen); if (!chosen.isValid(andOrValid.split(","), host.getController(), host)) { valid = new CardCollection(andOrCards); } else if (!chosen.isValid(changeValid.split(","), host.getController(), host)) { valid.removeAll((Collection<?>) andOrCards); } } j++; } if (!changeValid.isEmpty()) { game.getAction() .reveal( movedCards, chooser, true, chooser + " picked " + (movedCards.size() == 1 ? "this card" : "these cards") + " from "); } } if (sa.hasParam("ForgetOtherRemembered")) { host.clearRemembered(); } Collections.reverse(movedCards); for (Card c : movedCards) { final PlayerZone zone = c.getOwner().getZone(destZone1); if (zone.is(ZoneType.Library) || zone.is(ZoneType.PlanarDeck) || zone.is(ZoneType.SchemeDeck)) { if (libraryPosition == -1 || libraryPosition > zone.size()) { libraryPosition = zone.size(); } c = game.getAction().moveTo(zone, c, libraryPosition); } else { c = game.getAction().moveTo(zone, c); if (destZone1.equals(ZoneType.Battlefield)) { for (final String kw : keywords) { c.addExtrinsicKeyword(kw); } if (sa.hasParam("Tapped")) { c.setTapped(true); } } } if (sa.hasParam("ExileFaceDown")) { c.setState(CardStateName.FaceDown, true); } if (sa.hasParam("Imprint")) { host.addImprintedCard(c); } if (sa.hasParam("ForgetOtherRemembered")) { host.clearRemembered(); } if (sa.hasParam("RememberChanged")) { host.addRemembered(c); } rest.remove(c); } // now, move the rest to destZone2 if (destZone2 == ZoneType.Library || destZone2 == ZoneType.PlanarDeck || destZone2 == ZoneType.SchemeDeck) { CardCollection afterOrder = rest; if (sa.hasParam("RestRandomOrder")) { CardLists.shuffle(afterOrder); } else if (!skipReorder && rest.size() > 1) { afterOrder = (CardCollection) chooser.getController().orderMoveToZoneList(rest, destZone2); } if (libraryPosition2 != -1) { // Closest to top Collections.reverse(afterOrder); } for (final Card c : afterOrder) { if (destZone2 == ZoneType.Library) { game.getAction().moveToLibrary(c, libraryPosition2); } else { game.getAction().moveToVariantDeck(c, destZone2, libraryPosition2); } } } else { // just move them randomly for (int i = 0; i < rest.size(); i++) { Card c = rest.get(i); final PlayerZone toZone = c.getOwner().getZone(destZone2); c = game.getAction().moveTo(toZone, c); if (destZone2.equals(ZoneType.Battlefield) && !keywords.isEmpty()) { for (final String kw : keywords) { c.addExtrinsicKeyword(kw); } } } } } } } }
/* (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; }
/** {@inheritDoc} */ @Override public final boolean performTest(final java.util.Map<String, Object> runParams2) { final SpellAbility spellAbility = (SpellAbility) runParams2.get("CastSA"); if (spellAbility == null) { System.out.println( "TriggerSpellAbilityCast performTest encountered spellAbility == null. runParams2 = " + runParams2); return false; } final Card cast = spellAbility.getHostCard(); final Game game = cast.getGame(); final SpellAbilityStackInstance si = game.getStack().getInstanceFromSpellAbility(spellAbility); if (this.getMode() == TriggerType.SpellCast) { if (!spellAbility.isSpell()) { return false; } } else if (this.getMode() == TriggerType.AbilityCast) { if (!spellAbility.isAbility()) { return false; } } else if (this.getMode() == TriggerType.SpellAbilityCast) { // Empty block for readability. } if (this.mapParams.containsKey("ActivatedOnly")) { if (spellAbility.isTrigger()) { return false; } } if (this.mapParams.containsKey("ValidControllingPlayer")) { if (!matchesValid( cast.getController(), this.mapParams.get("ValidControllingPlayer").split(","), this.getHostCard())) { return false; } } if (this.mapParams.containsKey("ValidActivatingPlayer")) { if (si == null || !matchesValid( si.getSpellAbility(true).getActivatingPlayer(), this.mapParams.get("ValidActivatingPlayer").split(","), this.getHostCard())) { return false; } if (this.mapParams.containsKey("ActivatorThisTurnCast")) { String compare = this.mapParams.get("ActivatorThisTurnCast"); List<Card> thisTurnCast = CardUtil.getThisTurnCast( this.mapParams.containsKey("ValidCard") ? this.mapParams.get("ValidCard") : "Card", this.getHostCard()); thisTurnCast = CardLists.filterControlledBy( thisTurnCast, si.getSpellAbility(true).getActivatingPlayer()); int left = thisTurnCast.size(); int right = Integer.parseInt(compare.substring(2)); if (!Expressions.compare(left, compare, right)) { return false; } } } if (this.mapParams.containsKey("ValidCard")) { if (!matchesValid(cast, this.mapParams.get("ValidCard").split(","), this.getHostCard())) { return false; } } if (this.mapParams.containsKey("TargetsValid")) { SpellAbility sa = spellAbility; if (si != null) { sa = si.getSpellAbility(true); } boolean validTgtFound = false; while (sa != null && !validTgtFound) { for (final Card tgt : sa.getTargets().getTargetCards()) { if (tgt.isValid( this.mapParams.get("TargetsValid").split(","), this.getHostCard().getController(), this.getHostCard())) { validTgtFound = true; break; } } for (final Player p : sa.getTargets().getTargetPlayers()) { if (matchesValid(p, this.mapParams.get("TargetsValid").split(","), this.getHostCard())) { validTgtFound = true; break; } } sa = sa.getSubAbility(); } if (!validTgtFound) { return false; } } if (this.mapParams.containsKey("NonTapCost")) { final Cost cost = (Cost) (runParams2.get("Cost")); if (cost.hasTapCost()) { return false; } } if (this.mapParams.containsKey("Conspire")) { if (!spellAbility.isOptionalCostPaid(OptionalCost.Conspire)) { return false; } if (spellAbility.getConspireInstances() == 0) { return false; } else { spellAbility.subtractConspireInstance(); // System.out.println("Conspire instances left = " + spellAbility.getConspireInstances()); } } if (this.mapParams.containsKey("Outlast")) { if (!spellAbility.isOutlast()) { return false; } } if (this.mapParams.containsKey("IsSingleTarget")) { int numTargeted = 0; for (TargetChoices tc : spellAbility.getAllTargetChoices()) { numTargeted += tc.getNumTargeted(); } if (numTargeted != 1) { return false; } } if (this.mapParams.containsKey("SpellSpeed")) { if (this.mapParams.get("SpellSpeed").equals("NotSorcerySpeed")) { if (this.getHostCard().getController().couldCastSorcery(spellAbility)) { return false; } if (this.getHostCard() .hasKeyword( "You may cast CARDNAME as though it had flash. If you cast it any time a " + "sorcery couldn't have been cast, the controller of the permanent it becomes sacrifices it at the beginning" + " of the next cleanup step.")) { // for these cards the trigger must only fire if using their own ability to cast at // instant speed if (this.getHostCard().hasKeyword("Flash") || this.getHostCard() .getController() .hasKeyword("You may cast nonland cards as though they had flash.")) { return false; } } return true; } } 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