package forge.card.abilityFactory; import forge.*; import forge.card.cardFactory.CardFactoryUtil; import forge.card.spellability.*; import forge.gui.GuiUtils; import forge.gui.input.Input; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.Random; /** * <p>AbilityFactory_Counters class.</p> * * @author Forge * @version $Id: $ */ public class AbilityFactory_Counters { // An AbilityFactory subclass for Putting or Removing Counters on Cards. // ******************************************* // ********** PutCounters ***************** // ******************************************* /** * <p>createAbilityPutCounters.</p> * * @param af a {@link forge.card.abilityFactory.AbilityFactory} object. * @return a {@link forge.card.spellability.SpellAbility} object. */ public static SpellAbility createAbilityPutCounters(final AbilityFactory af) { final SpellAbility abPutCounter = new Ability_Activated(af.getHostCard(), af.getAbCost(), af.getAbTgt()) { private static final long serialVersionUID = -1259638699008542484L; @Override public String getStackDescription() { return putStackDescription(af, this); } @Override public boolean canPlayAI() { return putCanPlayAI(af, this); } @Override public void resolve() { putResolve(af, this); } @Override public boolean doTrigger(boolean mandatory) { return putDoTriggerAI(af, this, mandatory); } }; return abPutCounter; } /** * <p>createSpellPutCounters.</p> * * @param af a {@link forge.card.abilityFactory.AbilityFactory} object. * @return a {@link forge.card.spellability.SpellAbility} object. */ public static SpellAbility createSpellPutCounters(final AbilityFactory af) { final SpellAbility spPutCounter = new Spell(af.getHostCard(), af.getAbCost(), af.getAbTgt()) { private static final long serialVersionUID = -323471693082498224L; @Override public String getStackDescription() { return putStackDescription(af, this); } @Override public boolean canPlayAI() { // if X depends on abCost, the AI needs to choose which card he would sacrifice first // then call xCount with that card to properly calculate the amount // Or choosing how many to sacrifice return putCanPlayAI(af, this); } @Override public void resolve() { putResolve(af, this); } }; return spPutCounter; } /** * <p>createDrawbackPutCounters.</p> * * @param af a {@link forge.card.abilityFactory.AbilityFactory} object. * @return a {@link forge.card.spellability.SpellAbility} object. */ public static SpellAbility createDrawbackPutCounters(final AbilityFactory af) { final SpellAbility dbPutCounter = new Ability_Sub(af.getHostCard(), af.getAbTgt()) { private static final long serialVersionUID = -323471693082498224L; @Override public String getStackDescription() { return putStackDescription(af, this); } @Override public void resolve() { putResolve(af, this); } @Override public boolean chkAI_Drawback() { return putPlayDrawbackAI(af, this); } @Override public boolean doTrigger(boolean mandatory) { return putDoTriggerAI(af, this, mandatory); } }; return dbPutCounter; } /** * <p>putStackDescription.</p> * * @param af a {@link forge.card.abilityFactory.AbilityFactory} object. * @param sa a {@link forge.card.spellability.SpellAbility} object. * @return a {@link java.lang.String} object. */ private static String putStackDescription(AbilityFactory af, SpellAbility sa) { HashMap<String, String> params = af.getMapParams(); StringBuilder sb = new StringBuilder(); if (!(sa instanceof Ability_Sub)) sb.append(sa.getSourceCard().getName()).append(" - "); else sb.append(" "); Counters cType = Counters.valueOf(params.get("CounterType")); Card card = af.getHostCard(); int amount = AbilityFactory.calculateAmount(af.getHostCard(), params.get("CounterNum"), sa); sb.append("Put ").append(amount).append(" ").append(cType.getName()) .append(" counter"); if (amount != 1) sb.append("s"); sb.append(" on "); ArrayList<Card> tgtCards; Target tgt = af.getAbTgt(); if (tgt != null) tgtCards = tgt.getTargetCards(); else { tgtCards = AbilityFactory.getDefinedCards(card, params.get("Defined"), sa); } Iterator<Card> it = tgtCards.iterator(); while (it.hasNext()) { Card tgtC = it.next(); if (tgtC.isFaceDown()) sb.append("Morph"); else sb.append(tgtC); if (it.hasNext()) sb.append(", "); } sb.append("."); Ability_Sub abSub = sa.getSubAbility(); if (abSub != null) { sb.append(abSub.getStackDescription()); } return sb.toString(); } /** * <p>putCanPlayAI.</p> * * @param af a {@link forge.card.abilityFactory.AbilityFactory} object. * @param sa a {@link forge.card.spellability.SpellAbility} object. * @return a boolean. */ private static boolean putCanPlayAI(final AbilityFactory af, final SpellAbility sa) { // AI needs to be expanded, since this function can be pretty complex based on what the expected targets could be HashMap<String, String> params = af.getMapParams(); Random r = MyRandom.random; Cost abCost = sa.getPayCosts(); Target abTgt = sa.getTarget(); final Card source = sa.getSourceCard(); CardList list; Card choice = null; String type = params.get("CounterType"); String amountStr = params.get("CounterNum"); Player player = af.isCurse() ? AllZone.getHumanPlayer() : AllZone.getComputerPlayer(); list = AllZoneUtil.getPlayerCardsInPlay(player); list = list.filter(new CardListFilter() { public boolean addCard(Card c) { return CardFactoryUtil.canTarget(source, c) && !c.hasKeyword("CARDNAME can't have counters placed on it."); } }); if (abTgt != null) { list = list.getValidCards(abTgt.getValidTgts(), source.getController(), source); if (list.size() < abTgt.getMinTargets(source, sa)) return false; } else { // "put counter on this" PlayerZone pZone = AllZone.getZone(source); // Don't activate Curse abilities on my cards and non-curse abilites on my opponents if (!pZone.getPlayer().equals(player)) return false; } if (abCost != null) { // AI currently disabled for these costs if (abCost.getSacCost() && (!abCost.getSacThis() || source.isCreature())) { //only sacrifice something that's supposed to be sacrificed String sacType = abCost.getSacType(); CardList typeList = AllZoneUtil.getPlayerCardsInPlay(AllZone.getComputerPlayer()); typeList = typeList.getValidCards(sacType.split(","), source.getController(), source); if (ComputerUtil.getCardPreference(source, "SacCost", typeList) == null) return false; } if (abCost.getLifeCost()) return false; if (abCost.getDiscardCost()) return false; if (abCost.getSubCounter()) { // A card has a 25% chance per counter to be able to pass through here // 4+ counters will always pass. 0 counters will never int currentNum = source.getCounters(abCost.getCounterType()); double percent = .25 * (currentNum / abCost.getCounterNum()); if (percent <= r.nextFloat()) return false; } } if (!ComputerUtil.canPayCost(sa)) return false; // TODO handle proper calculation of X values based on Cost int amount = AbilityFactory.calculateAmount(af.getHostCard(), amountStr, sa); if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) { // Set PayX here to maximum value. amount = ComputerUtil.determineLeftoverMana(sa); source.setSVar("PayX", Integer.toString(amount)); // TODO: } //don't use it if no counters to add if (amount <= 0) return false; // prevent run-away activations - first time will always return true boolean chance = r.nextFloat() <= Math.pow(.6667, source.getAbilityUsed()); // Targeting if (abTgt != null) { abTgt.resetTargets(); // target loop while (abTgt.getNumTargeted() < abTgt.getMaxTargets(sa.getSourceCard(), sa)) { if (list.size() == 0) { if (abTgt.getNumTargeted() < abTgt.getMinTargets(sa.getSourceCard(), sa) || abTgt.getNumTargeted() == 0) { abTgt.resetTargets(); return false; } else { // TODO is this good enough? for up to amounts? break; } } if (af.isCurse()) { choice = chooseCursedTarget(list, type, amount); } else { choice = chooseBoonTarget(list, type); } if (choice == null) { // can't find anything left if (abTgt.getNumTargeted() < abTgt.getMinTargets(sa.getSourceCard(), sa) || abTgt.getNumTargeted() == 0) { abTgt.resetTargets(); return false; } else { // TODO is this good enough? for up to amounts? break; } } list.remove(choice); abTgt.addTarget(choice); } } else { // Placeholder: No targeting necessary int currCounters = sa.getSourceCard().getCounters(Counters.valueOf(type)); // each non +1/+1 counter on the card is a 10% chance of not activating this ability. if (!(type.equals("P1P1") || type.equals("ICE")) && r.nextFloat() < .1 * currCounters) return false; } //Don't use non P1P1/M1M1 counters before main 2 if possible if (AllZone.getPhase().isBefore(Constant.Phase.Main2) && !params.containsKey("ActivatingPhases") && !(type.equals("P1P1") || type.equals("M1M1"))) return false; Ability_Sub subAb = sa.getSubAbility(); if (subAb != null) chance &= subAb.chkAI_Drawback(); if (AbilityFactory.playReusable(sa)) return chance; return ((r.nextFloat() < .6667) && chance); }//putCanPlayAI /** * <p>putPlayDrawbackAI.</p> * * @param af a {@link forge.card.abilityFactory.AbilityFactory} object. * @param sa a {@link forge.card.spellability.SpellAbility} object. * @return a boolean. */ private static boolean putPlayDrawbackAI(final AbilityFactory af, final SpellAbility sa) { HashMap<String, String> params = af.getMapParams(); boolean chance = true; Target abTgt = sa.getTarget(); final Card source = sa.getSourceCard(); CardList list; Card choice = null; String type = params.get("CounterType"); String amountStr = params.get("CounterNum"); final int amount = AbilityFactory.calculateAmount(af.getHostCard(), amountStr, sa); Player player = af.isCurse() ? AllZone.getHumanPlayer() : AllZone.getComputerPlayer(); list = AllZoneUtil.getPlayerCardsInPlay(player); list = list.filter(new CardListFilter() { public boolean addCard(Card c) { return CardFactoryUtil.canTarget(source, c); } }); if (abTgt != null) { list = list.getValidCards(abTgt.getValidTgts(), source.getController(), source); if (list.size() == 0) return false; abTgt.resetTargets(); // target loop while (abTgt.getNumTargeted() < abTgt.getMaxTargets(sa.getSourceCard(), sa)) { if (list.size() == 0) { if (abTgt.getNumTargeted() < abTgt.getMinTargets(sa.getSourceCard(), sa) || abTgt.getNumTargeted() == 0) { abTgt.resetTargets(); return false; } else { break; } } if (af.isCurse()) { choice = chooseCursedTarget(list, type, amount); } else { } if (choice == null) { // can't find anything left if (abTgt.getNumTargeted() < abTgt.getMinTargets(sa.getSourceCard(), sa) || abTgt.getNumTargeted() == 0) { abTgt.resetTargets(); return false; } else { // TODO is this good enough? for up to amounts? break; } } list.remove(choice); abTgt.addTarget(choice); } } Ability_Sub subAb = sa.getSubAbility(); if (subAb != null) chance &= subAb.chkAI_Drawback(); return chance; }//putPlayDrawbackAI /** * <p>putDoTriggerAI.</p> * * @param af a {@link forge.card.abilityFactory.AbilityFactory} object. * @param sa a {@link forge.card.spellability.SpellAbility} object. * @param mandatory a boolean. * @return a boolean. */ private static boolean putDoTriggerAI(final AbilityFactory af, final SpellAbility sa, boolean mandatory) { // if there is a cost, it's gotta be optional if (!ComputerUtil.canPayCost(sa) && !mandatory) return false; HashMap<String, String> params = af.getMapParams(); Target abTgt = sa.getTarget(); final Card source = sa.getSourceCard(); boolean chance = true; boolean preferred = true; CardList list; Player player = af.isCurse() ? AllZone.getHumanPlayer() : AllZone.getComputerPlayer(); String type = params.get("CounterType"); String amountStr = params.get("CounterNum"); final int amount = AbilityFactory.calculateAmount(af.getHostCard(), amountStr, sa); if (abTgt == null) { // No target. So must be defined list = new CardList(AbilityFactory.getDefinedCards(source, params.get("Defined"), sa).toArray()); if (!mandatory) { // TODO: If Trigger isn't mandatory, when wouldn't we want to put a counter? // things like Powder Keg, which are way too complex for the AI } } else { list = AllZoneUtil.getPlayerCardsInPlay(player); list = list.getTargetableCards(source); if (abTgt != null) { list = list.getValidCards(abTgt.getValidTgts(), source.getController(), source); } if (list.isEmpty() && mandatory) { // If there isn't any prefered cards to target, gotta choose non-preferred ones list = AllZoneUtil.getPlayerCardsInPlay(player.getOpponent()); list = list.getTargetableCards(source); if (abTgt != null) { list = list.getValidCards(abTgt.getValidTgts(), source.getController(), source); } preferred = false; } // Not mandatory, or the the list was regenerated and is still empty, so return false since there are no targets if (list.isEmpty()) return false; Card choice = null; // Choose targets here: if (af.isCurse()) { if (preferred) choice = chooseCursedTarget(list, type, amount); else { if (type.equals("M1M1")) { choice = CardFactoryUtil.AI_getWorstCreature(list); } else { choice = CardFactoryUtil.getRandomCard(list); } } } else { if (preferred) choice = chooseBoonTarget(list, type); else { if (type.equals("P1P1")) { choice = CardFactoryUtil.AI_getWorstCreature(list); } else { choice = CardFactoryUtil.getRandomCard(list); } } } //TODO - I think choice can be null here. Is that ok for addTarget()? abTgt.addTarget(choice); } Ability_Sub subAb = sa.getSubAbility(); if (subAb != null) chance &= subAb.doTrigger(mandatory); return true; } /** * <p>chooseCursedTarget.</p> * * @param list a {@link forge.CardList} object. * @param type a {@link java.lang.String} object. * @param amount a int. * @return a {@link forge.Card} object. */ private static Card chooseCursedTarget(CardList list, String type, final int amount) { Card choice; if (type.equals("M1M1")) { // try to kill the best killable creature, or reduce the best one CardList killable = list.filter(new CardListFilter() { public boolean addCard(Card c) { return c.getNetDefense() <= amount; } }); if (killable.size() > 0) choice = CardFactoryUtil.AI_getBestCreature(killable); else choice = CardFactoryUtil.AI_getBestCreature(list); } else { // improve random choice here choice = CardFactoryUtil.getRandomCard(list); } return choice; } /** * <p>chooseBoonTarget.</p> * * @param list a {@link forge.CardList} object. * @param type a {@link java.lang.String} object. * @return a {@link forge.Card} object. */ private static Card chooseBoonTarget(CardList list, String type) { Card choice; if (type.equals("P1P1")) { choice = CardFactoryUtil.AI_getBestCreature(list); } else if(type.equals("DIVINITY")) { list = list.filter(new CardListFilter() { public boolean addCard(Card c) { return c.getCounters(Counters.DIVINITY) == 0; } }); choice = CardFactoryUtil.AI_getMostExpensivePermanent(list, null, false); } else { // The AI really should put counters on cards that can use it. // Charge counters on things with Charge abilities, etc. Expand these above choice = CardFactoryUtil.getRandomCard(list); } return choice; } /** * <p>putResolve.</p> * * @param af a {@link forge.card.abilityFactory.AbilityFactory} object. * @param sa a {@link forge.card.spellability.SpellAbility} object. */ private static void putResolve(final AbilityFactory af, final SpellAbility sa) { HashMap<String, String> params = af.getMapParams(); Card card = af.getHostCard(); String type = params.get("CounterType"); int counterAmount = AbilityFactory.calculateAmount(af.getHostCard(), params.get("CounterNum"), sa); ArrayList<Card> tgtCards; Target tgt = af.getAbTgt(); if (tgt != null) tgtCards = tgt.getTargetCards(); else { tgtCards = AbilityFactory.getDefinedCards(card, params.get("Defined"), sa); } for (Card tgtCard : tgtCards) { if (tgt == null || CardFactoryUtil.canTarget(card, tgtCard)) { if (AllZone.getZone(tgtCard).is(Constant.Zone.Battlefield)) tgtCard.addCounter(Counters.valueOf(type), counterAmount); else // adding counters to something like re-suspend cards tgtCard.addCounterFromNonEffect(Counters.valueOf(type), counterAmount); } } } // ******************************************* // ********** RemoveCounters ***************** // ******************************************* /** * <p>createAbilityRemoveCounters.</p> * * @param af a {@link forge.card.abilityFactory.AbilityFactory} object. * @return a {@link forge.card.spellability.SpellAbility} object. */ public static SpellAbility createAbilityRemoveCounters(final AbilityFactory af) { final SpellAbility abRemCounter = new Ability_Activated(af.getHostCard(), af.getAbCost(), af.getAbTgt()) { private static final long serialVersionUID = 8581011868395954121L; @Override public String getStackDescription() { return removeStackDescription(af, this); } @Override public boolean canPlayAI() { return removeCanPlayAI(af, this); } @Override public void resolve() { removeResolve(af, this); } @Override public boolean doTrigger(boolean mandatory) { return removeDoTriggerAI(af, this, mandatory); } }; return abRemCounter; } /** * <p>createSpellRemoveCounters.</p> * * @param af a {@link forge.card.abilityFactory.AbilityFactory} object. * @return a {@link forge.card.spellability.SpellAbility} object. */ public static SpellAbility createSpellRemoveCounters(final AbilityFactory af) { final SpellAbility spRemoveCounter = new Spell(af.getHostCard(), af.getAbCost(), af.getAbTgt()) { private static final long serialVersionUID = -5065591869141835456L; @Override public String getStackDescription() { return removeStackDescription(af, this); } @Override public boolean canPlayAI() { // if X depends on abCost, the AI needs to choose which card he would sacrifice first // then call xCount with that card to properly calculate the amount // Or choosing how many to sacrifice return removeCanPlayAI(af, this); } @Override public void resolve() { removeResolve(af, this); } }; return spRemoveCounter; } /** * <p>createDrawbackRemoveCounters.</p> * * @param af a {@link forge.card.abilityFactory.AbilityFactory} object. * @return a {@link forge.card.spellability.SpellAbility} object. */ public static SpellAbility createDrawbackRemoveCounters(final AbilityFactory af) { final SpellAbility spRemoveCounter = new Ability_Sub(af.getHostCard(), af.getAbTgt()) { private static final long serialVersionUID = -5065591869141835456L; @Override public String getStackDescription() { return removeStackDescription(af, this); } @Override public void resolve() { removeResolve(af, this); } @Override public boolean chkAI_Drawback() { return removePlayDrawbackAI(af, this); } @Override public boolean doTrigger(boolean mandatory) { return removeDoTriggerAI(af, this, mandatory); } }; return spRemoveCounter; } /** * <p>removeStackDescription.</p> * * @param af a {@link forge.card.abilityFactory.AbilityFactory} object. * @param sa a {@link forge.card.spellability.SpellAbility} object. * @return a {@link java.lang.String} object. */ private static String removeStackDescription(AbilityFactory af, SpellAbility sa) { HashMap<String, String> params = af.getMapParams(); Card card = af.getHostCard(); StringBuilder sb = new StringBuilder(); if (!(sa instanceof Ability_Sub)) sb.append(card).append(" - "); else sb.append(" "); Counters cType = Counters.valueOf(params.get("CounterType")); int amount = AbilityFactory.calculateAmount(af.getHostCard(), params.get("CounterNum"), sa); sb.append("Remove "); if (params.containsKey("UpTo")) sb.append("up to "); sb.append(amount).append(" ").append(cType.getName()).append(" counter"); if (amount != 1) sb.append("s"); sb.append(" from"); ArrayList<Card> tgtCards; Target tgt = af.getAbTgt(); if (tgt != null) tgtCards = tgt.getTargetCards(); else { tgtCards = AbilityFactory.getDefinedCards(card, params.get("Defined"), sa); } for (Card c : tgtCards) sb.append(" ").append(c); sb.append("."); Ability_Sub abSub = sa.getSubAbility(); if (abSub != null) { sb.append(abSub.getStackDescription()); } return sb.toString(); } /** * <p>removeCanPlayAI.</p> * * @param af a {@link forge.card.abilityFactory.AbilityFactory} object. * @param sa a {@link forge.card.spellability.SpellAbility} object. * @return a boolean. */ private static boolean removeCanPlayAI(final AbilityFactory af, final SpellAbility sa) { // AI needs to be expanded, since this function can be pretty complex based on what the expected targets could be Random r = MyRandom.random; Cost abCost = sa.getPayCosts(); //Target abTgt = sa.getTarget(); final Card source = sa.getSourceCard(); //CardList list; //Card choice = null; HashMap<String, String> params = af.getMapParams(); String type = params.get("CounterType"); //String amountStr = params.get("CounterNum"); //TODO - currently, not targeted, only for Self //Player player = af.isCurse() ? AllZone.getHumanPlayer() : AllZone.getComputerPlayer(); if (abCost != null) { // AI currently disabled for these costs if (abCost.getSacCost() && !abCost.getSacThis()) { //only sacrifice something that's supposed to be sacrificed String sacType = abCost.getSacType(); CardList typeList = AllZoneUtil.getPlayerCardsInPlay(AllZone.getComputerPlayer()); typeList = typeList.getValidCards(sacType.split(","), source.getController(), source); if (ComputerUtil.getCardPreference(source, "SacCost", typeList) == null) return false; } if (abCost.getLifeCost()) return false; if (abCost.getDiscardCost()) return false; if (abCost.getSubCounter()) { // A card has a 25% chance per counter to be able to pass through here // 4+ counters will always pass. 0 counters will never int currentNum = source.getCounters(abCost.getCounterType()); double percent = .25 * (currentNum / abCost.getCounterNum()); if (percent <= r.nextFloat()) return false; } } if (!ComputerUtil.canPayCost(sa)) return false; // TODO handle proper calculation of X values based on Cost //final int amount = calculateAmount(af.getHostCard(), amountStr, sa); // prevent run-away activations - first time will always return true boolean chance = r.nextFloat() <= Math.pow(.6667, source.getAbilityUsed()); //currently, not targeted // Placeholder: No targeting necessary int currCounters = sa.getSourceCard().getCounters(Counters.valueOf(type)); // each counter on the card is a 10% chance of not activating this ability. if (r.nextFloat() < .1 * currCounters) return false; Ability_Sub subAb = sa.getSubAbility(); if (subAb != null) chance &= subAb.chkAI_Drawback(); return ((r.nextFloat() < .6667) && chance); } /** * <p>removePlayDrawbackAI.</p> * * @param af a {@link forge.card.abilityFactory.AbilityFactory} object. * @param sa a {@link forge.card.spellability.SpellAbility} object. * @return a boolean. */ private static boolean removePlayDrawbackAI(final AbilityFactory af, final SpellAbility sa) { // AI needs to be expanded, since this function can be pretty complex based on what the expected targets could be //Target abTgt = sa.getTarget(); //final Card source = sa.getSourceCard(); //CardList list; //Card choice = null; //HashMap<String,String> params = af.getMapParams(); //String type = params.get("CounterType"); //String amountStr = params.get("CounterNum"); //TODO - currently, not targeted, only for Self //Player player = af.isCurse() ? AllZone.getHumanPlayer() : AllZone.getComputerPlayer(); // TODO handle proper calculation of X values based on Cost //final int amount = calculateAmount(af.getHostCard(), amountStr, sa); // prevent run-away activations - first time will always return true boolean chance = true; //currently, not targeted Ability_Sub subAb = sa.getSubAbility(); if (subAb != null) chance &= subAb.chkAI_Drawback(); return chance; } /** * <p>removeDoTriggerAI.</p> * * @param af a {@link forge.card.abilityFactory.AbilityFactory} object. * @param sa a {@link forge.card.spellability.SpellAbility} object. * @param mandatory a boolean. * @return a boolean. */ private static boolean removeDoTriggerAI(final AbilityFactory af, final SpellAbility sa, boolean mandatory) { // AI needs to be expanded, since this function can be pretty complex based on what the expected targets could be boolean chance = true; //TODO - currently, not targeted, only for Self // Note: Not many cards even use Trigger and Remove Counters. And even fewer are not mandatory // Since the targeting portion of this would be what if (!ComputerUtil.canPayCost(sa) && !mandatory) return false; Ability_Sub subAb = sa.getSubAbility(); if (subAb != null) chance &= subAb.doTrigger(mandatory); return chance; } /** * <p>removeResolve.</p> * * @param af a {@link forge.card.abilityFactory.AbilityFactory} object. * @param sa a {@link forge.card.spellability.SpellAbility} object. */ private static void removeResolve(final AbilityFactory af, final SpellAbility sa) { HashMap<String, String> params = af.getMapParams(); Card card = af.getHostCard(); String type = params.get("CounterType"); int counterAmount = AbilityFactory.calculateAmount(af.getHostCard(), params.get("CounterNum"), sa); ArrayList<Card> tgtCards; Target tgt = af.getAbTgt(); if (tgt != null) tgtCards = tgt.getTargetCards(); else { tgtCards = AbilityFactory.getDefinedCards(card, params.get("Defined"), sa); } for (Card tgtCard : tgtCards) if (tgt == null || CardFactoryUtil.canTarget(card, tgtCard)) { PlayerZone zone = AllZone.getZone(tgtCard); if (zone.is(Constant.Zone.Battlefield) || zone.is(Constant.Zone.Exile)) if (params.containsKey("UpTo") && sa.getActivatingPlayer().isHuman()) { ArrayList<String> choices = new ArrayList<String>(); for (int i = 0; i <= counterAmount; i++) choices.add("" + i); Object o = GuiUtils.getChoice("Select the number of " + type + " counters to remove", choices.toArray()); counterAmount = Integer.parseInt((String) o); } tgtCard.subtractCounter(Counters.valueOf(type), counterAmount); } } // ******************************************* // ********** Proliferate ******************** // ******************************************* /** * <p>createAbilityProliferate.</p> * * @param af a {@link forge.card.abilityFactory.AbilityFactory} object. * @return a {@link forge.card.spellability.SpellAbility} object. */ public static SpellAbility createAbilityProliferate(final AbilityFactory af) { final SpellAbility abProliferate = new Ability_Activated(af.getHostCard(), af.getAbCost(), af.getAbTgt()) { private static final long serialVersionUID = -6617234927365102930L; @Override public boolean canPlayAI() { return proliferateShouldPlayAI(this); } @Override public void resolve() { proliferateResolve(af, this); } @Override public String getStackDescription() { return proliferateStackDescription(this); } @Override public boolean doTrigger(boolean mandatory) { return proliferateDoTriggerAI(this, mandatory); } }; return abProliferate; } /** * <p>createSpellProliferate.</p> * * @param af a {@link forge.card.abilityFactory.AbilityFactory} object. * @return a {@link forge.card.spellability.SpellAbility} object. */ public static SpellAbility createSpellProliferate(final AbilityFactory af) { final SpellAbility spProliferate = new Spell(af.getHostCard(), af.getAbCost(), af.getAbTgt()) { private static final long serialVersionUID = 1265466498444897146L; @Override public boolean canPlayAI() { return proliferateShouldPlayAI(this); } @Override public void resolve() { proliferateResolve(af, this); } @Override public boolean canPlay() { // super takes care of AdditionalCosts return super.canPlay(); } @Override public String getStackDescription() { return proliferateStackDescription(this); } }; return spProliferate; } /** * <p>createDrawbackProliferate.</p> * * @param af a {@link forge.card.abilityFactory.AbilityFactory} object. * @return a {@link forge.card.spellability.SpellAbility} object. */ public static SpellAbility createDrawbackProliferate(final AbilityFactory af) { final SpellAbility dbProliferate = new Ability_Sub(af.getHostCard(), af.getAbTgt()) { private static final long serialVersionUID = 1265466498444897146L; @Override public boolean canPlayAI() { return proliferateShouldPlayAI(this); } @Override public void resolve() { proliferateResolve(af, this); } @Override public String getStackDescription() { return proliferateStackDescription(this); } @Override public boolean chkAI_Drawback() { return proliferateShouldPlayAI(this); } @Override public boolean doTrigger(boolean mandatory) { return proliferateDoTriggerAI(this, mandatory); } }; return dbProliferate; } /** * <p>proliferateStackDescription.</p> * * @param sa a {@link forge.card.spellability.SpellAbility} object. * @return a {@link java.lang.String} object. */ private static String proliferateStackDescription(SpellAbility sa) { StringBuilder sb = new StringBuilder(); if (!(sa instanceof Ability_Sub)) sb.append(sa.getSourceCard()).append(" - "); else sb.append(" "); sb.append("Proliferate."); sb.append(" (You choose any number of permanents and/or players with "); sb.append("counters on them, then give each another counter of a kind already there.)"); Ability_Sub abSub = sa.getSubAbility(); if (abSub != null) { sb.append(abSub.getStackDescription()); } return sb.toString(); } /** * <p>proliferateShouldPlayAI.</p> * * @param sa a {@link forge.card.spellability.SpellAbility} object. * @return a boolean. */ private static boolean proliferateShouldPlayAI(SpellAbility sa) { boolean chance = true; Ability_Sub subAb = sa.getSubAbility(); if (subAb != null) chance &= subAb.chkAI_Drawback(); // TODO: Make sure Human has poison counters or there are some counters we want to proliferate return chance; } /** * <p>proliferateDoTriggerAI.</p> * * @param sa a {@link forge.card.spellability.SpellAbility} object. * @param mandatory a boolean. * @return a boolean. */ private static boolean proliferateDoTriggerAI(SpellAbility sa, boolean mandatory) { boolean chance = true; Ability_Sub subAb = sa.getSubAbility(); if (subAb != null) chance &= subAb.doTrigger(mandatory); // TODO: Make sure Human has poison counters or there are some counters we want to proliferate return chance; } /** * <p>proliferateResolve.</p> * * @param AF a {@link forge.card.abilityFactory.AbilityFactory} object. * @param sa a {@link forge.card.spellability.SpellAbility} object. */ private static void proliferateResolve(final AbilityFactory AF, SpellAbility sa) { CardList hperms = AllZoneUtil.getPlayerCardsInPlay(AllZone.getHumanPlayer()); hperms = hperms.filter(new CardListFilter() { public boolean addCard(Card crd) { return !crd.getName().equals("Mana Pool") /*&& crd.hasCounters()*/; } }); CardList cperms = AllZoneUtil.getPlayerCardsInPlay(AllZone.getComputerPlayer()); cperms = cperms.filter(new CardListFilter() { public boolean addCard(Card crd) { return !crd.getName().equals("Mana Pool") /*&& crd.hasCounters()*/; } }); if (AF.getHostCard().getController().isHuman()) { cperms.addAll(hperms); final CardList unchosen = cperms; AllZone.getInputControl().setInput(new Input() { private static final long serialVersionUID = -1779224307654698954L; @Override public void showMessage() { ButtonUtil.enableOnlyCancel(); AllZone.getDisplay().showMessage("Proliferate: Choose permanents and/or players"); } @Override public void selectButtonCancel() { AllZone.getStack().chooseOrderOfSimultaneousStackEntryAll(); //Hacky intermittent solution to triggers that look for counters being put on. They used to wait for another priority passing after proliferate finished. stop(); } @Override public void selectCard(Card card, PlayerZone zone) { if (!unchosen.contains(card)) return; unchosen.remove(card); ArrayList<String> choices = new ArrayList<String>(); for (Counters c_1 : Counters.values()) if (card.getCounters(c_1) != 0) choices.add(c_1.getName()); if (choices.size() > 0) card.addCounter(Counters.getType((choices.size() == 1 ? choices.get(0) : GuiUtils.getChoice("Select counter type", choices.toArray()).toString())), 1); } boolean selComputer = false; boolean selHuman = false; @Override public void selectPlayer(Player player) { if (player.isHuman() && selHuman == false) { selHuman = true; if (AllZone.getHumanPlayer().getPoisonCounters() > 0) AllZone.getHumanPlayer().addPoisonCounters(1); } if (player.isComputer() && selComputer == false) { selComputer = true; if (AllZone.getComputerPlayer().getPoisonCounters() > 0) AllZone.getComputerPlayer().addPoisonCounters(1); } } }); } else { //Compy cperms = cperms.filter(new CardListFilter() { public boolean addCard(Card crd) { int pos = 0; int neg = 0; for (Counters c_1 : Counters.values()) { if (crd.getCounters(c_1) != 0) { if (CardFactoryUtil.isNegativeCounter(c_1)) neg++; else pos++; } } return pos > neg; } }); hperms = hperms.filter(new CardListFilter() { public boolean addCard(Card crd) { int pos = 0; int neg = 0; for (Counters c_1 : Counters.values()) { if (crd.getCounters(c_1) != 0) { if (CardFactoryUtil.isNegativeCounter(c_1)) neg++; else pos++; } } return pos < neg; } }); StringBuilder sb = new StringBuilder(); sb.append("<html>Proliferate: <br>Computer selects "); if (cperms.size() == 0 && hperms.size() == 0 && AllZone.getHumanPlayer().getPoisonCounters() == 0) sb.append("<b>nothing</b>."); else { if (cperms.size() > 0) { sb.append("<br>From Computer's permanents: <br><b>"); for (Card c : cperms) { sb.append(c); sb.append(" "); } sb.append("</b><br>"); } if (hperms.size() > 0) { sb.append("<br>From Human's permanents: <br><b>"); for (Card c : cperms) { sb.append(c); sb.append(" "); } sb.append("</b><br>"); } if (AllZone.getHumanPlayer().getPoisonCounters() > 0) sb.append("<b>Human Player</b>."); }//else sb.append("</html>"); //add a counter for each counter type, if it would benefit the computer for (Card c : cperms) { for (Counters c_1 : Counters.values()) if (c.getCounters(c_1) != 0) c.addCounter(c_1, 1); } //add a counter for each counter type, if it would screw over the player for (Card c : hperms) { for (Counters c_1 : Counters.values()) if (c.getCounters(c_1) != 0) c.addCounter(c_1, 1); } //give human a poison counter, if he has one if (AllZone.getHumanPlayer().getPoisonCounters() > 0) AllZone.getHumanPlayer().addPoisonCounters(1); } //comp } // ******************************************* // ********** PutCounterAll ****************** // ******************************************* /** * <p>createAbilityPutCounterAll.</p> * * @param af a {@link forge.card.abilityFactory.AbilityFactory} object. * @return a {@link forge.card.spellability.SpellAbility} object. */ public static SpellAbility createAbilityPutCounterAll(final AbilityFactory af) { final SpellAbility abPutCounterAll = new Ability_Activated(af.getHostCard(), af.getAbCost(), af.getAbTgt()) { private static final long serialVersionUID = -712473347429870385L; @Override public String getStackDescription() { return putAllStackDescription(af, this); } @Override public boolean canPlayAI() { return putAllCanPlayAI(af, this); } @Override public void resolve() { putAllResolve(af, this); } @Override public boolean doTrigger(boolean mandatory) { return putAllCanPlayAI(af, this); } }; return abPutCounterAll; } /** * <p>createSpellPutCounterAll.</p> * * @param af a {@link forge.card.abilityFactory.AbilityFactory} object. * @return a {@link forge.card.spellability.SpellAbility} object. */ public static SpellAbility createSpellPutCounterAll(final AbilityFactory af) { final SpellAbility spPutCounterAll = new Spell(af.getHostCard(), af.getAbCost(), af.getAbTgt()) { private static final long serialVersionUID = -4400684695467183219L; @Override public String getStackDescription() { return putAllStackDescription(af, this); } @Override public boolean canPlayAI() { return putAllCanPlayAI(af, this); } @Override public void resolve() { putAllResolve(af, this); } }; return spPutCounterAll; } /** * <p>createDrawbackPutCounterAll.</p> * * @param af a {@link forge.card.abilityFactory.AbilityFactory} object. * @return a {@link forge.card.spellability.SpellAbility} object. */ public static SpellAbility createDrawbackPutCounterAll(final AbilityFactory af) { final SpellAbility dbPutCounterAll = new Ability_Sub(af.getHostCard(), af.getAbTgt()) { private static final long serialVersionUID = -3101160929130043022L; @Override public String getStackDescription() { return putAllStackDescription(af, this); } @Override public void resolve() { putAllResolve(af, this); } @Override public boolean chkAI_Drawback() { return putAllPlayDrawbackAI(af, this); } @Override public boolean doTrigger(boolean mandatory) { return putAllPlayDrawbackAI(af, this); } }; return dbPutCounterAll; } /** * <p>putAllStackDescription.</p> * * @param af a {@link forge.card.abilityFactory.AbilityFactory} object. * @param sa a {@link forge.card.spellability.SpellAbility} object. * @return a {@link java.lang.String} object. */ private static String putAllStackDescription(AbilityFactory af, SpellAbility sa) { HashMap<String, String> params = af.getMapParams(); StringBuilder sb = new StringBuilder(); if (!(sa instanceof Ability_Sub)) sb.append(sa.getSourceCard().getName()).append(" - "); else sb.append(" "); Counters cType = Counters.valueOf(params.get("CounterType")); int amount = AbilityFactory.calculateAmount(af.getHostCard(), params.get("CounterNum"), sa); sb.append("Put ").append(amount).append(" ").append(cType.getName()).append(" counter"); if (amount != 1) sb.append("s"); sb.append(" on each valid permanent."); Ability_Sub abSub = sa.getSubAbility(); if (abSub != null) { sb.append(abSub.getStackDescription()); } return sb.toString(); } /** * <p>putAllCanPlayAI.</p> * * @param af a {@link forge.card.abilityFactory.AbilityFactory} object. * @param sa a {@link forge.card.spellability.SpellAbility} object. * @return a boolean. */ private static boolean putAllCanPlayAI(final AbilityFactory af, final SpellAbility sa) { // AI needs to be expanded, since this function can be pretty complex based on what the expected targets could be Random r = MyRandom.random; HashMap<String, String> params = af.getMapParams(); Cost abCost = sa.getPayCosts(); final Card source = sa.getSourceCard(); CardList hList; CardList cList; String type = params.get("CounterType"); String amountStr = params.get("CounterNum"); String valid = params.get("ValidCards"); boolean curse = af.isCurse(); Target tgt = sa.getTarget(); hList = AllZoneUtil.getPlayerCardsInPlay(AllZone.getHumanPlayer()); cList = AllZoneUtil.getPlayerCardsInPlay(AllZone.getComputerPlayer()); hList = hList.getValidCards(valid, source.getController(), source); cList = cList.getValidCards(valid, source.getController(), source); if (abCost != null) { // AI currently disabled for these costs if (abCost.getSacCost()) { return false; } if (abCost.getLifeCost()) return false; if (abCost.getDiscardCost()) return false; } if (!ComputerUtil.canPayCost(sa)) return false; if (tgt != null) { Player pl; if (curse) pl = AllZone.getHumanPlayer(); else pl = AllZone.getComputerPlayer(); tgt.addTarget(pl); hList = hList.getController(pl); cList = cList.getController(pl); } // TODO improve X value to don't overpay when extra mana won't do anything more useful final int amount; if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) { // Set PayX here to maximum value. amount = ComputerUtil.determineLeftoverMana(sa); source.setSVar("PayX", Integer.toString(amount)); } else { amount = AbilityFactory.calculateAmount(af.getHostCard(), amountStr, sa); } // prevent run-away activations - first time will always return true boolean chance = r.nextFloat() <= Math.pow(.6667, source.getAbilityUsed()); if (curse) { if (type.equals("M1M1")) { CardList killable = hList.filter(new CardListFilter() { public boolean addCard(Card c) { return c.getNetDefense() <= amount; } }); if (!(killable.size() > 2)) return false; } else { //make sure compy doesn't harm his stuff more than human's stuff if (cList.size() > hList.size()) return false; } } else { //human has more things that will benefit, don't play if (hList.size() >= cList.size()) return false; } Ability_Sub subAb = sa.getSubAbility(); if (subAb != null) chance &= subAb.chkAI_Drawback(); return ((r.nextFloat() < .6667) && chance); } /** * <p>putAllPlayDrawbackAI.</p> * * @param af a {@link forge.card.abilityFactory.AbilityFactory} object. * @param sa a {@link forge.card.spellability.SpellAbility} object. * @return a boolean. */ private static boolean putAllPlayDrawbackAI(final AbilityFactory af, final SpellAbility sa) { return putAllCanPlayAI(af, sa); } /** * <p>putAllResolve.</p> * * @param af a {@link forge.card.abilityFactory.AbilityFactory} object. * @param sa a {@link forge.card.spellability.SpellAbility} object. */ private static void putAllResolve(final AbilityFactory af, final SpellAbility sa) { HashMap<String, String> params = af.getMapParams(); String type = params.get("CounterType"); int counterAmount = AbilityFactory.calculateAmount(af.getHostCard(), params.get("CounterNum"), sa); String valid = params.get("ValidCards"); CardList cards = AllZoneUtil.getCardsInPlay(); cards = cards.getValidCards(valid, sa.getSourceCard().getController(), sa.getSourceCard()); Target tgt = sa.getTarget(); if (tgt != null) { Player pl = sa.getTargetPlayer(); cards = cards.getController(pl); } for (Card tgtCard : cards) { if (AllZone.getZone(tgtCard).is(Constant.Zone.Battlefield)) tgtCard.addCounter(Counters.valueOf(type), counterAmount); else // adding counters to something like re-suspend cards tgtCard.addCounterFromNonEffect(Counters.valueOf(type), counterAmount); } } // ******************************************* // ********** RemoveCounterAll *************** // ******************************************* /** * <p>createAbilityRemoveCounterAll.</p> * * @param af a {@link forge.card.abilityFactory.AbilityFactory} object. * @return a {@link forge.card.spellability.SpellAbility} object. */ public static SpellAbility createAbilityRemoveCounterAll(final AbilityFactory af) { final SpellAbility abRemoveCounterAll = new Ability_Activated(af.getHostCard(), af.getAbCost(), af.getAbTgt()) { private static final long serialVersionUID = 1189198508841846311L; @Override public String getStackDescription() { return removeCounterAllStackDescription(af, this); } @Override public boolean canPlayAI() { return removeCounterAllCanPlayAI(af, this); } @Override public void resolve() { removeCounterAllResolve(af, this); } @Override public boolean doTrigger(boolean mandatory) { return true; } }; return abRemoveCounterAll; } /** * <p>createSpellRemoveCounterAll.</p> * * @param af a {@link forge.card.abilityFactory.AbilityFactory} object. * @return a {@link forge.card.spellability.SpellAbility} object. */ public static SpellAbility createSpellRemoveCounterAll(final AbilityFactory af) { final SpellAbility spRemoveCounterAll = new Spell(af.getHostCard(), af.getAbCost(), af.getAbTgt()) { private static final long serialVersionUID = 4173468877313664704L; @Override public String getStackDescription() { return removeCounterAllStackDescription(af, this); } @Override public boolean canPlayAI() { return removeCounterAllCanPlayAI(af, this); } @Override public void resolve() { removeCounterAllResolve(af, this); } }; return spRemoveCounterAll; } /** * <p>createDrawbackRemoveCounterAll.</p> * * @param af a {@link forge.card.abilityFactory.AbilityFactory} object. * @return a {@link forge.card.spellability.SpellAbility} object. */ public static SpellAbility createDrawbackRemoveCounterAll(final AbilityFactory af) { final SpellAbility dbRemoveCounterAll = new Ability_Sub(af.getHostCard(), af.getAbTgt()) { private static final long serialVersionUID = 9210702927696563686L; @Override public String getStackDescription() { return removeCounterAllStackDescription(af, this); } @Override public void resolve() { removeCounterAllResolve(af, this); } @Override public boolean chkAI_Drawback() { return removeCounterAllPlayDrawbackAI(af, this); } @Override public boolean doTrigger(boolean mandatory) { return removeCounterAllPlayDrawbackAI(af, this); } }; return dbRemoveCounterAll; } /** * <p>removeCounterAllStackDescription.</p> * * @param af a {@link forge.card.abilityFactory.AbilityFactory} object. * @param sa a {@link forge.card.spellability.SpellAbility} object. * @return a {@link java.lang.String} object. */ private static String removeCounterAllStackDescription(AbilityFactory af, SpellAbility sa) { HashMap<String, String> params = af.getMapParams(); StringBuilder sb = new StringBuilder(); if (!(sa instanceof Ability_Sub)) sb.append(sa.getSourceCard()).append(" - "); else sb.append(" "); Counters cType = Counters.valueOf(params.get("CounterType")); int amount = AbilityFactory.calculateAmount(af.getHostCard(), params.get("CounterNum"), sa); sb.append("Remove ").append(amount).append(" ").append(cType.getName()).append(" counter"); if (amount != 1) sb.append("s"); sb.append(" from each valid permanent."); Ability_Sub abSub = sa.getSubAbility(); if (abSub != null) { sb.append(abSub.getStackDescription()); } return sb.toString(); } /** * <p>removeCounterAllCanPlayAI.</p> * * @param af a {@link forge.card.abilityFactory.AbilityFactory} object. * @param sa a {@link forge.card.spellability.SpellAbility} object. * @return a boolean. */ private static boolean removeCounterAllCanPlayAI(final AbilityFactory af, final SpellAbility sa) { //Heartmender is the only card using this, and it's from a trigger. //If at some point, other cards use this as a spell or ability, this will need to be implemented. return false; } /** * <p>removeCounterAllPlayDrawbackAI.</p> * * @param af a {@link forge.card.abilityFactory.AbilityFactory} object. * @param sa a {@link forge.card.spellability.SpellAbility} object. * @return a boolean. */ private static boolean removeCounterAllPlayDrawbackAI(final AbilityFactory af, final SpellAbility sa) { return removeCounterAllCanPlayAI(af, sa); } /** * <p>removeCounterAllResolve.</p> * * @param af a {@link forge.card.abilityFactory.AbilityFactory} object. * @param sa a {@link forge.card.spellability.SpellAbility} object. */ private static void removeCounterAllResolve(final AbilityFactory af, final SpellAbility sa) { HashMap<String, String> params = af.getMapParams(); String type = params.get("CounterType"); int counterAmount = AbilityFactory.calculateAmount(af.getHostCard(), params.get("CounterNum"), sa); String valid = params.get("ValidCards"); CardList cards = AllZoneUtil.getCardsInPlay(); cards = cards.getValidCards(valid, sa.getSourceCard().getController(), sa.getSourceCard()); Target tgt = sa.getTarget(); if (tgt != null) { Player pl = sa.getTargetPlayer(); cards = cards.getController(pl); } for (Card tgtCard : cards) { tgtCard.subtractCounter(Counters.valueOf(type), counterAmount); } } }//end class AbilityFactory_Counters