@Override public void resolve(SpellAbility sa) { final int numTurns = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumTurns"), sa); List<Player> tgtPlayers = getTargetPlayers(sa); for (final Player p : tgtPlayers) { if ((sa.getTargetRestrictions() == null) || p.canBeTargetedBy(sa)) { for (int i = 0; i < numTurns; i++) { ExtraTurn extra = p.getGame().getPhaseHandler().addExtraTurn(p); if (sa.hasParam("LoseAtEndStep")) { extra.setLoseAtEndStep(true); } if (sa.hasParam("SkipUntap")) { extra.setSkipUntap(true); } if (sa.hasParam("NoSchemes")) { extra.setCantSetSchemesInMotion(true); } if (sa.hasParam("ShowMessage")) { p.getGame().getAction().nofityOfValue(sa, p, p + " takes an extra turn.", null); } } } } }
/** {@inheritDoc} */ @Override public final void showMessage() { final Game game = player.getGame(); final StringBuilder sb = new StringBuilder(); if (startingPlayer == player) { sb.append(player).append(", you are going first!\n\n"); } else { sb.append(startingPlayer.getName()).append(" is going first.\n"); sb.append(player) .append(", you are going ") .append(Lang.getOrdinal(game.getPosition(player, startingPlayer))) .append(".\n\n"); } if (isCommander) { getController().getGui().updateButtons(getOwner(), "Keep", "Exile", true, false, true); sb.append( "Will you keep your hand or choose some cards to exile those and draw one less card?"); } else { getController().getGui().updateButtons(getOwner(), "Keep", "Mulligan", true, true, true); sb.append("Do you want to keep your hand?"); } showMessage(sb.toString()); }
/** {@inheritDoc} */ @Override public final void showMessage() { showMessage(getTurnPhasePriorityMessage(player.getGame())); chosenSa = null; if (getController() .canUndoLastAction()) { // allow undoing with cancel button if can undo last action ButtonUtil.update(getOwner(), "OK", "Undo", true, true, true); } else { // otherwise allow ending turn with cancel button ButtonUtil.update(getOwner(), "OK", "End Turn", true, true, true); } }
/* (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; }
/* * (non-Javadoc) * * @see * forge.card.cost.CostPart#canPay(forge.card.spellability.SpellAbility, * forge.Card, forge.Player, forge.card.cost.Cost) */ @Override public final boolean canPay(final SpellAbility ability) { final Player activator = ability.getActivatingPlayer(); final Card source = ability.getHostCard(); CardCollectionView typeList = activator.getGame().getCardsIn(ZoneType.Battlefield); typeList = CardLists.getValidCards(typeList, this.getType().split(";"), activator, source); Integer amount = this.convertAmount(); if (amount == null) { amount = AbilityUtils.calculateAmount(source, this.getAmount(), ability); } if (typeList.size() < amount) { return false; } return true; }
/* (non-Javadoc) * @see forge.card.ability.SpellAbilityEffect#resolve(forge.card.spellability.SpellAbility) */ @Override public void resolve(SpellAbility sa) { Player activator = sa.getActivatingPlayer(); Card source = sa.getHostCard(); Game game = activator.getGame(); String valid = sa.hasParam("Valid") ? sa.getParam("Valid") : "Card"; ZoneType zone = sa.hasParam("Zone") ? ZoneType.smartValueOf(sa.getParam("Zone")) : ZoneType.Battlefield; int min = Integer.MAX_VALUE; final FCollectionView<Player> players = game.getPlayersInTurnOrder(); final List<CardCollection> validCards = new ArrayList<CardCollection>(players.size()); for (int i = 0; i < players.size(); i++) { // Find the minimum of each Valid per player validCards.add( CardLists.getValidCards(players.get(i).getCardsIn(zone), valid, activator, source)); min = Math.min(min, validCards.get(i).size()); } for (int i = 0; i < players.size(); i++) { Player p = players.get(i); int numToBalance = validCards.get(i).size() - min; if (numToBalance == 0) { continue; } if (zone.equals(ZoneType.Hand)) { for (Card card : p.getController() .chooseCardsToDiscardFrom(p, sa, validCards.get(i), numToBalance, numToBalance)) { if (null == card) continue; p.discard(card, sa); } } else { // Battlefield // TODO: "can'e be sacrificed" for (Card card : p.getController() .choosePermanentsToSacrifice( sa, numToBalance, numToBalance, validCards.get(i), valid)) { if (null == card) continue; game.getAction().sacrifice(card, sa); } } } }
@Override protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) { final TargetRestrictions tgt = sa.getTargetRestrictions(); if (tgt == null) { return mandatory; } if (phasesPrefTargeting(tgt, sa, mandatory)) { return true; } else if (mandatory) { // not enough preferred targets, but mandatory so keep going: return phasesUnpreferredTargeting(aiPlayer.getGame(), sa, mandatory); } 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 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; }
@Override public Card chooseSingleCard( final Player aiChoser, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) { if ("NeedsPrevention".equals(sa.getParam("AILogic"))) { final Player ai = sa.getActivatingPlayer(); final Game game = ai.getGame(); if (!game.getStack().isEmpty()) { Card choseCard = chooseCardOnStack(sa, ai, game); if (choseCard != null) { return choseCard; } } final Combat combat = game.getCombat(); List<Card> permanentSources = CardLists.filter( options, new Predicate<Card>() { @Override public boolean apply(final Card c) { if (c == null || c.getZone() == null || c.getZone().getZoneType() != ZoneType.Battlefield || combat == null || !combat.isAttacking(c, ai) || !combat.isUnblocked(c)) { return false; } return ComputerUtilCombat.damageIfUnblocked(c, ai, combat, true) > 0; } }); return ComputerUtilCard.getBestCreatureAI(permanentSources); } else { return ComputerUtilCard.getBestAI(options); } }
@Override public void resolve(SpellAbility sa) { final Card host = sa.getHostCard(); final String[] choices = sa.getParam("Choices").split(","); final List<SpellAbility> abilities = new ArrayList<SpellAbility>(); for (String s : choices) { abilities.add(AbilityFactory.getAbility(host.getSVar(s), host)); } final List<Player> tgtPlayers = getDefinedPlayersOrTargeted(sa); final TargetRestrictions tgt = sa.getTargetRestrictions(); for (final Player p : tgtPlayers) { if (tgt != null && !p.canBeTargetedBy(sa)) { continue; } int idxChosen = 0; String chosenName; if (sa.hasParam("AtRandom")) { idxChosen = MyRandom.getRandom().nextInt(choices.length); chosenName = choices[idxChosen]; } else { SpellAbility saChosen = p.getController().chooseSingleSpellForEffect(abilities, sa, "Choose one"); idxChosen = abilities.indexOf(saChosen); chosenName = choices[idxChosen]; } SpellAbility chosenSA = AbilityFactory.getAbility(host.getSVar(chosenName), host); if (sa.hasParam("ShowChoice")) { p.getGame() .getAction() .nofityOfValue(sa, p, abilities.get(idxChosen).getDescription(), null); } chosenSA.setActivatingPlayer(sa.getActivatingPlayer()); ((AbilitySub) chosenSA).setParent(sa); AbilityUtils.resolve(chosenSA); } }
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 }
@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()
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()
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()
@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; }
@Override public void resolve(SpellAbility sa) { final Card card = sa.getHostCard(); // final int min = sa.containsKey("Min") ? Integer.parseInt(sa.get("Min")) : 0; // final int max = sa.containsKey("Max") ? Integer.parseInt(sa.get("Max")) : 99; final boolean random = sa.hasParam("Random"); final boolean anyNumber = sa.hasParam("ChooseAnyNumber"); final boolean secretlyChoose = sa.hasParam("SecretlyChoose"); final String sMin = sa.getParamOrDefault("Min", "0"); final int min = AbilityUtils.calculateAmount(card, sMin, sa); final String sMax = sa.getParamOrDefault("Max", "99"); final int max = AbilityUtils.calculateAmount(card, sMax, sa); final List<Player> tgtPlayers = getTargetPlayers(sa); final TargetRestrictions tgt = sa.getTargetRestrictions(); final Map<Player, Integer> chooseMap = new HashMap<Player, Integer>(); for (final Player p : tgtPlayers) { if ((tgt == null) || p.canBeTargetedBy(sa)) { int chosen; if (random) { final Random randomGen = new Random(); chosen = randomGen.nextInt(max - min) + min; p.getGame().getAction().nofityOfValue(sa, p, Integer.toString(chosen), null); } else { String title = sa.hasParam("ListTitle") ? sa.getParam("ListTitle") : "Choose a number"; if (anyNumber) { chosen = p.getController().announceRequirements(sa, title, true); } else { chosen = p.getController().chooseNumber(sa, title, min, max); } // don't notify here, because most scripts I've seen don't store that number in a long // term } if (secretlyChoose) { chooseMap.put(p, chosen); } else { card.setChosenNumber(chosen); } if (sa.hasParam("Notify")) { p.getGame().getAction().nofityOfValue(sa, card, p.getName() + " picked " + chosen, p); } } } if (secretlyChoose) { StringBuilder sb = new StringBuilder(); List<Player> highestNum = new ArrayList<Player>(); List<Player> lowestNum = new ArrayList<Player>(); int highest = 0; int lowest = Integer.MAX_VALUE; for (Entry<Player, Integer> ev : chooseMap.entrySet()) { int num = ev.getValue(); Player player = ev.getKey(); sb.append(player).append(" chose ").append(num); sb.append("\r\n"); if (num > highest) { highestNum.clear(); highest = num; } if (num == highest) { highestNum.add(player); } if (num < lowest) { lowestNum.clear(); lowest = num; } if (num == lowest) { lowestNum.add(player); } } card.getGame().getAction().nofityOfValue(sa, card, sb.toString(), null); if (sa.hasParam("ChooseNumberSubAbility")) { SpellAbility sub = AbilityFactory.getAbility(card.getSVar(sa.getParam("ChooseNumberSubAbility")), card); sub.setActivatingPlayer(sa.getActivatingPlayer()); ((AbilitySub) sub).setParent(sa); for (Player p : chooseMap.keySet()) { card.addRemembered(p); card.setChosenNumber(chooseMap.get(p)); AbilityUtils.resolve(sub); card.clearRemembered(); } } if (sa.hasParam("Lowest")) { SpellAbility action = AbilityFactory.getAbility(card.getSVar(sa.getParam("Lowest")), card); action.setActivatingPlayer(sa.getActivatingPlayer()); ((AbilitySub) action).setParent(sa); for (Player p : lowestNum) { card.addRemembered(p); card.setChosenNumber(lowest); AbilityUtils.resolve(action); card.clearRemembered(); } } if (sa.hasParam("Highest")) { SpellAbility action = AbilityFactory.getAbility(card.getSVar(sa.getParam("Highest")), card); action.setActivatingPlayer(sa.getActivatingPlayer()); ((AbilitySub) action).setParent(sa); for (Player p : highestNum) { card.addRemembered(p); card.setChosenNumber(highest); AbilityUtils.resolve(action); card.clearRemembered(); } if (sa.hasParam("RememberHighest")) { card.addRemembered(highestNum); } } } }