package forge;
import forge.card.abilityFactory.AbilityFactory;
import forge.card.cardFactory.CardFactoryUtil;
import forge.card.mana.ManaCost;
import forge.card.mana.ManaPool;
import forge.card.spellability.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import static forge.error.ErrorViewer.showError;
/**
* <p>ComputerUtil class.</p>
*
* @author Forge
* @version $Id: $
*/
public class ComputerUtil {
//if return true, go to next phase
/**
* <p>playCards.</p>
*
* @return a boolean.
*/
static public boolean playCards() {
return playCards(getSpellAbility());
}
//if return true, go to next phase
/**
* <p>playCards.</p>
*
* @param all an array of {@link forge.card.spellability.SpellAbility} objects.
* @return a boolean.
*/
static public boolean playCards(SpellAbility[] all) {
//not sure "playing biggest spell" matters?
sortSpellAbilityByCost(all);
// MyRandom.shuffle(all);
for (SpellAbility sa : all) {
// Don't add Counterspells to the "normal" playcard lookupss
AbilityFactory af = sa.getAbilityFactory();
if (af != null && af.getAPI().equals("Counter"))
continue;
sa.setActivatingPlayer(AllZone.getComputerPlayer());
if (canBePlayedAndPayedByAI(sa)) //checks everything nescessary
{
handlePlayingSpellAbility(sa);
return false;
}
}
return true;
}//playCards()
/**
* <p>playCards.</p>
*
* @param all a {@link java.util.ArrayList} object.
* @return a boolean.
*/
static public boolean playCards(ArrayList<SpellAbility> all) {
SpellAbility[] sas = new SpellAbility[all.size()];
for (int i = 0; i < sas.length; i++) {
sas[i] = all.get(i);
}
return playCards(sas);
}//playCards()
/**
* <p>handlePlayingSpellAbility.</p>
*
* @param sa a {@link forge.card.spellability.SpellAbility} object.
*/
static public void handlePlayingSpellAbility(SpellAbility sa) {
AllZone.getStack().freezeStack();
Card source = sa.getSourceCard();
if (sa.isSpell() && !source.isCopiedSpell())
AllZone.getGameAction().moveToStack(source);
Cost cost = sa.getPayCosts();
Target tgt = sa.getTarget();
if (cost == null) {
payManaCost(sa);
sa.chooseTargetAI();
sa.getBeforePayManaAI().execute();
AllZone.getStack().addAndUnfreeze(sa);
} else {
if (tgt != null && tgt.doesTarget())
sa.chooseTargetAI();
Cost_Payment pay = new Cost_Payment(cost, sa);
if (pay.payComputerCosts())
AllZone.getStack().addAndUnfreeze(sa);
}
}
/**
* <p>counterSpellRestriction.</p>
*
* @param sa a {@link forge.card.spellability.SpellAbility} object.
* @return a int.
*/
static public int counterSpellRestriction(SpellAbility sa) {
// Move this to AF?
// Restriction Level is Based off a handful of factors
int restrict = 0;
Card source = sa.getSourceCard();
Target tgt = sa.getTarget();
AbilityFactory af = sa.getAbilityFactory();
HashMap<String, String> params = af.getMapParams();
// Play higher costing spells first?
Cost cost = sa.getPayCosts();
// Convert cost to CMC
//String totalMana = source.getSVar("PayX"); // + cost.getCMC()
// Consider the costs here for relative "scoring"
if (cost.getDiscardType().equals("Hand")) {
// Null Brooch aid
restrict -= (AllZoneUtil.getPlayerHand(AllZone.getComputerPlayer()).size() * 20);
}
// Abilities before Spells (card advantage)
if (af.isAbility())
restrict += 40;
// TargetValidTargeting gets biggest bonus
if (tgt.getSAValidTargeting() != null) {
restrict += 35;
}
// Unless Cost gets significant bonus + 10-Payment Amount
String unless = params.get("UnlessCost");
if (unless != null) {
int amount = AbilityFactory.calculateAmount(source, unless, sa);
int usableManaSources = CardFactoryUtil.getUsableManaSources(AllZone.getHumanPlayer());
// If the Unless isn't enough, this should be less likely to be used
if (amount > usableManaSources)
restrict += 20 - (2 * amount);
else
restrict -= (10 - (2 * amount));
}
// Then base on Targeting Restriction
String[] validTgts = tgt.getValidTgts();
if (validTgts.length != 1 || !validTgts[0].equals("Card"))
restrict += 10;
// And lastly give some bonus points to least restrictive TargetType (Spell,Ability,Triggered)
String tgtType = tgt.getTargetSpellAbilityType();
restrict -= (5 * tgtType.split(",").length);
return restrict;
}
//if return true, go to next phase
/**
* <p>playCounterSpell.</p>
*
* @param possibleCounters a {@link java.util.ArrayList} object.
* @return a boolean.
*/
static public boolean playCounterSpell(ArrayList<SpellAbility> possibleCounters) {
SpellAbility bestSA = null;
int bestRestriction = Integer.MIN_VALUE;
for (SpellAbility sa : possibleCounters) {
sa.setActivatingPlayer(AllZone.getComputerPlayer());
if (canBePlayedAndPayedByAI(sa)) { //checks everything nescessary
if (bestSA == null) {
bestSA = sa;
bestRestriction = counterSpellRestriction(sa);
} else {
// Compare bestSA with this SA
int restrictionLevel = counterSpellRestriction(sa);
if (restrictionLevel > bestRestriction) {
bestRestriction = restrictionLevel;
bestSA = sa;
}
}
}
}
if (bestSA == null)
return false;
// TODO
// "Look" at Targeted SA and "calculate" the threshold
// if (bestRestriction < targetedThreshold) return false;
AllZone.getStack().freezeStack();
Card source = bestSA.getSourceCard();
if (bestSA.isSpell() && !source.isCopiedSpell())
AllZone.getGameAction().moveToStack(source);
Cost cost = bestSA.getPayCosts();
if (cost == null) {
// Honestly Counterspells shouldn't use this branch
payManaCost(bestSA);
bestSA.chooseTargetAI();
bestSA.getBeforePayManaAI().execute();
AllZone.getStack().addAndUnfreeze(bestSA);
} else {
Cost_Payment pay = new Cost_Payment(cost, bestSA);
if (pay.payComputerCosts())
AllZone.getStack().addAndUnfreeze(bestSA);
}
return true;
}//playCounterSpell()
//this is used for AI's counterspells
/**
* <p>playStack.</p>
*
* @param sa a {@link forge.card.spellability.SpellAbility} object.
*/
final static public void playStack(SpellAbility sa) {
if (canPayCost(sa)) {
Card source = sa.getSourceCard();
if (sa.isSpell() && !source.isCopiedSpell())
AllZone.getGameAction().moveToStack(source);
sa.setActivatingPlayer(AllZone.getComputerPlayer());
payManaCost(sa);
AllZone.getStack().add(sa);
}
}
/**
* <p>playStackFree.</p>
*
* @param sa a {@link forge.card.spellability.SpellAbility} object.
*/
final static public void playStackFree(SpellAbility sa) {
sa.setActivatingPlayer(AllZone.getComputerPlayer());
Card source = sa.getSourceCard();
if (sa.isSpell() && !source.isCopiedSpell())
AllZone.getGameAction().moveToStack(source);
AllZone.getStack().add(sa);
}
/**
* <p>playNoStack.</p>
*
* @param sa a {@link forge.card.spellability.SpellAbility} object.
*/
final static public void playNoStack(SpellAbility sa) {
// TODO: We should really restrict what doesn't use the Stack
if (canPayCost(sa)) {
Card source = sa.getSourceCard();
if (sa.isSpell() && !source.isCopiedSpell())
AllZone.getGameAction().moveToStack(source);
sa.setActivatingPlayer(AllZone.getComputerPlayer());
payManaCost(sa);
AbilityFactory.resolve(sa, false);
//destroys creatures if they have lethal damage, etc..
AllZone.getGameAction().checkStateEffects();
}
}//play()
//gets Spells of cards in hand and Abilities of cards in play
//checks to see
//1. if canPlay() returns true, 2. can pay for mana
/**
* <p>getSpellAbility.</p>
*
* @return an array of {@link forge.card.spellability.SpellAbility} objects.
*/
static public SpellAbility[] getSpellAbility() {
CardList all = new CardList();
all.addAll(AllZoneUtil.getPlayerCardsInPlay(AllZone.getComputerPlayer()));
all.addAll(AllZoneUtil.getPlayerHand(AllZone.getComputerPlayer()));
all.addAll(CardFactoryUtil.getExternalZoneActivationCards(AllZone.getComputerPlayer()));
CardList humanPlayable = new CardList();
humanPlayable.addAll(AllZoneUtil.getPlayerCardsInPlay(AllZone.getHumanPlayer()));
humanPlayable = humanPlayable.filter(new CardListFilter() {
public boolean addCard(Card c) {
return (c.canAnyPlayerActivate());
}
});
all.addAll(humanPlayable);
ArrayList<SpellAbility> spellAbility = new ArrayList<SpellAbility>();
for (int outer = 0; outer < all.size(); outer++) {
SpellAbility[] sa = all.get(outer).getSpellAbility();
for (int i = 0; i < sa.length; i++)
spellAbility.add(sa[i]);//this seems like it needs to be copied, not sure though
}
SpellAbility[] sa = new SpellAbility[spellAbility.size()];
spellAbility.toArray(sa);
return sa;
}
//This is for playing spells regularly (no Cascade/Ripple etc.)
/**
* <p>canBePlayedAndPayedByAI.</p>
*
* @param sa a {@link forge.card.spellability.SpellAbility} object.
* @return a boolean.
* @since 1.0.15
*/
static public boolean canBePlayedAndPayedByAI(SpellAbility sa) {
return sa.canPlayAI() && sa.canPlay() && canPayCost(sa);
}
/**
* <p>canPayCost.</p>
*
* @param sa a {@link forge.card.spellability.SpellAbility} object.
* @return a boolean.
*/
static public boolean canPayCost(SpellAbility sa) {
return canPayCost(sa, AllZone.getComputerPlayer());
}//canPayCost()
/**
* <p>canPayCost.</p>
*
* @param sa a {@link forge.card.spellability.SpellAbility} object.
* @param player a {@link forge.Player} object.
* @return a boolean.
*/
static public boolean canPayCost(SpellAbility sa, Player player) {
if (!payManaCost(sa, player, true, 0))
return false;
return canPayAdditionalCosts(sa, player);
}//canPayCost()
/**
* <p>determineLeftoverMana.</p>
*
* @param sa a {@link forge.card.spellability.SpellAbility} object.
* @return a int.
*/
static public int determineLeftoverMana(SpellAbility sa) {
return determineLeftoverMana(sa, AllZone.getComputerPlayer());
}
/**
* <p>determineLeftoverMana.</p>
*
* @param sa a {@link forge.card.spellability.SpellAbility} object.
* @param player a {@link forge.Player} object.
* @return a int.
* @since 1.0.15
*/
static public int determineLeftoverMana(SpellAbility sa, Player player) {
int xMana = 0;
for (int i = 1; i < 99; i++) {
if (!payManaCost(sa, player, true, xMana))
break;
xMana = i;
}
return xMana;
}
/**
* <p>canPayAdditionalCosts.</p>
*
* @param sa a {@link forge.card.spellability.SpellAbility} object.
* @return a boolean.
*/
static public boolean canPayAdditionalCosts(SpellAbility sa) {
return canPayAdditionalCosts(sa, AllZone.getComputerPlayer());
}
/**
* <p>canPayAdditionalCosts.</p>
*
* @param sa a {@link forge.card.spellability.SpellAbility} object.
* @param player a {@link forge.Player} object.
* @return a boolean.
*/
static public boolean canPayAdditionalCosts(SpellAbility sa, Player player) {
// Add additional cost checks here before attempting to activate abilities
Cost cost = sa.getPayCosts();
if (cost == null)
return true;
Card card = sa.getSourceCard();
if (cost.getTap() && (card.isTapped() || card.isSick()))
return false;
if (cost.getUntap() && (card.isUntapped() || card.isSick()))
return false;
if (cost.getTapXTypeCost()) {
CardList typeList = AllZoneUtil.getPlayerCardsInPlay(player);
typeList = typeList.getValidCards(cost.getTapXType().split(","), sa.getActivatingPlayer(), sa.getSourceCard());
if (cost.getTap())
typeList.remove(sa.getSourceCard());
typeList = typeList.filter(AllZoneUtil.untapped);
if (cost.getTapXTypeAmount() > typeList.size())
return false;
}
if (cost.getSubCounter()) {
Counters c = cost.getCounterType();
if (card.getCounters(c) - cost.getCounterNum() < 0 || !AllZoneUtil.isCardInPlay(card)) {
return false;
}
}
if (cost.getAddCounter()) {
// this should always be true
}
if (cost.getLifeCost()) {
if (player.getLife() <= cost.getLifeAmount())
return false;
}
if (cost.getDiscardCost()) {
CardList handList = AllZoneUtil.getPlayerHand(player);
String discType = cost.getDiscardType();
int discAmount = cost.getDiscardAmount();
if (cost.getDiscardThis()) {
if (!AllZone.getZone(card).getZoneName().equals(Constant.Zone.Hand))
return false;
} else if (discType.equals("LastDrawn")) {
//compy can't yet use this effectively
return false;
} else if (discType.equals("Hand")) {
// this will always work
} else {
if (!discType.equals("Any") && !discType.equals("Random")) {
String validType[] = discType.split(",");
handList = handList.getValidCards(validType, sa.getActivatingPlayer(), sa.getSourceCard());
}
if (discAmount > handList.size()) {
// not enough cards in hand to pay
return false;
}
}
}
if (cost.getSacCost()) {
// if there's a sacrifice in the cost, just because we can Pay it doesn't mean we want to.
if (!cost.getSacThis()) {
CardList typeList = AllZoneUtil.getPlayerCardsInPlay(player);
typeList = typeList.getValidCards(cost.getSacType().split(","), sa.getActivatingPlayer(), sa.getSourceCard());
Card target = sa.getTargetCard();
if (target != null && target.getController().isPlayer(player)) // don't sacrifice the card we're pumping
typeList.remove(target);
if (cost.getSacAmount() > typeList.size())
return false;
} else if (cost.getSacThis() && !AllZoneUtil.isCardInPlay(card))
return false;
}
if (cost.getExileCost()) {
// if there's an exile in the cost, just because we can Pay it doesn't mean we want to.
if (!cost.getExileThis()) {
CardList typeList = AllZoneUtil.getPlayerCardsInPlay(player);
typeList = typeList.getValidCards(cost.getExileType().split(","), sa.getActivatingPlayer(), sa.getSourceCard());
Card target = sa.getTargetCard();
if (target != null && target.getController().isPlayer(player)) // don't exile the card we're pumping
typeList.remove(target);
if (cost.getExileAmount() > typeList.size())
return false;
} else if (cost.getExileThis() && !AllZoneUtil.isCardInPlay(card))
return false;
}
if (cost.getExileFromHandCost()) {
// if there's an exile in the cost, just because we can Pay it doesn't mean we want to.
if (!cost.getExileFromHandThis()) {
CardList typeList = AllZoneUtil.getPlayerHand(player);
typeList = typeList.getValidCards(cost.getExileFromHandType().split(","), sa.getActivatingPlayer(), sa.getSourceCard());
Card target = sa.getTargetCard();
if (target != null && target.getController().isPlayer(player)) // don't exile the card we're pumping
typeList.remove(target);
if (cost.getExileFromHandAmount() > typeList.size())
return false;
} else if (cost.getExileFromHandThis() && !AllZoneUtil.isCardInPlayerHand(player, card))
return false;
}
if (cost.getExileFromGraveCost()) {
if (!cost.getExileFromGraveThis()) {
CardList typeList = AllZoneUtil.getPlayerGraveyard(player);
typeList = typeList.getValidCards(cost.getExileFromGraveType().split(","), sa.getActivatingPlayer(), sa.getSourceCard());
Card target = sa.getTargetCard();
if (target != null && target.getController().isPlayer(player)) // don't exile the card we're pumping
typeList.remove(target);
if (cost.getExileFromGraveAmount() > typeList.size())
return false;
} else if (cost.getExileFromGraveThis() && !AllZoneUtil.isCardInPlayerGraveyard(player, card))
return false;
}
if (cost.getExileFromTopCost()) {
if (!cost.getExileFromTopThis()) {
CardList typeList = AllZoneUtil.getPlayerCardsInLibrary(player);
typeList = typeList.getValidCards(cost.getExileFromTopType().split(","), sa.getActivatingPlayer(), sa.getSourceCard());
Card target = sa.getTargetCard();
if (target != null && target.getController().isPlayer(player)) // don't exile the card we're pumping
typeList.remove(target);
if (cost.getExileFromTopAmount() > typeList.size())
return false;
} else if (cost.getExileFromTopThis() && !AllZoneUtil.isCardInPlayerLibrary(player, card))
return false;
}
if (cost.getReturnCost()) {
// if there's a return in the cost, just because we can Pay it doesn't mean we want to.
if (!cost.getReturnThis()) {
CardList typeList = AllZoneUtil.getPlayerCardsInPlay(player);
typeList = typeList.getValidCards(cost.getReturnType().split(","), sa.getActivatingPlayer(), sa.getSourceCard());
Card target = sa.getTargetCard();
if (target != null && target.getController().isPlayer(player)) // don't bounce the card we're pumping
typeList.remove(target);
if (cost.getReturnAmount() > typeList.size())
return false;
} else if (!AllZoneUtil.isCardInPlay(card))
return false;
}
return true;
}
/**
* <p>payManaCost.</p>
*
* @param sa a {@link forge.card.spellability.SpellAbility} object.
*/
static public void payManaCost(SpellAbility sa) {
payManaCost(sa, AllZone.getComputerPlayer(), false, 0);
}
/**
* <p>payManaCost.</p>
*
* @param sa a {@link forge.card.spellability.SpellAbility} object.
* @param player a {@link forge.Player} object.
* @param test (is for canPayCost, if true does not change the game state)
* @param extraMana a int.
* @return a boolean.
* @since 1.0.15
*/
static public boolean payManaCost(SpellAbility sa, Player player, boolean test, int extraMana) {
String mana = sa.getPayCosts() != null ? sa.getPayCosts().getTotalMana() : sa.getManaCost();
ManaCost cost = new ManaCost(mana);
cost = AllZone.getGameAction().getSpellCostChange(sa, cost);
ManaPool manapool = AllZone.getComputerManaPool();
if (player.isHuman()) manapool = AllZone.getManaPool();
Card card = sa.getSourceCard();
// Tack xMana Payments into mana here if X is a set value
if (sa.getPayCosts() != null && cost.getXcounter() > 0) {
int manaToAdd = 0;
if (test && extraMana > 0)
manaToAdd = extraMana * cost.getXcounter();
else {
// For Count$xPaid set PayX in the AFs then use that here
// Else calculate it as appropriate.
String xSvar = card.getSVar("X").equals("Count$xPaid") ? "PayX" : "X";
if (!card.getSVar(xSvar).equals("")) {
if (xSvar.equals("PayX"))
manaToAdd = Integer.parseInt(card.getSVar(xSvar)) * cost.getXcounter(); // X has already been decided
else {
manaToAdd = AbilityFactory.calculateAmount(card, xSvar, sa) * cost.getXcounter();
}
}
}
cost.increaseColorlessMana(manaToAdd);
if (!test)
card.setXManaCostPaid(manaToAdd);
}
if (cost.isPaid())
return true;
ArrayList<String> colors;
cost = ((ManaPool) manapool).subtractMana(sa, cost);
CardList manaSources = getAvailableMana();
//this is to prevent errors for mana sources that have abilities that cost mana.
manaSources.remove(sa.getSourceCard());
for (int i = 0; i < manaSources.size(); i++) {
Card sourceCard = manaSources.get(i);
ArrayList<Ability_Mana> manaAbilities = sourceCard.getAIPlayableMana();
boolean used = false; //this is for testing paying mana only
manaAbilities = sortForNeeded(cost, manaAbilities, player);
for (Ability_Mana m : manaAbilities) {
if (used) break; //mana source already used in the test
//if the AI can't pay the additional costs skip the mana ability
if (m.getPayCosts() != null) {
if (!canPayAdditionalCosts(m, player))
continue;
} else if (sourceCard.isTapped())
continue;
//don't use abilities with dangerous drawbacks
if (m.getSubAbility() != null)
if (!m.getSubAbility().chkAI_Drawback())
continue;
colors = getProduceableColors(m, player);
for (int j = 0; j < colors.size(); j++) {
if (used) break; //mana source already used in the test
if (cost.isNeeded(colors.get(j))) {
if (!test) {
//Pay additional costs
if (m.getPayCosts() != null) {
Cost_Payment pay = new Cost_Payment(m.getPayCosts(), m);
if (!pay.payComputerCosts()) continue;
} else
sourceCard.tap();
} else used = true; // mana source is now used in the test
cost.payMana(colors.get(j));
if (!test) {
//resolve subabilities
AbilityFactory af = m.getAbilityFactory();
if (af != null)
AbilityFactory.resolveSubAbilities(m);
if (sourceCard.getName().equals("Undiscovered Paradise")) {
sourceCard.setBounceAtUntap(true);
}
if (sourceCard.getName().equals("Rainbow Vale")) {
sourceCard.addExtrinsicKeyword("An opponent gains control of CARDNAME at the beginning of the next end step.");
}
//System.out.println("just subtracted " + colors.get(j) + ", cost is now: " + cost.toString());
//Run triggers
HashMap<String, Object> runParams = new HashMap<String, Object>();
runParams.put("Card", sourceCard);
runParams.put("Player", player);
runParams.put("Produced", colors.get(j)); //can't tell what mana the computer just paid?
AllZone.getTriggerHandler().runTrigger("TapsForMana", runParams);
}//not a test
}
if (cost.isPaid()) {
//if (sa instanceof Spell_Permanent) // should probably add this
sa.getSourceCard().setColorsPaid(cost.getColorsPaid());
sa.getSourceCard().setSunburstValue(cost.getSunburst());
manapool.clearPay(sa, test);
return true;
}
}
}
}
if (!test) // real payment should not arrive here
throw new RuntimeException("ComputerUtil : payManaCost() cost was not paid for " + sa.getSourceCard().getName());
return false;
}//payManaCost()
/**
* <p>getProduceableColors.</p>
*
* @param m a {@link forge.card.spellability.Ability_Mana} object.
* @param player a {@link forge.Player} object.
* @return a {@link java.util.ArrayList} object.
* @since 1.0.15
*/
public static ArrayList<String> getProduceableColors(Ability_Mana m, Player player) {
ArrayList<String> colors = new ArrayList<String>();
//if the mana ability is not avaiable move to the next one
m.setActivatingPlayer(player);
if (!m.canPlay()) return colors;
if (!colors.contains(Constant.Color.Black) && m.isBasic() && m.mana().equals("B"))
colors.add(Constant.Color.Black);
if (!colors.contains(Constant.Color.White) && m.isBasic() && m.mana().equals("W"))
colors.add(Constant.Color.White);
if (!colors.contains(Constant.Color.Green) && m.isBasic() && m.mana().equals("G"))
colors.add(Constant.Color.Green);
if (!colors.contains(Constant.Color.Red) && m.isBasic() && m.mana().equals("R"))
colors.add(Constant.Color.Red);
if (!colors.contains(Constant.Color.Blue) && m.isBasic() && m.mana().equals("U"))
colors.add(Constant.Color.Blue);
if (!colors.contains(Constant.Color.Colorless) && m.isBasic() && m.mana().equals("1"))
colors.add(Constant.Color.Colorless);
return colors;
}
/**
* <p>getAvailableMana.</p>
*
* @return a {@link forge.CardList} object.
*/
static public CardList getAvailableMana() {
return getAvailableMana(AllZone.getComputerPlayer());
}//getAvailableMana()
//gets available mana sources and sorts them
/**
* <p>getAvailableMana.</p>
*
* @param player a {@link forge.Player} object.
* @return a {@link forge.CardList} object.
*/
static public CardList getAvailableMana(final Player player) {
CardList list = AllZoneUtil.getPlayerCardsInPlay(player);
CardList manaSources = list.filter(new CardListFilter() {
public boolean addCard(Card c) {
for (Ability_Mana am : c.getAIPlayableMana()) {
am.setActivatingPlayer(player);
if (am.canPlay()) return true;
}
return false;
}
});//CardListFilter
CardList sortedManaSources = new CardList();
// 1. Use lands that can only produce colorless mana without drawback/cost first
for (int i = 0; i < manaSources.size(); i++) {
Card card = manaSources.get(i);
if (card.isCreature()) continue; //don't use creatures before other permanents
int usableManaAbilities = 0;
boolean needsLimitedResources = false;
ArrayList<Ability_Mana> manaAbilities = card.getAIPlayableMana();
for (Ability_Mana m : manaAbilities) {
Cost cost = m.getPayCosts();
//if the AI can't pay the additional costs skip the mana ability
if (cost != null) {
if (!canPayAdditionalCosts(m, player))
continue;
if (cost.getSubCounter() || cost.getLifeCost())
needsLimitedResources = true;
} else if (card.isTapped())
continue;
//don't use abilities with dangerous drawbacks
if (m.getSubAbility() != null) {
if (!m.getSubAbility().chkAI_Drawback())
continue;
needsLimitedResources = true; //TODO: check for good drawbacks (gainLife)
}
usableManaAbilities++;
}
//use lands that can only produce colorless mana first
if (usableManaAbilities == 1 && !needsLimitedResources && manaAbilities.get(0).mana().equals("1"))
sortedManaSources.add(card);
}
// 2. Search for mana sources that have a certain number of mana abilities (start with 1 and go up to 5) and no drawback/costs
for (int number = 1; number < 6; number++)
for (int i = 0; i < manaSources.size(); i++) {
Card card = manaSources.get(i);
if (card.isCreature()) continue; //don't use creatures before other permanents
int usableManaAbilities = 0;
boolean needsLimitedResources = false;
ArrayList<Ability_Mana> manaAbilities = card.getAIPlayableMana();
for (Ability_Mana m : manaAbilities) {
Cost cost = m.getPayCosts();
//if the AI can't pay the additional costs skip the mana ability
if (cost != null) {
if (!canPayAdditionalCosts(m, player))
continue;
if (cost.getSubCounter() || cost.getLifeCost())
needsLimitedResources = true;
} else if (card.isTapped())
continue;
//don't use abilities with dangerous drawbacks
if (m.getSubAbility() != null) {
if (!m.getSubAbility().chkAI_Drawback())
continue;
needsLimitedResources = true; //TODO: check for good drawbacks (gainLife)
}
usableManaAbilities++;
}
if (usableManaAbilities == number && !needsLimitedResources && !sortedManaSources.contains(card))
sortedManaSources.add(card);
}
//Add the rest
for (int j = 0; j < manaSources.size(); j++) {
if (!sortedManaSources.contains(manaSources.get(j)))
sortedManaSources.add(manaSources.get(j));
}
return sortedManaSources;
}//getAvailableMana()
// sorts the most needed mana abilities to come first
/**
* <p>sortForNeeded.</p>
*
* @param cost a {@link forge.card.mana.ManaCost} object.
* @param manaAbilities a {@link java.util.ArrayList} object.
* @param player a {@link forge.Player} object.
* @return a {@link java.util.ArrayList} object.
* @since 1.0.15
*/
static public ArrayList<Ability_Mana> sortForNeeded(ManaCost cost, ArrayList<Ability_Mana> manaAbilities, Player player) {
ArrayList<String> colors;
ArrayList<Ability_Mana> res = new ArrayList<Ability_Mana>();
ManaCost onlyColored = new ManaCost(cost.toString());
onlyColored.removeColorlessMana();
for (Ability_Mana am : manaAbilities) {
colors = getProduceableColors(am, player);
for (int j = 0; j < colors.size(); j++) {
if (onlyColored.isNeeded(colors.get(j))) {
res.add(am);
break;
}
}
}
for (Ability_Mana am : manaAbilities) {
if (res.contains(am)) break;
colors = getProduceableColors(am, player);
for (int j = 0; j < colors.size(); j++) {
if (cost.isNeeded(colors.get(j))) {
res.add(am);
break;
}
}
}
return res;
}
//plays a land if one is available
/**
* <p>chooseLandsToPlay.</p>
*
* @return a boolean.
*/
static public boolean chooseLandsToPlay() {
Player computer = AllZone.getComputerPlayer();
CardList landList = AllZoneUtil.getPlayerHand(computer);
landList = landList.filter(AllZoneUtil.lands);
if (AllZoneUtil.getPlayerCardsInPlay(computer, "Crucible of Worlds").size() > 0) {
CardList lands = AllZoneUtil.getPlayerTypeInGraveyard(computer, "Land");
for (Card crd : lands)
landList.add(crd);
}
landList = landList.filter(new CardListFilter() {
public boolean addCard(Card c) {
if (c.getSVar("NeedsToPlay").length() > 0) {
String needsToPlay = c.getSVar("NeedsToPlay");
CardList list = AllZoneUtil.getCardsInPlay();
list = list.getValidCards(needsToPlay.split(","), c.getController(), c);
if (list.isEmpty()) return false;
}
if (c.isType("Legendary")
&& !c.getName().equals("Flagstones of Trokair")) {
CardList list = AllZoneUtil.getPlayerCardsInPlay(AllZone.getComputerPlayer());
if (list.containsName(c.getName()))
return false;
}
//don't play the land if it has cycling and enough lands are available
ArrayList<SpellAbility> spellAbilities = c.getSpellAbilities();
for (SpellAbility sa : spellAbilities)
if (sa.isCycling()) {
CardList hand = AllZoneUtil.getPlayerHand(AllZone.getComputerPlayer());
CardList lands = AllZoneUtil.getPlayerCardsInPlay(AllZone.getComputerPlayer());
lands.addAll(hand);
lands = lands.getType("Land");
if (lands.size() >= Math.max(hand.getHighestConvertedManaCost(), 6))
return false;
}
return true;
}
});
while (!landList.isEmpty() && computer.canPlayLand()) {
// play as many lands as you can
int ix = 0;
while (landList.get(ix).isReflectedLand() && (ix + 1 < landList.size())) {
// Skip through reflected lands. Choose last if they are all reflected.
ix++;
}
Card land = landList.get(ix);
landList.remove(ix);
computer.playLand(land);
if (AllZone.getStack().size() != 0)
return true;
}
return false;
}
/**
* <p>getCardPreference.</p>
*
* @param activate a {@link forge.Card} object.
* @param pref a {@link java.lang.String} object.
* @param typeList a {@link forge.CardList} object.
* @return a {@link forge.Card} object.
*/
static public Card getCardPreference(Card activate, String pref, CardList typeList) {
String[] prefValid = activate.getSVar("AIPreference").split("\\$");
if (prefValid[0].equals(pref)) {
CardList prefList = typeList.getValidCards(prefValid[1].split(","), activate.getController(), activate);
if (prefList.size() != 0) {
prefList.shuffle();
return prefList.get(0);
}
}
if (pref.contains("SacCost")) { // search for permanents with SacMe
for (int ip = 0; ip < 9; ip++) { // priority 0 is the lowest, priority 5 the highest
final int priority = 9 - ip;
CardList SacMeList = typeList.filter(new CardListFilter() {
public boolean addCard(Card c) {
return (!c.getSVar("SacMe").equals("") && Integer.parseInt(c.getSVar("SacMe")) == priority);
}
});
if (SacMeList.size() != 0) {
SacMeList.shuffle();
return SacMeList.get(0);
}
}
}
return null;
}
/**
* <p>chooseSacrificeType.</p>
*
* @param type a {@link java.lang.String} object.
* @param activate a {@link forge.Card} object.
* @param target a {@link forge.Card} object.
* @param amount a int.
* @return a {@link forge.CardList} object.
*/
static public CardList chooseSacrificeType(String type, Card activate, Card target, int amount) {
CardList typeList = AllZoneUtil.getPlayerCardsInPlay(AllZone.getComputerPlayer());
typeList = typeList.getValidCards(type.split(","), activate.getController(), activate);
if (target != null && target.getController().isComputer() && typeList.contains(target))
typeList.remove(target); // don't sacrifice the card we're pumping
if (typeList.size() == 0)
return null;
CardList sacList = new CardList();
int count = 0;
while (count < amount) {
Card prefCard = getCardPreference(activate, "SacCost", typeList);
if (prefCard != null) {
sacList.add(prefCard);
typeList.remove(prefCard);
count++;
} else
break;
}
CardListUtil.sortAttackLowFirst(typeList);
for (int i = count; i < amount; i++) sacList.add(typeList.get(i));
return sacList;
}
/**
* <p>chooseExileType.</p>
*
* @param type a {@link java.lang.String} object.
* @param activate a {@link forge.Card} object.
* @param target a {@link forge.Card} object.
* @param amount a int.
* @return a {@link forge.CardList} object.
*/
static public CardList chooseExileType(String type, Card activate, Card target, int amount) {
return chooseExileFrom(Constant.Zone.Battlefield, type, activate, target, amount);
}
/**
* <p>chooseExileFromHandType.</p>
*
* @param type a {@link java.lang.String} object.
* @param activate a {@link forge.Card} object.
* @param target a {@link forge.Card} object.
* @param amount a int.
* @return a {@link forge.CardList} object.
*/
static public CardList chooseExileFromHandType(String type, Card activate, Card target, int amount) {
return chooseExileFrom(Constant.Zone.Hand, type, activate, target, amount);
}
/**
* <p>chooseExileFromGraveType.</p>
*
* @param type a {@link java.lang.String} object.
* @param activate a {@link forge.Card} object.
* @param target a {@link forge.Card} object.
* @param amount a int.
* @return a {@link forge.CardList} object.
*/
static public CardList chooseExileFromGraveType(String type, Card activate, Card target, int amount) {
return chooseExileFrom(Constant.Zone.Graveyard, type, activate, target, amount);
}
/**
* <p>chooseExileFrom.</p>
*
* @param zone a {@link java.lang.String} object.
* @param type a {@link java.lang.String} object.
* @param activate a {@link forge.Card} object.
* @param target a {@link forge.Card} object.
* @param amount a int.
* @return a {@link forge.CardList} object.
*/
static public CardList chooseExileFrom(String zone, String type, Card activate, Card target, int amount) {
CardList typeList = AllZoneUtil.getCardsInZone(zone, AllZone.getComputerPlayer());
typeList = typeList.getValidCards(type.split(","), activate.getController(), activate);
if (target != null && target.getController().isComputer() && typeList.contains(target))
typeList.remove(target); // don't exile the card we're pumping
if (typeList.size() == 0)
return null;
CardListUtil.sortAttackLowFirst(typeList);
CardList exileList = new CardList();
for (int i = 0; i < amount; i++) exileList.add(typeList.get(i));
return exileList;
}
/**
* <p>chooseTapType.</p>
*
* @param type a {@link java.lang.String} object.
* @param activate a {@link forge.Card} object.
* @param tap a boolean.
* @param amount a int.
* @return a {@link forge.CardList} object.
*/
static public CardList chooseTapType(String type, Card activate, boolean tap, int amount) {
CardList typeList = AllZoneUtil.getPlayerCardsInPlay(AllZone.getComputerPlayer());
typeList = typeList.getValidCards(type.split(","), activate.getController(), activate);
//is this needed?
typeList = typeList.filter(AllZoneUtil.untapped);
if (tap)
typeList.remove(activate);
if (typeList.size() == 0 || amount >= typeList.size())
return null;
CardListUtil.sortAttackLowFirst(typeList);
CardList tapList = new CardList();
for (int i = 0; i < amount; i++) tapList.add(typeList.get(i));
return tapList;
}
/**
* <p>chooseReturnType.</p>
*
* @param type a {@link java.lang.String} object.
* @param activate a {@link forge.Card} object.
* @param target a {@link forge.Card} object.
* @param amount a int.
* @return a {@link forge.CardList} object.
*/
static public CardList chooseReturnType(String type, Card activate, Card target, int amount) {
CardList typeList = AllZoneUtil.getPlayerCardsInPlay(AllZone.getComputerPlayer());
typeList = typeList.getValidCards(type.split(","), activate.getController(), activate);
if (target != null && target.getController().isComputer() && typeList.contains(target)) // don't bounce the card we're pumping
typeList.remove(target);
if (typeList.size() == 0)
return null;
CardListUtil.sortAttackLowFirst(typeList);
CardList returnList = new CardList();
for (int i = 0; i < amount; i++) returnList.add(typeList.get(i));
return returnList;
}
/**
* <p>getPossibleAttackers.</p>
*
* @return a {@link forge.CardList} object.
*/
static public CardList getPossibleAttackers() {
CardList list = AllZoneUtil.getPlayerCardsInPlay(AllZone.getComputerPlayer());
list = list.filter(new CardListFilter() {
public boolean addCard(Card c) {
return CombatUtil.canAttack(c);
}
});
return list;
}
/**
* <p>getAttackers.</p>
*
* @return a {@link forge.Combat} object.
*/
static public Combat getAttackers() {
ComputerUtil_Attack2 att = new ComputerUtil_Attack2(AllZoneUtil.getPlayerCardsInPlay(AllZone.getComputerPlayer()),
AllZoneUtil.getPlayerCardsInPlay(AllZone.getHumanPlayer()), AllZone.getHumanPlayer().getLife());
return att.getAttackers();
}
/**
* <p>getBlockers.</p>
*
* @return a {@link forge.Combat} object.
*/
static public Combat getBlockers() {
CardList blockers = AllZoneUtil.getPlayerCardsInPlay(AllZone.getComputerPlayer());
return ComputerUtil_Block2.getBlockers(AllZone.getCombat(), blockers);
}
/**
* <p>sortSpellAbilityByCost.</p>
*
* @param sa an array of {@link forge.card.spellability.SpellAbility} objects.
*/
static void sortSpellAbilityByCost(SpellAbility sa[]) {
//sort from highest cost to lowest
//we want the highest costs first
Comparator<SpellAbility> c = new Comparator<SpellAbility>() {
public int compare(SpellAbility a, SpellAbility b) {
int a1 = CardUtil.getConvertedManaCost(a);
int b1 = CardUtil.getConvertedManaCost(b);
//puts creatures in front of spells
if (a.getSourceCard().isCreature())
a1 += 1;
if (b.getSourceCard().isCreature())
b1 += 1;
return b1 - a1;
}
};//Comparator
Arrays.sort(sa, c);
}//sortSpellAbilityByCost()
/**
* <p>sacrificePermanents.</p>
*
* @param amount a int.
* @param list a {@link forge.CardList} object.
*/
static public void sacrificePermanents(int amount, CardList list) {
// used in Annihilator and AF_Sacrifice
int max = list.size();
if (max > amount)
max = amount;
CardListUtil.sortCMC(list);
list.reverse();
for (int i = 0; i < max; i++) {
// TODO: use getWorstPermanent() would be wayyyy better
Card c;
if (list.getNotType("Creature").size() == 0) {
c = CardFactoryUtil.AI_getWorstCreature(list);
} else if (list.getNotType("Land").size() == 0) {
c = CardFactoryUtil.getWorstLand(AllZone.getComputerPlayer());
} else {
c = list.get(0);
}
ArrayList<Card> auras = c.getEnchantedBy();
if (auras.size() > 0) {
// TODO: choose "worst" controlled enchanting Aura
for (int j = 0; j < auras.size(); j++) {
Card aura = auras.get(j);
if (aura.getController().isPlayer(c.getController()) && list.contains(aura)) {
c = aura;
break;
}
}
}
list.remove(c);
AllZone.getGameAction().sacrifice(c);
}
}
/**
* <p>canRegenerate.</p>
*
* @param card a {@link forge.Card} object.
* @return a boolean.
*/
public static boolean canRegenerate(Card card) {
if (card.hasKeyword("CARDNAME can't be regenerated.")) return false;
Player controller = card.getController();
CardList l = AllZoneUtil.getPlayerCardsInPlay(controller);
for (Card c : l)
for (SpellAbility sa : c.getSpellAbility())
// if SA is from AF_Counter don't add to getPlayable
//This try/catch should fix the "computer is thinking" bug
try {
if (sa.canPlay() && ComputerUtil.canPayCost(sa, controller) && sa.getAbilityFactory() != null && sa.isAbility()) {
AbilityFactory af = sa.getAbilityFactory();
HashMap<String, String> mapParams = af.getMapParams();
if (mapParams.get("AB").equals("Regenerate")) {
if (AbilityFactory.getDefinedCards(sa.getSourceCard(), mapParams.get("Defined"), sa).contains(card))
return true;
Target tgt = sa.getTarget();
if (tgt != null) {
if (AllZoneUtil.getCardsInPlay().getValidCards(tgt.getValidTgts(), controller, af.getHostCard())
.contains(card))
return true;
}
}
}
} catch (Exception ex) {
showError(ex, "There is an error in the card code for %s:%n", c.getName(), ex.getMessage());
}
return false;
}
/**
* <p>possibleDamagePrevention.</p>
*
* @param card a {@link forge.Card} object.
* @return a int.
*/
public static int possibleDamagePrevention(Card card) {
int prevented = 0;
Player controller = card.getController();
CardList l = AllZoneUtil.getPlayerCardsInPlay(controller);
for (Card c : l)
for (SpellAbility sa : c.getSpellAbility())
// if SA is from AF_Counter don't add to getPlayable
//This try/catch should fix the "computer is thinking" bug
try {
if (sa.canPlay() && ComputerUtil.canPayCost(sa, controller) && sa.getAbilityFactory() != null && sa.isAbility()) {
AbilityFactory af = sa.getAbilityFactory();
HashMap<String, String> mapParams = af.getMapParams();
if (mapParams.get("AB").equals("PreventDamage")) {
if (AbilityFactory.getDefinedCards(sa.getSourceCard(), mapParams.get("Defined"), sa).contains(card))
prevented += AbilityFactory.calculateAmount(af.getHostCard(), mapParams.get("Amount"), sa);
Target tgt = sa.getTarget();
if (tgt != null) {
if (AllZoneUtil.getCardsInPlay().getValidCards(tgt.getValidTgts(), controller, af.getHostCard())
.contains(card))
prevented += AbilityFactory.calculateAmount(af.getHostCard(), mapParams.get("Amount"), sa);
}
}
}
} catch (Exception ex) {
showError(ex, "There is an error in the card code for %s:%n", c.getName(), ex.getMessage());
}
return prevented;
}
}