package forge.card.abilityFactory; import forge.*; import forge.card.cardFactory.CardFactoryUtil; import forge.card.spellability.*; import java.util.ArrayList; import java.util.HashMap; import java.util.Random; /** * <p>AbilityFactory_Sacrifice class.</p> * * @author Forge * @version $Id: $ */ public class AbilityFactory_Sacrifice { //************************************************************** // *************************** Sacrifice *********************** //************************************************************** /** * <p>createAbilitySacrifice.</p> * * @param af a {@link forge.card.abilityFactory.AbilityFactory} object. * @return a {@link forge.card.spellability.SpellAbility} object. */ public static SpellAbility createAbilitySacrifice(final AbilityFactory af) { final SpellAbility abSacrifice = new Ability_Activated(af.getHostCard(), af.getAbCost(), af.getAbTgt()) { private static final long serialVersionUID = -1933592438783630254L; @Override public boolean canPlayAI() { return sacrificeCanPlayAI(af, this); } @Override public void resolve() { sacrificeResolve(af, this); } @Override public String getStackDescription() { return sacrificeDescription(af, this); } @Override public boolean doTrigger(boolean mandatory) { return sacrificeTriggerAI(af, this, mandatory); } }; return abSacrifice; } /** * <p>createSpellSacrifice.</p> * * @param af a {@link forge.card.abilityFactory.AbilityFactory} object. * @return a {@link forge.card.spellability.SpellAbility} object. */ public static SpellAbility createSpellSacrifice(final AbilityFactory af) { final SpellAbility spSacrifice = new Spell(af.getHostCard(), af.getAbCost(), af.getAbTgt()) { private static final long serialVersionUID = -5141246507533353605L; @Override public boolean canPlayAI() { return sacrificeCanPlayAI(af, this); } @Override public void resolve() { sacrificeResolve(af, this); } @Override public String getStackDescription() { return sacrificeDescription(af, this); } }; return spSacrifice; } /** * <p>createDrawbackSacrifice.</p> * * @param af a {@link forge.card.abilityFactory.AbilityFactory} object. * @return a {@link forge.card.spellability.SpellAbility} object. */ public static SpellAbility createDrawbackSacrifice(final AbilityFactory af) { final SpellAbility dbSacrifice = new Ability_Sub(af.getHostCard(), af.getAbTgt()) { private static final long serialVersionUID = -5141246507533353605L; @Override public void resolve() { sacrificeResolve(af, this); } @Override public boolean chkAI_Drawback() { return sacrificePlayDrawbackAI(af, this); } @Override public String getStackDescription() { return sacrificeDescription(af, this); } @Override public boolean doTrigger(boolean mandatory) { return sacrificeTriggerAI(af, this, mandatory); } }; return dbSacrifice; } /** * <p>sacrificeDescription.</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 sacrificeDescription(final AbilityFactory af, SpellAbility sa) { HashMap<String, String> params = af.getMapParams(); StringBuilder sb = new StringBuilder(); if (sa instanceof Ability_Sub) sb.append(" "); else sb.append(sa.getSourceCard().getName()).append(" - "); String conditionDesc = params.get("ConditionDescription"); if (conditionDesc != null) sb.append(conditionDesc).append(" "); Target tgt = af.getAbTgt(); ArrayList<Player> tgts; if (tgt != null) tgts = tgt.getTargetPlayers(); else tgts = AbilityFactory.getDefinedPlayers(sa.getSourceCard(), params.get("Defined"), sa); String valid = params.get("SacValid"); if (valid == null) valid = "Self"; String num = params.get("Amount"); num = (num == null) ? "1" : num; int amount = AbilityFactory.calculateAmount(sa.getSourceCard(), num, sa); if (valid.equals("Self")) sb.append("Sacrifice ").append(sa.getSourceCard().toString()); else if (valid.equals("Card.AttachedBy")) { Card toSac = sa.getSourceCard().getEnchantingCard(); sb.append(toSac.getController()).append(" sacrifices ").append(toSac).append("."); } else { for (Player p : tgts) sb.append(p.getName()).append(" "); String msg = params.get("SacMessage"); if (msg == null) msg = valid; sb.append("Sacrifices ").append(amount).append(" ").append(msg).append("."); } Ability_Sub abSub = sa.getSubAbility(); if (abSub != null) sb.append(abSub.getStackDescription()); return sb.toString(); } /** * <p>sacrificeCanPlayAI.</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 sacrificeCanPlayAI(final AbilityFactory af, SpellAbility sa) { HashMap<String, String> params = af.getMapParams(); boolean chance = sacrificeTgtAI(af, sa); // Some additional checks based on what is being sacrificed, and who is sacrificing Target tgt = af.getAbTgt(); if (tgt != null) { String valid = params.get("SacValid"); String num = params.get("Amount"); num = (num == null) ? "1" : num; int amount = AbilityFactory.calculateAmount(sa.getSourceCard(), num, sa); CardList list = AllZoneUtil.getPlayerCardsInPlay(AllZone.getHumanPlayer()); list = list.getValidCards(valid.split(","), sa.getActivatingPlayer(), sa.getSourceCard()); if (list.size() == 0) return false; Card source = sa.getSourceCard(); if (num.equals("X") && source.getSVar(num).equals("Count$xPaid")) { // Set PayX here to maximum value. int xPay = Math.min(ComputerUtil.determineLeftoverMana(sa), amount); source.setSVar("PayX", Integer.toString(xPay)); } int half = amount / 2 + amount % 2; // Half of amount rounded up // If the Human has at least half rounded up of the amount to be sacrificed, cast the spell if (list.size() < half) return false; } Ability_Sub subAb = sa.getSubAbility(); if (subAb != null) chance &= subAb.chkAI_Drawback(); return chance; } /** * <p>sacrificePlayDrawbackAI.</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 sacrificePlayDrawbackAI(final AbilityFactory af, SpellAbility sa) { // AI should only activate this during Human's turn boolean chance = sacrificeTgtAI(af, sa); // TODO: restrict the subAbility a bit Ability_Sub subAb = sa.getSubAbility(); if (subAb != null) chance &= subAb.chkAI_Drawback(); return chance; } /** * <p>sacrificeTriggerAI.</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 sacrificeTriggerAI(final AbilityFactory af, SpellAbility sa, boolean mandatory) { if (!ComputerUtil.canPayCost(sa)) // If there is a cost payment return false; // AI should only activate this during Human's turn boolean chance = sacrificeTgtAI(af, sa); // Improve AI for triggers. If source is a creature with: // When ETB, sacrifice a creature. Check to see if the AI has something to sacrifice // Eventually, we can call the trigger of ETB abilities with not mandatory as part of the checks to cast something Ability_Sub subAb = sa.getSubAbility(); if (subAb != null) chance &= subAb.chkAI_Drawback(); return chance || mandatory; } /** * <p>sacrificeTgtAI.</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 sacrificeTgtAI(AbilityFactory af, SpellAbility sa) { HashMap<String, String> params = af.getMapParams(); Card card = sa.getSourceCard(); Target tgt = af.getAbTgt(); if (tgt != null) { tgt.resetTargets(); if (AllZone.getHumanPlayer().canTarget(sa.getSourceCard())) tgt.addTarget(AllZone.getHumanPlayer()); else return false; } else { String defined = params.get("Defined"); if (defined == null) { // Self Sacrifice. } else if (defined.equals("Each")) { // If Sacrifice hits both players: // Only cast it if Human has the full amount of valid // Only cast it if AI doesn't have the full amount of Valid // TODO: Cast if the type is favorable: my "worst" valid is worse than his "worst" valid String valid = params.get("SacValid"); String num = params.containsKey("Amount") ? params.get("Amount") : "1"; int amount = AbilityFactory.calculateAmount(card, num, sa); Card source = sa.getSourceCard(); if (num.equals("X") && source.getSVar(num).equals("Count$xPaid")) { // Set PayX here to maximum value. amount = Math.min(ComputerUtil.determineLeftoverMana(sa), amount); } CardList humanList = AllZoneUtil.getPlayerCardsInPlay(AllZone.getHumanPlayer()); humanList = humanList.getValidCards(valid.split(","), sa.getActivatingPlayer(), sa.getSourceCard()); CardList computerList = AllZoneUtil.getPlayerCardsInPlay(AllZone.getComputerPlayer()); computerList = computerList.getValidCards(valid.split(","), sa.getActivatingPlayer(), sa.getSourceCard()); //Since all of the cards have remAIDeck:True, I enabled 1 for 1 (or X for X) trades for special decks if (humanList.size() < amount /*|| computerList.size() >= amount */) return false; } } return true; } /** * <p>sacrificeResolve.</p> * * @param af a {@link forge.card.abilityFactory.AbilityFactory} object. * @param sa a {@link forge.card.spellability.SpellAbility} object. */ public static void sacrificeResolve(final AbilityFactory af, final SpellAbility sa) { HashMap<String, String> params = af.getMapParams(); Card card = sa.getSourceCard(); // Expand Sacrifice keyword here depending on what we need out of it. String num = params.containsKey("Amount") ? params.get("Amount") : "1"; int amount = AbilityFactory.calculateAmount(card, num, sa); Target tgt = af.getAbTgt(); ArrayList<Player> tgts; if (tgt != null) tgts = tgt.getTargetPlayers(); else tgts = AbilityFactory.getDefinedPlayers(sa.getSourceCard(), params.get("Defined"), sa); String valid = params.get("SacValid"); if (valid == null) valid = "Self"; String msg = params.get("SacMessage"); if (msg == null) msg = valid; msg = "Sacrifice a " + msg; if (valid.equals("Self")) { if (AllZone.getZone(sa.getSourceCard()).is(Constant.Zone.Battlefield)) AllZone.getGameAction().sacrifice(sa.getSourceCard()); } //TODO - maybe this can be done smarter... else if (valid.equals("Card.AttachedBy")) { Card toSac = sa.getSourceCard().getEnchantingCard(); if (AllZone.getZone(sa.getSourceCard()).is(Constant.Zone.Battlefield) && AllZoneUtil.isCardInPlay(toSac)) { AllZone.getGameAction().sacrifice(toSac); } } else if (valid.equals("TriggeredCard")) { Card equipee = (Card) sa.getTriggeringObject("Card"); if (tgts.contains(card.getController()) && AllZoneUtil.isCardInPlay(equipee)) { AllZone.getGameAction().sacrifice(equipee); } } else { for (Player p : tgts) { if (p.isComputer()) sacrificeAI(p, amount, valid, sa); else sacrificeHuman(p, amount, valid, sa, msg); } } } /** * <p>sacrificeAI.</p> * * @param p a {@link forge.Player} object. * @param amount a int. * @param valid a {@link java.lang.String} object. * @param sa a {@link forge.card.spellability.SpellAbility} object. */ private static void sacrificeAI(Player p, int amount, String valid, SpellAbility sa) { CardList list = AllZoneUtil.getPlayerCardsInPlay(p); list = list.getValidCards(valid.split(","), sa.getActivatingPlayer(), sa.getSourceCard()); ComputerUtil.sacrificePermanents(amount, list); } /** * <p>sacrificeHuman.</p> * * @param p a {@link forge.Player} object. * @param amount a int. * @param valid a {@link java.lang.String} object. * @param sa a {@link forge.card.spellability.SpellAbility} object. * @param message a {@link java.lang.String} object. */ private static void sacrificeHuman(Player p, int amount, String valid, SpellAbility sa, String message) { CardList list = AllZoneUtil.getPlayerCardsInPlay(p); list = list.getValidCards(valid.split(","), sa.getActivatingPlayer(), sa.getSourceCard()); // TODO: Wait for Input to finish before moving on with the rest of Resolution AllZone.getInputControl().setInput(PlayerUtil.input_sacrificePermanentsFromList(amount, list, message), true); } //************************************************************** //*********************** SacrificeAll ************************* //************************************************************** /** * <p>createAbilitySacrificeAll.</p> * * @param af a {@link forge.card.abilityFactory.AbilityFactory} object. * @return a {@link forge.card.spellability.SpellAbility} object. * @since 1.0.15 */ public static SpellAbility createAbilitySacrificeAll(final AbilityFactory af) { final SpellAbility abSacrifice = new Ability_Activated(af.getHostCard(), af.getAbCost(), af.getAbTgt()) { private static final long serialVersionUID = -1933592438783630254L; @Override public boolean canPlayAI() { return sacrificeAllCanPlayAI(af, this); } @Override public void resolve() { sacrificeAllResolve(af, this); } @Override public String getStackDescription() { return sacrificeAllStackDescription(af, this); } @Override public boolean doTrigger(boolean mandatory) { return sacrificeAllCanPlayAI(af, this); } }; return abSacrifice; } /** * <p>createSpellSacrificeAll.</p> * * @param af a {@link forge.card.abilityFactory.AbilityFactory} object. * @return a {@link forge.card.spellability.SpellAbility} object. * @since 1.0.15 */ public static SpellAbility createSpellSacrificeAll(final AbilityFactory af) { final SpellAbility spSacrifice = new Spell(af.getHostCard(), af.getAbCost(), af.getAbTgt()) { private static final long serialVersionUID = -5141246507533353605L; @Override public boolean canPlayAI() { return sacrificeAllCanPlayAI(af, this); } @Override public void resolve() { sacrificeAllResolve(af, this); } @Override public String getStackDescription() { return sacrificeAllStackDescription(af, this); } }; return spSacrifice; } /** * <p>createDrawbackSacrificeAll.</p> * * @param af a {@link forge.card.abilityFactory.AbilityFactory} object. * @return a {@link forge.card.spellability.SpellAbility} object. * @since 1.0.15 */ public static SpellAbility createDrawbackSacrificeAll(final AbilityFactory af) { final SpellAbility dbSacrifice = new Ability_Sub(af.getHostCard(), af.getAbTgt()) { private static final long serialVersionUID = -5141246507533353605L; @Override public void resolve() { sacrificeAllResolve(af, this); } @Override public boolean chkAI_Drawback() { return true; } @Override public String getStackDescription() { return sacrificeAllStackDescription(af, this); } @Override public boolean doTrigger(boolean mandatory) { return sacrificeAllCanPlayAI(af, this); } }; return dbSacrifice; } /** * <p>sacrificeAllStackDescription.</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. * @since 1.0.15 */ public static String sacrificeAllStackDescription(final AbilityFactory af, SpellAbility sa) { // when getStackDesc is called, just build exactly what is happening StringBuilder sb = new StringBuilder(); Card host = af.getHostCard(); HashMap<String, String> params = af.getMapParams(); if (sa instanceof Ability_Sub) sb.append(" "); else sb.append(host).append(" - "); String conditionDesc = params.get("ConditionDescription"); if (conditionDesc != null) sb.append(conditionDesc).append(" "); /* This is not currently targeted ArrayList<Player> tgtPlayers; Target tgt = af.getAbTgt(); if (tgt != null) tgtPlayers = tgt.getTargetPlayers(); else tgtPlayers = AbilityFactory.getDefinedPlayers(sa.getSourceCard(), params.get("Defined"), sa); */ sb.append("Sacrifice permanents."); Ability_Sub abSub = sa.getSubAbility(); if (abSub != null) { sb.append(abSub.getStackDescription()); } return sb.toString(); } /** * <p>sacrificeAllCanPlayAI.</p> * * @param af a {@link forge.card.abilityFactory.AbilityFactory} object. * @param sa a {@link forge.card.spellability.SpellAbility} object. * @return a boolean. * @since 1.0.15 */ public static boolean sacrificeAllCanPlayAI(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(); final Card source = sa.getSourceCard(); final HashMap<String, String> params = af.getMapParams(); String Valid = ""; if (params.containsKey("ValidCards")) Valid = params.get("ValidCards"); if (Valid.contains("X") && source.getSVar("X").equals("Count$xPaid")) { // Set PayX here to maximum value. int xPay = ComputerUtil.determineLeftoverMana(sa); source.setSVar("PayX", Integer.toString(xPay)); Valid = Valid.replace("X", Integer.toString(xPay)); } CardList humanlist = AllZoneUtil.getPlayerCardsInPlay(AllZone.getHumanPlayer()); CardList computerlist = AllZoneUtil.getPlayerCardsInPlay(AllZone.getComputerPlayer()); humanlist = humanlist.getValidCards(Valid.split(","), source.getController(), source); computerlist = computerlist.getValidCards(Valid.split(","), source.getController(), source); if (abCost != null) { // AI currently disabled for some costs if (abCost.getSacCost()) { //OK } if (abCost.getLifeCost()) { if (AllZone.getComputerPlayer().getLife() - abCost.getLifeAmount() < 4) return false; } if (abCost.getDiscardCost()) ;//OK if (abCost.getSubCounter()) { // OK } } if (!ComputerUtil.canPayCost(sa)) return false; // prevent run-away activations - first time will always return true boolean chance = r.nextFloat() <= Math.pow(.6667, source.getAbilityUsed()); // if only creatures are affected evaluate both lists and pass only if human creatures are more valuable if (humanlist.getNotType("Creature").size() == 0 && computerlist.getNotType("Creature").size() == 0) { if (CardFactoryUtil.evaluateCreatureList(computerlist) + 200 >= CardFactoryUtil.evaluateCreatureList(humanlist)) return false; }//only lands involved else if (humanlist.getNotType("Land").size() == 0 && computerlist.getNotType("Land").size() == 0) { if (CardFactoryUtil.evaluatePermanentList(computerlist) + 1 >= CardFactoryUtil.evaluatePermanentList(humanlist)) return false; } // otherwise evaluate both lists by CMC and pass only if human permanents are more valuable else if (CardFactoryUtil.evaluatePermanentList(computerlist) + 3 >= CardFactoryUtil.evaluatePermanentList(humanlist)) return false; Ability_Sub subAb = sa.getSubAbility(); if (subAb != null) chance &= subAb.chkAI_Drawback(); return ((r.nextFloat() < .9667) && chance); } /** * <p>sacrificeAllResolve.</p> * * @param af a {@link forge.card.abilityFactory.AbilityFactory} object. * @param sa a {@link forge.card.spellability.SpellAbility} object. * @since 1.0.15 */ public static void sacrificeAllResolve(final AbilityFactory af, final SpellAbility sa) { HashMap<String, String> params = af.getMapParams(); Card card = sa.getSourceCard(); String Valid = ""; if (params.containsKey("ValidCards")) Valid = params.get("ValidCards"); // Ugh. If calculateAmount needs to be called with DestroyAll it _needs_ to use the X variable // We really need a better solution to this if (Valid.contains("X")) Valid = Valid.replace("X", Integer.toString(AbilityFactory.calculateAmount(card, "X", sa))); CardList list = AllZoneUtil.getCardsInPlay(); boolean remSacrificed = params.containsKey("RememberSacrificed"); if (remSacrificed) card.clearRemembered(); list = list.getValidCards(Valid.split(","), card.getController(), card); for (int i = 0; i < list.size(); i++) if (AllZone.getGameAction().sacrifice(list.get(i)) && remSacrificed) card.addRemembered(list.get(i)); } }//end class AbilityFactory_Sacrifice