package forge.card.abilityFactory; import forge.*; import forge.card.spellability.*; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.Random; /** * <p>AbilityFactory_AlterLife class.</p> * * @author Forge * @version $Id: $ */ public class AbilityFactory_AlterLife { // An AbilityFactory subclass for Gaining, Losing, or Setting Life totals. // ************************************************************************* // ************************* GAIN LIFE ************************************* // ************************************************************************* /** * <p>createAbilityGainLife.</p> * * @param AF a {@link forge.card.abilityFactory.AbilityFactory} object. * @return a {@link forge.card.spellability.SpellAbility} object. */ public static SpellAbility createAbilityGainLife(final AbilityFactory AF) { final SpellAbility abGainLife = new Ability_Activated(AF.getHostCard(), AF.getAbCost(), AF.getAbTgt()) { private static final long serialVersionUID = 8869422603616247307L; final AbilityFactory af = AF; @Override public String getStackDescription() { // when getStackDesc is called, just build exactly what is happening return gainLifeStackDescription(af, this); } @Override public boolean canPlayAI() { return gainLifeCanPlayAI(af, this); } @Override public void resolve() { gainLifeResolve(af, this); } @Override public boolean doTrigger(boolean mandatory) { return gainLifeDoTriggerAI(af, this, mandatory); } }; return abGainLife; } /** * <p>createSpellGainLife.</p> * * @param AF a {@link forge.card.abilityFactory.AbilityFactory} object. * @return a {@link forge.card.spellability.SpellAbility} object. */ public static SpellAbility createSpellGainLife(final AbilityFactory AF) { final SpellAbility spGainLife = new Spell(AF.getHostCard(), AF.getAbCost(), AF.getAbTgt()) { private static final long serialVersionUID = 6631124959690157874L; final AbilityFactory af = AF; @Override public String getStackDescription() { // when getStackDesc is called, just build exactly what is happening return gainLifeStackDescription(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 gainLifeCanPlayAI(af, this); } @Override public void resolve() { gainLifeResolve(af, this); } }; return spGainLife; } /** * <p>createDrawbackGainLife.</p> * * @param AF a {@link forge.card.abilityFactory.AbilityFactory} object. * @return a {@link forge.card.spellability.SpellAbility} object. */ public static SpellAbility createDrawbackGainLife(final AbilityFactory AF) { final SpellAbility dbGainLife = new Ability_Sub(AF.getHostCard(), AF.getAbTgt()) { private static final long serialVersionUID = 6631124959690157874L; final AbilityFactory af = AF; @Override public String getStackDescription() { // when getStackDesc is called, just build exactly what is happening return gainLifeStackDescription(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 gainLifeCanPlayAI(af, this); } @Override public void resolve() { gainLifeResolve(af, this); } @Override public boolean chkAI_Drawback() { return true; } @Override public boolean doTrigger(boolean mandatory) { return gainLifeDoTriggerAI(af, this, mandatory); } }; return dbGainLife; } /** * <p>gainLifeStackDescription.</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. */ public static String gainLifeStackDescription(AbilityFactory af, SpellAbility sa) { HashMap<String, String> params = af.getMapParams(); StringBuilder sb = new StringBuilder(); int amount = AbilityFactory.calculateAmount(af.getHostCard(), params.get("LifeAmount"), sa); if (!(sa instanceof Ability_Sub)) sb.append(sa.getSourceCard().getName()).append(" - "); else sb.append(" "); String conditionDesc = params.get("ConditionDescription"); if (conditionDesc != null) sb.append(conditionDesc).append(" "); ArrayList<Player> tgtPlayers; Target tgt = af.getAbTgt(); if (tgt != null) tgtPlayers = tgt.getTargetPlayers(); else tgtPlayers = AbilityFactory.getDefinedPlayers(sa.getSourceCard(), params.get("Defined"), sa); for (Player player : tgtPlayers) sb.append(player).append(" "); sb.append("gains ").append(amount).append(" life."); Ability_Sub abSub = sa.getSubAbility(); if (abSub != null) { sb.append(abSub.getStackDescription()); } return sb.toString(); } /** * <p>gainLifeCanPlayAI.</p> * * @param af a {@link forge.card.abilityFactory.AbilityFactory} object. * @param sa a {@link forge.card.spellability.SpellAbility} object. * @return a boolean. */ public static boolean gainLifeCanPlayAI(final AbilityFactory af, final SpellAbility sa) { Random r = MyRandom.random; HashMap<String, String> params = af.getMapParams(); Cost abCost = sa.getPayCosts(); final Card source = sa.getSourceCard(); int life = AllZone.getComputerPlayer().getLife(); int lifeAmount = AbilityFactory.calculateAmount(af.getHostCard(), params.get("LifeAmount"), sa); String amountStr = params.get("LifeAmount"); //don't use it if no life to gain if (lifeAmount <= 0) return false; if (abCost != null) { if (abCost.getSacCost() && life > 4) { if (abCost.getSacThis() && life > 6) return false; else { //only sacrifice something that's supposed to be sacrificed String type = abCost.getSacType(); CardList typeList = AllZoneUtil.getPlayerCardsInPlay(AllZone.getComputerPlayer()); typeList = typeList.getValidCards(type.split(","), source.getController(), source); if (ComputerUtil.getCardPreference(source, "SacCost", typeList) == null) return false; } } if (abCost.getLifeCost() && life > 5) return false; if (abCost.getDiscardCost() && life > 5) return false; if (abCost.getSubCounter()) { //non +1/+1 counters should be used if (abCost.getCounterType().equals(Counters.P1P1)) { // 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; if (!AllZone.getComputerPlayer().canGainLife()) return false; //Don't use lifegain before main 2 if possible if (AllZone.getPhase().isBefore(Constant.Phase.Main2) && !params.containsKey("ActivatingPhases")) return false; //Don't tap creatures that may be able to block if (AbilityFactory.waitForBlocking(sa)) return false; // TODO handle proper calculation of X values based on Cost and what would be paid //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()); Target tgt = sa.getTarget(); if (tgt != null) { tgt.resetTargets(); if (tgt.canOnlyTgtOpponent()) tgt.addTarget(AllZone.getHumanPlayer()); else tgt.addTarget(AllZone.getComputerPlayer()); } if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) { // Set PayX here to maximum value. int xPay = ComputerUtil.determineLeftoverMana(sa); source.setSVar("PayX", Integer.toString(xPay)); } boolean randomReturn = r.nextFloat() <= .6667; if (AbilityFactory.playReusable(sa)) randomReturn = true; return (randomReturn && chance); } /** * <p>gainLifeDoTriggerAI.</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. */ public static boolean gainLifeDoTriggerAI(AbilityFactory af, SpellAbility sa, boolean mandatory) { if (!ComputerUtil.canPayCost(sa) && !mandatory) // If there is a cost payment it's usually not mandatory return false; HashMap<String, String> params = af.getMapParams(); // If the Target is gaining life, target self. // if the Target is modifying how much life is gained, this needs to be handled better Target tgt = sa.getTarget(); if (tgt != null) { tgt.resetTargets(); if (tgt.canOnlyTgtOpponent()) tgt.addTarget(AllZone.getHumanPlayer()); else tgt.addTarget(AllZone.getComputerPlayer()); } Card source = sa.getSourceCard(); String amountStr = params.get("LifeAmount"); if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) { // Set PayX here to maximum value. int xPay = ComputerUtil.determineLeftoverMana(sa); source.setSVar("PayX", Integer.toString(xPay)); } // check SubAbilities DoTrigger? Ability_Sub abSub = sa.getSubAbility(); if (abSub != null) { return abSub.doTrigger(mandatory); } return true; } /** * <p>gainLifeResolve.</p> * * @param af a {@link forge.card.abilityFactory.AbilityFactory} object. * @param sa a {@link forge.card.spellability.SpellAbility} object. */ public static void gainLifeResolve(final AbilityFactory af, final SpellAbility sa) { HashMap<String, String> params = af.getMapParams(); int lifeAmount = AbilityFactory.calculateAmount(af.getHostCard(), params.get("LifeAmount"), sa); ArrayList<Player> tgtPlayers; Target tgt = af.getAbTgt(); if (tgt != null && !params.containsKey("Defined")) tgtPlayers = tgt.getTargetPlayers(); else tgtPlayers = AbilityFactory.getDefinedPlayers(sa.getSourceCard(), params.get("Defined"), sa); for (Player p : tgtPlayers) if (tgt == null || p.canTarget(af.getHostCard())) p.gainLife(lifeAmount, sa.getSourceCard()); } // ************************************************************************* // ************************* LOSE LIFE ************************************* // ************************************************************************* /** * <p>createAbilityLoseLife.</p> * * @param AF a {@link forge.card.abilityFactory.AbilityFactory} object. * @return a {@link forge.card.spellability.SpellAbility} object. */ public static SpellAbility createAbilityLoseLife(final AbilityFactory AF) { final SpellAbility abLoseLife = new Ability_Activated(AF.getHostCard(), AF.getAbCost(), AF.getAbTgt()) { private static final long serialVersionUID = 1129762905315395160L; final AbilityFactory af = AF; @Override public String getStackDescription() { // when getStackDesc is called, just build exactly what is happening return loseLifeStackDescription(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 loseLifeCanPlayAI(af, this); } @Override public void resolve() { loseLifeResolve(af, this); } @Override public boolean doTrigger(boolean mandatory) { return loseLifeDoTriggerAI(af, this, mandatory); } }; return abLoseLife; } /** * <p>createSpellLoseLife.</p> * * @param AF a {@link forge.card.abilityFactory.AbilityFactory} object. * @return a {@link forge.card.spellability.SpellAbility} object. */ public static SpellAbility createSpellLoseLife(final AbilityFactory AF) { final SpellAbility spLoseLife = new Spell(AF.getHostCard(), AF.getAbCost(), AF.getAbTgt()) { private static final long serialVersionUID = -2966932725306192437L; final AbilityFactory af = AF; @Override public String getStackDescription() { // when getStackDesc is called, just build exactly what is happening return loseLifeStackDescription(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 loseLifeCanPlayAI(af, this); } @Override public void resolve() { loseLifeResolve(af, this); } }; return spLoseLife; } /** * <p>createDrawbackLoseLife.</p> * * @param AF a {@link forge.card.abilityFactory.AbilityFactory} object. * @return a {@link forge.card.spellability.SpellAbility} object. */ public static SpellAbility createDrawbackLoseLife(final AbilityFactory AF) { final SpellAbility dbLoseLife = new Ability_Sub(AF.getHostCard(), AF.getAbTgt()) { private static final long serialVersionUID = -2966932725306192437L; final AbilityFactory af = AF; @Override public String getStackDescription() { // when getStackDesc is called, just build exactly what is happening return loseLifeStackDescription(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 loseLifeCanPlayAI(af, this); } @Override public void resolve() { loseLifeResolve(af, this); } @Override public boolean chkAI_Drawback() { return true; } @Override public boolean doTrigger(boolean mandatory) { return loseLifeDoTriggerAI(af, this, mandatory); } }; return dbLoseLife; } /** * <p>loseLifeStackDescription.</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. */ static String loseLifeStackDescription(AbilityFactory af, SpellAbility sa) { HashMap<String, String> params = af.getMapParams(); StringBuilder sb = new StringBuilder(); int amount = AbilityFactory.calculateAmount(af.getHostCard(), params.get("LifeAmount"), sa); if (!(sa instanceof Ability_Sub)) sb.append(sa.getSourceCard().getName()).append(" - "); else sb.append(" "); String conditionDesc = params.get("ConditionDescription"); if (conditionDesc != null) sb.append(conditionDesc).append(" "); ArrayList<Player> tgtPlayers; Target tgt = af.getAbTgt(); if (tgt != null) tgtPlayers = tgt.getTargetPlayers(); else tgtPlayers = AbilityFactory.getDefinedPlayers(sa.getSourceCard(), params.get("Defined"), sa); for (Player player : tgtPlayers) sb.append(player).append(" "); sb.append("loses ").append(amount).append(" life."); Ability_Sub abSub = sa.getSubAbility(); if (abSub != null) { sb.append(abSub.getStackDescription()); } return sb.toString(); } /** * <p>loseLifeCanPlayAI.</p> * * @param af a {@link forge.card.abilityFactory.AbilityFactory} object. * @param sa a {@link forge.card.spellability.SpellAbility} object. * @return a boolean. */ public static boolean loseLifeCanPlayAI(final AbilityFactory af, final SpellAbility sa) { Random r = MyRandom.random; Cost abCost = sa.getPayCosts(); final Card source = sa.getSourceCard(); HashMap<String, String> params = af.getMapParams(); int humanLife = AllZone.getHumanPlayer().getLife(); int aiLife = AllZone.getComputerPlayer().getLife(); String amountStr = params.get("LifeAmount"); // TODO handle proper calculation of X values based on Cost and what would be paid final int amount = AbilityFactory.calculateAmount(af.getHostCard(), amountStr, sa); if (abCost != null) { // AI currently disabled for these costs if (abCost.getSacCost()) { if (amountStr.contains("X")) return false; if (!abCost.getSacThis()) { //only sacrifice something that's supposed to be sacrificed String type = abCost.getSacType(); CardList typeList = AllZoneUtil.getPlayerCardsInPlay(AllZone.getComputerPlayer()); typeList = typeList.getValidCards(type.split(","), source.getController(), source); if (ComputerUtil.getCardPreference(source, "SacCost", typeList) == null) return false; } } if (abCost.getLifeCost() && aiLife - abCost.getLifeAmount() < humanLife - amount) 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; if (!AllZone.getHumanPlayer().canLoseLife()) return false; //Don't use loselife before main 2 if possible if (AllZone.getPhase().isBefore(Constant.Phase.Main2) && !params.containsKey("ActivatingPhases")) return false; //Don't tap creatures that may be able to block if (AbilityFactory.waitForBlocking(sa)) return false; // prevent run-away activations - first time will always return true boolean chance = r.nextFloat() <= Math.pow(.6667, source.getAbilityUsed()); Target tgt = sa.getTarget(); if (sa.getTarget() != null) { tgt.resetTargets(); sa.getTarget().addTarget(AllZone.getHumanPlayer()); } if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) { // Set PayX here to maximum value. int xPay = ComputerUtil.determineLeftoverMana(sa); source.setSVar("PayX", Integer.toString(xPay)); } boolean randomReturn = r.nextFloat() <= .6667; if (AbilityFactory.playReusable(sa)) randomReturn = true; return (randomReturn && chance); } /** * <p>loseLifeDoTriggerAI.</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. */ public static boolean loseLifeDoTriggerAI(AbilityFactory af, SpellAbility sa, boolean mandatory) { if (!ComputerUtil.canPayCost(sa) && !mandatory) // If there is a cost payment it's usually not mandatory return false; HashMap<String, String> params = af.getMapParams(); Target tgt = sa.getTarget(); if (tgt != null) { tgt.addTarget(AllZone.getHumanPlayer()); } Card source = sa.getSourceCard(); String amountStr = params.get("LifeAmount"); int amount = 0; if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) { // Set PayX here to maximum value. int xPay = ComputerUtil.determineLeftoverMana(sa); source.setSVar("PayX", Integer.toString(xPay)); amount = xPay; } else amount = AbilityFactory.calculateAmount(source, amountStr, sa); ArrayList<Player> tgtPlayers; if (tgt != null) tgtPlayers = tgt.getTargetPlayers(); else tgtPlayers = AbilityFactory.getDefinedPlayers(sa.getSourceCard(), params.get("Defined"), sa); if (tgtPlayers.contains(AllZone.getComputerPlayer())) { // For cards like Foul Imp, ETB you lose life if (amount + 3 > AllZone.getComputerPlayer().getLife()) return false; } // check SubAbilities DoTrigger? Ability_Sub abSub = sa.getSubAbility(); if (abSub != null) { return abSub.doTrigger(mandatory); } return true; } /** * <p>loseLifeResolve.</p> * * @param af a {@link forge.card.abilityFactory.AbilityFactory} object. * @param sa a {@link forge.card.spellability.SpellAbility} object. */ public static void loseLifeResolve(final AbilityFactory af, final SpellAbility sa) { HashMap<String, String> params = af.getMapParams(); int lifeAmount = AbilityFactory.calculateAmount(af.getHostCard(), params.get("LifeAmount"), sa); ArrayList<Player> tgtPlayers; Target tgt = af.getAbTgt(); if (tgt != null) tgtPlayers = tgt.getTargetPlayers(); else tgtPlayers = AbilityFactory.getDefinedPlayers(sa.getSourceCard(), params.get("Defined"), sa); for (Player p : tgtPlayers) if (tgt == null || p.canTarget(af.getHostCard())) p.loseLife(lifeAmount, sa.getSourceCard()); } // ************************************************************************* // ************************** Poison Counters ****************************** // ************************************************************************* // // Made more sense here than in AF_Counters since it affects players and their health /** * <p>createAbilityPoison.</p> * * @param af a {@link forge.card.abilityFactory.AbilityFactory} object. * @return a {@link forge.card.spellability.SpellAbility} object. */ public static SpellAbility createAbilityPoison(final AbilityFactory af) { final SpellAbility abPoison = new Ability_Activated(af.getHostCard(), af.getAbCost(), af.getAbTgt()) { private static final long serialVersionUID = 6598936088284756268L; @Override public String getStackDescription() { // when getStackDesc is called, just build exactly what is happening return poisonStackDescription(af, this); } @Override public boolean canPlayAI() { return poisonCanPlayAI(af, this); } @Override public void resolve() { poisonResolve(af, this); } @Override public boolean doTrigger(boolean mandatory) { return poisonDoTriggerAI(af, this, mandatory); } }; return abPoison; } /** * <p>createSpellPoison.</p> * * @param af a {@link forge.card.abilityFactory.AbilityFactory} object. * @return a {@link forge.card.spellability.SpellAbility} object. */ public static SpellAbility createSpellPoison(final AbilityFactory af) { final SpellAbility spPoison = new Spell(af.getHostCard(), af.getAbCost(), af.getAbTgt()) { private static final long serialVersionUID = -1495708415138457833L; @Override public String getStackDescription() { return poisonStackDescription(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 poisonCanPlayAI(af, this); } @Override public void resolve() { poisonResolve(af, this); } }; return spPoison; } /** * <p>createDrawbackPoison.</p> * * @param af a {@link forge.card.abilityFactory.AbilityFactory} object. * @return a {@link forge.card.spellability.SpellAbility} object. */ public static SpellAbility createDrawbackPoison(final AbilityFactory af) { final SpellAbility dbPoison = new Ability_Sub(af.getHostCard(), af.getAbTgt()) { private static final long serialVersionUID = -1173479041548558016L; @Override public String getStackDescription() { return poisonStackDescription(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 poisonCanPlayAI(af, this); } @Override public void resolve() { poisonResolve(af, this); } @Override public boolean chkAI_Drawback() { return true; } @Override public boolean doTrigger(boolean mandatory) { return poisonDoTriggerAI(af, this, mandatory); } }; return dbPoison; } /** * <p>poisonDoTriggerAI.</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 poisonDoTriggerAI(AbilityFactory af, SpellAbility sa, boolean mandatory) { if (!ComputerUtil.canPayCost(sa) && !mandatory) // If there is a cost payment it's usually not mandatory return false; HashMap<String, String> params = af.getMapParams(); Target tgt = sa.getTarget(); if (tgt != null) { tgt.addTarget(AllZone.getHumanPlayer()); } else { ArrayList<Player> players = AbilityFactory.getDefinedPlayers(sa.getSourceCard(), params.get("Defined"), sa); for (Player p : players) if (!mandatory && p.isComputer() && p.getPoisonCounters() > p.getOpponent().getPoisonCounters()) return false; } // check SubAbilities DoTrigger? Ability_Sub abSub = sa.getSubAbility(); if (abSub != null) { return abSub.doTrigger(mandatory); } return true; } /** * <p>poisonResolve.</p> * * @param af a {@link forge.card.abilityFactory.AbilityFactory} object. * @param sa a {@link forge.card.spellability.SpellAbility} object. */ private static void poisonResolve(final AbilityFactory af, final SpellAbility sa) { final HashMap<String, String> params = af.getMapParams(); int amount = AbilityFactory.calculateAmount(af.getHostCard(), params.get("Num"), sa); ArrayList<Player> tgtPlayers; Target tgt = af.getAbTgt(); if (tgt != null) tgtPlayers = tgt.getTargetPlayers(); else tgtPlayers = AbilityFactory.getDefinedPlayers(sa.getSourceCard(), params.get("Defined"), sa); for (Player p : tgtPlayers) if (tgt == null || p.canTarget(af.getHostCard())) p.addPoisonCounters(amount); } /** * <p>poisonStackDescription.</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 poisonStackDescription(AbilityFactory af, SpellAbility sa) { HashMap<String, String> params = af.getMapParams(); StringBuilder sb = new StringBuilder(); int amount = AbilityFactory.calculateAmount(af.getHostCard(), params.get("Num"), sa); if (!(sa instanceof Ability_Sub)) sb.append(sa.getSourceCard()).append(" - "); else sb.append(" "); String conditionDesc = params.get("ConditionDescription"); if (conditionDesc != null) sb.append(conditionDesc).append(" "); ArrayList<Player> tgtPlayers; Target tgt = af.getAbTgt(); if (tgt != null) tgtPlayers = tgt.getTargetPlayers(); else tgtPlayers = AbilityFactory.getDefinedPlayers(sa.getSourceCard(), params.get("Defined"), sa); if (tgtPlayers.size() > 0) { Iterator<Player> it = tgtPlayers.iterator(); while (it.hasNext()) { Player p = it.next(); sb.append(p); if (it.hasNext()) sb.append(", "); else sb.append(" "); } } sb.append("get"); if (tgtPlayers.size() < 2) sb.append("s"); sb.append(" ").append(amount).append(" poison counter"); if (amount != 1) sb.append("s."); else sb.append("."); Ability_Sub abSub = sa.getSubAbility(); if (abSub != null) { sb.append(abSub.getStackDescription()); } return sb.toString(); } /** * <p>poisonCanPlayAI.</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 poisonCanPlayAI(final AbilityFactory af, final SpellAbility sa) { Cost abCost = sa.getPayCosts(); final Card source = af.getHostCard(); HashMap<String, String> params = af.getMapParams(); //int humanPoison = AllZone.getHumanPlayer().getPoisonCounters(); //int humanLife = AllZone.getHumanPlayer().getLife(); //int aiPoison = AllZone.getComputerPlayer().getPoisonCounters(); int aiLife = AllZone.getComputerPlayer().getLife(); String amountStr = params.get("Num"); // TODO handle proper calculation of X values based on Cost and what would be paid //final int amount = AbilityFactory.calculateAmount(af.getHostCard(), amountStr, sa); if (abCost != null) { // AI currently disabled for these costs if (abCost.getSacCost()) { if (amountStr.contains("X")) return false; if (!abCost.getSacThis()) { //only sacrifice something that's supposed to be sacrificed String type = abCost.getSacType(); CardList typeList = AllZoneUtil.getPlayerCardsInPlay(AllZone.getComputerPlayer()); typeList = typeList.getValidCards(type.split(","), source.getController(), source); if (ComputerUtil.getCardPreference(source, "SacCost", typeList) == null) return false; } } if (abCost.getLifeCost() && aiLife - abCost.getLifeAmount() <= 0) return false; } if (!ComputerUtil.canPayCost(sa)) return false; //Don't use poison before main 2 if possible if (AllZone.getPhase().isBefore(Constant.Phase.Main2) && !params.containsKey("ActivatingPhases")) return false; //Don't tap creatures that may be able to block if (AbilityFactory.waitForBlocking(sa)) return false; Target tgt = sa.getTarget(); if (sa.getTarget() != null) { tgt.resetTargets(); sa.getTarget().addTarget(AllZone.getHumanPlayer()); } return true; } // ************************************************************************* // ************************** SET LIFE ************************************* // ************************************************************************* /** * <p>createAbilitySetLife.</p> * * @param af a {@link forge.card.abilityFactory.AbilityFactory} object. * @return a {@link forge.card.spellability.SpellAbility} object. */ public static SpellAbility createAbilitySetLife(final AbilityFactory af) { final SpellAbility abSetLife = new Ability_Activated(af.getHostCard(), af.getAbCost(), af.getAbTgt()) { private static final long serialVersionUID = -7375434097541097668L; @Override public String getStackDescription() { return setLifeStackDescription(af, this); } @Override public boolean canPlayAI() { return setLifeCanPlayAI(af, this); } @Override public void resolve() { setLifeResolve(af, this); } @Override public boolean doTrigger(boolean mandatory) { return setLifeDoTriggerAI(af, this, mandatory); } }; return abSetLife; } /** * <p>createSpellSetLife.</p> * * @param af a {@link forge.card.abilityFactory.AbilityFactory} object. * @return a {@link forge.card.spellability.SpellAbility} object. */ public static SpellAbility createSpellSetLife(final AbilityFactory af) { final SpellAbility spSetLife = new Spell(af.getHostCard(), af.getAbCost(), af.getAbTgt()) { private static final long serialVersionUID = -94657822256270222L; @Override public String getStackDescription() { return setLifeStackDescription(af, this); } 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 setLifeCanPlayAI(af, this); } @Override public void resolve() { setLifeResolve(af, this); } }; return spSetLife; } /** * <p>createDrawbackSetLife.</p> * * @param af a {@link forge.card.abilityFactory.AbilityFactory} object. * @return a {@link forge.card.spellability.SpellAbility} object. */ public static SpellAbility createDrawbackSetLife(final AbilityFactory af) { final SpellAbility dbSetLife = new Ability_Sub(af.getHostCard(), af.getAbTgt()) { private static final long serialVersionUID = -7634729949893534023L; @Override public String getStackDescription() { return setLifeStackDescription(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 setLifeCanPlayAI(af, this); } @Override public void resolve() { setLifeResolve(af, this); } @Override public boolean chkAI_Drawback() { return true; } @Override public boolean doTrigger(boolean mandatory) { return setLifeDoTriggerAI(af, this, mandatory); } }; return dbSetLife; } /** * <p>setLifeStackDescription.</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 setLifeStackDescription(AbilityFactory af, SpellAbility sa) { HashMap<String, String> params = af.getMapParams(); StringBuilder sb = new StringBuilder(); int amount = AbilityFactory.calculateAmount(af.getHostCard(), params.get("LifeAmount"), sa); if (!(sa instanceof Ability_Sub)) sb.append(sa.getSourceCard()).append(" -"); else sb.append(" "); String conditionDesc = params.get("ConditionDescription"); if (conditionDesc != null) sb.append(conditionDesc).append(" "); ArrayList<Player> tgtPlayers; Target tgt = af.getAbTgt(); if (tgt != null) tgtPlayers = tgt.getTargetPlayers(); else tgtPlayers = AbilityFactory.getDefinedPlayers(sa.getSourceCard(), params.get("Defined"), sa); for (Player player : tgtPlayers) sb.append(player).append(" "); sb.append("life total becomes ").append(amount).append("."); Ability_Sub abSub = sa.getSubAbility(); if (abSub != null) { sb.append(abSub.getStackDescription()); } return sb.toString(); } /** * <p>setLifeCanPlayAI.</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 setLifeCanPlayAI(final AbilityFactory af, final SpellAbility sa) { Random r = MyRandom.random; //Ability_Cost abCost = sa.getPayCosts(); final Card source = sa.getSourceCard(); int life = AllZone.getComputerPlayer().getLife(); int hlife = AllZone.getHumanPlayer().getLife(); HashMap<String, String> params = af.getMapParams(); String amountStr = params.get("LifeAmount"); if (!ComputerUtil.canPayCost(sa)) return false; if (!AllZone.getComputerPlayer().canGainLife()) return false; //Don't use setLife before main 2 if possible if (AllZone.getPhase().isBefore(Constant.Phase.Main2) && !params.containsKey("ActivatingPhases")) return false; // TODO handle proper calculation of X values based on Cost and what would be paid int amount; //we shouldn't have to worry too much about PayX for SetLife if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) { // Set PayX here to maximum value. int xPay = ComputerUtil.determineLeftoverMana(sa); source.setSVar("PayX", Integer.toString(xPay)); amount = xPay; } 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()); Target tgt = sa.getTarget(); if (tgt != null) { tgt.resetTargets(); if (tgt.canOnlyTgtOpponent()) { tgt.addTarget(AllZone.getHumanPlayer()); //if we can only target the human, and the Human's life would go up, don't play it. //possibly add a combo here for Magister Sphinx and Higedetsu's (sp?) Second Rite if (amount > hlife || !AllZone.getHumanPlayer().canLoseLife()) return false; } else { if (amount > life && life <= 10) tgt.addTarget(AllZone.getComputerPlayer()); else if (hlife > amount) tgt.addTarget(AllZone.getHumanPlayer()); else if (amount > life) tgt.addTarget(AllZone.getComputerPlayer()); else return false; } } else { if (params.containsKey("Each") && params.get("Defined").equals("Each")) { if (amount == 0) return false; else if (life > amount) { //will decrease computer's life if (life < 5 || ((life - amount) > (hlife - amount))) return false; } } if (amount < life) return false; } //if life is in danger, always activate if (life < 3 && amount > life) return true; return ((r.nextFloat() < .6667) && chance); } /** * <p>setLifeDoTriggerAI.</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 setLifeDoTriggerAI(AbilityFactory af, SpellAbility sa, boolean mandatory) { int life = AllZone.getComputerPlayer().getLife(); int hlife = AllZone.getHumanPlayer().getLife(); Card source = sa.getSourceCard(); HashMap<String, String> params = af.getMapParams(); String amountStr = params.get("LifeAmount"); if (!ComputerUtil.canPayCost(sa) && !mandatory) // If there is a cost payment it's usually not mandatory return false; int amount; if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) { // Set PayX here to maximum value. int xPay = ComputerUtil.determineLeftoverMana(sa); source.setSVar("PayX", Integer.toString(xPay)); amount = xPay; } else amount = AbilityFactory.calculateAmount(af.getHostCard(), amountStr, sa); if (source.getName().equals("Eternity Vessel") && (AllZoneUtil.isCardInPlay("Vampire Hexmage", AllZone.getHumanPlayer()) || (source.getCounters(Counters.CHARGE) == 0))) return false; // If the Target is gaining life, target self. // if the Target is modifying how much life is gained, this needs to be handled better Target tgt = sa.getTarget(); if (tgt != null) { tgt.resetTargets(); if (tgt.canOnlyTgtOpponent()) tgt.addTarget(AllZone.getHumanPlayer()); else { if (amount > life && life <= 10) tgt.addTarget(AllZone.getComputerPlayer()); else if (hlife > amount) tgt.addTarget(AllZone.getHumanPlayer()); else if (amount > life) tgt.addTarget(AllZone.getComputerPlayer()); else return false; } } // check SubAbilities DoTrigger? Ability_Sub abSub = sa.getSubAbility(); if (abSub != null) { return abSub.doTrigger(mandatory); } return true; } /** * <p>setLifeResolve.</p> * * @param af a {@link forge.card.abilityFactory.AbilityFactory} object. * @param sa a {@link forge.card.spellability.SpellAbility} object. */ private static void setLifeResolve(final AbilityFactory af, final SpellAbility sa) { HashMap<String, String> params = af.getMapParams(); int lifeAmount = AbilityFactory.calculateAmount(af.getHostCard(), params.get("LifeAmount"), sa); ArrayList<Player> tgtPlayers; Target tgt = af.getAbTgt(); if (tgt != null && !params.containsKey("Defined")) tgtPlayers = tgt.getTargetPlayers(); else tgtPlayers = AbilityFactory.getDefinedPlayers(sa.getSourceCard(), params.get("Defined"), sa); for (Player p : tgtPlayers) if (tgt == null || p.canTarget(af.getHostCard())) p.setLife(lifeAmount, sa.getSourceCard()); } }//end class AbilityFactory_AlterLife