package forge; import com.esotericsoftware.minlog.Log; import forge.card.abilityFactory.AbilityFactory; import forge.card.cardFactory.CardFactoryUtil; import forge.card.spellability.Ability; import forge.card.trigger.Trigger; import forge.gui.GuiUtils; import forge.gui.input.Input_PayManaCost_Ability; import java.util.ArrayList; import java.util.HashMap; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * <p>CombatUtil class.</p> * * @author Forge * @version $Id: $ */ public class CombatUtil { //can the creature block given the combat state? /** * <p>canBlock.</p> * * @param blocker a {@link forge.Card} object. * @param combat a {@link forge.Combat} object. * @return a boolean. */ public static boolean canBlock(Card blocker, Combat combat) { if (blocker == null) return false; if (combat.getAllBlockers().size() > 1 && AllZoneUtil.isCardInPlay("Caverns of Despair")) return false; if (combat.getAllBlockers().size() > 0 && AllZoneUtil.isCardInPlay("Silent Arbiter")) return false; if (combat.getAllBlockers().size() > 0 && AllZoneUtil.isCardInPlay("Dueling Grounds")) return false; return canBlock(blocker); } //can the creature block at all? /** * <p>canBlock.</p> * * @param blocker a {@link forge.Card} object. * @return a boolean. */ public static boolean canBlock(Card blocker) { if (blocker == null) return false; if (blocker.isTapped() && !AllZoneUtil.isCardInPlay("Masako the Humorless", blocker.getController())) return false; if (blocker.hasKeyword("CARDNAME can't block.") || blocker.hasKeyword("CARDNAME can't attack or block.")) return false; CardList kulrath = AllZoneUtil.getCardsInPlay("Kulrath Knight"); if (kulrath.size() > 0) { for (int i = 0; i < kulrath.size(); i++) { Card cKK = kulrath.get(i); Player oppKK = cKK.getController().getOpponent(); if (blocker.getController().equals(oppKK) && blocker.hasCounters()) return false; } } return true; } //can the attacker be blocked at all? /** * <p>canBeBlocked.</p> * * @param attacker a {@link forge.Card} object. * @param combat a {@link forge.Combat} object. * @return a boolean. */ public static boolean canBeBlocked(Card attacker, Combat combat) { if (attacker == null) return true; if (attacker.hasKeyword("CARDNAME can't be blocked by more than one creature.") && combat.getBlockers(attacker).size() > 0) return false; return canBeBlocked(attacker); } //can the attacker be blocked at all? /** * <p>canBeBlocked.</p> * * @param attacker a {@link forge.Card} object. * @return a boolean. */ public static boolean canBeBlocked(Card attacker) { if (attacker == null) return true; if (attacker.hasKeyword("Unblockable")) return false; //Landwalk if (!AllZoneUtil.isCardInPlay("Staff of the Ages")) { //"Creatures with landwalk abilities can be blocked as though they didn't have those abilities." CardList blkCL = AllZoneUtil.getPlayerCardsInPlay(attacker.getController().getOpponent()); CardList temp = new CardList(); if (attacker.hasKeyword("Plainswalk")) { temp = blkCL.getType("Plains"); if (!AllZoneUtil.isCardInPlay("Lord Magnus") && !AllZoneUtil.isCardInPlay("Great Wall") && !temp.isEmpty()) return false; } if (attacker.hasKeyword("Islandwalk")) { temp = blkCL.getType("Island"); if (!AllZoneUtil.isCardInPlay("Undertow") && !AllZoneUtil.isCardInPlay("Gosta Dirk") && !temp.isEmpty()) return false; } if (attacker.hasKeyword("Swampwalk")) { temp = blkCL.getType("Swamp"); if (!AllZoneUtil.isCardInPlay("Ur-drago") && !AllZoneUtil.isCardInPlay("Quagmire") && !temp.isEmpty()) return false; } if (attacker.hasKeyword("Mountainwalk")) { temp = blkCL.getType("Mountain"); if (!AllZoneUtil.isCardInPlay("Crevasse") && !temp.isEmpty()) return false; } if (attacker.hasKeyword("Forestwalk")) { temp = blkCL.getType("Forest"); if (!AllZoneUtil.isCardInPlay("Lord Magnus") && !AllZoneUtil.isCardInPlay("Deadfall") && !temp.isEmpty()) return false; } if (attacker.hasKeyword("Legendary landwalk")) { temp = blkCL.filter(new CardListFilter() { public boolean addCard(Card c) { return c.isLand() && c.isType("Legendary"); } }); if (!temp.isEmpty()) return false; } if (attacker.hasKeyword("Snow swampwalk")) { temp = blkCL.filter(new CardListFilter() { public boolean addCard(Card c) { return c.isType("Swamp") && c.isSnow(); } }); if (!temp.isEmpty()) return false; } if (attacker.hasKeyword("Snow forestwalk")) { temp = blkCL.filter(new CardListFilter() { public boolean addCard(Card c) { return c.isType("Forest") && c.isSnow(); } }); if (!temp.isEmpty()) return false; } if (attacker.hasKeyword("Snow islandwalk")) { temp = blkCL.filter(new CardListFilter() { public boolean addCard(Card c) { return c.isType("Island") && c.isSnow(); } }); if (!temp.isEmpty()) return false; } if (attacker.hasKeyword("Snow plainswalk")) { temp = blkCL.filter(new CardListFilter() { public boolean addCard(Card c) { return c.isType("Plains") && c.isSnow(); } }); if (!temp.isEmpty()) return false; } if (attacker.hasKeyword("Snow mountainwalk")) { temp = blkCL.filter(new CardListFilter() { public boolean addCard(Card c) { return c.isType("Mountain") && c.isSnow(); } }); if (!temp.isEmpty()) return false; } if (attacker.hasKeyword("Snow landwalk")) { temp = blkCL.filter(new CardListFilter() { public boolean addCard(Card c) { return c.isLand() && c.isSnow(); } }); if (!temp.isEmpty()) return false; } if (attacker.hasKeyword("Desertwalk")) { temp = blkCL.filter(new CardListFilter() { public boolean addCard(Card c) { return c.isLand() && c.isType("Desert"); } }); if (!temp.isEmpty()) return false; } if (attacker.hasKeyword("Nonbasic landwalk")) { temp = blkCL.filter(new CardListFilter() { public boolean addCard(Card c) { return c.isLand() && !c.isBasicLand(); } }); if (!temp.isEmpty()) return false; } } return true; } // Has the player chosen all mandatory blocks? /** * <p>finishedMandatotyBlocks.</p> * * @param combat a {@link forge.Combat} object. * @return a boolean. */ public static boolean finishedMandatotyBlocks(Combat combat) { CardList blockers = AllZoneUtil.getCreaturesInPlay(AllZone.getHumanPlayer()); CardList attackers = new CardList(combat.getAttackers()); //if a creature does not block but should, return false for (Card blocker : blockers) { //lure effects if (!combat.getAllBlockers().contains(blocker) && canBlockAnAttackerWithLure(blocker, combat)) return false; //"CARDNAME blocks each turn if able." if (!combat.getAllBlockers().contains(blocker) && blocker.hasKeyword("CARDNAME blocks each turn if able.")) for (Card attacker : attackers) if (canBlock(attacker, blocker, combat)) return false; } return true; } // can the blocker block an attacker with a lure effect? /** * <p>canBlockAnAttackerWithLure.</p> * * @param blocker a {@link forge.Card} object. * @param combat a {@link forge.Combat} object. * @return a boolean. */ public static boolean canBlockAnAttackerWithLure(Card blocker, Combat combat) { if (blocker == null) return false; if (canBlock(blocker, combat) == false) return false; CardList attackersWithLure = new CardList(combat.getAttackers()); attackersWithLure = attackersWithLure.getKeyword("All creatures able to block CARDNAME do so."); for (Card attacker : attackersWithLure) { if (canBlock(blocker, combat) && canBlock(attacker, blocker)) return true; } return false; } // can the blocker block the attacker given the combat state? /** * <p>canBlock.</p> * * @param attacker a {@link forge.Card} object. * @param blocker a {@link forge.Card} object. * @param combat a {@link forge.Combat} object. * @return a boolean. */ public static boolean canBlock(Card attacker, Card blocker, Combat combat) { if (attacker == null || blocker == null) return false; if (canBlock(blocker, combat) == false) return false; if (canBeBlocked(attacker, combat) == false) return false; //if the attacker has no lure effect, but the blocker can block another attacker with lure, the blocker can't block the former if (!attacker.hasKeyword("All creatures able to block CARDNAME do so.") && canBlockAnAttackerWithLure(blocker, combat)) return false; if (blocker.hasStartOfKeyword("CARDNAME can't block ")) { for (String kw : blocker.getKeyword()) { if (kw.startsWith("CARDNAME can't block ")) { String unblockableCard = kw.substring(21); int ID = Integer.parseInt(unblockableCard.substring(unblockableCard.lastIndexOf("(") + 1, unblockableCard.length() - 1)); if (attacker.getUniqueNumber() == ID) { return false; } } } } return canBlock(attacker, blocker); } // can the blocker block the attacker? /** * <p>canBlock.</p> * * @param attacker a {@link forge.Card} object. * @param blocker a {@link forge.Card} object. * @return a boolean. */ public static boolean canBlock(Card attacker, Card blocker) { if (attacker == null || blocker == null) return false; if (canBlock(blocker) == false) return false; if (canBeBlocked(attacker) == false) return false; if (CardFactoryUtil.hasProtectionFrom(blocker, attacker)) return false; //rare case: if (blocker.hasKeyword("Shadow") && blocker.hasKeyword( "CARDNAME can block creatures with shadow as though they didn't have shadow.")) return false; if (attacker.hasKeyword("Shadow") && !blocker.hasKeyword("Shadow") && !blocker.hasKeyword( "CARDNAME can block creatures with shadow as though they didn't have shadow.")) return false; if (!attacker.hasKeyword("Shadow") && blocker.hasKeyword("Shadow")) return false; if (blocker.hasKeyword("CARDNAME can't block white creatures with power 2 or greater.")) { if (attacker.isWhite() && attacker.getNetAttack() >= 2) return false; } if (blocker.hasKeyword("CARDNAME can't block black creatures.")) { if (attacker.isBlack()) return false; } // CARDNAME can't block creatures with power ... int powerLimit[] = {0}; int keywordPosition = 0; boolean hasKeyword = false; ArrayList<String> blockerKeywords = blocker.getKeyword(); for (int i = 0; i < blockerKeywords.size(); i++) { if (blockerKeywords.get(i).toString().startsWith("CARDNAME can't block creatures with power")) { hasKeyword = true; keywordPosition = i; } } if (attacker.hasKeyword("Creatures with power less than CARDNAME's power can't block it.") && attacker.getNetAttack() > blocker.getNetAttack()) return false; if (hasKeyword) { // The keyword "CARDNAME can't block creatures with power" ... is present String tmpString = blocker.getKeyword().get(keywordPosition).toString(); String asSeparateWords[] = tmpString.trim().split(" "); if (asSeparateWords.length >= 9) { if (asSeparateWords[6].matches("[0-9][0-9]?")) { powerLimit[0] = Integer.parseInt((asSeparateWords[6]).trim()); if (attacker.getNetAttack() >= powerLimit[0] && blocker.hasKeyword("CARDNAME can't block creatures with power " + powerLimit[0] + " or greater.")) return false; if (attacker.getNetAttack() <= powerLimit[0] && blocker.hasKeyword("CARDNAME can't block creatures with power " + powerLimit[0] + " or less.")) return false; } } if (attacker.getNetAttack() > blocker.getNetAttack() && blocker.hasKeyword("CARDNAME can't block creatures with power greater than CARDNAME's power.")) return false; if (attacker.getNetAttack() >= blocker.getNetDefense() && blocker.hasKeyword("CARDNAME can't block creatures with power equal to or greater than CARDNAME's toughness.")) return false; }// hasKeyword CARDNAME can't block creatures with power ... // CARDNAME can't be blocked by creatures with power ... int powerLimit2[] = {0}; int keywordPosition2 = 0; boolean hasKeyword2 = false; ArrayList<String> attackerKeywords = attacker.getKeyword(); for (int i = 0; i < attackerKeywords.size(); i++) { if (attackerKeywords.get(i).toString().startsWith("CARDNAME can't be blocked by creatures with power")) { hasKeyword2 = true; keywordPosition2 = i; } } if (hasKeyword2) { // The keyword "CARDNAME can't be blocked by creatures with power" ... is present String tmpString = attacker.getKeyword().get(keywordPosition2).toString(); String asSeparateWords[] = tmpString.trim().split(" "); if (asSeparateWords.length >= 9) { if (asSeparateWords[8].matches("[0-9][0-9]?")) { powerLimit2[0] = Integer.parseInt((asSeparateWords[8]).trim()); if (blocker.getNetAttack() >= powerLimit2[0] && attacker.hasKeyword ("CARDNAME can't be blocked by creatures with power " + powerLimit2[0] + " or greater.")) return false; if (blocker.getNetAttack() <= powerLimit2[0] && attacker.hasKeyword ("CARDNAME can't be blocked by creatures with power " + powerLimit2[0] + " or less.")) return false; } } if (blocker.getNetAttack() > attacker.getNetAttack() && blocker.hasKeyword("CARDNAME can't be blocked by creatures with power greater than CARDNAME's power.")) return false; if (blocker.getNetAttack() >= attacker.getNetDefense() && blocker.hasKeyword("CARDNAME can't be blocked by creatures with power equal to or greater than CARDNAME's toughness.")) return false; }// hasKeyword CARDNAME can't be blocked by creatures with power ... if (attacker.hasStartOfKeyword("CantBeBlockedBy")) { int KeywordPosition = attacker.getKeywordPosition("CantBeBlockedBy"); String parse = attacker.getKeyword().get(KeywordPosition).toString(); String k[] = parse.split(" ", 2); final String restrictions[] = k[1].split(","); if (blocker.isValidCard(restrictions, attacker.getController(), attacker)) return false; } if (attacker.hasKeyword("CARDNAME can't be blocked by black creatures.") && blocker.isBlack()) return false; if (attacker.hasKeyword("CARDNAME can't be blocked by blue creatures.") && blocker.isBlue()) return false; if (attacker.hasKeyword("CARDNAME can't be blocked by green creatures.") && blocker.isGreen()) return false; if (attacker.hasKeyword("CARDNAME can't be blocked by red creatures.") && blocker.isRed()) return false; if (attacker.hasKeyword("CARDNAME can't be blocked by white creatures.") && blocker.isWhite()) return false; if (blocker.hasKeyword("CARDNAME can block only creatures with flying.") && !attacker.hasKeyword("Flying")) return false; if (attacker.hasKeyword("Flying") || attacker.hasKeyword("CARDNAME can't be blocked except by creatures with flying or reach.")) { if (!blocker.hasKeyword("Flying") && !blocker.hasKeyword("Reach")) return false; } if (attacker.hasKeyword("Horsemanship")) { if (!blocker.hasKeyword("Horsemanship")) return false; } if (attacker.hasKeyword("Fear")) { if (!blocker.isArtifact() && !blocker.isBlack()) return false; } if (attacker.hasKeyword("Intimidate")) { if (!blocker.isArtifact() && !blocker.sharesColorWith(attacker)) return false; } if (attacker.hasKeyword("CARDNAME can't be blocked by Walls.") && blocker.isWall()) return false; if (attacker.hasKeyword("CARDNAME can't be blocked except by Walls.") && !blocker.isWall()) return false; if (attacker.hasKeyword("CARDNAME can't be blocked except by black creatures.") && !blocker.isBlack()) return false; if (AllZoneUtil.isCardInPlay("Shifting Sliver")) { if (attacker.isType("Sliver") && !blocker.isType("Sliver")) return false; } return true; }//canBlock() //can a creature attack given the combat state /** * <p>canAttack.</p> * * @param c a {@link forge.Card} object. * @param combat a {@link forge.Combat} object. * @return a boolean. */ public static boolean canAttack(Card c, Combat combat) { if (combat.getAttackers().length > 1 && AllZoneUtil.isCardInPlay("Crawlspace", c.getController().getOpponent())) return false; if (combat.getAttackers().length > 1 && AllZoneUtil.isCardInPlay("Caverns of Despair")) return false; if (combat.getAttackers().length > 0 && AllZoneUtil.isCardInPlay("Silent Arbiter")) return false; if (combat.getAttackers().length > 0 && AllZoneUtil.isCardInPlay("Dueling Grounds")) return false; return canAttack(c); } //can a creature attack at the moment? /** * <p>canAttack.</p> * * @param c a {@link forge.Card} object. * @return a boolean. */ public static boolean canAttack(Card c) { if (c.isTapped() || (c.isSick() && !c.isEnchantedBy("Instill Energy"))) return false; return canAttackNextTurn(c); } //can a creature attack if untapped and without summoning sickness? /** * <p>canAttackNextTurn.</p> * * @param c a {@link forge.Card} object. * @return a boolean. */ public static boolean canAttackNextTurn(Card c) { if (!c.isCreature()) return false; if (AllZoneUtil.isCardInPlay("Peacekeeper")) return false; // CARDNAME can't attack if defending player controls an untapped creature with power ... final int powerLimit[] = {0}; int keywordPosition = 0; boolean hasKeyword = false; ArrayList<String> attackerKeywords = c.getKeyword(); for (int i = 0; i < attackerKeywords.size(); i++) { if (attackerKeywords.get(i).toString().startsWith("CARDNAME can't attack if defending player controls an untapped creature with power")) { hasKeyword = true; keywordPosition = i; } } // The keyword "CARDNAME can't attack if defending player controls an untapped creature with power" ... is present if (hasKeyword) { String tmpString = c.getKeyword().get(keywordPosition).toString(); final String asSeparateWords[] = tmpString.trim().split(" "); if (asSeparateWords.length >= 15) { if (asSeparateWords[12].matches("[0-9][0-9]?")) { powerLimit[0] = Integer.parseInt((asSeparateWords[12]).trim()); CardList list = AllZoneUtil.getCreaturesInPlay(c.getController().getOpponent()); list = list.filter(new CardListFilter() { public boolean addCard(Card ct) { return ((ct.isUntapped() && ct.getNetAttack() >= powerLimit[0] && asSeparateWords[14].contains("greater")) || (ct.isUntapped() && ct.getNetAttack() <= powerLimit[0] && asSeparateWords[14].contains("less"))); } }); if (!list.isEmpty()) return false; } } } // hasKeyword = CARDNAME can't attack if defending player controls an untapped creature with power ... CardList list = AllZoneUtil.getPlayerCardsInPlay(c.getController().getOpponent()); CardList temp; if (c.hasKeyword("CARDNAME can't attack unless defending player controls an Island.")) { temp = list.getType("Island"); if (temp.isEmpty()) return false; } if (c.hasKeyword("CARDNAME can't attack unless defending player controls a Forest.")) { temp = list.getType("Forest"); if (temp.isEmpty()) return false; } if (c.hasKeyword("CARDNAME can't attack unless defending player controls a Swamp.")) { temp = list.getType("Swamp"); if (temp.isEmpty()) return false; } if (c.hasKeyword("CARDNAME can't attack unless defending player controls a Mountain.")) { temp = list.getType("Montain"); if (temp.isEmpty()) return false; } if (c.hasKeyword("CARDNAME can't attack unless defending player controls a snow land.")) { temp = list.filter(new CardListFilter() { public boolean addCard(Card c) { return c.isLand() && c.isSnow(); } }); if (temp.isEmpty()) return false; } if (c.hasKeyword("CARDNAME can't attack unless defending player controls a blue permanent.")) { temp = list.getColor(Constant.Color.Blue); if (temp.isEmpty()) return false; } if (c.getName().equals("Harbor Serpent")) { CardList allislands = AllZoneUtil.getTypeInPlay("Island"); if (allislands.size() < 5) return false; } //The creature won't untap next turn if (c.isTapped() && !PhaseUtil.canUntap(c)) return false; if (AllZoneUtil.isCardInPlay("Blazing Archon", c.getController().getOpponent()) || c.hasKeyword("CARDNAME can't attack.") || c.hasKeyword("CARDNAME can't attack or block.") || (AllZoneUtil.isCardInPlay("Reverence", c.getController().getOpponent()) && c.getNetAttack() < 3)) return false; if (c.hasKeyword("Defender") && !c.hasKeyword("CARDNAME can attack as though it didn't have defender.")) { return false; } if (AllZoneUtil.isCardInPlay("Ensnaring Bridge")) { int limit = Integer.MAX_VALUE; CardList Human = AllZoneUtil.getPlayerCardsInPlay(AllZone.getHumanPlayer()); if (Human.getName("Ensnaring Bridge").size() > 0) { CardList Hand = AllZoneUtil.getPlayerHand(AllZone.getHumanPlayer()); limit = Hand.size(); } CardList Compi = AllZoneUtil.getPlayerCardsInPlay(AllZone.getComputerPlayer()); if (Compi.getName("Ensnaring Bridge").size() > 0) { CardList Hand = AllZoneUtil.getPlayerHand(AllZone.getComputerPlayer()); if (Hand.size() < limit) limit = Hand.size(); } if (c.getNetAttack() > limit) return false; } if (AllZoneUtil.isCardInPlay("Kulrath Knight")) { CardList all = AllZoneUtil.getCardsInPlay("Kulrath Knight"); for (int i = 0; i < all.size(); i++) { Card cKK = all.get(i); Player oppKK = cKK.getController().getOpponent(); if (c.getController().equals(oppKK) && c.hasCounters()) return false; } } return true; }//canAttack() /** * <p>getTotalFirstStrikeBlockPower.</p> * * @param attacker a {@link forge.Card} object. * @param player a {@link forge.Player} object. * @return a int. */ public static int getTotalFirstStrikeBlockPower(Card attacker, Player player) { final Card att = attacker; CardList list = AllZoneUtil.getCreaturesInPlay(player); list = list.filter(new CardListFilter() { public boolean addCard(Card c) { return canBlock(att, c) && (c.hasFirstStrike() || c.hasDoubleStrike()); } }); return totalDamageOfBlockers(attacker, list); } //This function takes Doran and Double Strike into account /** * <p>getAttack.</p> * * @param c a {@link forge.Card} object. * @return a int. */ public static int getAttack(Card c) { int n = c.getNetCombatDamage(); if (c.hasDoubleStrike()) n *= 2; return n; } //Returns the damage an unblocked attacker would deal /** * <p>damageIfUnblocked.</p> * * @param attacker a {@link forge.Card} object. * @param attacked a {@link forge.Player} object. * @param combat a {@link forge.Combat} object. * @return a int. */ public static int damageIfUnblocked(Card attacker, Player attacked, Combat combat) { int damage = attacker.getNetCombatDamage(); int sum = 0; damage += predictPowerBonusOfAttacker(attacker, null, combat); if (!attacker.hasKeyword("Infect")) { sum = attacked.predictDamage(damage, attacker, true); if (attacker.hasKeyword("Double Strike")) sum += attacked.predictDamage(damage, attacker, true); } return sum; } //Returns the poison an unblocked attacker would deal /** * <p>poisonIfUnblocked.</p> * * @param attacker a {@link forge.Card} object. * @param attacked a {@link forge.Player} object. * @param combat a {@link forge.Combat} object. * @return a int. */ public static int poisonIfUnblocked(Card attacker, Player attacked, Combat combat) { int damage = attacker.getNetCombatDamage(); int poison = 0; damage += predictPowerBonusOfAttacker(attacker, null, null); if (attacker.hasKeyword("Infect")) { poison += attacked.predictDamage(damage, attacker, true); if (attacker.hasKeyword("Double Strike")) poison += attacked.predictDamage(damage, attacker, true); } if (attacker.hasKeyword("Poisonous") && damage > 0) poison += attacker.getKeywordMagnitude("Poisonous"); return poison; } //Returns the damage unblocked attackers would deal /** * <p>sumDamageIfUnblocked.</p> * * @param attackers a {@link forge.CardList} object. * @param attacked a {@link forge.Player} object. * @return a int. */ private static int sumDamageIfUnblocked(CardList attackers, Player attacked) { int sum = 0; for (Card attacker : attackers) { sum += damageIfUnblocked(attacker, attacked, null); } return sum; } //Returns the number of poison counters unblocked attackers would deal /** * <p>sumPoisonIfUnblocked.</p> * * @param attackers a {@link forge.CardList} object. * @param attacked a {@link forge.Player} object. * @return a int. */ private static int sumPoisonIfUnblocked(CardList attackers, Player attacked) { int sum = 0; for (Card attacker : attackers) { sum += poisonIfUnblocked(attacker, attacked, null); } return sum; } //calculates the amount of life that will remain after the attack /** * <p>lifeThatWouldRemain.</p> * * @param combat a {@link forge.Combat} object. * @return a int. */ public static int lifeThatWouldRemain(Combat combat) { int damage = 0; CardList attackers = combat.sortAttackerByDefender()[0]; CardList unblocked = new CardList(); for (Card attacker : attackers) { CardList blockers = combat.getBlockers(attacker); if (blockers.size() == 0) unblocked.add(attacker); else if (attacker.hasKeyword("Trample") && getAttack(attacker) > CombatUtil.totalShieldDamage(attacker, blockers)) { if (!attacker.hasKeyword("Infect")) damage += getAttack(attacker) - CombatUtil.totalShieldDamage(attacker, blockers); } } damage += sumDamageIfUnblocked(unblocked, AllZone.getComputerPlayer()); if (!AllZone.getComputerPlayer().canLoseLife()) damage = 0; return AllZone.getComputerPlayer().getLife() - damage; } //calculates the amount of poison counters after the attack /** * <p>resultingPoison.</p> * * @param combat a {@link forge.Combat} object. * @return a int. */ public static int resultingPoison(Combat combat) { int poison = 0; CardList attackers = combat.sortAttackerByDefender()[0]; CardList unblocked = new CardList(); for (Card attacker : attackers) { CardList blockers = combat.getBlockers(attacker); if (blockers.size() == 0) unblocked.add(attacker); else if (attacker.hasKeyword("Trample") && getAttack(attacker) > CombatUtil.totalShieldDamage(attacker, blockers)) { if (attacker.hasKeyword("Infect")) poison += getAttack(attacker) - CombatUtil.totalShieldDamage(attacker, blockers); if (attacker.hasKeyword("Poisonous")) poison += attacker.getKeywordMagnitude("Poisonous"); } } poison += sumPoisonIfUnblocked(unblocked, AllZone.getComputerPlayer()); return AllZone.getComputerPlayer().getPoisonCounters() + poison; } //Checks if the life of the attacked Player/Planeswalker is in danger /** * <p>lifeInDanger.</p> * * @param combat a {@link forge.Combat} object. * @return a boolean. */ public static boolean lifeInDanger(Combat combat) { // life in danger only cares about the player's life. Not about a Planeswalkers life if (AllZone.getComputerPlayer().cantLose()) return false; if (lifeThatWouldRemain(combat) < Math.min(4, AllZone.getComputerPlayer().getLife()) && !AllZone.getComputerPlayer().cantLoseForZeroOrLessLife()) return true; return (resultingPoison(combat) > Math.max(7, AllZone.getComputerPlayer().getPoisonCounters())); } //Checks if the life of the attacked Player would be reduced /** * <p>wouldLoseLife.</p> * * @param combat a {@link forge.Combat} object. * @return a boolean. */ public static boolean wouldLoseLife(Combat combat) { return (lifeThatWouldRemain(combat) < AllZone.getComputerPlayer().getLife()); } //Checks if the life of the attacked Player/Planeswalker is in danger /** * <p>lifeInSeriousDanger.</p> * * @param combat a {@link forge.Combat} object. * @return a boolean. */ public static boolean lifeInSeriousDanger(Combat combat) { // life in danger only cares about the player's life. Not about a Planeswalkers life if (AllZone.getComputerPlayer().cantLose()) return false; if (lifeThatWouldRemain(combat) < 1 && !AllZone.getComputerPlayer().cantLoseForZeroOrLessLife()) return true; return (resultingPoison(combat) > 9); } // This calculates the amount of damage a blockgang can deal to the attacker (first strike not supported) /** * <p>totalDamageOfBlockers.</p> * * @param attacker a {@link forge.Card} object. * @param defenders a {@link forge.CardList} object. * @return a int. */ public static int totalDamageOfBlockers(Card attacker, CardList defenders) { int damage = 0; for (Card defender : defenders) damage += dealsDamageAsBlocker(attacker, defender); return damage; } // This calculates the amount of damage a blocker in a blockgang can deal to the attacker /** * <p>dealsDamageAsBlocker.</p> * * @param attacker a {@link forge.Card} object. * @param defender a {@link forge.Card} object. * @return a int. */ public static int dealsDamageAsBlocker(Card attacker, Card defender) { if (attacker.getName().equals("Sylvan Basilisk") && !defender.hasKeyword("Indestructible")) return 0; int flankingMagnitude = 0; if (attacker.hasKeyword("Flanking") && !defender.hasKeyword("Flanking")) { flankingMagnitude = attacker.getAmountOfKeyword("Flanking"); if (flankingMagnitude >= defender.getNetDefense()) return 0; if (flankingMagnitude >= defender.getNetDefense() - defender.getDamage() && !defender.hasKeyword("Indestructible")) return 0; }//flanking if (attacker.hasKeyword("Indestructible") && !(defender.hasKeyword("Wither") || defender.hasKeyword("Infect"))) return 0; int defBushidoMagnitude = defender.getKeywordMagnitude("Bushido"); int defenderDamage = defender.getNetCombatDamage() - flankingMagnitude + defBushidoMagnitude; // consider static Damage Prevention defenderDamage = attacker.predictDamage(defenderDamage, defender, true); if (defender.hasKeyword("Double Strike")) defenderDamage += attacker.predictDamage(defenderDamage, defender, true); return defenderDamage; } // This calculates the amount of damage a blocker in a blockgang can take from the attacker (for trampling attackers) /** * <p>totalShieldDamage.</p> * * @param attacker a {@link forge.Card} object. * @param defenders a {@link forge.CardList} object. * @return a int. */ public static int totalShieldDamage(Card attacker, CardList defenders) { int defenderDefense = 0; for (Card defender : defenders) defenderDefense += shieldDamage(attacker, defender); return defenderDefense; } // This calculates the amount of damage a blocker in a blockgang can take from the attacker (for trampling attackers) /** * <p>shieldDamage.</p> * * @param attacker a {@link forge.Card} object. * @param defender a {@link forge.Card} object. * @return a int. */ public static int shieldDamage(Card attacker, Card defender) { if (!canDestroyBlocker(defender, attacker, null, false)) return 100; int flankingMagnitude = 0; if (attacker.hasKeyword("Flanking") && !defender.hasKeyword("Flanking")) { flankingMagnitude = attacker.getAmountOfKeyword("Flanking"); if (flankingMagnitude >= defender.getNetDefense()) return 0; if (flankingMagnitude >= defender.getNetDefense() - defender.getDamage() && !defender.hasKeyword("Indestructible")) return 0; }//flanking int defBushidoMagnitude = defender.getKeywordMagnitude("Bushido"); int defenderDefense = defender.getNetDefense() - flankingMagnitude + defBushidoMagnitude; return defenderDefense; }//shieldDamage //For AI safety measures like Regeneration /** * <p>combatantWouldBeDestroyed.</p> * * @param combatant a {@link forge.Card} object. * @return a boolean. */ public static boolean combatantWouldBeDestroyed(Card combatant) { if (combatant.isAttacking()) return attackerWouldBeDestroyed(combatant); if (combatant.isBlocking()) return blockerWouldBeDestroyed(combatant); return false; } //For AI safety measures like Regeneration /** * <p>attackerWouldBeDestroyed.</p> * * @param attacker a {@link forge.Card} object. * @return a boolean. */ public static boolean attackerWouldBeDestroyed(Card attacker) { CardList blockers = AllZone.getCombat().getBlockers(attacker); for (Card defender : blockers) { if (CombatUtil.canDestroyAttacker(attacker, defender, AllZone.getCombat(), true) && !(defender.hasKeyword("Wither") || defender.hasKeyword("Infect"))) return true; } return totalDamageOfBlockers(attacker, blockers) >= attacker.getKillDamage(); } //Will this trigger trigger? /** * <p>combatTriggerWillTrigger.</p> * * @param attacker a {@link forge.Card} object. * @param defender a {@link forge.Card} object. * @param trigger a {@link forge.card.trigger.Trigger} object. * @param combat a {@link forge.Combat} object. * @return a boolean. */ public static boolean combatTriggerWillTrigger(Card attacker, Card defender, Trigger trigger, Combat combat) { HashMap<String, String> trigParams = trigger.getMapParams(); boolean willTrigger = false; Card source = trigger.getHostCard(); if (combat == null) combat = AllZone.getCombat(); if (!trigger.zonesCheck()) return false; if (!trigger.requirementsCheck()) return false; if (trigParams.get("Mode").equals("Attacks")) { willTrigger = true; if (attacker.isAttacking()) return false; //The trigger should have triggered already if (trigParams.containsKey("ValidCard")) { if (!trigger.matchesValid(attacker, trigParams.get("ValidCard").split(","), source) && !(combat.isAttacking(source) && trigger.matchesValid(source, trigParams.get("ValidCard").split(","), source))) return false; } } // defender == null means unblocked if (defender == null && trigParams.get("Mode").equals("AttackerUnblocked")) { willTrigger = true; if (trigParams.containsKey("ValidCard")) if (!trigger.matchesValid(attacker, trigParams.get("ValidCard").split(","), source)) return false; } if (defender == null) return willTrigger; if (trigParams.get("Mode").equals("Blocks")) { willTrigger = true; if (trigParams.containsKey("ValidBlocked")) if (!trigger.matchesValid(attacker, trigParams.get("ValidBlocked").split(","), source)) return false; if (trigParams.containsKey("ValidCard")) if (!trigger.matchesValid(defender, trigParams.get("ValidCard").split(","), source)) return false; } else if (trigParams.get("Mode").equals("AttackerBlocked")) { willTrigger = true; if (trigParams.containsKey("ValidBlocker")) if (!trigger.matchesValid(defender, trigParams.get("ValidBlocker").split(","), source)) return false; if (trigParams.containsKey("ValidCard")) if (!trigger.matchesValid(attacker, trigParams.get("ValidCard").split(","), source)) return false; } return willTrigger; } //Predict the Power bonus of the blocker if blocking the attacker (Flanking, Bushido and other triggered abilities) /** * <p>predictPowerBonusOfBlocker.</p> * * @param attacker a {@link forge.Card} object. * @param defender a {@link forge.Card} object. * @return a int. */ public static int predictPowerBonusOfBlocker(Card attacker, Card defender) { int power = 0; if (attacker.hasKeyword("Flanking") && !defender.hasKeyword("Flanking")) power -= attacker.getAmountOfKeyword("Flanking"); //if the attacker has first strike and wither the blocker will deal less damage than expected if ((attacker.hasKeyword("First Strike") || attacker.hasKeyword("Double Strike")) && (attacker.hasKeyword("Wither") || attacker.hasKeyword("Infect")) && !(defender.hasKeyword("First Strike") || defender.hasKeyword("Double Strike") || defender.hasKeyword("CARDNAME can't have counters placed on it."))) power -= attacker.getNetCombatDamage(); power += defender.getKeywordMagnitude("Bushido"); ArrayList<Trigger> registeredTriggers = AllZone.getTriggerHandler().getRegisteredTriggers(); for (Trigger trigger : registeredTriggers) { HashMap<String, String> trigParams = trigger.getMapParams(); Card source = trigger.getHostCard(); if (!combatTriggerWillTrigger(attacker, defender, trigger, null) || !trigParams.containsKey("Execute")) continue; String ability = source.getSVar(trigParams.get("Execute")); AbilityFactory AF = new AbilityFactory(); HashMap<String, String> abilityParams = AF.getMapParams(ability, source); if (abilityParams.containsKey("AB") && !abilityParams.get("AB").equals("Pump")) continue; if (abilityParams.containsKey("DB") && !abilityParams.get("DB").equals("Pump")) continue; if (abilityParams.containsKey("ValidTgts") || abilityParams.containsKey("Tgt")) continue; //targeted pumping not supported ArrayList<Card> list = AbilityFactory.getDefinedCards(source, abilityParams.get("Defined"), null); if (abilityParams.containsKey("Defined") && abilityParams.get("Defined").equals("TriggeredBlocker")) list.add(defender); if (list.isEmpty()) continue; if (!list.contains(defender)) continue; if (!abilityParams.containsKey("NumAtt")) continue; String att = abilityParams.get("NumAtt"); if (att.startsWith("+")) att = att.substring(1); try { power += Integer.parseInt(att); } catch (NumberFormatException nfe) { //can't parse the number (X for example) power += 0; } } return power; } //Predict the Toughness bonus of the blocker if blocking the attacker (Flanking, Bushido and other triggered abilities) /** * <p>predictToughnessBonusOfBlocker.</p> * * @param attacker a {@link forge.Card} object. * @param defender a {@link forge.Card} object. * @return a int. */ public static int predictToughnessBonusOfBlocker(Card attacker, Card defender) { int toughness = 0; if (attacker.hasKeyword("Flanking") && !defender.hasKeyword("Flanking")) toughness -= attacker.getAmountOfKeyword("Flanking"); toughness += defender.getKeywordMagnitude("Bushido"); ArrayList<Trigger> registeredTriggers = AllZone.getTriggerHandler().getRegisteredTriggers(); for (Trigger trigger : registeredTriggers) { HashMap<String, String> trigParams = trigger.getMapParams(); Card source = trigger.getHostCard(); if (!combatTriggerWillTrigger(attacker, defender, trigger, null) || !trigParams.containsKey("Execute")) continue; String ability = source.getSVar(trigParams.get("Execute")); AbilityFactory AF = new AbilityFactory(); HashMap<String, String> abilityParams = AF.getMapParams(ability, source); if (abilityParams.containsKey("AB") && !abilityParams.get("AB").equals("Pump")) continue; if (abilityParams.containsKey("DB") && !abilityParams.get("DB").equals("Pump")) continue; if (abilityParams.containsKey("ValidTgts") || abilityParams.containsKey("Tgt")) continue; //targeted pumping not supported ArrayList<Card> list = AbilityFactory.getDefinedCards(source, abilityParams.get("Defined"), null); if (abilityParams.containsKey("Defined") && abilityParams.get("Defined").equals("TriggeredBlocker")) list.add(defender); if (list.isEmpty()) continue; if (!list.contains(defender)) continue; if (!abilityParams.containsKey("NumDef")) continue; String def = abilityParams.get("NumDef"); if (def.startsWith("+")) def = def.substring(1); try { toughness += Integer.parseInt(def); } catch (NumberFormatException nfe) { //can't parse the number (X for example) toughness += 0; } } return toughness; } //Predict the Power bonus of the blocker if blocking the attacker (Flanking, Bushido and other triggered abilities) /** * <p>predictPowerBonusOfAttacker.</p> * * @param attacker a {@link forge.Card} object. * @param defender a {@link forge.Card} object. * @param combat a {@link forge.Combat} object. * @return a int. */ public static int predictPowerBonusOfAttacker(Card attacker, Card defender, Combat combat) { int power = 0; power += attacker.getKeywordMagnitude("Bushido"); //if the defender has first strike and wither the attacker will deal less damage than expected if (null != defender) { if ((defender.hasKeyword("First Strike") || defender.hasKeyword("Double Strike")) && (defender.hasKeyword("Wither") || defender.hasKeyword("Infect")) && !(attacker.hasKeyword("First Strike") || attacker.hasKeyword("Double Strike") || attacker.hasKeyword("CARDNAME can't have counters placed on it."))) power -= defender.getNetCombatDamage(); } ArrayList<Trigger> registeredTriggers = AllZone.getTriggerHandler().getRegisteredTriggers(); for (Trigger trigger : registeredTriggers) { HashMap<String, String> trigParams = trigger.getMapParams(); Card source = trigger.getHostCard(); if (!combatTriggerWillTrigger(attacker, defender, trigger, null) || !trigParams.containsKey("Execute")) continue; String ability = source.getSVar(trigParams.get("Execute")); AbilityFactory AF = new AbilityFactory(); HashMap<String, String> abilityParams = AF.getMapParams(ability, source); if (abilityParams.containsKey("ValidTgts") || abilityParams.containsKey("Tgt")) continue; //targeted pumping not supported if (abilityParams.containsKey("AB") && !abilityParams.get("AB").equals("Pump") && !abilityParams.get("AB").equals("PumpAll")) continue; if (abilityParams.containsKey("DB") && !abilityParams.get("DB").equals("Pump") && !abilityParams.get("DB").equals("PumpAll")) continue; ArrayList<Card> list = new ArrayList<Card>(); if (!abilityParams.containsKey("ValidCards")) //no pumpAll list = AbilityFactory.getDefinedCards(source, abilityParams.get("Defined"), null); if (abilityParams.containsKey("Defined") && abilityParams.get("Defined").equals("TriggeredAttacker")) list.add(attacker); if (abilityParams.containsKey("ValidCards")) if (attacker.isValidCard(abilityParams.get("ValidCards").split(","), source.getController(), source) || attacker.isValidCard(abilityParams.get("ValidCards").replace("attacking+", "").split(",") , source.getController(), source)) list.add(attacker); if (list.isEmpty()) continue; if (!list.contains(attacker)) continue; if (!abilityParams.containsKey("NumAtt")) continue; String att = abilityParams.get("NumAtt"); if (att.startsWith("+")) att = att.substring(1); try { power += Integer.parseInt(att); } catch (NumberFormatException nfe) { //can't parse the number (X for example) power += 0; } } return power; } //Predict the Toughness bonus of the blocker if blocking the attacker (Flanking, Bushido and other triggered abilities) /** * <p>predictToughnessBonusOfAttacker.</p> * * @param attacker a {@link forge.Card} object. * @param defender a {@link forge.Card} object. * @param combat a {@link forge.Combat} object. * @return a int. */ public static int predictToughnessBonusOfAttacker(Card attacker, Card defender, Combat combat) { int toughness = 0; toughness += attacker.getKeywordMagnitude("Bushido"); ArrayList<Trigger> registeredTriggers = AllZone.getTriggerHandler().getRegisteredTriggers(); for (Trigger trigger : registeredTriggers) { HashMap<String, String> trigParams = trigger.getMapParams(); Card source = trigger.getHostCard(); if (!combatTriggerWillTrigger(attacker, defender, trigger, null) || !trigParams.containsKey("Execute")) continue; String ability = source.getSVar(trigParams.get("Execute")); AbilityFactory AF = new AbilityFactory(); HashMap<String, String> abilityParams = AF.getMapParams(ability, source); if (abilityParams.containsKey("ValidTgts") || abilityParams.containsKey("Tgt")) continue; //targeted pumping not supported if (abilityParams.containsKey("AB") && !abilityParams.get("AB").equals("Pump") && !abilityParams.get("AB").equals("PumpAll")) continue; if (abilityParams.containsKey("DB") && !abilityParams.get("DB").equals("Pump") && !abilityParams.get("DB").equals("PumpAll")) continue; ArrayList<Card> list = new ArrayList<Card>(); if (!abilityParams.containsKey("ValidCards")) //no pumpAll list = AbilityFactory.getDefinedCards(source, abilityParams.get("Defined"), null); if (abilityParams.containsKey("Defined") && abilityParams.get("Defined").equals("TriggeredAttacker")) list.add(attacker); if (abilityParams.containsKey("ValidCards")) if (attacker.isValidCard(abilityParams.get("ValidCards").split(","), source.getController(), source) || attacker.isValidCard(abilityParams.get("ValidCards").replace("attacking+", "").split(",") , source.getController(), source)) list.add(attacker); if (list.isEmpty()) continue; if (!list.contains(attacker)) continue; if (!abilityParams.containsKey("NumDef")) continue; String def = abilityParams.get("NumDef"); if (def.startsWith("+")) def = def.substring(1); try { toughness += Integer.parseInt(def); } catch (NumberFormatException nfe) { //can't parse the number (X for example) toughness += 0; } } return toughness; } //can the blocker destroy the attacker? /** * <p>canDestroyAttacker.</p> * * @param attacker a {@link forge.Card} object. * @param defender a {@link forge.Card} object. * @param combat a {@link forge.Combat} object. * @param withoutAbilities a boolean. * @return a boolean. */ public static boolean canDestroyAttacker(Card attacker, Card defender, Combat combat, boolean withoutAbilities) { if (attacker.getName().equals("Sylvan Basilisk") && !defender.hasKeyword("Indestructible")) return false; int flankingMagnitude = 0; if (attacker.hasKeyword("Flanking") && !defender.hasKeyword("Flanking")) { flankingMagnitude = attacker.getAmountOfKeyword("Flanking"); if (flankingMagnitude >= defender.getNetDefense()) return false; if (flankingMagnitude >= defender.getNetDefense() - defender.getDamage() && !defender.hasKeyword("Indestructible")) return false; }//flanking if ((attacker.hasKeyword("Indestructible") || (ComputerUtil.canRegenerate(attacker) && !withoutAbilities)) && !(defender.hasKeyword("Wither") || defender.hasKeyword("Infect"))) return false; int defenderDamage = defender.getNetAttack() + predictPowerBonusOfBlocker(attacker, defender); int attackerDamage = attacker.getNetAttack() + predictPowerBonusOfAttacker(attacker, defender, combat); if (AllZoneUtil.isCardInPlay("Doran, the Siege Tower")) { defenderDamage = defender.getNetDefense() + predictToughnessBonusOfBlocker(attacker, defender); attackerDamage = attacker.getNetDefense() + predictToughnessBonusOfAttacker(attacker, defender, combat); } int possibleDefenderPrevention = 0; int possibleAttackerPrevention = 0; if (!withoutAbilities) { possibleDefenderPrevention = ComputerUtil.possibleDamagePrevention(defender); possibleAttackerPrevention = ComputerUtil.possibleDamagePrevention(attacker); } // consider Damage Prevention/Replacement defenderDamage = attacker.predictDamage(defenderDamage, possibleAttackerPrevention, defender, true); attackerDamage = defender.predictDamage(attackerDamage, possibleDefenderPrevention, attacker, true); int defenderLife = defender.getKillDamage() + predictToughnessBonusOfBlocker(attacker, defender); int attackerLife = attacker.getKillDamage() + predictToughnessBonusOfAttacker(attacker, defender, combat); if (defender.hasKeyword("Double Strike")) { if (defender.hasKeyword("Deathtouch") && defenderDamage > 0) return true; if (defenderDamage >= attackerLife) return true; //Attacker may kill the blocker before he can deal normal (secondary) damage if ((attacker.hasKeyword("Double Strike") || attacker.hasKeyword("First Strike")) && !defender.hasKeyword("Indestructible")) { if (attackerDamage >= defenderLife) return false; if (attackerDamage > 0 && attacker.hasKeyword("Deathtouch")) return false; } if (attackerLife <= 2 * defenderDamage) return true; }//defender double strike else //no double strike for defender { //Attacker may kill the blocker before he can deal any damage if (attacker.hasKeyword("Double Strike") || attacker.hasKeyword("First Strike") && !defender.hasKeyword("Indestructible") && !defender.hasKeyword("First Strike")) { if (attackerDamage >= defenderLife) return false; if (attackerDamage > 0 && attacker.hasKeyword("Deathtouch")) return false; } if (defender.hasKeyword("Deathtouch") && defenderDamage > 0) return true; return defenderDamage >= attackerLife; }//defender no double strike return false; //should never arrive here } //canDestroyAttacker //For AI safety measures like Regeneration /** * <p>blockerWouldBeDestroyed.</p> * * @param blocker a {@link forge.Card} object. * @return a boolean. */ public static boolean blockerWouldBeDestroyed(Card blocker) { Card attacker = AllZone.getCombat().getAttackerBlockedBy(blocker); if (canDestroyBlocker(blocker, attacker, AllZone.getCombat(), true) && !(attacker.hasKeyword("Wither") || attacker.hasKeyword("Infect"))) return true; return false; } //can the attacker destroy this blocker? /** * <p>canDestroyBlocker.</p> * * @param defender a {@link forge.Card} object. * @param attacker a {@link forge.Card} object. * @param combat a {@link forge.Combat} object. * @param withoutAbilities a boolean. * @return a boolean. */ public static boolean canDestroyBlocker(Card defender, Card attacker, Combat combat, boolean withoutAbilities) { int flankingMagnitude = 0; if (attacker.hasKeyword("Flanking") && !defender.hasKeyword("Flanking")) { flankingMagnitude = attacker.getAmountOfKeyword("Flanking"); if (flankingMagnitude >= defender.getNetDefense()) return true; if ((flankingMagnitude >= defender.getKillDamage()) && !defender.hasKeyword("Indestructible")) return true; }//flanking if ((defender.hasKeyword("Indestructible") || (ComputerUtil.canRegenerate(defender) && !withoutAbilities)) && !(attacker.hasKeyword("Wither") || attacker.hasKeyword("Infect"))) return false; if (attacker.getName().equals("Sylvan Basilisk") && !defender.hasKeyword("Indestructible")) return true; int defenderDamage = defender.getNetAttack() + predictPowerBonusOfBlocker(attacker, defender); int attackerDamage = attacker.getNetAttack() + predictPowerBonusOfAttacker(attacker, defender, combat); if (AllZoneUtil.isCardInPlay("Doran, the Siege Tower")) { defenderDamage = defender.getNetDefense() + predictToughnessBonusOfBlocker(attacker, defender); attackerDamage = attacker.getNetDefense() + predictToughnessBonusOfAttacker(attacker, defender, combat); } int possibleDefenderPrevention = 0; int possibleAttackerPrevention = 0; if (!withoutAbilities) { possibleDefenderPrevention = ComputerUtil.possibleDamagePrevention(defender); possibleAttackerPrevention = ComputerUtil.possibleDamagePrevention(attacker); } // consider Damage Prevention/Replacement defenderDamage = attacker.predictDamage(defenderDamage, possibleAttackerPrevention, defender, true); attackerDamage = defender.predictDamage(attackerDamage, possibleDefenderPrevention, attacker, true); int defenderLife = defender.getKillDamage() + predictToughnessBonusOfBlocker(attacker, defender); int attackerLife = attacker.getKillDamage() + predictToughnessBonusOfAttacker(attacker, defender, combat); if (attacker.hasKeyword("Double Strike")) { if (attacker.hasKeyword("Deathtouch") && attackerDamage > 0) return true; if (attackerDamage >= defenderLife) return true; //Attacker may kill the blocker before he can deal normal (secondary) damage if ((defender.hasKeyword("Double Strike") || defender.hasKeyword("First Strike")) && !attacker.hasKeyword("Indestructible")) { if (defenderDamage >= attackerLife) return false; if (defenderDamage > 0 && defender.hasKeyword("Deathtouch")) return false; } if (defenderLife <= 2 * attackerDamage) return true; }//attacker double strike else //no double strike for attacker { //Defender may kill the attacker before he can deal any damage if (defender.hasKeyword("Double Strike") || defender.hasKeyword("First Strike") && !attacker.hasKeyword("Indestructible") && !attacker.hasKeyword("First Strike")) { if (defenderDamage >= attackerLife) return false; if (defenderDamage > 0 && defender.hasKeyword("Deathtouch")) return false; } if (attacker.hasKeyword("Deathtouch") && attackerDamage > 0) return true; return attackerDamage >= defenderLife; }//attacker no double strike return false; //should never arrive here }//canDestroyBlocker /** * <p>removeAllDamage.</p> */ public static void removeAllDamage() { CardList cl = AllZoneUtil.getCardsInPlay(); for (Card c : cl) { c.setDamage(0); } } /** * <p>showCombat.</p> */ public static void showCombat() { AllZone.getDisplay().showCombat(""); Card defend[] = null; StringBuilder display = new StringBuilder(); // Loop through Defenders // Append Defending Player/Planeswalker ArrayList<Object> defenders = AllZone.getCombat().getDefenders(); CardList attackers[] = AllZone.getCombat().sortAttackerByDefender(); // Not a big fan of the triple nested loop here for (int def = 0; def < defenders.size(); def++) { if (attackers[def] == null || attackers[def].size() == 0) continue; if (def > 0) display.append("\n"); display.append("Defender - "); display.append(defenders.get(def).toString()); display.append("\n"); CardList list = attackers[def]; for (Card c : list) { //loop through attackers display.append("-> "); display.append(combatantToString(c)).append("\n"); defend = AllZone.getCombat().getBlockers(c).toArray(); //loop through blockers for (int inner = 0; inner < defend.length; inner++) { display.append(" [ "); display.append(combatantToString(defend[inner])).append("\n"); } }//loop through attackers } AllZone.getDisplay().showCombat(display.toString().trim()); }//showBlockers() /** * <p>combatantToString.</p> * * @param c a {@link forge.Card} object. * @return a {@link java.lang.String} object. */ private static String combatantToString(Card c) { StringBuilder sb = new StringBuilder(); String name = (c.isFaceDown()) ? "Morph" : c.getName(); sb.append(name); sb.append(" (").append(c.getUniqueNumber()).append(") "); sb.append(c.getNetAttack()).append("/").append(c.getNetDefense()); return sb.toString(); } /** * <p>isDoranInPlay.</p> * * @return a boolean. */ public static boolean isDoranInPlay() { return AllZoneUtil.isCardInPlay("Doran, the Siege Tower"); } /** * <p>checkPropagandaEffects.</p> * * @param c a {@link forge.Card} object. * @param bLast a boolean. */ public static void checkPropagandaEffects(Card c, final boolean bLast) { String cost = CardFactoryUtil.getPropagandaCost(c); if (cost.equals("0")) { if (!c.hasKeyword("Vigilance")) c.tap(); if (bLast) PhaseUtil.handleAttackingTriggers(); return; } final Card crd = c; String phase = AllZone.getPhase().getPhase(); if (phase.equals(Constant.Phase.Combat_Declare_Attackers) || phase.equals(Constant.Phase.Combat_Declare_Attackers_InstantAbility)) { if (!cost.equals("0")) { final Ability ability = new Ability(c, cost) { @Override public void resolve() { } }; final Command unpaidCommand = new Command() { private static final long serialVersionUID = -6483405139208343935L; public void execute() { AllZone.getCombat().removeFromCombat(crd); if (bLast) PhaseUtil.handleAttackingTriggers(); } }; final Command paidCommand = new Command() { private static final long serialVersionUID = -8303368287601871955L; public void execute() { // if Propaganda is paid, tap this card if (!crd.hasKeyword("Vigilance")) crd.tap(); if (bLast) PhaseUtil.handleAttackingTriggers(); } }; if (c.getController().isHuman()) { AllZone.getInputControl().setInput(new Input_PayManaCost_Ability(c + " - Pay to Attack\r\n", ability.getManaCost(), paidCommand, unpaidCommand)); } else { //computer if (ComputerUtil.canPayCost(ability)) { ComputerUtil.playNoStack(ability); if (!crd.hasKeyword("Vigilance")) crd.tap(); } else { // TODO: remove the below line after Propaganda occurs during Declare_Attackers AllZone.getCombat().removeFromCombat(crd); } } } } } /** * <p>checkDeclareAttackers.</p> * * @param c a {@link forge.Card} object. */ public static void checkDeclareAttackers(Card c) //this method checks triggered effects of attacking creatures, right before defending player declares blockers { //Run triggers HashMap<String, Object> runParams = new HashMap<String, Object>(); runParams.put("Attacker", c); CardList otherAttackers = new CardList(AllZone.getCombat().getAttackers()); otherAttackers.remove(c); runParams.put("OtherAttackers", otherAttackers); AllZone.getTriggerHandler().runTrigger("Attacks", runParams); //Annihilator: if (!c.getCreatureAttackedThisCombat()) { ArrayList<String> kws = c.getKeyword(); Pattern p = Pattern.compile("Annihilator [0-9]+"); Matcher m; for (String key : kws) { m = p.matcher(key); if (m.find()) { String k[] = key.split(" "); final int a = Integer.valueOf(k[1]); final Card crd = c; final Ability ability = new Ability(c, "0") { public void resolve() { if (crd.getController().isHuman()) { CardList list = AllZoneUtil.getPlayerCardsInPlay(AllZone.getComputerPlayer()); ComputerUtil.sacrificePermanents(a, list); } else { AllZone.getInputControl().setInput(PlayerUtil.input_sacrificePermanents(a)); } } }; StringBuilder sb = new StringBuilder(); sb.append("Annihilator - Defending player sacrifices ").append(a).append(" permanents."); ability.setStackDescription(sb.toString()); AllZone.getStack().add(ability); } //find } //for }//creatureAttacked //Annihilator //Mijae Djinn if (c.getName().equals("Mijae Djinn")) { if (!GameActionUtil.flipACoin(c.getController(), c)) { AllZone.getCombat().removeFromCombat(c); c.tap(); } }//Mijae Djinn if (c.getName().equals("Zur the Enchanter") && !c.getCreatureAttackedThisCombat()) { //hack, to make sure this doesn't break grabbing an oblivion ring: c.setCreatureAttackedThisCombat(true); CardList enchantments = AllZoneUtil.getPlayerCardsInLibrary(c.getController()); enchantments = enchantments.filter(new CardListFilter() { public boolean addCard(Card c) { if (c.isEnchantment() && c.getCMC() <= 3) return true; else return false; } }); if (enchantments.size() > 0) { if (c.getController().isHuman()) { Object o = GuiUtils.getChoiceOptional("Pick an enchantment to put onto the battlefield", enchantments.toArray()); if (o != null) { Card crd = (Card) o; AllZone.getGameAction().moveToPlay(crd); if (crd.isAura()) { Object obj = null; if (crd.hasKeyword("Enchant creature")) { CardList creats = AllZoneUtil.getCreaturesInPlay(); obj = GuiUtils.getChoiceOptional("Pick a creature to attach " + crd.getName() + " to", creats.toArray()); } else if (crd.hasKeyword("Enchant land") || crd.hasKeyword("Enchant land you control")) { CardList lands = AllZoneUtil.getLandsInPlay(); if (lands.size() > 0) obj = GuiUtils.getChoiceOptional( "Pick a land to attach " + crd.getName() + " to", lands.toArray()); } if (obj != null) { Card target = (Card) obj; if (AllZoneUtil.isCardInPlay(target)) { crd.enchantCard(target); } } } c.getController().shuffle(); //we have to have cards like glorious anthem take effect immediately: for (String effect : AllZone.getStaticEffects().getStateBasedMap().keySet()) { Command com = GameActionUtil.commands.get(effect); com.execute(); } } } else if (c.getController().isComputer()) { enchantments = enchantments.filter(new CardListFilter() { public boolean addCard(Card c) { return !c.isAura(); } }); if (enchantments.size() > 0) { Card card = CardFactoryUtil.AI_getBestEnchantment(enchantments, c, false); AllZone.getGameAction().moveToPlay(card); c.getController().shuffle(); } } } //enchantments.size > 0 }//Zur the enchanter else if (c.getName().equals("Spectral Bears")) { Player opp = c.getController().getOpponent(); CardList list = AllZoneUtil.getPlayerCardsInPlay(opp); list = list.filter(new CardListFilter() { public boolean addCard(Card crd) { return crd.isBlack() && !crd.isToken(); } }); if (list.size() == 0) { c.addExtrinsicKeyword("This card doesn't untap during your next untap step."); } } else if (c.getName().equals("Spectral Force")) { Player opp = c.getController().getOpponent(); CardList list = AllZoneUtil.getPlayerCardsInPlay(opp); list = list.filter(AllZoneUtil.black); if (list.size() == 0) { c.addExtrinsicKeyword("This card doesn't untap during your next untap step."); } } else if (c.getName().equals("Witch-Maw Nephilim") && !c.getCreatureAttackedThisCombat() && c.getNetAttack() >= 10) { final Card charger = c; Ability ability2 = new Ability(c, "0") { @Override public void resolve() { final Command untilEOT = new Command() { private static final long serialVersionUID = -1703473800920781454L; public void execute() { if (AllZoneUtil.isCardInPlay(charger)) { charger.removeIntrinsicKeyword("Trample"); } } };//Command if (AllZoneUtil.isCardInPlay(charger)) { charger.addIntrinsicKeyword("Trample"); AllZone.getEndOfTurn().addUntil(untilEOT); } }//resolve };//ability StringBuilder sb2 = new StringBuilder(); sb2.append(c.getName()).append(" - gains trample until end of turn if its power is 10 or greater."); ability2.setStackDescription(sb2.toString()); AllZone.getStack().add(ability2); }//Witch-Maw Nephilim else if (c.getName().equals("Preeminent Captain") && !c.getCreatureAttackedThisCombat()) { System.out.println("Preeminent Captain Attacks"); CardList soldiers = AllZoneUtil.getPlayerHand(c.getController()); soldiers = soldiers.getType("Soldier"); if (soldiers.size() > 0) { if (c.getController().isHuman()) { Object o = GuiUtils.getChoiceOptional("Pick a soldier to put onto the battlefield", soldiers.toArray()); if (o != null) { Card card = (Card) o; AllZone.getGameAction().moveToPlay(card); card.tap(); AllZone.getCombat().addAttacker(card); card.setCreatureAttackedThisCombat(true); } } else if (c.getController().isComputer()) { Card card = CardFactoryUtil.AI_getBestCreature(soldiers); if (card != null) { AllZone.getGameAction().moveToPlay(card); card.tap(); AllZone.getCombat().addAttacker(card); card.setCreatureAttackedThisCombat(true); } } } //if (creatures.size() > 0) }//Preeminent Captain else if (c.getName().equals("Sapling of Colfenor") && !c.getCreatureAttackedThisCombat()) { Player player = c.getController(); PlayerZone lib = AllZone.getZone(Constant.Zone.Library, player); if (lib.size() > 0) { CardList cl = new CardList(); cl.add(lib.get(0)); GuiUtils.getChoiceOptional("Top card", cl.toArray()); Card top = lib.get(0); if (top.isCreature()) { player.gainLife(top.getBaseDefense(), c); player.loseLife(top.getBaseAttack(), c); AllZone.getGameAction().moveToHand(top); } } }//Sapling of Colfenor c.setCreatureAttackedThisCombat(true); }//checkDeclareAttackers /** * <p>checkUnblockedAttackers.</p> * * @param c a {@link forge.Card} object. */ public static void checkUnblockedAttackers(Card c) { //Run triggers HashMap<String, Object> runParams = new HashMap<String, Object>(); runParams.put("Card", c); AllZone.getTriggerHandler().runTrigger("AttackerUnblocked", runParams); } /** * <p>checkDeclareBlockers.</p> * * @param cl a {@link forge.CardList} object. */ public static void checkDeclareBlockers(CardList cl) { for (Card c : cl) { if (!c.getCreatureBlockedThisCombat()) { for (Ability ab : CardFactoryUtil.getBushidoEffects(c)) { AllZone.getStack().add(ab); } } c.setCreatureBlockedThisCombat(true); }//for }//checkDeclareBlockers /** * <p>checkBlockedAttackers.</p> * * @param a a {@link forge.Card} object. * @param b a {@link forge.Card} object. */ public static void checkBlockedAttackers(final Card a, Card b) { //System.out.println(a.getName() + " got blocked by " + b.getName()); //Run triggers HashMap<String, Object> runParams = new HashMap<String, Object>(); runParams.put("Attacker", a); runParams.put("Blocker", b); AllZone.getTriggerHandler().runTrigger("Blocks", runParams); if (!a.getCreatureGotBlockedThisCombat()) { final int blockers = AllZone.getCombat().getBlockers(a).size(); runParams.put("NumBlockers", blockers); AllZone.getTriggerHandler().runTrigger("AttackerBlocked", runParams); //Bushido for (Ability ab : CardFactoryUtil.getBushidoEffects(a)) AllZone.getStack().add(ab); //Rampage ArrayList<String> keywords = a.getKeyword(); Pattern p = Pattern.compile("Rampage [0-9]+"); Matcher m; for (String keyword : keywords) { m = p.matcher(keyword); if (m.find()) { String k[] = keyword.split(" "); final int magnitude = Integer.valueOf(k[1]); final int numBlockers = AllZone.getCombat().getBlockers(a).size(); if (numBlockers > 1) { executeRampageAbility(a, magnitude, numBlockers); } } //find }//end Rampage } if (a.hasKeyword("Flanking") && !b.hasKeyword("Flanking")) { int flankingMagnitude = 0; String kw = ""; ArrayList<String> list = a.getKeyword(); for (int i = 0; i < list.size(); i++) { kw = list.get(i); if (kw.equals("Flanking")) flankingMagnitude++; } final int mag = flankingMagnitude; final Card blocker = b; Ability ability2 = new Ability(b, "0") { @Override public void resolve() { final Command untilEOT = new Command() { private static final long serialVersionUID = 7662543891117427727L; public void execute() { if (AllZoneUtil.isCardInPlay(blocker)) { blocker.addTempAttackBoost(mag); blocker.addTempDefenseBoost(mag); } } };//Command if (AllZoneUtil.isCardInPlay(blocker)) { blocker.addTempAttackBoost(-mag); blocker.addTempDefenseBoost(-mag); AllZone.getEndOfTurn().addUntil(untilEOT); System.out.println("Flanking!"); } }//resolve };//ability StringBuilder sb2 = new StringBuilder(); sb2.append(b.getName()).append(" - gets -").append(mag).append("/-").append(mag).append(" until EOT."); ability2.setStackDescription(sb2.toString()); AllZone.getStack().add(ability2); Log.debug("Adding Flanking!"); }//flanking if (a.getName().equals("Robber Fly") && !a.getCreatureGotBlockedThisCombat()) { Player opp = b.getController(); CardList list = AllZoneUtil.getPlayerHand(opp); int handSize = list.size(); // opponent discards their hand, opp.discardRandom(handSize, a.getSpellAbility()[0]); opp.drawCards(handSize); } a.setCreatureGotBlockedThisCombat(true); } /** * <p>executeExaltedAbility.</p> * * @param c a {@link forge.Card} object. * @param magnitude a int. */ public static void executeExaltedAbility(Card c, int magnitude) { final Card crd = c; Ability ability; for (int i = 0; i < magnitude; i++) { ability = new Ability(c, "0") { @Override public void resolve() { final Command untilEOT = new Command() { private static final long serialVersionUID = 1497565871061029469L; public void execute() { if (AllZoneUtil.isCardInPlay(crd)) { crd.addTempAttackBoost(-1); crd.addTempDefenseBoost(-1); } } };//Command if (AllZoneUtil.isCardInPlay(crd)) { crd.addTempAttackBoost(1); crd.addTempDefenseBoost(1); AllZone.getEndOfTurn().addUntil(untilEOT); } }//resolve };//ability StringBuilder sb = new StringBuilder(); sb.append(c).append(" - (Exalted) gets +1/+1 until EOT."); ability.setStackDescription(sb.toString()); AllZone.getStack().addSimultaneousStackEntry(ability); } Player phasingPlayer = c.getController(); // Finest Hour untaps the creature on the first combat phase if ((AllZoneUtil.getPlayerCardsInPlay(phasingPlayer, "Finest Hour").size() > 0) && AllZone.getPhase().isFirstCombat()) { // Untap the attacking creature Ability fhUntap = new Ability(c, "0") { public void resolve() { crd.untap(); } }; StringBuilder sbUntap = new StringBuilder(); sbUntap.append(c).append(" - (Exalted) untap."); fhUntap.setStackDescription(sbUntap.toString()); AllZone.getStack().addSimultaneousStackEntry(fhUntap); // If any Finest Hours, queue up a new combat phase for (int ix = 0; ix < AllZoneUtil.getPlayerCardsInPlay(phasingPlayer, "Finest Hour").size(); ix++) { Ability fhAddCombat = new Ability(c, "0") { public void resolve() { AllZone.getPhase().addExtraCombat(); } }; StringBuilder sbACom = new StringBuilder(); sbACom.append(c).append(" - (Exalted) ").append(phasingPlayer).append(" gets Extra Combat Phase."); fhAddCombat.setStackDescription(sbACom.toString()); AllZone.getStack().addSimultaneousStackEntry(fhAddCombat); } } if (AllZoneUtil.getPlayerCardsInPlay(phasingPlayer, "Sovereigns of Lost Alara").size() > 0) { for (int i = 0; i < AllZoneUtil.getPlayerCardsInPlay(phasingPlayer, "Sovereigns of Lost Alara").size(); i++) { final Card attacker = c; Ability ability4 = new Ability(c, "0") { @Override public void resolve() { CardList enchantments = AllZoneUtil.getPlayerCardsInLibrary(attacker.getController()); enchantments = enchantments.filter(new CardListFilter() { public boolean addCard(Card c) { if (attacker.hasKeyword("Protection from enchantments") || (attacker.hasKeyword("Protection from everything"))) return false; return (c.isEnchantment() && c.hasKeyword("Enchant creature") && !CardFactoryUtil.hasProtectionFrom(c, attacker)); } }); Player player = attacker.getController(); Card Enchantment = null; if (player.isHuman()) { Card[] Target = new Card[enchantments.size()]; for (int j = 0; j < enchantments.size(); j++) { Card crd = enchantments.get(j); Target[j] = crd; } Object check = GuiUtils.getChoiceOptional("Select enchantment to enchant exalted creature", Target); if (check != null) { Enchantment = ((Card) check); } } else { Enchantment = CardFactoryUtil.AI_getBestEnchantment(enchantments, attacker, false); } if (Enchantment != null && AllZoneUtil.isCardInPlay(attacker)) { AllZone.getGameAction().moveToPlay(Enchantment); Enchantment.enchantCard(attacker); } attacker.getController().shuffle(); }//resolve };// ability4 StringBuilder sb4 = new StringBuilder(); sb4.append(c).append(" - (Exalted) searches library for an Aura card that could enchant that creature, "); sb4.append("put it onto the battlefield attached to that creature, then shuffles library."); ability4.setStackDescription(sb4.toString()); AllZone.getStack().addSimultaneousStackEntry(ability4); } // For } } /** * executes Rampage abilities for a given card * * @param c the card to add rampage bonus to * @param magnitude the magnitude of rampage (ie Rampage 2 means magnitude should be 2) * @param numBlockers - the number of creatures blocking this rampaging creature */ private static void executeRampageAbility(Card c, int magnitude, int numBlockers) { final Card crd = c; final int pump = magnitude; Ability ability; //numBlockers -1 since it is for every creature beyond the first for (int i = 0; i < numBlockers - 1; i++) { ability = new Ability(c, "0") { @Override public void resolve() { final Command untilEOT = new Command() { private static final long serialVersionUID = -3215615538474963181L; public void execute() { if (AllZoneUtil.isCardInPlay(crd)) { crd.addTempAttackBoost(-pump); crd.addTempDefenseBoost(-pump); } } };//Command if (AllZoneUtil.isCardInPlay(crd)) { crd.addTempAttackBoost(pump); crd.addTempDefenseBoost(pump); AllZone.getEndOfTurn().addUntil(untilEOT); } }//resolve };//ability StringBuilder sb = new StringBuilder(); sb.append(c).append(" - (Rampage) gets +").append(pump).append("/+").append(pump).append(" until EOT."); ability.setStackDescription(sb.toString()); AllZone.getStack().add(ability); } } }//end class CombatUtil