diff --git a/.gitattributes b/.gitattributes index 19e086cf8c2..84a3dcd9e22 100644 --- a/.gitattributes +++ b/.gitattributes @@ -13855,6 +13855,7 @@ src/main/java/forge/game/ai/AiInputBlock.java -text src/main/java/forge/game/ai/AiInputCommon.java svneol=native#text/plain src/main/java/forge/game/ai/ComputerUtil.java svneol=native#text/plain src/main/java/forge/game/ai/ComputerUtilBlock.java svneol=native#text/plain +src/main/java/forge/game/ai/ComputerUtilCombat.java -text src/main/java/forge/game/ai/ComputerUtilCost.java -text src/main/java/forge/game/ai/ComputerUtilMana.java -text src/main/java/forge/game/event/BlockerAssignedEvent.java -text diff --git a/src/main/java/forge/card/abilityfactory/ai/ChangeZoneAi.java b/src/main/java/forge/card/abilityfactory/ai/ChangeZoneAi.java index 09e388cefbb..f1a95e3d8c3 100644 --- a/src/main/java/forge/card/abilityfactory/ai/ChangeZoneAi.java +++ b/src/main/java/forge/card/abilityfactory/ai/ChangeZoneAi.java @@ -30,12 +30,12 @@ import forge.card.spellability.SpellAbility; import forge.card.spellability.SpellPermanent; import forge.card.spellability.Target; import forge.game.GlobalRuleChange; +import forge.game.ai.ComputerUtilCombat; import forge.game.ai.ComputerUtil; import forge.game.ai.ComputerUtilBlock; import forge.game.ai.ComputerUtilCost; import forge.game.ai.ComputerUtilMana; import forge.game.phase.Combat; -import forge.game.phase.CombatUtil; import forge.game.phase.PhaseType; import forge.game.player.Player; import forge.game.zone.ZoneType; @@ -495,7 +495,7 @@ public class ChangeZoneAi extends SpellAiLogic { } combat = ComputerUtilBlock.getBlockers(ai, combat, ai.getCreaturesInPlay()); - if (CombatUtil.lifeInDanger(ai, combat)) { + if (ComputerUtilCombat.lifeInDanger(ai, combat)) { // need something AI can cast now CardLists.sortByEvaluateCreature(list); for (Card c : list) { @@ -741,7 +741,7 @@ public class ChangeZoneAi extends SpellAiLogic { CardLists.sortByEvaluateCreature(combatants); for (final Card c : combatants) { - if (c.getShield() == 0 && CombatUtil.combatantWouldBeDestroyed(c) && !c.getOwner().isHuman() && !c.isToken()) { + if (c.getShield() == 0 && ComputerUtilCombat.combatantWouldBeDestroyed(c) && !c.getOwner().isHuman() && !c.isToken()) { tgt.addTarget(c); return true; } diff --git a/src/main/java/forge/card/abilityfactory/ai/ChooseSourceAi.java b/src/main/java/forge/card/abilityfactory/ai/ChooseSourceAi.java index 7371811f563..b9f13137113 100644 --- a/src/main/java/forge/card/abilityfactory/ai/ChooseSourceAi.java +++ b/src/main/java/forge/card/abilityfactory/ai/ChooseSourceAi.java @@ -14,8 +14,8 @@ import forge.card.abilityfactory.SpellAiLogic; import forge.card.cost.Cost; import forge.card.spellability.SpellAbility; import forge.card.spellability.Target; +import forge.game.ai.ComputerUtilCombat; import forge.game.ai.ComputerUtilCost; -import forge.game.phase.CombatUtil; import forge.game.phase.PhaseType; import forge.game.player.Player; import forge.game.zone.ZoneType; @@ -112,7 +112,7 @@ public class ChooseSourceAi extends SpellAiLogic { if (!c.isAttacking(ai) || !Singletons.getModel().getGame().getCombat().isUnblocked(c)) { return false; } - return CombatUtil.damageIfUnblocked(c, ai, Singletons.getModel().getGame().getCombat()) > 0; + return ComputerUtilCombat.damageIfUnblocked(c, ai, Singletons.getModel().getGame().getCombat()) > 0; } }); if (choices.isEmpty()) { diff --git a/src/main/java/forge/card/abilityfactory/ai/DamagePreventAi.java b/src/main/java/forge/card/abilityfactory/ai/DamagePreventAi.java index bded20b79ea..da11ace1d2d 100644 --- a/src/main/java/forge/card/abilityfactory/ai/DamagePreventAi.java +++ b/src/main/java/forge/card/abilityfactory/ai/DamagePreventAi.java @@ -13,8 +13,8 @@ import forge.card.cardfactory.CardFactoryUtil; import forge.card.cost.Cost; import forge.card.spellability.SpellAbility; import forge.card.spellability.Target; +import forge.game.ai.ComputerUtilCombat; import forge.game.ai.ComputerUtilCost; -import forge.game.phase.CombatUtil; import forge.game.phase.PhaseHandler; import forge.game.phase.PhaseType; import forge.game.player.Player; @@ -68,13 +68,13 @@ public class DamagePreventAi extends SpellAiLogic { for (final Object o : objects) { if (o instanceof Card) { final Card c = (Card) o; - flag |= CombatUtil.combatantWouldBeDestroyed(c); + flag |= ComputerUtilCombat.combatantWouldBeDestroyed(c); } else if (o instanceof Player) { // Don't need to worry about Combat Damage during AI's turn final Player p = (Player) o; if (!handler.isPlayerTurn(p)) { - flag |= (p.isComputer() && ((CombatUtil.wouldLoseLife(ai, Singletons.getModel().getGame().getCombat()) && sa - .isAbility()) || CombatUtil.lifeInDanger(ai, Singletons.getModel().getGame().getCombat()))); + flag |= (p.isComputer() && ((ComputerUtilCombat.wouldLoseLife(ai, Singletons.getModel().getGame().getCombat()) && sa + .isAbility()) || ComputerUtilCombat.lifeInDanger(ai, Singletons.getModel().getGame().getCombat()))); } } } @@ -117,8 +117,8 @@ public class DamagePreventAi extends SpellAiLogic { } // Protect combatants else if (Singletons.getModel().getGame().getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS_INSTANT_ABILITY)) { - if (sa.canTarget(ai) && CombatUtil.wouldLoseLife(ai, Singletons.getModel().getGame().getCombat()) - && (CombatUtil.lifeInDanger(ai, Singletons.getModel().getGame().getCombat()) || sa.isAbility()) + if (sa.canTarget(ai) && ComputerUtilCombat.wouldLoseLife(ai, Singletons.getModel().getGame().getCombat()) + && (ComputerUtilCombat.lifeInDanger(ai, Singletons.getModel().getGame().getCombat()) || sa.isAbility()) && Singletons.getModel().getGame().getPhaseHandler().isPlayerTurn(ai.getOpponent())) { tgt.addTarget(ai); chance = true; @@ -134,7 +134,7 @@ public class DamagePreventAi extends SpellAiLogic { CardLists.sortByEvaluateCreature(combatants); for (final Card c : combatants) { - if (CombatUtil.combatantWouldBeDestroyed(c)) { + if (ComputerUtilCombat.combatantWouldBeDestroyed(c)) { tgt.addTarget(c); chance = true; break; @@ -195,7 +195,7 @@ public class DamagePreventAi extends SpellAiLogic { CardLists.sortByEvaluateCreature(combatants); if (Singletons.getModel().getGame().getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS_INSTANT_ABILITY)) { for (final Card c : combatants) { - if (CombatUtil.combatantWouldBeDestroyed(c)) { + if (ComputerUtilCombat.combatantWouldBeDestroyed(c)) { tgt.addTarget(c); return true; } diff --git a/src/main/java/forge/card/abilityfactory/ai/EffectAi.java b/src/main/java/forge/card/abilityfactory/ai/EffectAi.java index 639d2bd8dba..e239a28335a 100644 --- a/src/main/java/forge/card/abilityfactory/ai/EffectAi.java +++ b/src/main/java/forge/card/abilityfactory/ai/EffectAi.java @@ -13,6 +13,7 @@ import forge.card.cardfactory.CardFactoryUtil; import forge.card.spellability.SpellAbility; import forge.card.spellability.Target; import forge.game.GameState; +import forge.game.ai.ComputerUtilCombat; import forge.game.phase.CombatUtil; import forge.game.phase.PhaseHandler; import forge.game.phase.PhaseType; @@ -50,7 +51,7 @@ public class EffectAi extends SpellAiLogic { if (game.getPhaseHandler().isPreventCombatDamageThisTurn()) { return false; } - if (!CombatUtil.lifeInDanger(ai, game.getCombat())) { + if (!ComputerUtilCombat.lifeInDanger(ai, game.getCombat())) { return false; } final Target tgt = sa.getTarget(); diff --git a/src/main/java/forge/card/abilityfactory/ai/FogAi.java b/src/main/java/forge/card/abilityfactory/ai/FogAi.java index 9a095aaeb74..105ded8d260 100644 --- a/src/main/java/forge/card/abilityfactory/ai/FogAi.java +++ b/src/main/java/forge/card/abilityfactory/ai/FogAi.java @@ -4,7 +4,7 @@ package forge.card.abilityfactory.ai; import forge.Singletons; import forge.card.abilityfactory.SpellAiLogic; import forge.card.spellability.SpellAbility; -import forge.game.phase.CombatUtil; +import forge.game.ai.ComputerUtilCombat; import forge.game.phase.PhaseType; import forge.game.player.Player; @@ -34,7 +34,7 @@ public class FogAi extends SpellAiLogic { } // Cast it if life is in danger - return CombatUtil.lifeInDanger(ai, Singletons.getModel().getGame().getCombat()); + return ComputerUtilCombat.lifeInDanger(ai, Singletons.getModel().getGame().getCombat()); } @Override diff --git a/src/main/java/forge/card/abilityfactory/ai/LifeGainAi.java b/src/main/java/forge/card/abilityfactory/ai/LifeGainAi.java index ec3483b0b42..280d31ee1b1 100644 --- a/src/main/java/forge/card/abilityfactory/ai/LifeGainAi.java +++ b/src/main/java/forge/card/abilityfactory/ai/LifeGainAi.java @@ -10,10 +10,10 @@ import forge.card.cost.Cost; import forge.card.spellability.AbilitySub; import forge.card.spellability.SpellAbility; import forge.card.spellability.Target; +import forge.game.ai.ComputerUtilCombat; import forge.game.ai.ComputerUtil; import forge.game.ai.ComputerUtilCost; import forge.game.ai.ComputerUtilMana; -import forge.game.phase.CombatUtil; import forge.game.phase.PhaseType; import forge.game.player.Player; import forge.util.MyRandom; @@ -67,7 +67,7 @@ public class LifeGainAi extends SpellAiLogic { return false; } boolean lifeCritical = life <= 5; - lifeCritical |= (Singletons.getModel().getGame().getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DAMAGE) && CombatUtil + lifeCritical |= (Singletons.getModel().getGame().getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DAMAGE) && ComputerUtilCombat .lifeInDanger(ai, Singletons.getModel().getGame().getCombat())); if (abCost != null && !lifeCritical) { diff --git a/src/main/java/forge/card/abilityfactory/ai/MustBlockAi.java b/src/main/java/forge/card/abilityfactory/ai/MustBlockAi.java index 77db4b370b0..640d3d2f6e2 100644 --- a/src/main/java/forge/card/abilityfactory/ai/MustBlockAi.java +++ b/src/main/java/forge/card/abilityfactory/ai/MustBlockAi.java @@ -13,6 +13,7 @@ import forge.card.abilityfactory.SpellAiLogic; import forge.card.cardfactory.CardFactoryUtil; import forge.card.spellability.SpellAbility; import forge.card.spellability.Target; +import forge.game.ai.ComputerUtilCombat; import forge.game.phase.CombatUtil; import forge.game.phase.PhaseType; import forge.game.player.Player; @@ -72,10 +73,10 @@ public class MustBlockAi extends SpellAiLogic { if (!CombatUtil.canBlock(definedAttacker, c)) { return false; } - if (CombatUtil.canDestroyAttacker(definedAttacker, c, null, false)) { + if (ComputerUtilCombat.canDestroyAttacker(definedAttacker, c, null, false)) { return false; } - if (!CombatUtil.canDestroyBlocker(c, definedAttacker, null, false)) { + if (!ComputerUtilCombat.canDestroyBlocker(c, definedAttacker, null, false)) { return false; } c.setTapped(tapped); diff --git a/src/main/java/forge/card/abilityfactory/ai/ProtectAi.java b/src/main/java/forge/card/abilityfactory/ai/ProtectAi.java index a424c79ebea..13c4e626737 100644 --- a/src/main/java/forge/card/abilityfactory/ai/ProtectAi.java +++ b/src/main/java/forge/card/abilityfactory/ai/ProtectAi.java @@ -15,8 +15,8 @@ import forge.card.cardfactory.CardFactoryUtil; import forge.card.cost.Cost; import forge.card.spellability.SpellAbility; import forge.card.spellability.Target; +import forge.game.ai.ComputerUtilCombat; import forge.game.ai.ComputerUtilCost; -import forge.game.phase.CombatUtil; import forge.game.phase.PhaseType; import forge.game.player.Player; import forge.game.zone.ZoneType; @@ -90,14 +90,14 @@ public class ProtectAi extends SpellAiLogic { // is the creature blocking and unable to destroy the attacker // or would be destroyed itself? if (c.isBlocking() - && (CombatUtil.blockerWouldBeDestroyed(c))) { + && (ComputerUtilCombat.blockerWouldBeDestroyed(c))) { return true; } // is the creature in blocked and the blocker would survive if (Singletons.getModel().getGame().getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS) && Singletons.getModel().getGame().getCombat().isAttacking(c) && Singletons.getModel().getGame().getCombat().isBlocked(c) - && CombatUtil.blockerWouldBeDestroyed(Singletons.getModel().getGame().getCombat().getBlockers(c).get(0))) { + && ComputerUtilCombat.blockerWouldBeDestroyed(Singletons.getModel().getGame().getCombat().getBlockers(c).get(0))) { return true; } diff --git a/src/main/java/forge/card/abilityfactory/ai/PumpAiBase.java b/src/main/java/forge/card/abilityfactory/ai/PumpAiBase.java index 58fdcede7ee..3d25ba6bd76 100644 --- a/src/main/java/forge/card/abilityfactory/ai/PumpAiBase.java +++ b/src/main/java/forge/card/abilityfactory/ai/PumpAiBase.java @@ -16,6 +16,7 @@ import forge.card.abilityfactory.AbilityFactory; import forge.card.abilityfactory.SpellAiLogic; import forge.card.cardfactory.CardFactoryUtil; import forge.card.spellability.SpellAbility; +import forge.game.ai.ComputerUtilCombat; import forge.game.phase.Combat; import forge.game.phase.CombatUtil; import forge.game.phase.PhaseHandler; @@ -180,7 +181,7 @@ public abstract class PumpAiBase extends SpellAiLogic { && !CardLists.getKeyword(Singletons.getModel().getGame().getCombat().getAttackerList(), "Flying").isEmpty() && !card.hasKeyword("Reach") && CombatUtil.canBlock(card) - && CombatUtil.lifeInDanger(ai, Singletons.getModel().getGame().getCombat())) { + && ComputerUtilCombat.lifeInDanger(ai, Singletons.getModel().getGame().getCombat())) { return true; } Predicate flyingOrReach = Predicates.or(CardPredicates.hasKeyword("Flying"), CardPredicates.hasKeyword("Reach")); @@ -196,7 +197,7 @@ public abstract class PumpAiBase extends SpellAiLogic { && ph.getPhase().equals(PhaseType.COMBAT_DECLARE_ATTACKERS_INSTANT_ABILITY) && !CardLists.getKeyword(Singletons.getModel().getGame().getCombat().getAttackerList(), "Horsemanship").isEmpty() && CombatUtil.canBlock(card) - && CombatUtil.lifeInDanger(ai, Singletons.getModel().getGame().getCombat())) { + && ComputerUtilCombat.lifeInDanger(ai, Singletons.getModel().getGame().getCombat())) { return true; } if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || card.isAttacking()) @@ -222,7 +223,7 @@ public abstract class PumpAiBase extends SpellAiLogic { List attackers = combat.getAttackers(); for (Card attacker : attackers) { if (CombatUtil.canBlock(attacker, card, combat) - && !CombatUtil.canDestroyAttacker(attacker, card, combat, false)) { + && !ComputerUtilCombat.canDestroyAttacker(attacker, card, combat, false)) { return true; } } @@ -231,7 +232,7 @@ public abstract class PumpAiBase extends SpellAiLogic { List blockers = opp.getCreaturesInPlay(); for (Card blocker : blockers) { if (CombatUtil.canBlock(card, blocker, combat) - && !CombatUtil.canDestroyBlocker(blocker, card, combat, false)) { + && !ComputerUtilCombat.canDestroyBlocker(blocker, card, combat, false)) { return true; } } @@ -407,13 +408,13 @@ public abstract class PumpAiBase extends SpellAiLogic { // is the creature blocking and unable to destroy the attacker // or would be destroyed itself? if (phase.is(PhaseType.COMBAT_DECLARE_BLOCKERS_INSTANT_ABILITY) && c.isBlocking()) { - if (defense > 0 && CombatUtil.blockerWouldBeDestroyed(c)) { + if (defense > 0 && ComputerUtilCombat.blockerWouldBeDestroyed(c)) { return true; } List blockedBy = Singletons.getModel().getGame().getCombat().getAttackersBlockedBy(c); // For now, Only care the first creature blocked by a card. // TODO Add in better BlockAdditional support - if (!blockedBy.isEmpty() && attack > 0 && !CombatUtil.attackerWouldBeDestroyed(blockedBy.get(0))) { + if (!blockedBy.isEmpty() && attack > 0 && !ComputerUtilCombat.attackerWouldBeDestroyed(blockedBy.get(0))) { return true; } } @@ -431,7 +432,7 @@ public abstract class PumpAiBase extends SpellAiLogic { && Singletons.getModel().getGame().getCombat().isBlocked(c) && Singletons.getModel().getGame().getCombat().getBlockers(c) != null && !Singletons.getModel().getGame().getCombat().getBlockers(c).isEmpty() - && !CombatUtil.blockerWouldBeDestroyed(Singletons.getModel().getGame().getCombat().getBlockers(c).get(0))) { + && !ComputerUtilCombat.blockerWouldBeDestroyed(Singletons.getModel().getGame().getCombat().getBlockers(c).get(0))) { return true; } @@ -447,7 +448,7 @@ public abstract class PumpAiBase extends SpellAiLogic { && c.isBlocking() && defense > 0 && attackerHasTrample - && (sa.isAbility() || CombatUtil.lifeInDanger(ai, Singletons.getModel().getGame().getCombat()))) { + && (sa.isAbility() || ComputerUtilCombat.lifeInDanger(ai, Singletons.getModel().getGame().getCombat()))) { return true; } diff --git a/src/main/java/forge/card/abilityfactory/ai/PumpAllAi.java b/src/main/java/forge/card/abilityfactory/ai/PumpAllAi.java index d04418def66..d6a73faeb1e 100644 --- a/src/main/java/forge/card/abilityfactory/ai/PumpAllAi.java +++ b/src/main/java/forge/card/abilityfactory/ai/PumpAllAi.java @@ -13,6 +13,7 @@ import forge.card.abilityfactory.AbilityFactory; import forge.card.cardfactory.CardFactoryUtil; import forge.card.spellability.SpellAbility; import forge.card.spellability.Target; +import forge.game.ai.ComputerUtilCombat; import forge.game.ai.ComputerUtil; import forge.game.phase.CombatUtil; import forge.game.phase.PhaseType; @@ -90,7 +91,7 @@ public class PumpAllAi extends PumpAiBase { totalPower += Math.min(c.getNetAttack(), power * -1); if (phase == PhaseType.COMBAT_DECLARE_BLOCKERS_INSTANT_ABILITY && Singletons.getModel().getGame().getCombat().getUnblockedAttackers().contains(c)) { - if (CombatUtil.lifeInDanger(sa.getActivatingPlayer(), Singletons.getModel().getGame().getCombat())) { + if (ComputerUtilCombat.lifeInDanger(sa.getActivatingPlayer(), Singletons.getModel().getGame().getCombat())) { return true; } totalPower += Math.min(c.getNetAttack(), power * -1); diff --git a/src/main/java/forge/card/abilityfactory/ai/RegenerateAi.java b/src/main/java/forge/card/abilityfactory/ai/RegenerateAi.java index 10265e4f72c..93c8160b78b 100644 --- a/src/main/java/forge/card/abilityfactory/ai/RegenerateAi.java +++ b/src/main/java/forge/card/abilityfactory/ai/RegenerateAi.java @@ -31,8 +31,8 @@ import forge.card.cardfactory.CardFactoryUtil; import forge.card.cost.Cost; import forge.card.spellability.SpellAbility; import forge.card.spellability.Target; +import forge.game.ai.ComputerUtilCombat; import forge.game.ai.ComputerUtilCost; -import forge.game.phase.CombatUtil; import forge.game.phase.PhaseType; import forge.game.player.Player; import forge.game.zone.ZoneType; @@ -95,7 +95,7 @@ public class RegenerateAi extends SpellAiLogic { for (final Card c : list) { if (c.getShield() == 0) { - flag |= CombatUtil.combatantWouldBeDestroyed(c); + flag |= ComputerUtilCombat.combatantWouldBeDestroyed(c); } } @@ -139,7 +139,7 @@ public class RegenerateAi extends SpellAiLogic { CardLists.sortByEvaluateCreature(combatants); for (final Card c : combatants) { - if ((c.getShield() == 0) && CombatUtil.combatantWouldBeDestroyed(c)) { + if ((c.getShield() == 0) && ComputerUtilCombat.combatantWouldBeDestroyed(c)) { tgt.addTarget(c); chance = true; break; @@ -190,7 +190,7 @@ public class RegenerateAi extends SpellAiLogic { CardLists.sortByEvaluateCreature(combatants); if (Singletons.getModel().getGame().getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS_INSTANT_ABILITY)) { for (final Card c : combatants) { - if ((c.getShield() == 0) && CombatUtil.combatantWouldBeDestroyed(c)) { + if ((c.getShield() == 0) && ComputerUtilCombat.combatantWouldBeDestroyed(c)) { tgt.addTarget(c); return true; } diff --git a/src/main/java/forge/card/abilityfactory/ai/RegenerateAllAi.java b/src/main/java/forge/card/abilityfactory/ai/RegenerateAllAi.java index 429df94684e..4a9a9e906eb 100644 --- a/src/main/java/forge/card/abilityfactory/ai/RegenerateAllAi.java +++ b/src/main/java/forge/card/abilityfactory/ai/RegenerateAllAi.java @@ -11,8 +11,8 @@ import forge.card.abilityfactory.AbilityFactory; import forge.card.abilityfactory.SpellAiLogic; import forge.card.cost.Cost; import forge.card.spellability.SpellAbility; +import forge.game.ai.ComputerUtilCombat; import forge.game.ai.ComputerUtilCost; -import forge.game.phase.CombatUtil; import forge.game.phase.PhaseType; import forge.game.player.Player; import forge.game.zone.ZoneType; @@ -68,7 +68,7 @@ public class RegenerateAllAi extends SpellAiLogic { final List combatants = CardLists.filter(list, CardPredicates.Presets.CREATURES); for (final Card c : combatants) { - if (c.getShield() == 0 && CombatUtil.combatantWouldBeDestroyed(c)) { + if (c.getShield() == 0 && ComputerUtilCombat.combatantWouldBeDestroyed(c)) { numSaved++; } } diff --git a/src/main/java/forge/card/abilityfactory/ai/StoreSVarAi.java b/src/main/java/forge/card/abilityfactory/ai/StoreSVarAi.java index 058c01788d3..2f435263d8c 100644 --- a/src/main/java/forge/card/abilityfactory/ai/StoreSVarAi.java +++ b/src/main/java/forge/card/abilityfactory/ai/StoreSVarAi.java @@ -4,8 +4,8 @@ import forge.Card; import forge.Singletons; import forge.card.abilityfactory.SpellAiLogic; import forge.card.spellability.SpellAbility; +import forge.game.ai.ComputerUtilCombat; import forge.game.ai.ComputerUtil; -import forge.game.phase.CombatUtil; import forge.game.player.Player; public class StoreSVarAi extends SpellAiLogic { @@ -16,7 +16,7 @@ public class StoreSVarAi extends SpellAiLogic { final Card source = sa.getSourceCard(); if (ComputerUtil.waitForBlocking(sa) || ai.getLife() + 1 >= source.getNetDefense() - || (ai.getLife() > 5 && !CombatUtil.lifeInSeriousDanger(ai, Singletons.getModel().getGame().getCombat()))) { + || (ai.getLife() > 5 && !ComputerUtilCombat.lifeInSeriousDanger(ai, Singletons.getModel().getGame().getCombat()))) { return false; } diff --git a/src/main/java/forge/card/abilityfactory/effects/ChooseSourceEffect.java b/src/main/java/forge/card/abilityfactory/effects/ChooseSourceEffect.java index f9c0d0b1c89..d1eb98e0b6f 100644 --- a/src/main/java/forge/card/abilityfactory/effects/ChooseSourceEffect.java +++ b/src/main/java/forge/card/abilityfactory/effects/ChooseSourceEffect.java @@ -14,7 +14,7 @@ import forge.card.cardfactory.CardFactoryUtil; import forge.card.spellability.SpellAbility; import forge.card.spellability.SpellAbilityStackInstance; import forge.card.spellability.Target; -import forge.game.phase.CombatUtil; +import forge.game.ai.ComputerUtilCombat; import forge.game.player.Player; import forge.game.zone.ZoneType; import forge.gui.GuiChoose; @@ -178,7 +178,7 @@ public class ChooseSourceEffect extends SpellEffect { if (!c.isAttacking(ai) || !Singletons.getModel().getGame().getCombat().isUnblocked(c)) { return false; } - return CombatUtil.damageIfUnblocked(c, ai, Singletons.getModel().getGame().getCombat()) > 0; + return ComputerUtilCombat.damageIfUnblocked(c, ai, Singletons.getModel().getGame().getCombat()) > 0; } }); chosen.add(CardFactoryUtil.getBestCreatureAI(sourcesToChooseFrom)); diff --git a/src/main/java/forge/game/ai/AiAttackController.java b/src/main/java/forge/game/ai/AiAttackController.java index 60de39482c2..91c8ea9a80d 100644 --- a/src/main/java/forge/game/ai/AiAttackController.java +++ b/src/main/java/forge/game/ai/AiAttackController.java @@ -140,15 +140,15 @@ public class AiAttackController { public final boolean isEffectiveAttacker(final Player ai, final Card attacker, final Combat combat) { // if the attacker will die when attacking don't attack - if ((attacker.getNetDefense() + CombatUtil.predictToughnessBonusOfAttacker(attacker, null, combat, true)) <= 0) { + if ((attacker.getNetDefense() + ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, null, combat, true)) <= 0) { return false; } final Player opp = ai.getOpponent(); - if (CombatUtil.damageIfUnblocked(attacker, opp, combat) > 0) { + if (ComputerUtilCombat.damageIfUnblocked(attacker, opp, combat) > 0) { return true; } - if (CombatUtil.poisonIfUnblocked(attacker, opp) > 0) { + if (ComputerUtilCombat.poisonIfUnblocked(attacker, opp) > 0) { return true; } if (this.attackers.size() == 1 && attacker.hasKeyword("Exalted")) { @@ -158,7 +158,7 @@ public class AiAttackController { final List controlledByCompy = ai.getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES); for (final Card c : controlledByCompy) { for (final Trigger trigger : c.getTriggers()) { - if (CombatUtil.combatTriggerWillTrigger(attacker, null, trigger, combat)) { + if (ComputerUtilCombat.combatTriggerWillTrigger(attacker, null, trigger, combat)) { return true; } } @@ -350,8 +350,8 @@ public class AiAttackController { blockersLeft--; continue; } - totalAttack += CombatUtil.damageIfUnblocked(attacker, ai, null); - totalPoison += CombatUtil.poisonIfUnblocked(attacker, ai); + totalAttack += ComputerUtilCombat.damageIfUnblocked(attacker, ai, null); + totalPoison += ComputerUtilCombat.poisonIfUnblocked(attacker, ai); } if (totalAttack > 0 && ai.getLife() <= totalAttack @@ -425,12 +425,12 @@ public class AiAttackController { } unblockedAttackers.addAll(remainingAttackers); - if ((CombatUtil.sumDamageIfUnblocked(remainingAttackers, opp) >= opp.getLife()) + if ((ComputerUtilCombat.sumDamageIfUnblocked(remainingAttackers, opp) >= opp.getLife()) && !((opp.cantLoseForZeroOrLessLife() || ai.cantWin()) && (opp.getLife() < 1))) { return true; } - if (CombatUtil.sumPoisonIfUnblocked(remainingAttackers, opp) >= (10 - opp.getPoisonCounters())) { + if (ComputerUtilCombat.sumPoisonIfUnblocked(remainingAttackers, opp) >= (10 - opp.getPoisonCounters())) { return true; } @@ -621,7 +621,7 @@ public class AiAttackController { if (CombatUtil.canAttackNextTurn(pCard)) { candidateAttackers.add(pCard); if (pCard.getNetCombatDamage() > 0) { - candidateUnblockedDamage += CombatUtil.damageIfUnblocked(pCard, opp, null); + candidateUnblockedDamage += ComputerUtilCombat.damageIfUnblocked(pCard, opp, null); computerForces += 1; } @@ -717,7 +717,7 @@ public class AiAttackController { } } if (isUnblockableCreature) { - unblockableDamage += CombatUtil.damageIfUnblocked(attacker, opp, combat); + unblockableDamage += ComputerUtilCombat.damageIfUnblocked(attacker, opp, combat); } } for (final Card attacker : nextTurnAttackers) { @@ -731,7 +731,7 @@ public class AiAttackController { } } if (isUnblockableCreature) { - nextUnblockableDamage += CombatUtil.damageIfUnblocked(attacker, opp, null); + nextUnblockableDamage += ComputerUtilCombat.damageIfUnblocked(attacker, opp, null); } } if (unblockableDamage > 0 && !opp.cantLoseForZeroOrLessLife() @@ -785,7 +785,7 @@ public class AiAttackController { for (int i = 0; i < attackersLeft.size(); i++) { final Card attacker = attackersLeft.get(i); if (this.aiAggression < 5 && !attacker.hasFirstStrike() && !attacker.hasDoubleStrike() - && CombatUtil.getTotalFirstStrikeBlockPower(attacker, ai.getOpponent()) + && ComputerUtilCombat.getTotalFirstStrikeBlockPower(attacker, ai.getOpponent()) >= attacker.getKillDamage()) { continue; } @@ -803,7 +803,7 @@ public class AiAttackController { CardLists.sortAttackLowFirst(attacking); for (Card atta : attacking) { if (attackNum >= blockNum || !CombatUtil.canBeBlocked(attacker, this.blockers)) { - damage += CombatUtil.damageIfUnblocked(atta, opp, null); + damage += ComputerUtilCombat.damageIfUnblocked(atta, opp, null); } else if (CombatUtil.canBeBlocked(attacker, this.blockers)) { attackNum++; } @@ -901,7 +901,7 @@ public class AiAttackController { if ((isWorthLessThanAllKillers || canKillAllDangerous || numberOfPossibleBlockers < 2) && CombatUtil.canBlock(attacker, defender)) { numberOfPossibleBlockers += 1; - if (isWorthLessThanAllKillers && CombatUtil.canDestroyAttacker(attacker, defender, combat, false) + if (isWorthLessThanAllKillers && ComputerUtilCombat.canDestroyAttacker(attacker, defender, combat, false) && !(attacker.hasKeyword("Undying") && attacker.getCounters(CounterType.P1P1) == 0)) { canBeKilledByOne = true; // there is a single creature on // the battlefield that can kill @@ -915,7 +915,7 @@ public class AiAttackController { } // see if this attacking creature can destroy this defender, if // not record that it can't kill everything - if (canKillAllDangerous && !CombatUtil.canDestroyBlocker(defender, attacker, combat, false)) { + if (canKillAllDangerous && !ComputerUtilCombat.canDestroyBlocker(defender, attacker, combat, false)) { canKillAll = false; if (!canKillAllDangerous) { continue; diff --git a/src/main/java/forge/game/ai/ComputerUtil.java b/src/main/java/forge/game/ai/ComputerUtil.java index da38420ef2c..9457967fb04 100644 --- a/src/main/java/forge/game/ai/ComputerUtil.java +++ b/src/main/java/forge/game/ai/ComputerUtil.java @@ -1006,7 +1006,7 @@ public class ComputerUtil { } } combat = ComputerUtilBlock.getBlockers(ai, combat, ai.getCreaturesInPlay()); - if (!CombatUtil.lifeInDanger(ai, combat)) { + if (!ComputerUtilCombat.lifeInDanger(ai, combat)) { // Otherwise, return false. Do not play now. ret = false; } diff --git a/src/main/java/forge/game/ai/ComputerUtilBlock.java b/src/main/java/forge/game/ai/ComputerUtilBlock.java index 5c1798d78ab..a5e5183fa19 100644 --- a/src/main/java/forge/game/ai/ComputerUtilBlock.java +++ b/src/main/java/forge/game/ai/ComputerUtilBlock.java @@ -233,7 +233,7 @@ public class ComputerUtilBlock { final List blockers = new ArrayList(); for (final Card b : blockersLeft) { - if (!CombatUtil.canDestroyBlocker(b, attacker, combat, false)) { + if (!ComputerUtilCombat.canDestroyBlocker(b, attacker, combat, false)) { blockers.add(b); } } @@ -259,7 +259,7 @@ public class ComputerUtilBlock { final List blockers = new ArrayList(); for (final Card b : blockersLeft) { - if (CombatUtil.canDestroyAttacker(attacker, b, combat, false)) { + if (ComputerUtilCombat.canDestroyAttacker(attacker, b, combat, false)) { blockers.add(b); } } @@ -293,7 +293,7 @@ public class ComputerUtilBlock { return firstAttacker; } - final boolean bLifeInDanger = CombatUtil.lifeInDanger(ai, combat); + final boolean bLifeInDanger = ComputerUtilCombat.lifeInDanger(ai, combat); // TODO Add creatures attacking Planeswalkers in order of which we want // to protect @@ -427,14 +427,14 @@ public class ComputerUtilBlock { CardLists.sortAttack(firstStrikeBlockers); for (final Card blocker : firstStrikeBlockers) { final int damageNeeded = attacker.getKillDamage() - + CombatUtil.predictToughnessBonusOfAttacker(attacker, blocker, combat, false); + + ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, blocker, combat, false); // if the total damage of the blockgang was not enough // without but is enough with this blocker finish the // blockgang - if (CombatUtil.totalDamageOfBlockers(attacker, blockGang) < damageNeeded + if (ComputerUtilCombat.totalDamageOfBlockers(attacker, blockGang) < damageNeeded || CombatUtil.needsBlockers(attacker) > blockGang.size()) { blockGang.add(blocker); - if (CombatUtil.totalDamageOfBlockers(attacker, blockGang) >= damageNeeded) { + if (ComputerUtilCombat.totalDamageOfBlockers(attacker, blockGang) >= damageNeeded) { currentAttackers.remove(attacker); for (final Card b : blockGang) { if (CombatUtil.canBlock(attacker, blocker, combat)) { @@ -488,13 +488,13 @@ public class ComputerUtilBlock { for (final Card blocker : usableBlockers) { // Add an additional blocker if the current blockers are not // enough and the new one would deal the remaining damage - final int currentDamage = CombatUtil.totalDamageOfBlockers(attacker, blockGang); - final int additionalDamage = CombatUtil.dealsDamageAsBlocker(attacker, blocker); + final int currentDamage = ComputerUtilCombat.totalDamageOfBlockers(attacker, blockGang); + final int additionalDamage = ComputerUtilCombat.dealsDamageAsBlocker(attacker, blocker); final int absorbedDamage2 = blocker .getEnoughDamageToKill(attacker.getNetCombatDamage(), attacker, true); final int addedValue = CardFactoryUtil.evaluateCreature(blocker); final int damageNeeded = attacker.getKillDamage() - + CombatUtil.predictToughnessBonusOfAttacker(attacker, blocker, combat, false); + + ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, blocker, combat, false); if ((damageNeeded > currentDamage || CombatUtil.needsBlockers(attacker) > blockGang.size()) && !(damageNeeded > currentDamage + additionalDamage) // The attacker will be killed @@ -502,7 +502,7 @@ public class ComputerUtilBlock { // only one blocker can be killed || currentValue + addedValue - 50 <= CardFactoryUtil.evaluateCreature(attacker) // or attacker is worth more - || (lifeInDanger && CombatUtil.lifeInDanger(ai, combat))) + || (lifeInDanger && ComputerUtilCombat.lifeInDanger(ai, combat))) // or life is in danger && CombatUtil.canBlock(attacker, blocker, combat)) { // this is needed for attackers that can't be blocked by @@ -545,7 +545,7 @@ public class ComputerUtilBlock { killingBlockers = ComputerUtilBlock.getKillingBlockers(attacker, ComputerUtilBlock.getPossibleBlockers(attacker, ComputerUtilBlock.getBlockersLeft(), combat, true), combat); - if ((killingBlockers.size() > 0) && CombatUtil.lifeInDanger(ai, combat)) { + if ((killingBlockers.size() > 0) && ComputerUtilCombat.lifeInDanger(ai, combat)) { final Card blocker = CardFactoryUtil.getWorstCreatureAI(killingBlockers); combat.addBlocker(attacker, blocker); currentAttackers.remove(attacker); @@ -579,7 +579,7 @@ public class ComputerUtilBlock { chumpBlockers = ComputerUtilBlock .getPossibleBlockers(attacker, ComputerUtilBlock.getBlockersLeft(), combat, true); - if ((chumpBlockers.size() > 0) && CombatUtil.lifeInDanger(ai, combat)) { + if ((chumpBlockers.size() > 0) && ComputerUtilCombat.lifeInDanger(ai, combat)) { final Card blocker = CardFactoryUtil.getWorstCreatureAI(chumpBlockers); combat.addBlocker(attacker, blocker); currentAttackers.remove(attacker); @@ -626,9 +626,9 @@ public class ComputerUtilBlock { for (final Card blocker : chumpBlockers) { // Add an additional blocker if the current blockers are not // enough and the new one would suck some of the damage - if (CombatUtil.getAttack(attacker) > CombatUtil.totalShieldDamage(attacker, combat.getBlockers(attacker)) - && CombatUtil.shieldDamage(attacker, blocker) > 0 - && CombatUtil.canBlock(attacker, blocker, combat) && CombatUtil.lifeInDanger(ai, combat)) { + if (ComputerUtilCombat.getAttack(attacker) > ComputerUtilCombat.totalShieldDamage(attacker, combat.getBlockers(attacker)) + && ComputerUtilCombat.shieldDamage(attacker, blocker) > 0 + && CombatUtil.canBlock(attacker, blocker, combat) && ComputerUtilCombat.lifeInDanger(ai, combat)) { combat.addBlocker(attacker, blocker); } } @@ -667,11 +667,11 @@ public class ComputerUtilBlock { safeBlockers = ComputerUtilBlock.getSafeBlockers(attacker, blockers, combat); for (final Card blocker : safeBlockers) { final int damageNeeded = attacker.getKillDamage() - + CombatUtil.predictToughnessBonusOfAttacker(attacker, blocker, combat, false); + + ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, blocker, combat, false); // Add an additional blocker if the current blockers are not // enough and the new one would deal additional damage - if ((damageNeeded > CombatUtil.totalDamageOfBlockers(attacker, combat.getBlockers(attacker))) - && (CombatUtil.dealsDamageAsBlocker(attacker, blocker) > 0) + if ((damageNeeded > ComputerUtilCombat.totalDamageOfBlockers(attacker, combat.getBlockers(attacker))) + && ComputerUtilCombat.dealsDamageAsBlocker(attacker, blocker) > 0 && CombatUtil.canBlock(attacker, blocker, combat)) { combat.addBlocker(attacker, blocker); } @@ -691,11 +691,11 @@ public class ComputerUtilBlock { for (final Card blocker : safeBlockers) { final int damageNeeded = attacker.getKillDamage() - + CombatUtil.predictToughnessBonusOfAttacker(attacker, blocker, combat, false); + + ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, blocker, combat, false); // Add an additional blocker if the current blockers are not // enough and the new one would deal the remaining damage - final int currentDamage = CombatUtil.totalDamageOfBlockers(attacker, combat.getBlockers(attacker)); - final int additionalDamage = CombatUtil.dealsDamageAsBlocker(attacker, blocker); + final int currentDamage = ComputerUtilCombat.totalDamageOfBlockers(attacker, combat.getBlockers(attacker)); + final int additionalDamage = ComputerUtilCombat.dealsDamageAsBlocker(attacker, blocker); if ((damageNeeded > currentDamage) && !(damageNeeded > (currentDamage + additionalDamage)) && ((CardFactoryUtil.evaluateCreature(blocker) + ComputerUtilBlock.getDiff()) < CardFactoryUtil @@ -810,13 +810,13 @@ public class ComputerUtilBlock { // == 1. choose best blocks first == combat = ComputerUtilBlock.makeGoodBlocks(combat); combat = ComputerUtilBlock.makeGangBlocks(ai, combat); - if (CombatUtil.lifeInDanger(ai, combat)) { + if (ComputerUtilCombat.lifeInDanger(ai, combat)) { combat = ComputerUtilBlock.makeTradeBlocks(ai, combat); // choose // necessary // trade blocks } // if life is in danger - if (CombatUtil.lifeInDanger(ai, combat)) { + if (ComputerUtilCombat.lifeInDanger(ai, combat)) { combat = ComputerUtilBlock.makeChumpBlocks(ai, combat); // choose // necessary // chump blocks @@ -824,18 +824,18 @@ public class ComputerUtilBlock { // if life is still in danger // Reinforce blockers blocking attackers with trample if life is still // in danger - if (CombatUtil.lifeInDanger(ai, combat)) { + if (ComputerUtilCombat.lifeInDanger(ai, combat)) { combat = ComputerUtilBlock.reinforceBlockersAgainstTrample(ai, combat); } // Support blockers not destroying the attacker with more blockers to // try to kill the attacker - if (!CombatUtil.lifeInDanger(ai, combat)) { + if (!ComputerUtilCombat.lifeInDanger(ai, combat)) { combat = ComputerUtilBlock.reinforceBlockersToKill(combat); } // == 2. If the AI life would still be in danger make a safer approach // == - if (CombatUtil.lifeInDanger(ai, combat)) { + if (ComputerUtilCombat.lifeInDanger(ai, combat)) { lifeInDanger = true; combat = ComputerUtilBlock.resetBlockers(combat, possibleBlockers); // reset // every @@ -846,7 +846,7 @@ public class ComputerUtilBlock { // trade blocks // if life is in danger combat = ComputerUtilBlock.makeGoodBlocks(combat); - if (CombatUtil.lifeInDanger(ai, combat)) { + if (ComputerUtilCombat.lifeInDanger(ai, combat)) { combat = ComputerUtilBlock.makeChumpBlocks(ai, combat); // choose // necessary // chump @@ -855,7 +855,7 @@ public class ComputerUtilBlock { // danger // Reinforce blockers blocking attackers with trample if life is // still in danger - if (CombatUtil.lifeInDanger(ai, combat)) { + if (ComputerUtilCombat.lifeInDanger(ai, combat)) { combat = ComputerUtilBlock.reinforceBlockersAgainstTrample(ai, combat); } combat = ComputerUtilBlock.makeGangBlocks(ai, combat); @@ -864,20 +864,20 @@ public class ComputerUtilBlock { // == 3. If the AI life would be in serious danger make an even safer // approach == - if (CombatUtil.lifeInSeriousDanger(ai, combat)) { + if (ComputerUtilCombat.lifeInSeriousDanger(ai, combat)) { combat = ComputerUtilBlock.resetBlockers(combat, possibleBlockers); // reset // every // block // assignment combat = ComputerUtilBlock.makeChumpBlocks(ai, combat); // choose chump // blocks - if (CombatUtil.lifeInDanger(ai, combat)) { + if (ComputerUtilCombat.lifeInDanger(ai, combat)) { combat = ComputerUtilBlock.makeTradeBlocks(ai, combat); // choose // necessary // trade } - if (!CombatUtil.lifeInDanger(ai, combat)) { + if (!ComputerUtilCombat.lifeInDanger(ai, combat)) { combat = ComputerUtilBlock.makeGoodBlocks(combat); } // Reinforce blockers blocking attackers with trample if life is diff --git a/src/main/java/forge/game/ai/ComputerUtilCombat.java b/src/main/java/forge/game/ai/ComputerUtilCombat.java new file mode 100644 index 00000000000..d8182736aea --- /dev/null +++ b/src/main/java/forge/game/ai/ComputerUtilCombat.java @@ -0,0 +1,1624 @@ +/* + * Forge: Play Magic: the Gathering. + * Copyright (C) 2011 Forge Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package forge.game.ai; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.google.common.base.Predicate; + +import forge.Card; +import forge.CardLists; +import forge.CounterType; +import forge.Singletons; +import forge.card.TriggerReplacementBase; +import forge.card.abilityfactory.AbilityFactory; +import forge.card.abilityfactory.ApiType; +import forge.card.cardfactory.CardFactoryUtil; +import forge.card.spellability.AbilityActivated; +import forge.card.spellability.SpellAbility; +import forge.card.staticability.StaticAbility; +import forge.card.trigger.Trigger; +import forge.card.trigger.TriggerHandler; +import forge.card.trigger.TriggerType; +import forge.game.GlobalRuleChange; +import forge.game.phase.Combat; +import forge.game.phase.CombatUtil; +import forge.game.player.Player; +import forge.game.zone.ZoneType; + + +/** + *

+ * ComputerCombatUtil class. + *

+ * + * @author Forge + * @version $Id: ComputerUtil.java 19179 2013-01-25 18:48:29Z Max mtg $ + */ +public class ComputerUtilCombat { + + /** + *

+ * getTotalFirstStrikeBlockPower. + *

+ * + * @param attacker + * a {@link forge.Card} object. + * @param player + * a {@link forge.game.player.Player} object. + * @return a int. + */ + public static int getTotalFirstStrikeBlockPower(final Card attacker, final Player player) { + final Card att = attacker; + + List list = player.getCreaturesInPlay(); + list = CardLists.filter(list, new Predicate() { + @Override + public boolean apply(final Card c) { + return CombatUtil.canBlock(att, c) && (c.hasFirstStrike() || c.hasDoubleStrike()); + } + }); + + return ComputerUtilCombat.totalDamageOfBlockers(attacker, list); + } + + + // This function takes Doran and Double Strike into account + /** + *

+ * getAttack. + *

+ * + * @param c + * a {@link forge.Card} object. + * @return a int. + */ + public static int getAttack(final Card c) { + int n = c.getNetCombatDamage(); + + if (c.hasDoubleStrike()) { + n *= 2; + } + + return n; + } + + + // Returns the damage an unblocked attacker would deal + /** + *

+ * damageIfUnblocked. + *

+ * + * @param attacker + * a {@link forge.Card} object. + * @param attacked + * a {@link forge.game.player.Player} object. + * @param combat + * a {@link forge.game.phase.Combat} object. + * @return a int. + */ + public static int damageIfUnblocked(final Card attacker, final Player attacked, final Combat combat) { + int damage = attacker.getNetCombatDamage(); + int sum = 0; + if (!attacked.canLoseLife()) { + return 0; + } + damage += ComputerUtilCombat.predictPowerBonusOfAttacker(attacker, null, combat, false); + 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 + /** + *

+ * poisonIfUnblocked. + *

+ * + * @param attacker + * a {@link forge.Card} object. + * @param attacked + * a {@link forge.game.player.Player} object. + * @param combat + * a {@link forge.game.phase.Combat} object. + * @return a int. + */ + public static int poisonIfUnblocked(final Card attacker, final Player attacked) { + int damage = attacker.getNetCombatDamage(); + int poison = 0; + damage += ComputerUtilCombat.predictPowerBonusOfAttacker(attacker, null, null, false); + 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 + /** + *

+ * sumDamageIfUnblocked. + *

+ * + * @param attackers + * a {@link forge.CardList} object. + * @param attacked + * a {@link forge.game.player.Player} object. + * @return a int. + */ + public static int sumDamageIfUnblocked(final List attackers, final Player attacked) { + int sum = 0; + for (final Card attacker : attackers) { + sum += ComputerUtilCombat.damageIfUnblocked(attacker, attacked, null); + } + return sum; + } + + // Returns the number of poison counters unblocked attackers would deal + /** + *

+ * sumPoisonIfUnblocked. + *

+ * + * @param attackers + * a {@link forge.CardList} object. + * @param attacked + * a {@link forge.game.player.Player} object. + * @return a int. + */ + public static int sumPoisonIfUnblocked(final List attackers, final Player attacked) { + int sum = 0; + for (final Card attacker : attackers) { + sum += ComputerUtilCombat.poisonIfUnblocked(attacker, attacked); + } + return sum; + } + + // calculates the amount of life that will remain after the attack + /** + *

+ * lifeThatWouldRemain. + *

+ * + * @param combat + * a {@link forge.game.phase.Combat} object. + * @return a int. + */ + public static int lifeThatWouldRemain(final Player ai, final Combat combat) { + + int damage = 0; + + final List attackers = combat.getAttackersByDefenderSlot(0); + final List unblocked = new ArrayList(); + + for (final Card attacker : attackers) { + + final List blockers = combat.getBlockers(attacker); + + if ((blockers.size() == 0) + || attacker.hasKeyword("You may have CARDNAME assign its combat damage " + + "as though it weren't blocked.")) { + unblocked.add(attacker); + } else if (attacker.hasKeyword("Trample") + && (ComputerUtilCombat.getAttack(attacker) > ComputerUtilCombat.totalShieldDamage(attacker, blockers))) { + if (!attacker.hasKeyword("Infect")) { + damage += ComputerUtilCombat.getAttack(attacker) - ComputerUtilCombat.totalShieldDamage(attacker, blockers); + } + } + } + + damage += ComputerUtilCombat.sumDamageIfUnblocked(unblocked, ai); + + if (!ai.canLoseLife()) { + damage = 0; + } + + return ai.getLife() - damage; + } + + // calculates the amount of poison counters after the attack + /** + *

+ * resultingPoison. + *

+ * + * @param combat + * a {@link forge.game.phase.Combat} object. + * @return a int. + */ + public static int resultingPoison(final Player ai, final Combat combat) { + + int poison = 0; + + final List attackers = combat.getAttackersByDefenderSlot(0); + final List unblocked = new ArrayList(); + + for (final Card attacker : attackers) { + + final List blockers = combat.getBlockers(attacker); + + if ((blockers.size() == 0) + || attacker.hasKeyword("You may have CARDNAME assign its combat damage" + + " as though it weren't blocked.")) { + unblocked.add(attacker); + } else if (attacker.hasKeyword("Trample") + && (ComputerUtilCombat.getAttack(attacker) > ComputerUtilCombat.totalShieldDamage(attacker, blockers))) { + if (attacker.hasKeyword("Infect")) { + poison += ComputerUtilCombat.getAttack(attacker) - ComputerUtilCombat.totalShieldDamage(attacker, blockers); + } + if (attacker.hasKeyword("Poisonous")) { + poison += attacker.getKeywordMagnitude("Poisonous"); + } + } + } + + poison += ComputerUtilCombat.sumPoisonIfUnblocked(unblocked, ai); + + return ai.getPoisonCounters() + poison; + } + + // Checks if the life of the attacked Player/Planeswalker is in danger + /** + *

+ * lifeInDanger. + *

+ * + * @param combat + * a {@link forge.game.phase.Combat} object. + * @return a boolean. + */ + public static boolean lifeInDanger(final Player ai, final Combat combat) { + // life in danger only cares about the player's life. Not Planeswalkers' life + if (ai.cantLose()) { + return false; + } + + // check for creatures that must be blocked + final List attackers = combat.getAttackersByDefenderSlot(0); + + for (final Card attacker : attackers) { + + final List blockers = combat.getBlockers(attacker); + + if (blockers.size() == 0) { + if (!attacker.getSVar("MustBeBlocked").equals("")) { + return true; + } + } + } + + if (ComputerUtilCombat.lifeThatWouldRemain(ai, combat) < Math.min(4, ai.getLife()) + && !ai.cantLoseForZeroOrLessLife()) { + return true; + } + + return (ComputerUtilCombat.resultingPoison(ai, combat) > Math.max(7, ai.getPoisonCounters())); + } + + // Checks if the life of the attacked Player would be reduced + /** + *

+ * wouldLoseLife. + *

+ * + * @param combat + * a {@link forge.game.phase.Combat} object. + * @return a boolean. + */ + public static boolean wouldLoseLife(final Player ai, final Combat combat) { + + return (ComputerUtilCombat.lifeThatWouldRemain(ai, combat) < ai.getLife()); + } + + // Checks if the life of the attacked Player/Planeswalker is in danger + /** + *

+ * lifeInSeriousDanger. + *

+ * + * @param combat + * a {@link forge.game.phase.Combat} object. + * @return a boolean. + */ + public static boolean lifeInSeriousDanger(final Player ai, final Combat combat) { + // life in danger only cares about the player's life. Not about a + // Planeswalkers life + if (ai.cantLose()) { + return false; + } + + // check for creatures that must be blocked + final List attackers = combat.getAttackersByDefenderSlot(0); + + for (final Card attacker : attackers) { + + final List blockers = combat.getBlockers(attacker); + + if (blockers.size() == 0) { + if (!attacker.getSVar("MustBeBlocked").equals("")) { + return true; + } + } + } + + if (ComputerUtilCombat.lifeThatWouldRemain(ai, combat) < 1 && !ai.cantLoseForZeroOrLessLife()) { + return true; + } + + return (ComputerUtilCombat.resultingPoison(ai, combat) > 9); + } + + + // This calculates the amount of damage a blockgang can deal to the attacker + // (first strike not supported) + /** + *

+ * totalDamageOfBlockers. + *

+ * + * @param attacker + * a {@link forge.Card} object. + * @param defenders + * a {@link forge.CardList} object. + * @return a int. + */ + public static int totalDamageOfBlockers(final Card attacker, final List defenders) { + int damage = 0; + + for (final Card defender : defenders) { + damage += ComputerUtilCombat.dealsDamageAsBlocker(attacker, defender); + } + return damage; + } + + // This calculates the amount of damage a blocker in a blockgang can deal to + // the attacker + /** + *

+ * dealsDamageAsBlocker. + *

+ * + * @param attacker + * a {@link forge.Card} object. + * @param defender + * a {@link forge.Card} object. + * @return a int. + */ + public static int dealsDamageAsBlocker(final Card attacker, final 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 defenderDamage = defender.getNetAttack() + ComputerUtilCombat.predictPowerBonusOfBlocker(attacker, defender, true); + if (Singletons.getModel().getGame().getStaticEffects().getGlobalRuleChange(GlobalRuleChange.toughnessAssignsDamage)) { + defenderDamage = defender.getNetDefense() + ComputerUtilCombat.predictToughnessBonusOfBlocker(attacker, defender, true); + } + + // 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) + /** + *

+ * totalShieldDamage. + *

+ * + * @param attacker + * a {@link forge.Card} object. + * @param defenders + * a {@link forge.CardList} object. + * @return a int. + */ + public static int totalShieldDamage(final Card attacker, final List defenders) { + + int defenderDefense = 0; + + for (final Card defender : defenders) { + defenderDefense += ComputerUtilCombat.shieldDamage(attacker, defender); + } + + return defenderDefense; + } + + // This calculates the amount of damage a blocker in a blockgang can take + // from the attacker (for trampling attackers) + /** + *

+ * shieldDamage. + *

+ * + * @param attacker + * a {@link forge.Card} object. + * @param defender + * a {@link forge.Card} object. + * @return a int. + */ + public static int shieldDamage(final Card attacker, final Card defender) { + + 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 + + final int defBushidoMagnitude = defender.getKeywordMagnitude("Bushido"); + + final int defenderDefense = (defender.getLethalDamage() - flankingMagnitude) + defBushidoMagnitude; + + return defenderDefense; + } // shieldDamage + + // For AI safety measures like Regeneration + /** + *

+ * combatantWouldBeDestroyed. + *

+ * + * @param combatant + * a {@link forge.Card} object. + * @return a boolean. + */ + public static boolean combatantWouldBeDestroyed(final Card combatant) { + + if (combatant.isAttacking()) { + return ComputerUtilCombat.attackerWouldBeDestroyed(combatant); + } + if (combatant.isBlocking()) { + return ComputerUtilCombat.blockerWouldBeDestroyed(combatant); + } + return false; + } + + // For AI safety measures like Regeneration + /** + *

+ * attackerWouldBeDestroyed. + *

+ * + * @param attacker + * a {@link forge.Card} object. + * @return a boolean. + */ + public static boolean attackerWouldBeDestroyed(final Card attacker) { + final List blockers = Singletons.getModel().getGame().getCombat().getBlockers(attacker); + + for (final Card defender : blockers) { + if (ComputerUtilCombat.canDestroyAttacker(attacker, defender, Singletons.getModel().getGame().getCombat(), true) + && !(defender.hasKeyword("Wither") || defender.hasKeyword("Infect"))) { + return true; + } + } + + return ComputerUtilCombat.totalDamageOfBlockers(attacker, blockers) >= attacker.getKillDamage(); + } + + // Will this trigger trigger? + /** + *

+ * combatTriggerWillTrigger. + *

+ * + * @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.game.phase.Combat} object. + * @return a boolean. + */ + public static boolean combatTriggerWillTrigger(final Card attacker, final Card defender, final Trigger trigger, + Combat combat) { + final HashMap trigParams = trigger.getMapParams(); + boolean willTrigger = false; + final Card source = trigger.getHostCard(); + if (combat == null) { + combat = Singletons.getModel().getGame().getCombat(); + } + + if (!trigger.zonesCheck(Singletons.getModel().getGame().getZoneOf(trigger.getHostCard()))) { + return false; + } + if (!trigger.requirementsCheck()) { + return false; + } + + TriggerType mode = trigger.getMode(); + if (mode == TriggerType.Attacks) { + willTrigger = true; + if (attacker.isAttacking()) { + return false; // The trigger should have triggered already + } + if (trigParams.containsKey("ValidCard")) { + if (!TriggerReplacementBase.matchesValid(attacker, trigParams.get("ValidCard").split(","), source) + && !(combat.isAttacking(source) && TriggerReplacementBase.matchesValid(source, + trigParams.get("ValidCard").split(","), source) + && !trigParams.containsKey("Alone"))) { + return false; + } + } + } + + // defender == null means unblocked + if ((defender == null) && mode == TriggerType.AttackerUnblocked) { + willTrigger = true; + if (trigParams.containsKey("ValidCard")) { + if (!TriggerReplacementBase.matchesValid(attacker, trigParams.get("ValidCard").split(","), source)) { + return false; + } + } + } + + if (defender == null) { + return willTrigger; + } + + if (mode == TriggerType.Blocks) { + willTrigger = true; + if (trigParams.containsKey("ValidBlocked")) { + if (!TriggerReplacementBase.matchesValid(attacker, trigParams.get("ValidBlocked").split(","), source)) { + return false; + } + } + if (trigParams.containsKey("ValidCard")) { + if (!TriggerReplacementBase.matchesValid(defender, trigParams.get("ValidCard").split(","), source)) { + return false; + } + } + } else if (mode == TriggerType.AttackerBlocked) { + willTrigger = true; + if (trigParams.containsKey("ValidBlocker")) { + if (!TriggerReplacementBase.matchesValid(defender, trigParams.get("ValidBlocker").split(","), source)) { + return false; + } + } + if (trigParams.containsKey("ValidCard")) { + if (!TriggerReplacementBase.matchesValid(attacker, trigParams.get("ValidCard").split(","), source)) { + return false; + } + } + } else if (mode == TriggerType.DamageDone) { + willTrigger = true; + if (trigParams.containsKey("ValidSource")) { + if (TriggerReplacementBase.matchesValid(defender, trigParams.get("ValidSource").split(","), source) + && defender.getNetCombatDamage() > 0 + && (!trigParams.containsKey("ValidTarget") + || TriggerReplacementBase.matchesValid(attacker, trigParams.get("ValidTarget").split(","), source))) { + return true; + } + if (TriggerReplacementBase.matchesValid(attacker, trigParams.get("ValidSource").split(","), source) + && attacker.getNetCombatDamage() > 0 + && (!trigParams.containsKey("ValidTarget") + || TriggerReplacementBase.matchesValid(defender, trigParams.get("ValidTarget").split(","), source))) { + return true; + } + } + return false; + } + + return willTrigger; + } + + // Predict the Power bonus of the blocker if blocking the attacker + // (Flanking, Bushido and other triggered abilities) + /** + *

+ * predictPowerBonusOfBlocker. + *

+ * + * @param attacker + * a {@link forge.Card} object. + * @param defender + * a {@link forge.Card} object. + * @return a int. + */ + public static int predictPowerBonusOfBlocker(final Card attacker, final Card defender, boolean withoutAbilities) { + 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"); + + // look out for continuous static abilities that only care for blocking + // creatures + final List cardList = Singletons.getModel().getGame().getCardsIn(ZoneType.Battlefield); + for (final Card card : cardList) { + for (final StaticAbility stAb : card.getStaticAbilities()) { + final HashMap params = stAb.getMapParams(); + if (!params.get("Mode").equals("Continuous")) { + continue; + } + if (!params.containsKey("Affected") || !params.get("Affected").contains("blocking")) { + continue; + } + final String valid = params.get("Affected").replace("blocking", "Creature"); + if (!defender.isValid(valid, card.getController(), card)) { + continue; + } + if (params.containsKey("AddPower")) { + if (params.get("AddPower").equals("X")) { + power += CardFactoryUtil.xCount(card, card.getSVar("X")); + } else if (params.get("AddPower").equals("Y")) { + power += CardFactoryUtil.xCount(card, card.getSVar("Y")); + } else { + power += Integer.valueOf(params.get("AddPower")); + } + } + } + } + + final ArrayList theTriggers = new ArrayList(); + for (Card card : Singletons.getModel().getGame().getCardsIn(ZoneType.Battlefield)) { + theTriggers.addAll(card.getTriggers()); + } + theTriggers.addAll(attacker.getTriggers()); + for (final Trigger trigger : theTriggers) { + final HashMap trigParams = trigger.getMapParams(); + final Card source = trigger.getHostCard(); + + if (!ComputerUtilCombat.combatTriggerWillTrigger(attacker, defender, trigger, null) + || !trigParams.containsKey("Execute")) { + continue; + } + final String ability = source.getSVar(trigParams.get("Execute")); + final Map abilityParams = AbilityFactory.getMapParams(ability); + 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 + } + final List 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 (final NumberFormatException nfe) { + // can't parse the number (X for example) + power += 0; + } + } + if (withoutAbilities) { + return power; + } + for (SpellAbility ability : defender.getAllSpellAbilities()) { + if (!(ability instanceof AbilityActivated) || ability.getPayCosts() == null) { + continue; + } + if (ability.getApi() != ApiType.Pump) { + continue; + } + + if (!ability.hasParam("NumAtt")) { + continue; + } + + if (ComputerUtilCost.canPayCost(ability, defender.getController())) { + int pBonus = AbilityFactory.calculateAmount(ability.getSourceCard(), ability.getParam("NumAtt"), ability); + if (pBonus > 0) { + power += pBonus; + } + } + } + return power; + } + + // Predict the Toughness bonus of the blocker if blocking the attacker + // (Flanking, Bushido and other triggered abilities) + /** + *

+ * predictToughnessBonusOfBlocker. + *

+ * + * @param attacker + * a {@link forge.Card} object. + * @param defender + * a {@link forge.Card} object. + * @return a int. + */ + public static int predictToughnessBonusOfBlocker(final Card attacker, final Card defender, boolean withoutAbilities) { + int toughness = 0; + + if (attacker.hasKeyword("Flanking") && !defender.hasKeyword("Flanking")) { + toughness -= attacker.getAmountOfKeyword("Flanking"); + } + + toughness += defender.getKeywordMagnitude("Bushido"); + + final ArrayList theTriggers = new ArrayList(); + for (Card card : Singletons.getModel().getGame().getCardsIn(ZoneType.Battlefield)) { + theTriggers.addAll(card.getTriggers()); + } + theTriggers.addAll(attacker.getTriggers()); + for (final Trigger trigger : theTriggers) { + final HashMap trigParams = trigger.getMapParams(); + final Card source = trigger.getHostCard(); + + if (!ComputerUtilCombat.combatTriggerWillTrigger(attacker, defender, trigger, null) + || !trigParams.containsKey("Execute")) { + continue; + } + final String ability = source.getSVar(trigParams.get("Execute")); + final Map abilityParams = AbilityFactory.getMapParams(ability); + + // DealDamage triggers + if ((abilityParams.containsKey("AB") && abilityParams.get("AB").equals("DealDamage")) + || (abilityParams.containsKey("DB") && abilityParams.get("DB").equals("DealDamage"))) { + if (!abilityParams.containsKey("Defined") || !abilityParams.get("Defined").equals("TriggeredBlocker")) { + continue; + } + int damage = 0; + try { + damage = Integer.parseInt(abilityParams.get("NumDmg")); + } catch (final NumberFormatException nfe) { + // can't parse the number (X for example) + continue; + } + toughness -= defender.predictDamage(damage, 0, source, false); + continue; + } + + // Pump triggers + 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 + } + final List 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 (final NumberFormatException nfe) { + // can't parse the number (X for example) + + } + } + if (withoutAbilities) { + return toughness; + } + for (SpellAbility ability : defender.getAllSpellAbilities()) { + if (!(ability instanceof AbilityActivated) || ability.getPayCosts() == null) { + continue; + } + + if (ability.getApi() != ApiType.Pump || !ability.hasParam("NumDef")) { + continue; + } + + if (ComputerUtilCost.canPayCost(ability, defender.getController())) { + int tBonus = AbilityFactory.calculateAmount(ability.getSourceCard(), ability.getParam("NumDef"), ability); + if (tBonus > 0) { + toughness += tBonus; + } + } + } + return toughness; + } + + // Predict the Power bonus of the blocker if blocking the attacker + // (Flanking, Bushido and other triggered abilities) + /** + *

+ * predictPowerBonusOfAttacker. + *

+ * + * @param attacker + * a {@link forge.Card} object. + * @param defender + * a {@link forge.Card} object. + * @param combat + * a {@link forge.game.phase.Combat} object. + * @return a int. + */ + public static int predictPowerBonusOfAttacker(final Card attacker, final Card defender, final Combat combat + , boolean withoutAbilities) { + int power = 0; + + power += attacker.getKeywordMagnitude("Bushido"); + //check Exalted only for the first attacker + if (combat != null && combat.getAttackers().isEmpty()) { + for (Card card : attacker.getController().getCardsIn(ZoneType.Battlefield)) { + power += card.getKeywordAmount("Exalted"); + } + } + + final ArrayList theTriggers = new ArrayList(); + for (Card card : Singletons.getModel().getGame().getCardsIn(ZoneType.Battlefield)) { + theTriggers.addAll(card.getTriggers()); + } + // 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(); + } + theTriggers.addAll(defender.getTriggers()); + } + + // look out for continuous static abilities that only care for attacking + // creatures + final List cardList = Singletons.getModel().getGame().getCardsIn(ZoneType.Battlefield); + for (final Card card : cardList) { + for (final StaticAbility stAb : card.getStaticAbilities()) { + final HashMap params = stAb.getMapParams(); + if (!params.get("Mode").equals("Continuous")) { + continue; + } + if (!params.containsKey("Affected") || !params.get("Affected").contains("attacking")) { + continue; + } + final String valid = params.get("Affected").replace("attacking", "Creature"); + if (!attacker.isValid(valid, card.getController(), card)) { + continue; + } + if (params.containsKey("AddPower")) { + if (params.get("AddPower").equals("X")) { + power += CardFactoryUtil.xCount(card, card.getSVar("X")); + } else if (params.get("AddPower").equals("Y")) { + power += CardFactoryUtil.xCount(card, card.getSVar("Y")); + } else { + power += Integer.valueOf(params.get("AddPower")); + } + } + } + } + + for (final Trigger trigger : theTriggers) { + final HashMap trigParams = trigger.getMapParams(); + final Card source = trigger.getHostCard(); + + if (!ComputerUtilCombat.combatTriggerWillTrigger(attacker, defender, trigger, combat) + || !trigParams.containsKey("Execute")) { + continue; + } + final String ability = source.getSVar(trigParams.get("Execute")); + final Map abilityParams = AbilityFactory.getMapParams(ability); + 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; + } + List list = new ArrayList(); + if (!abilityParams.containsKey("ValidCards")) { + 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.isValid(abilityParams.get("ValidCards").split(","), source.getController(), source) + || attacker.isValid(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); + } + if (att.matches("[0-9][0-9]?") || att.matches("-" + "[0-9][0-9]?")) { + power += Integer.parseInt(att); + } else { + String bonus = new String(source.getSVar(att)); + if (bonus.contains("TriggerCount$NumBlockers")) { + bonus = bonus.replace("TriggerCount$NumBlockers", "Number$1"); + } + power += CardFactoryUtil.xCount(source, bonus); + + } + } + if (withoutAbilities) { + return power; + } + for (SpellAbility ability : attacker.getAllSpellAbilities()) { + if (!(ability instanceof AbilityActivated) || ability.getPayCosts() == null) { + continue; + } + if (ability.getApi() != ApiType.Pump) { + continue; + } + + if (!ability.hasParam("NumAtt")) { + continue; + } + + if (!ability.getPayCosts().hasTapCost() && ComputerUtilCost.canPayCost(ability, attacker.getController())) { + int pBonus = AbilityFactory.calculateAmount(ability.getSourceCard(), ability.getParam("NumAtt"), ability); + if (pBonus > 0) { + power += pBonus; + } + } + } + return power; + } + + // Predict the Toughness bonus of the attacker if blocked by the blocker + // (Flanking, Bushido and other triggered abilities) + /** + *

+ * predictToughnessBonusOfAttacker. + *

+ * + * @param attacker + * a {@link forge.Card} object. + * @param defender + * a {@link forge.Card} object. + * @param combat + * a {@link forge.game.phase.Combat} object. + * @return a int. + */ + public static int predictToughnessBonusOfAttacker(final Card attacker, final Card defender, final Combat combat + , boolean withoutAbilities) { + int toughness = 0; + + //check Exalted only for the first attacker + if (combat != null && combat.getAttackers().isEmpty()) { + for (Card card : attacker.getController().getCardsIn(ZoneType.Battlefield)) { + toughness += card.getKeywordAmount("Exalted"); + } + } + + final ArrayList theTriggers = new ArrayList(); + for (Card card : Singletons.getModel().getGame().getCardsIn(ZoneType.Battlefield)) { + theTriggers.addAll(card.getTriggers()); + } + if (defender != null) { + toughness += attacker.getKeywordMagnitude("Bushido"); + theTriggers.addAll(defender.getTriggers()); + } + + // look out for continuous static abilities that only care for attacking + // creatures + final List cardList = Singletons.getModel().getGame().getCardsIn(ZoneType.Battlefield); + for (final Card card : cardList) { + for (final StaticAbility stAb : card.getStaticAbilities()) { + final HashMap params = stAb.getMapParams(); + if (!params.get("Mode").equals("Continuous")) { + continue; + } + if (!params.containsKey("Affected") || !params.get("Affected").contains("attacking")) { + continue; + } + final String valid = params.get("Affected").replace("attacking", "Creature"); + if (!attacker.isValid(valid, card.getController(), card)) { + continue; + } + if (params.containsKey("AddToughness")) { + if (params.get("AddToughness").equals("X")) { + toughness += CardFactoryUtil.xCount(card, card.getSVar("X")); + } else if (params.get("AddToughness").equals("Y")) { + toughness += CardFactoryUtil.xCount(card, card.getSVar("Y")); + } else { + toughness += Integer.valueOf(params.get("AddToughness")); + } + } + } + } + + for (final Trigger trigger : theTriggers) { + final HashMap trigParams = trigger.getMapParams(); + final Card source = trigger.getHostCard(); + + if (!ComputerUtilCombat.combatTriggerWillTrigger(attacker, defender, trigger, combat) + || !trigParams.containsKey("Execute")) { + continue; + } + final String ability = source.getSVar(trigParams.get("Execute")); + final Map abilityParams = AbilityFactory.getMapParams(ability); + if (abilityParams.containsKey("ValidTgts") || abilityParams.containsKey("Tgt")) { + continue; // targeted pumping not supported + } + + // DealDamage triggers + if ((abilityParams.containsKey("AB") && abilityParams.get("AB").equals("DealDamage")) + || (abilityParams.containsKey("DB") && abilityParams.get("DB").equals("DealDamage"))) { + if (!abilityParams.containsKey("Defined") || !abilityParams.get("Defined").equals("TriggeredAttacker")) { + continue; + } + int damage = 0; + try { + damage = Integer.parseInt(abilityParams.get("NumDmg")); + } catch (final NumberFormatException nfe) { + // can't parse the number (X for example) + continue; + } + toughness -= attacker.predictDamage(damage, 0, source, false); + continue; + } + + // Pump triggers + 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; + } + List list = new ArrayList(); + if (!abilityParams.containsKey("ValidCards")) { + 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.isValid(abilityParams.get("ValidCards").split(","), source.getController(), source) + || attacker.isValid(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); + } + if (def.matches("[0-9][0-9]?") || def.matches("-" + "[0-9][0-9]?")) { + toughness += Integer.parseInt(def); + } else { + String bonus = new String(source.getSVar(def)); + if (bonus.contains("TriggerCount$NumBlockers")) { + bonus = bonus.replace("TriggerCount$NumBlockers", "Number$1"); + } + toughness += CardFactoryUtil.xCount(source, bonus); + } + } + if (withoutAbilities) { + return toughness; + } + for (SpellAbility ability : attacker.getAllSpellAbilities()) { + if (!(ability instanceof AbilityActivated) || ability.getPayCosts() == null) { + continue; + } + + if (ability.getApi() != ApiType.Pump || !ability.hasParam("NumDef")) { + continue; + } + + if (!ability.getPayCosts().hasTapCost() && ComputerUtilCost.canPayCost(ability, attacker.getController())) { + int tBonus = AbilityFactory.calculateAmount(ability.getSourceCard(), ability.getParam("NumDef"), ability); + if (tBonus > 0) { + toughness += tBonus; + } + } + } + return toughness; + } + + // Sylvan Basilisk and friends + /** + *

+ * checkDestroyBlockerTrigger. + *

+ * + * @param attacker + * a {@link forge.Card} object. + * @param defender + * a {@link forge.Card} object. + * @return a boolean. + */ + public static boolean checkDestroyBlockerTrigger(final Card attacker, final Card defender) { + final ArrayList theTriggers = new ArrayList(); + for (Card card : Singletons.getModel().getGame().getCardsIn(ZoneType.Battlefield)) { + theTriggers.addAll(card.getTriggers()); + } + for (Trigger trigger : theTriggers) { + HashMap trigParams = trigger.getMapParams(); + final Card source = trigger.getHostCard(); + + if (!ComputerUtilCombat.combatTriggerWillTrigger(attacker, defender, trigger, null)) { + continue; + } + //consider delayed triggers + if (trigParams.containsKey("DelayedTrigger")) { + String sVarName = trigParams.get("DelayedTrigger"); + trigger = TriggerHandler.parseTrigger(source.getSVar(sVarName), trigger.getHostCard(), true); + trigParams = trigger.getMapParams(); + } + if (!trigParams.containsKey("Execute")) { + continue; + } + String ability = source.getSVar(trigParams.get("Execute")); + final Map abilityParams = AbilityFactory.getMapParams(ability); + // Destroy triggers + if ((abilityParams.containsKey("AB") && abilityParams.get("AB").equals("Destroy")) + || (abilityParams.containsKey("DB") && abilityParams.get("DB").equals("Destroy"))) { + if (!abilityParams.containsKey("Defined")) { + continue; + } + if (abilityParams.get("Defined").equals("TriggeredBlocker")) { + return true; + } + if (abilityParams.get("Defined").equals("Self") && source.equals(defender)) { + return true; + } + if (abilityParams.get("Defined").equals("TriggeredTarget") && source.equals(attacker)) { + return true; + } + } + } + return false; + } + + // Cockatrice and friends + /** + *

+ * checkDestroyBlockerTrigger. + *

+ * + * @param attacker + * a {@link forge.Card} object. + * @param defender + * a {@link forge.Card} object. + * @return a boolean. + */ + public static boolean checkDestroyAttackerTrigger(final Card attacker, final Card defender) { + final ArrayList theTriggers = new ArrayList(); + for (Card card : Singletons.getModel().getGame().getCardsIn(ZoneType.Battlefield)) { + theTriggers.addAll(card.getTriggers()); + } + for (Trigger trigger : theTriggers) { + HashMap trigParams = trigger.getMapParams(); + final Card source = trigger.getHostCard(); + + if (!ComputerUtilCombat.combatTriggerWillTrigger(attacker, defender, trigger, null)) { + continue; + } + //consider delayed triggers + if (trigParams.containsKey("DelayedTrigger")) { + String sVarName = trigParams.get("DelayedTrigger"); + trigger = TriggerHandler.parseTrigger(source.getSVar(sVarName), trigger.getHostCard(), true); + trigParams = trigger.getMapParams(); + } + if (!trigParams.containsKey("Execute")) { + continue; + } + String ability = source.getSVar(trigParams.get("Execute")); + final Map abilityParams = AbilityFactory.getMapParams(ability); + // Destroy triggers + if ((abilityParams.containsKey("AB") && abilityParams.get("AB").equals("Destroy")) + || (abilityParams.containsKey("DB") && abilityParams.get("DB").equals("Destroy"))) { + if (!abilityParams.containsKey("Defined")) { + continue; + } + if (abilityParams.get("Defined").equals("TriggeredAttacker")) { + return true; + } + if (abilityParams.get("Defined").equals("Self") && source.equals(attacker)) { + return true; + } + if (abilityParams.get("Defined").equals("TriggeredTarget") && source.equals(defender)) { + return true; + } + } + } + return false; + } + + // can the blocker destroy the attacker? + /** + *

+ * canDestroyAttacker. + *

+ * + * @param attacker + * a {@link forge.Card} object. + * @param defender + * a {@link forge.Card} object. + * @param combat + * a {@link forge.game.phase.Combat} object. + * @param withoutAbilities + * a boolean. + * @return a boolean. + */ + public static boolean canDestroyAttacker(final Card attacker, final Card defender, final Combat combat, + final 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"))) + || (attacker.hasKeyword("Persist") && !attacker.canHaveCountersPlacedOnIt(CounterType.M1M1) && (attacker + .getCounters(CounterType.M1M1) == 0)) + || (attacker.hasKeyword("Undying") && !attacker.canHaveCountersPlacedOnIt(CounterType.P1P1) && (attacker + .getCounters(CounterType.P1P1) == 0))) { + return false; + } + if (checkDestroyAttackerTrigger(attacker, defender) && !attacker.hasKeyword("Indestructible")) { + return true; + } + + int defenderDamage = defender.getNetAttack() + + ComputerUtilCombat.predictPowerBonusOfBlocker(attacker, defender, withoutAbilities); + int attackerDamage = attacker.getNetAttack() + + ComputerUtilCombat.predictPowerBonusOfAttacker(attacker, defender, combat, withoutAbilities); + if (Singletons.getModel().getGame().getStaticEffects().getGlobalRuleChange(GlobalRuleChange.toughnessAssignsDamage)) { + defenderDamage = defender.getNetDefense() + + ComputerUtilCombat.predictToughnessBonusOfBlocker(attacker, defender, withoutAbilities); + attackerDamage = attacker.getNetDefense() + + ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, defender, combat, withoutAbilities); + } + + 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); + + final int defenderLife = defender.getKillDamage() + + ComputerUtilCombat.predictToughnessBonusOfBlocker(attacker, defender, withoutAbilities); + final int attackerLife = attacker.getKillDamage() + + ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, defender, combat, withoutAbilities); + + if (defender.hasKeyword("Double Strike")) { + if (defenderDamage > 0 && (defender.hasKeyword("Deathtouch") + || attacker.hasStartOfKeyword("When CARDNAME is dealt damage, destroy it."))) { + 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") + || defender.hasStartOfKeyword("When CARDNAME is dealt damage, destroy it."))) { + 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") + || defender.hasStartOfKeyword("When CARDNAME is dealt damage, destroy it."))) { + return false; + } + } + + if (defenderDamage > 0 && (defender.hasKeyword("Deathtouch") + || attacker.hasStartOfKeyword("When CARDNAME is dealt damage, destroy it."))) { + return true; + } + + return defenderDamage >= attackerLife; + + } // defender no double strike + return false; // should never arrive here + } // canDestroyAttacker + + // For AI safety measures like Regeneration + /** + *

+ * blockerWouldBeDestroyed. + *

+ * + * @param blocker + * a {@link forge.Card} object. + * @return a boolean. + */ + public static boolean blockerWouldBeDestroyed(final Card blocker) { + // TODO THis function only checks if a single attacker at a time would destroy a blocker + // This needs to expand to tally up damage + final List attackers = Singletons.getModel().getGame().getCombat().getAttackersBlockedBy(blocker); + + for (Card attacker : attackers) { + if (ComputerUtilCombat.canDestroyBlocker(blocker, attacker, Singletons.getModel().getGame().getCombat(), true) + && !(attacker.hasKeyword("Wither") || attacker.hasKeyword("Infect"))) { + return true; + } + } + return false; + } + + // can the attacker destroy this blocker? + /** + *

+ * canDestroyBlocker. + *

+ * + * @param defender + * a {@link forge.Card} object. + * @param attacker + * a {@link forge.Card} object. + * @param combat + * a {@link forge.game.phase.Combat} object. + * @param withoutAbilities + * a boolean. + * @return a boolean. + */ + public static boolean canDestroyBlocker(final Card defender, final Card attacker, final Combat combat, + final 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"))) + || (defender.hasKeyword("Persist") && !defender.canHaveCountersPlacedOnIt(CounterType.M1M1) && (defender + .getCounters(CounterType.M1M1) == 0)) + || (defender.hasKeyword("Undying") && !defender.canHaveCountersPlacedOnIt(CounterType.P1P1) && (defender + .getCounters(CounterType.P1P1) == 0))) { + return false; + } + + if (checkDestroyBlockerTrigger(attacker, defender) && !defender.hasKeyword("Indestructible")) { + return true; + } + + int defenderDamage = defender.getNetAttack() + + ComputerUtilCombat.predictPowerBonusOfBlocker(attacker, defender, withoutAbilities); + int attackerDamage = attacker.getNetAttack() + + ComputerUtilCombat.predictPowerBonusOfAttacker(attacker, defender, combat, withoutAbilities); + if (Singletons.getModel().getGame().getStaticEffects().getGlobalRuleChange(GlobalRuleChange.toughnessAssignsDamage)) { + defenderDamage = defender.getNetDefense() + + ComputerUtilCombat.predictToughnessBonusOfBlocker(attacker, defender, withoutAbilities); + attackerDamage = attacker.getNetDefense() + + ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, defender, combat, withoutAbilities); + } + + 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); + + if (combat != null) { + for (Card atkr : combat.getAttackersBlockedBy(defender)) { + if (!atkr.equals(attacker)) { + attackerDamage += defender.predictDamage(atkr.getNetCombatDamage(), 0, atkr, true); + } + } + } + + final int defenderLife = defender.getKillDamage() + + ComputerUtilCombat.predictToughnessBonusOfBlocker(attacker, defender, withoutAbilities); + final int attackerLife = attacker.getKillDamage() + + ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, defender, combat, withoutAbilities); + + if (attacker.hasKeyword("Double Strike")) { + if (attackerDamage > 0 && (attacker.hasKeyword("Deathtouch") + || defender.hasStartOfKeyword("When CARDNAME is dealt damage, destroy it."))) { + 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") + || attacker.hasStartOfKeyword("When CARDNAME is dealt damage, destroy it."))) { + 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") + || attacker.hasStartOfKeyword("When CARDNAME is dealt damage, destroy it."))) { + return false; + } + } + + if (attackerDamage > 0 && (attacker.hasKeyword("Deathtouch") + || defender.hasStartOfKeyword("When CARDNAME is dealt damage, destroy it."))) { + return true; + } + + return attackerDamage >= defenderLife; + + } // attacker no double strike + return false; // should never arrive here + } // canDestroyBlocker +} diff --git a/src/main/java/forge/game/phase/CombatUtil.java b/src/main/java/forge/game/phase/CombatUtil.java index 4e136a1d1f6..67383e33c50 100644 --- a/src/main/java/forge/game/phase/CombatUtil.java +++ b/src/main/java/forge/game/phase/CombatUtil.java @@ -20,7 +20,6 @@ package forge.game.phase; import java.util.ArrayList; import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -35,25 +34,17 @@ import forge.CardLists; import forge.CardPredicates; import forge.Command; import forge.Constant; -import forge.CounterType; import forge.GameActionUtil; import forge.GameEntity; import forge.Singletons; import forge.card.SpellManaCost; -import forge.card.TriggerReplacementBase; -import forge.card.abilityfactory.AbilityFactory; -import forge.card.abilityfactory.ApiType; import forge.card.abilityfactory.effects.SacrificeEffect; import forge.card.cardfactory.CardFactoryUtil; import forge.card.cost.Cost; import forge.card.cost.CostUtil; import forge.card.spellability.Ability; -import forge.card.spellability.AbilityActivated; import forge.card.spellability.AbilityStatic; -import forge.card.spellability.SpellAbility; import forge.card.staticability.StaticAbility; -import forge.card.trigger.Trigger; -import forge.card.trigger.TriggerHandler; import forge.card.trigger.TriggerType; import forge.game.GameState; import forge.game.GlobalRuleChange; @@ -980,1570 +971,6 @@ public class CombatUtil { return true; } // canAttack() - /** - *

- * getTotalFirstStrikeBlockPower. - *

- * - * @param attacker - * a {@link forge.Card} object. - * @param player - * a {@link forge.game.player.Player} object. - * @return a int. - */ - public static int getTotalFirstStrikeBlockPower(final Card attacker, final Player player) { - final Card att = attacker; - - List list = player.getCreaturesInPlay(); - list = CardLists.filter(list, new Predicate() { - @Override - public boolean apply(final Card c) { - return CombatUtil.canBlock(att, c) && (c.hasFirstStrike() || c.hasDoubleStrike()); - } - }); - - return CombatUtil.totalDamageOfBlockers(attacker, list); - - } - - // This function takes Doran and Double Strike into account - /** - *

- * getAttack. - *

- * - * @param c - * a {@link forge.Card} object. - * @return a int. - */ - public static int getAttack(final Card c) { - int n = c.getNetCombatDamage(); - - if (c.hasDoubleStrike()) { - n *= 2; - } - - return n; - } - - // Returns the damage an unblocked attacker would deal - /** - *

- * damageIfUnblocked. - *

- * - * @param attacker - * a {@link forge.Card} object. - * @param attacked - * a {@link forge.game.player.Player} object. - * @param combat - * a {@link forge.game.phase.Combat} object. - * @return a int. - */ - public static int damageIfUnblocked(final Card attacker, final Player attacked, final Combat combat) { - int damage = attacker.getNetCombatDamage(); - int sum = 0; - if (!attacked.canLoseLife()) { - return 0; - } - damage += CombatUtil.predictPowerBonusOfAttacker(attacker, null, combat, false); - 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 - /** - *

- * poisonIfUnblocked. - *

- * - * @param attacker - * a {@link forge.Card} object. - * @param attacked - * a {@link forge.game.player.Player} object. - * @param combat - * a {@link forge.game.phase.Combat} object. - * @return a int. - */ - public static int poisonIfUnblocked(final Card attacker, final Player attacked) { - int damage = attacker.getNetCombatDamage(); - int poison = 0; - damage += CombatUtil.predictPowerBonusOfAttacker(attacker, null, null, false); - 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 - /** - *

- * sumDamageIfUnblocked. - *

- * - * @param attackers - * a {@link forge.CardList} object. - * @param attacked - * a {@link forge.game.player.Player} object. - * @return a int. - */ - public static int sumDamageIfUnblocked(final List attackers, final Player attacked) { - int sum = 0; - for (final Card attacker : attackers) { - sum += CombatUtil.damageIfUnblocked(attacker, attacked, null); - } - return sum; - } - - // Returns the number of poison counters unblocked attackers would deal - /** - *

- * sumPoisonIfUnblocked. - *

- * - * @param attackers - * a {@link forge.CardList} object. - * @param attacked - * a {@link forge.game.player.Player} object. - * @return a int. - */ - public static int sumPoisonIfUnblocked(final List attackers, final Player attacked) { - int sum = 0; - for (final Card attacker : attackers) { - sum += CombatUtil.poisonIfUnblocked(attacker, attacked); - } - return sum; - } - - // calculates the amount of life that will remain after the attack - /** - *

- * lifeThatWouldRemain. - *

- * - * @param combat - * a {@link forge.game.phase.Combat} object. - * @return a int. - */ - public static int lifeThatWouldRemain(final Player ai, final Combat combat) { - - int damage = 0; - - final List attackers = combat.getAttackersByDefenderSlot(0); - final List unblocked = new ArrayList(); - - for (final Card attacker : attackers) { - - final List blockers = combat.getBlockers(attacker); - - if ((blockers.size() == 0) - || attacker.hasKeyword("You may have CARDNAME assign its combat damage " - + "as though it weren't blocked.")) { - unblocked.add(attacker); - } else if (attacker.hasKeyword("Trample") - && (CombatUtil.getAttack(attacker) > CombatUtil.totalShieldDamage(attacker, blockers))) { - if (!attacker.hasKeyword("Infect")) { - damage += CombatUtil.getAttack(attacker) - CombatUtil.totalShieldDamage(attacker, blockers); - } - } - } - - damage += CombatUtil.sumDamageIfUnblocked(unblocked, ai); - - if (!ai.canLoseLife()) { - damage = 0; - } - - return ai.getLife() - damage; - } - - // calculates the amount of poison counters after the attack - /** - *

- * resultingPoison. - *

- * - * @param combat - * a {@link forge.game.phase.Combat} object. - * @return a int. - */ - public static int resultingPoison(final Player ai, final Combat combat) { - - int poison = 0; - - final List attackers = combat.getAttackersByDefenderSlot(0); - final List unblocked = new ArrayList(); - - for (final Card attacker : attackers) { - - final List blockers = combat.getBlockers(attacker); - - if ((blockers.size() == 0) - || attacker.hasKeyword("You may have CARDNAME assign its combat damage" - + " as though it weren't blocked.")) { - unblocked.add(attacker); - } else if (attacker.hasKeyword("Trample") - && (CombatUtil.getAttack(attacker) > CombatUtil.totalShieldDamage(attacker, blockers))) { - if (attacker.hasKeyword("Infect")) { - poison += CombatUtil.getAttack(attacker) - CombatUtil.totalShieldDamage(attacker, blockers); - } - if (attacker.hasKeyword("Poisonous")) { - poison += attacker.getKeywordMagnitude("Poisonous"); - } - } - } - - poison += CombatUtil.sumPoisonIfUnblocked(unblocked, ai); - - return ai.getPoisonCounters() + poison; - } - - // Checks if the life of the attacked Player/Planeswalker is in danger - /** - *

- * lifeInDanger. - *

- * - * @param combat - * a {@link forge.game.phase.Combat} object. - * @return a boolean. - */ - public static boolean lifeInDanger(final Player ai, final Combat combat) { - // life in danger only cares about the player's life. Not Planeswalkers' life - if (ai.cantLose()) { - return false; - } - - // check for creatures that must be blocked - final List attackers = combat.getAttackersByDefenderSlot(0); - - for (final Card attacker : attackers) { - - final List blockers = combat.getBlockers(attacker); - - if (blockers.size() == 0) { - if (!attacker.getSVar("MustBeBlocked").equals("")) { - return true; - } - } - } - - if ((CombatUtil.lifeThatWouldRemain(ai, combat) < Math.min(4, ai.getLife())) - && !ai.cantLoseForZeroOrLessLife()) { - return true; - } - - return (CombatUtil.resultingPoison(ai, combat) > Math.max(7, ai.getPoisonCounters())); - } - - // Checks if the life of the attacked Player would be reduced - /** - *

- * wouldLoseLife. - *

- * - * @param combat - * a {@link forge.game.phase.Combat} object. - * @return a boolean. - */ - public static boolean wouldLoseLife(final Player ai, final Combat combat) { - - return (CombatUtil.lifeThatWouldRemain(ai, combat) < ai.getLife()); - } - - // Checks if the life of the attacked Player/Planeswalker is in danger - /** - *

- * lifeInSeriousDanger. - *

- * - * @param combat - * a {@link forge.game.phase.Combat} object. - * @return a boolean. - */ - public static boolean lifeInSeriousDanger(final Player ai, final Combat combat) { - // life in danger only cares about the player's life. Not about a - // Planeswalkers life - if (ai.cantLose()) { - return false; - } - - // check for creatures that must be blocked - final List attackers = combat.getAttackersByDefenderSlot(0); - - for (final Card attacker : attackers) { - - final List blockers = combat.getBlockers(attacker); - - if (blockers.size() == 0) { - if (!attacker.getSVar("MustBeBlocked").equals("")) { - return true; - } - } - } - - if ((CombatUtil.lifeThatWouldRemain(ai, combat) < 1) && !ai.cantLoseForZeroOrLessLife()) { - return true; - } - - return (CombatUtil.resultingPoison(ai, combat) > 9); - } - - // This calculates the amount of damage a blockgang can deal to the attacker - // (first strike not supported) - /** - *

- * totalDamageOfBlockers. - *

- * - * @param attacker - * a {@link forge.Card} object. - * @param defenders - * a {@link forge.CardList} object. - * @return a int. - */ - public static int totalDamageOfBlockers(final Card attacker, final List defenders) { - int damage = 0; - - for (final Card defender : defenders) { - damage += CombatUtil.dealsDamageAsBlocker(attacker, defender); - } - return damage; - } - - // This calculates the amount of damage a blocker in a blockgang can deal to - // the attacker - /** - *

- * dealsDamageAsBlocker. - *

- * - * @param attacker - * a {@link forge.Card} object. - * @param defender - * a {@link forge.Card} object. - * @return a int. - */ - public static int dealsDamageAsBlocker(final Card attacker, final 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 defenderDamage = defender.getNetAttack() + CombatUtil.predictPowerBonusOfBlocker(attacker, defender, true); - if (Singletons.getModel().getGame().getStaticEffects().getGlobalRuleChange(GlobalRuleChange.toughnessAssignsDamage)) { - defenderDamage = defender.getNetDefense() + CombatUtil.predictToughnessBonusOfBlocker(attacker, defender, true); - } - - // 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) - /** - *

- * totalShieldDamage. - *

- * - * @param attacker - * a {@link forge.Card} object. - * @param defenders - * a {@link forge.CardList} object. - * @return a int. - */ - public static int totalShieldDamage(final Card attacker, final List defenders) { - - int defenderDefense = 0; - - for (final Card defender : defenders) { - defenderDefense += CombatUtil.shieldDamage(attacker, defender); - } - - return defenderDefense; - } - - // This calculates the amount of damage a blocker in a blockgang can take - // from the attacker (for trampling attackers) - /** - *

- * shieldDamage. - *

- * - * @param attacker - * a {@link forge.Card} object. - * @param defender - * a {@link forge.Card} object. - * @return a int. - */ - public static int shieldDamage(final Card attacker, final Card defender) { - - 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 - - final int defBushidoMagnitude = defender.getKeywordMagnitude("Bushido"); - - final int defenderDefense = (defender.getLethalDamage() - flankingMagnitude) + defBushidoMagnitude; - - return defenderDefense; - } // shieldDamage - - // For AI safety measures like Regeneration - /** - *

- * combatantWouldBeDestroyed. - *

- * - * @param combatant - * a {@link forge.Card} object. - * @return a boolean. - */ - public static boolean combatantWouldBeDestroyed(final Card combatant) { - - if (combatant.isAttacking()) { - return CombatUtil.attackerWouldBeDestroyed(combatant); - } - if (combatant.isBlocking()) { - return CombatUtil.blockerWouldBeDestroyed(combatant); - } - return false; - } - - // For AI safety measures like Regeneration - /** - *

- * attackerWouldBeDestroyed. - *

- * - * @param attacker - * a {@link forge.Card} object. - * @return a boolean. - */ - public static boolean attackerWouldBeDestroyed(final Card attacker) { - final List blockers = Singletons.getModel().getGame().getCombat().getBlockers(attacker); - - for (final Card defender : blockers) { - if (CombatUtil.canDestroyAttacker(attacker, defender, Singletons.getModel().getGame().getCombat(), true) - && !(defender.hasKeyword("Wither") || defender.hasKeyword("Infect"))) { - return true; - } - } - - return CombatUtil.totalDamageOfBlockers(attacker, blockers) >= attacker.getKillDamage(); - } - - // Will this trigger trigger? - /** - *

- * combatTriggerWillTrigger. - *

- * - * @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.game.phase.Combat} object. - * @return a boolean. - */ - public static boolean combatTriggerWillTrigger(final Card attacker, final Card defender, final Trigger trigger, - Combat combat) { - final HashMap trigParams = trigger.getMapParams(); - boolean willTrigger = false; - final Card source = trigger.getHostCard(); - if (combat == null) { - combat = Singletons.getModel().getGame().getCombat(); - } - - if (!trigger.zonesCheck(Singletons.getModel().getGame().getZoneOf(trigger.getHostCard()))) { - return false; - } - if (!trigger.requirementsCheck()) { - return false; - } - - TriggerType mode = trigger.getMode(); - if (mode == TriggerType.Attacks) { - willTrigger = true; - if (attacker.isAttacking()) { - return false; // The trigger should have triggered already - } - if (trigParams.containsKey("ValidCard")) { - if (!TriggerReplacementBase.matchesValid(attacker, trigParams.get("ValidCard").split(","), source) - && !(combat.isAttacking(source) && TriggerReplacementBase.matchesValid(source, - trigParams.get("ValidCard").split(","), source) - && !trigParams.containsKey("Alone"))) { - return false; - } - } - } - - // defender == null means unblocked - if ((defender == null) && mode == TriggerType.AttackerUnblocked) { - willTrigger = true; - if (trigParams.containsKey("ValidCard")) { - if (!TriggerReplacementBase.matchesValid(attacker, trigParams.get("ValidCard").split(","), source)) { - return false; - } - } - } - - if (defender == null) { - return willTrigger; - } - - if (mode == TriggerType.Blocks) { - willTrigger = true; - if (trigParams.containsKey("ValidBlocked")) { - if (!TriggerReplacementBase.matchesValid(attacker, trigParams.get("ValidBlocked").split(","), source)) { - return false; - } - } - if (trigParams.containsKey("ValidCard")) { - if (!TriggerReplacementBase.matchesValid(defender, trigParams.get("ValidCard").split(","), source)) { - return false; - } - } - } else if (mode == TriggerType.AttackerBlocked) { - willTrigger = true; - if (trigParams.containsKey("ValidBlocker")) { - if (!TriggerReplacementBase.matchesValid(defender, trigParams.get("ValidBlocker").split(","), source)) { - return false; - } - } - if (trigParams.containsKey("ValidCard")) { - if (!TriggerReplacementBase.matchesValid(attacker, trigParams.get("ValidCard").split(","), source)) { - return false; - } - } - } else if (mode == TriggerType.DamageDone) { - willTrigger = true; - if (trigParams.containsKey("ValidSource")) { - if (TriggerReplacementBase.matchesValid(defender, trigParams.get("ValidSource").split(","), source) - && defender.getNetCombatDamage() > 0 - && (!trigParams.containsKey("ValidTarget") - || TriggerReplacementBase.matchesValid(attacker, trigParams.get("ValidTarget").split(","), source))) { - return true; - } - if (TriggerReplacementBase.matchesValid(attacker, trigParams.get("ValidSource").split(","), source) - && attacker.getNetCombatDamage() > 0 - && (!trigParams.containsKey("ValidTarget") - || TriggerReplacementBase.matchesValid(defender, trigParams.get("ValidTarget").split(","), source))) { - return true; - } - } - return false; - } - - return willTrigger; - } - - // Predict the Power bonus of the blocker if blocking the attacker - // (Flanking, Bushido and other triggered abilities) - /** - *

- * predictPowerBonusOfBlocker. - *

- * - * @param attacker - * a {@link forge.Card} object. - * @param defender - * a {@link forge.Card} object. - * @return a int. - */ - public static int predictPowerBonusOfBlocker(final Card attacker, final Card defender, boolean withoutAbilities) { - 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"); - - // look out for continuous static abilities that only care for blocking - // creatures - final List cardList = Singletons.getModel().getGame().getCardsIn(ZoneType.Battlefield); - for (final Card card : cardList) { - for (final StaticAbility stAb : card.getStaticAbilities()) { - final HashMap params = stAb.getMapParams(); - if (!params.get("Mode").equals("Continuous")) { - continue; - } - if (!params.containsKey("Affected") || !params.get("Affected").contains("blocking")) { - continue; - } - final String valid = params.get("Affected").replace("blocking", "Creature"); - if (!defender.isValid(valid, card.getController(), card)) { - continue; - } - if (params.containsKey("AddPower")) { - if (params.get("AddPower").equals("X")) { - power += CardFactoryUtil.xCount(card, card.getSVar("X")); - } else if (params.get("AddPower").equals("Y")) { - power += CardFactoryUtil.xCount(card, card.getSVar("Y")); - } else { - power += Integer.valueOf(params.get("AddPower")); - } - } - } - } - - final ArrayList theTriggers = new ArrayList(); - for (Card card : Singletons.getModel().getGame().getCardsIn(ZoneType.Battlefield)) { - theTriggers.addAll(card.getTriggers()); - } - theTriggers.addAll(attacker.getTriggers()); - for (final Trigger trigger : theTriggers) { - final HashMap trigParams = trigger.getMapParams(); - final Card source = trigger.getHostCard(); - - if (!CombatUtil.combatTriggerWillTrigger(attacker, defender, trigger, null) - || !trigParams.containsKey("Execute")) { - continue; - } - final String ability = source.getSVar(trigParams.get("Execute")); - final Map abilityParams = AbilityFactory.getMapParams(ability); - 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 - } - final List 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 (final NumberFormatException nfe) { - // can't parse the number (X for example) - power += 0; - } - } - if (withoutAbilities) { - return power; - } - for (SpellAbility ability : defender.getAllSpellAbilities()) { - if (!(ability instanceof AbilityActivated) || ability.getPayCosts() == null) { - continue; - } - if (ability.getApi() != ApiType.Pump) { - continue; - } - - if (!ability.hasParam("NumAtt")) { - continue; - } - - if (ComputerUtilCost.canPayCost(ability, defender.getController())) { - int pBonus = AbilityFactory.calculateAmount(ability.getSourceCard(), ability.getParam("NumAtt"), ability); - if (pBonus > 0) { - power += pBonus; - } - } - } - return power; - } - - // Predict the Toughness bonus of the blocker if blocking the attacker - // (Flanking, Bushido and other triggered abilities) - /** - *

- * predictToughnessBonusOfBlocker. - *

- * - * @param attacker - * a {@link forge.Card} object. - * @param defender - * a {@link forge.Card} object. - * @return a int. - */ - public static int predictToughnessBonusOfBlocker(final Card attacker, final Card defender, boolean withoutAbilities) { - int toughness = 0; - - if (attacker.hasKeyword("Flanking") && !defender.hasKeyword("Flanking")) { - toughness -= attacker.getAmountOfKeyword("Flanking"); - } - - toughness += defender.getKeywordMagnitude("Bushido"); - - final ArrayList theTriggers = new ArrayList(); - for (Card card : Singletons.getModel().getGame().getCardsIn(ZoneType.Battlefield)) { - theTriggers.addAll(card.getTriggers()); - } - theTriggers.addAll(attacker.getTriggers()); - for (final Trigger trigger : theTriggers) { - final HashMap trigParams = trigger.getMapParams(); - final Card source = trigger.getHostCard(); - - if (!CombatUtil.combatTriggerWillTrigger(attacker, defender, trigger, null) - || !trigParams.containsKey("Execute")) { - continue; - } - final String ability = source.getSVar(trigParams.get("Execute")); - final Map abilityParams = AbilityFactory.getMapParams(ability); - - // DealDamage triggers - if ((abilityParams.containsKey("AB") && abilityParams.get("AB").equals("DealDamage")) - || (abilityParams.containsKey("DB") && abilityParams.get("DB").equals("DealDamage"))) { - if (!abilityParams.containsKey("Defined") || !abilityParams.get("Defined").equals("TriggeredBlocker")) { - continue; - } - int damage = 0; - try { - damage = Integer.parseInt(abilityParams.get("NumDmg")); - } catch (final NumberFormatException nfe) { - // can't parse the number (X for example) - continue; - } - toughness -= defender.predictDamage(damage, 0, source, false); - continue; - } - - // Pump triggers - 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 - } - final List 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 (final NumberFormatException nfe) { - // can't parse the number (X for example) - - } - } - if (withoutAbilities) { - return toughness; - } - for (SpellAbility ability : defender.getAllSpellAbilities()) { - if (!(ability instanceof AbilityActivated) || ability.getPayCosts() == null) { - continue; - } - - if (ability.getApi() != ApiType.Pump || !ability.hasParam("NumDef")) { - continue; - } - - if (ComputerUtilCost.canPayCost(ability, defender.getController())) { - int tBonus = AbilityFactory.calculateAmount(ability.getSourceCard(), ability.getParam("NumDef"), ability); - if (tBonus > 0) { - toughness += tBonus; - } - } - } - return toughness; - } - - // Predict the Power bonus of the blocker if blocking the attacker - // (Flanking, Bushido and other triggered abilities) - /** - *

- * predictPowerBonusOfAttacker. - *

- * - * @param attacker - * a {@link forge.Card} object. - * @param defender - * a {@link forge.Card} object. - * @param combat - * a {@link forge.game.phase.Combat} object. - * @return a int. - */ - public static int predictPowerBonusOfAttacker(final Card attacker, final Card defender, final Combat combat - , boolean withoutAbilities) { - int power = 0; - - power += attacker.getKeywordMagnitude("Bushido"); - //check Exalted only for the first attacker - if (combat != null && combat.getAttackers().isEmpty()) { - for (Card card : attacker.getController().getCardsIn(ZoneType.Battlefield)) { - power += card.getKeywordAmount("Exalted"); - } - } - - final ArrayList theTriggers = new ArrayList(); - for (Card card : Singletons.getModel().getGame().getCardsIn(ZoneType.Battlefield)) { - theTriggers.addAll(card.getTriggers()); - } - // 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(); - } - theTriggers.addAll(defender.getTriggers()); - } - - // look out for continuous static abilities that only care for attacking - // creatures - final List cardList = Singletons.getModel().getGame().getCardsIn(ZoneType.Battlefield); - for (final Card card : cardList) { - for (final StaticAbility stAb : card.getStaticAbilities()) { - final HashMap params = stAb.getMapParams(); - if (!params.get("Mode").equals("Continuous")) { - continue; - } - if (!params.containsKey("Affected") || !params.get("Affected").contains("attacking")) { - continue; - } - final String valid = params.get("Affected").replace("attacking", "Creature"); - if (!attacker.isValid(valid, card.getController(), card)) { - continue; - } - if (params.containsKey("AddPower")) { - if (params.get("AddPower").equals("X")) { - power += CardFactoryUtil.xCount(card, card.getSVar("X")); - } else if (params.get("AddPower").equals("Y")) { - power += CardFactoryUtil.xCount(card, card.getSVar("Y")); - } else { - power += Integer.valueOf(params.get("AddPower")); - } - } - } - } - - for (final Trigger trigger : theTriggers) { - final HashMap trigParams = trigger.getMapParams(); - final Card source = trigger.getHostCard(); - - if (!CombatUtil.combatTriggerWillTrigger(attacker, defender, trigger, combat) - || !trigParams.containsKey("Execute")) { - continue; - } - final String ability = source.getSVar(trigParams.get("Execute")); - final Map abilityParams = AbilityFactory.getMapParams(ability); - 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; - } - List list = new ArrayList(); - if (!abilityParams.containsKey("ValidCards")) { - 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.isValid(abilityParams.get("ValidCards").split(","), source.getController(), source) - || attacker.isValid(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); - } - if (att.matches("[0-9][0-9]?") || att.matches("-" + "[0-9][0-9]?")) { - power += Integer.parseInt(att); - } else { - String bonus = new String(source.getSVar(att)); - if (bonus.contains("TriggerCount$NumBlockers")) { - bonus = bonus.replace("TriggerCount$NumBlockers", "Number$1"); - } - power += CardFactoryUtil.xCount(source, bonus); - - } - } - if (withoutAbilities) { - return power; - } - for (SpellAbility ability : attacker.getAllSpellAbilities()) { - if (!(ability instanceof AbilityActivated) || ability.getPayCosts() == null) { - continue; - } - if (ability.getApi() != ApiType.Pump) { - continue; - } - - if (!ability.hasParam("NumAtt")) { - continue; - } - - if (!ability.getPayCosts().hasTapCost() && ComputerUtilCost.canPayCost(ability, attacker.getController())) { - int pBonus = AbilityFactory.calculateAmount(ability.getSourceCard(), ability.getParam("NumAtt"), ability); - if (pBonus > 0) { - power += pBonus; - } - } - } - return power; - } - - // Predict the Toughness bonus of the attacker if blocked by the blocker - // (Flanking, Bushido and other triggered abilities) - /** - *

- * predictToughnessBonusOfAttacker. - *

- * - * @param attacker - * a {@link forge.Card} object. - * @param defender - * a {@link forge.Card} object. - * @param combat - * a {@link forge.game.phase.Combat} object. - * @return a int. - */ - public static int predictToughnessBonusOfAttacker(final Card attacker, final Card defender, final Combat combat - , boolean withoutAbilities) { - int toughness = 0; - - //check Exalted only for the first attacker - if (combat != null && combat.getAttackers().isEmpty()) { - for (Card card : attacker.getController().getCardsIn(ZoneType.Battlefield)) { - toughness += card.getKeywordAmount("Exalted"); - } - } - - final ArrayList theTriggers = new ArrayList(); - for (Card card : Singletons.getModel().getGame().getCardsIn(ZoneType.Battlefield)) { - theTriggers.addAll(card.getTriggers()); - } - if (defender != null) { - toughness += attacker.getKeywordMagnitude("Bushido"); - theTriggers.addAll(defender.getTriggers()); - } - - // look out for continuous static abilities that only care for attacking - // creatures - final List cardList = Singletons.getModel().getGame().getCardsIn(ZoneType.Battlefield); - for (final Card card : cardList) { - for (final StaticAbility stAb : card.getStaticAbilities()) { - final HashMap params = stAb.getMapParams(); - if (!params.get("Mode").equals("Continuous")) { - continue; - } - if (!params.containsKey("Affected") || !params.get("Affected").contains("attacking")) { - continue; - } - final String valid = params.get("Affected").replace("attacking", "Creature"); - if (!attacker.isValid(valid, card.getController(), card)) { - continue; - } - if (params.containsKey("AddToughness")) { - if (params.get("AddToughness").equals("X")) { - toughness += CardFactoryUtil.xCount(card, card.getSVar("X")); - } else if (params.get("AddToughness").equals("Y")) { - toughness += CardFactoryUtil.xCount(card, card.getSVar("Y")); - } else { - toughness += Integer.valueOf(params.get("AddToughness")); - } - } - } - } - - for (final Trigger trigger : theTriggers) { - final HashMap trigParams = trigger.getMapParams(); - final Card source = trigger.getHostCard(); - - if (!CombatUtil.combatTriggerWillTrigger(attacker, defender, trigger, combat) - || !trigParams.containsKey("Execute")) { - continue; - } - final String ability = source.getSVar(trigParams.get("Execute")); - final Map abilityParams = AbilityFactory.getMapParams(ability); - if (abilityParams.containsKey("ValidTgts") || abilityParams.containsKey("Tgt")) { - continue; // targeted pumping not supported - } - - // DealDamage triggers - if ((abilityParams.containsKey("AB") && abilityParams.get("AB").equals("DealDamage")) - || (abilityParams.containsKey("DB") && abilityParams.get("DB").equals("DealDamage"))) { - if (!abilityParams.containsKey("Defined") || !abilityParams.get("Defined").equals("TriggeredAttacker")) { - continue; - } - int damage = 0; - try { - damage = Integer.parseInt(abilityParams.get("NumDmg")); - } catch (final NumberFormatException nfe) { - // can't parse the number (X for example) - continue; - } - toughness -= attacker.predictDamage(damage, 0, source, false); - continue; - } - - // Pump triggers - 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; - } - List list = new ArrayList(); - if (!abilityParams.containsKey("ValidCards")) { - 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.isValid(abilityParams.get("ValidCards").split(","), source.getController(), source) - || attacker.isValid(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); - } - if (def.matches("[0-9][0-9]?") || def.matches("-" + "[0-9][0-9]?")) { - toughness += Integer.parseInt(def); - } else { - String bonus = new String(source.getSVar(def)); - if (bonus.contains("TriggerCount$NumBlockers")) { - bonus = bonus.replace("TriggerCount$NumBlockers", "Number$1"); - } - toughness += CardFactoryUtil.xCount(source, bonus); - } - } - if (withoutAbilities) { - return toughness; - } - for (SpellAbility ability : attacker.getAllSpellAbilities()) { - if (!(ability instanceof AbilityActivated) || ability.getPayCosts() == null) { - continue; - } - - if (ability.getApi() != ApiType.Pump || !ability.hasParam("NumDef")) { - continue; - } - - if (!ability.getPayCosts().hasTapCost() && ComputerUtilCost.canPayCost(ability, attacker.getController())) { - int tBonus = AbilityFactory.calculateAmount(ability.getSourceCard(), ability.getParam("NumDef"), ability); - if (tBonus > 0) { - toughness += tBonus; - } - } - } - return toughness; - } - - // Sylvan Basilisk and friends - /** - *

- * checkDestroyBlockerTrigger. - *

- * - * @param attacker - * a {@link forge.Card} object. - * @param defender - * a {@link forge.Card} object. - * @return a boolean. - */ - public static boolean checkDestroyBlockerTrigger(final Card attacker, final Card defender) { - final ArrayList theTriggers = new ArrayList(); - for (Card card : Singletons.getModel().getGame().getCardsIn(ZoneType.Battlefield)) { - theTriggers.addAll(card.getTriggers()); - } - for (Trigger trigger : theTriggers) { - HashMap trigParams = trigger.getMapParams(); - final Card source = trigger.getHostCard(); - - if (!CombatUtil.combatTriggerWillTrigger(attacker, defender, trigger, null)) { - continue; - } - //consider delayed triggers - if (trigParams.containsKey("DelayedTrigger")) { - String sVarName = trigParams.get("DelayedTrigger"); - trigger = TriggerHandler.parseTrigger(source.getSVar(sVarName), trigger.getHostCard(), true); - trigParams = trigger.getMapParams(); - } - if (!trigParams.containsKey("Execute")) { - continue; - } - String ability = source.getSVar(trigParams.get("Execute")); - final Map abilityParams = AbilityFactory.getMapParams(ability); - // Destroy triggers - if ((abilityParams.containsKey("AB") && abilityParams.get("AB").equals("Destroy")) - || (abilityParams.containsKey("DB") && abilityParams.get("DB").equals("Destroy"))) { - if (!abilityParams.containsKey("Defined")) { - continue; - } - if (abilityParams.get("Defined").equals("TriggeredBlocker")) { - return true; - } - if (abilityParams.get("Defined").equals("Self") && source.equals(defender)) { - return true; - } - if (abilityParams.get("Defined").equals("TriggeredTarget") && source.equals(attacker)) { - return true; - } - } - } - return false; - } - - // Cockatrice and friends - /** - *

- * checkDestroyBlockerTrigger. - *

- * - * @param attacker - * a {@link forge.Card} object. - * @param defender - * a {@link forge.Card} object. - * @return a boolean. - */ - public static boolean checkDestroyAttackerTrigger(final Card attacker, final Card defender) { - final ArrayList theTriggers = new ArrayList(); - for (Card card : Singletons.getModel().getGame().getCardsIn(ZoneType.Battlefield)) { - theTriggers.addAll(card.getTriggers()); - } - for (Trigger trigger : theTriggers) { - HashMap trigParams = trigger.getMapParams(); - final Card source = trigger.getHostCard(); - - if (!CombatUtil.combatTriggerWillTrigger(attacker, defender, trigger, null)) { - continue; - } - //consider delayed triggers - if (trigParams.containsKey("DelayedTrigger")) { - String sVarName = trigParams.get("DelayedTrigger"); - trigger = TriggerHandler.parseTrigger(source.getSVar(sVarName), trigger.getHostCard(), true); - trigParams = trigger.getMapParams(); - } - if (!trigParams.containsKey("Execute")) { - continue; - } - String ability = source.getSVar(trigParams.get("Execute")); - final Map abilityParams = AbilityFactory.getMapParams(ability); - // Destroy triggers - if ((abilityParams.containsKey("AB") && abilityParams.get("AB").equals("Destroy")) - || (abilityParams.containsKey("DB") && abilityParams.get("DB").equals("Destroy"))) { - if (!abilityParams.containsKey("Defined")) { - continue; - } - if (abilityParams.get("Defined").equals("TriggeredAttacker")) { - return true; - } - if (abilityParams.get("Defined").equals("Self") && source.equals(attacker)) { - return true; - } - if (abilityParams.get("Defined").equals("TriggeredTarget") && source.equals(defender)) { - return true; - } - } - } - return false; - } - - // can the blocker destroy the attacker? - /** - *

- * canDestroyAttacker. - *

- * - * @param attacker - * a {@link forge.Card} object. - * @param defender - * a {@link forge.Card} object. - * @param combat - * a {@link forge.game.phase.Combat} object. - * @param withoutAbilities - * a boolean. - * @return a boolean. - */ - public static boolean canDestroyAttacker(final Card attacker, final Card defender, final Combat combat, - final 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"))) - || (attacker.hasKeyword("Persist") && !attacker.canHaveCountersPlacedOnIt(CounterType.M1M1) && (attacker - .getCounters(CounterType.M1M1) == 0)) - || (attacker.hasKeyword("Undying") && !attacker.canHaveCountersPlacedOnIt(CounterType.P1P1) && (attacker - .getCounters(CounterType.P1P1) == 0))) { - return false; - } - if (checkDestroyAttackerTrigger(attacker, defender) && !attacker.hasKeyword("Indestructible")) { - return true; - } - - int defenderDamage = defender.getNetAttack() - + CombatUtil.predictPowerBonusOfBlocker(attacker, defender, withoutAbilities); - int attackerDamage = attacker.getNetAttack() - + CombatUtil.predictPowerBonusOfAttacker(attacker, defender, combat, withoutAbilities); - if (Singletons.getModel().getGame().getStaticEffects().getGlobalRuleChange(GlobalRuleChange.toughnessAssignsDamage)) { - defenderDamage = defender.getNetDefense() - + CombatUtil.predictToughnessBonusOfBlocker(attacker, defender, withoutAbilities); - attackerDamage = attacker.getNetDefense() - + CombatUtil.predictToughnessBonusOfAttacker(attacker, defender, combat, withoutAbilities); - } - - 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); - - final int defenderLife = defender.getKillDamage() - + CombatUtil.predictToughnessBonusOfBlocker(attacker, defender, withoutAbilities); - final int attackerLife = attacker.getKillDamage() - + CombatUtil.predictToughnessBonusOfAttacker(attacker, defender, combat, withoutAbilities); - - if (defender.hasKeyword("Double Strike")) { - if (defenderDamage > 0 && (defender.hasKeyword("Deathtouch") - || attacker.hasStartOfKeyword("When CARDNAME is dealt damage, destroy it."))) { - 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") - || defender.hasStartOfKeyword("When CARDNAME is dealt damage, destroy it."))) { - 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") - || defender.hasStartOfKeyword("When CARDNAME is dealt damage, destroy it."))) { - return false; - } - } - - if (defenderDamage > 0 && (defender.hasKeyword("Deathtouch") - || attacker.hasStartOfKeyword("When CARDNAME is dealt damage, destroy it."))) { - return true; - } - - return defenderDamage >= attackerLife; - - } // defender no double strike - return false; // should never arrive here - } // canDestroyAttacker - - // For AI safety measures like Regeneration - /** - *

- * blockerWouldBeDestroyed. - *

- * - * @param blocker - * a {@link forge.Card} object. - * @return a boolean. - */ - public static boolean blockerWouldBeDestroyed(final Card blocker) { - // TODO THis function only checks if a single attacker at a time would destroy a blocker - // This needs to expand to tally up damage - final List attackers = Singletons.getModel().getGame().getCombat().getAttackersBlockedBy(blocker); - - for (Card attacker : attackers) { - if (CombatUtil.canDestroyBlocker(blocker, attacker, Singletons.getModel().getGame().getCombat(), true) - && !(attacker.hasKeyword("Wither") || attacker.hasKeyword("Infect"))) { - return true; - } - } - return false; - } - - // can the attacker destroy this blocker? - /** - *

- * canDestroyBlocker. - *

- * - * @param defender - * a {@link forge.Card} object. - * @param attacker - * a {@link forge.Card} object. - * @param combat - * a {@link forge.game.phase.Combat} object. - * @param withoutAbilities - * a boolean. - * @return a boolean. - */ - public static boolean canDestroyBlocker(final Card defender, final Card attacker, final Combat combat, - final 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"))) - || (defender.hasKeyword("Persist") && !defender.canHaveCountersPlacedOnIt(CounterType.M1M1) && (defender - .getCounters(CounterType.M1M1) == 0)) - || (defender.hasKeyword("Undying") && !defender.canHaveCountersPlacedOnIt(CounterType.P1P1) && (defender - .getCounters(CounterType.P1P1) == 0))) { - return false; - } - - if (checkDestroyBlockerTrigger(attacker, defender) && !defender.hasKeyword("Indestructible")) { - return true; - } - - int defenderDamage = defender.getNetAttack() - + CombatUtil.predictPowerBonusOfBlocker(attacker, defender, withoutAbilities); - int attackerDamage = attacker.getNetAttack() - + CombatUtil.predictPowerBonusOfAttacker(attacker, defender, combat, withoutAbilities); - if (Singletons.getModel().getGame().getStaticEffects().getGlobalRuleChange(GlobalRuleChange.toughnessAssignsDamage)) { - defenderDamage = defender.getNetDefense() - + CombatUtil.predictToughnessBonusOfBlocker(attacker, defender, withoutAbilities); - attackerDamage = attacker.getNetDefense() - + CombatUtil.predictToughnessBonusOfAttacker(attacker, defender, combat, withoutAbilities); - } - - 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); - - if (combat != null) { - for (Card atkr : combat.getAttackersBlockedBy(defender)) { - if (!atkr.equals(attacker)) { - attackerDamage += defender.predictDamage(atkr.getNetCombatDamage(), 0, atkr, true); - } - } - } - - final int defenderLife = defender.getKillDamage() - + CombatUtil.predictToughnessBonusOfBlocker(attacker, defender, withoutAbilities); - final int attackerLife = attacker.getKillDamage() - + CombatUtil.predictToughnessBonusOfAttacker(attacker, defender, combat, withoutAbilities); - - if (attacker.hasKeyword("Double Strike")) { - if (attackerDamage > 0 && (attacker.hasKeyword("Deathtouch") - || defender.hasStartOfKeyword("When CARDNAME is dealt damage, destroy it."))) { - 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") - || attacker.hasStartOfKeyword("When CARDNAME is dealt damage, destroy it."))) { - 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") - || attacker.hasStartOfKeyword("When CARDNAME is dealt damage, destroy it."))) { - return false; - } - } - - if (attackerDamage > 0 && (attacker.hasKeyword("Deathtouch") - || defender.hasStartOfKeyword("When CARDNAME is dealt damage, destroy it."))) { - return true; - } - - return attackerDamage >= defenderLife; - - } // attacker no double strike - return false; // should never arrive here - } // canDestroyBlocker /** * gets a string for the GameLog regarding attackers.