diff --git a/forge-ai/src/main/java/forge/ai/AiCostDecision.java b/forge-ai/src/main/java/forge/ai/AiCostDecision.java index 4d6e94929dd..2d93d12cf52 100644 --- a/forge-ai/src/main/java/forge/ai/AiCostDecision.java +++ b/forge-ai/src/main/java/forge/ai/AiCostDecision.java @@ -347,6 +347,9 @@ public class AiCostDecision extends CostDecisionMakerBase { if (source.getName().equals("Maralen of the Mornsong Avatar")) { return PaymentDecision.number(2); } + if (source.getName().equals("Necrologia")) { + return PaymentDecision.number(Integer.parseInt(ability.getSVar("ChosenX"))); + } return null; } else { c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability); diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtil.java b/forge-ai/src/main/java/forge/ai/ComputerUtil.java index 8145507723b..41318103cd5 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtil.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtil.java @@ -2831,4 +2831,39 @@ public class ComputerUtil { return srcList; } + + // Check if AI life is in danger/serious danger based on next expected combat + // assuming a loss of "payment" life + // call this to determine if it's safe to use a life payment spell + // or trigger "emergency" strategies such as holding mana for Spike Weaver of Counterspell. + public static boolean aiLifeInDanger(Player ai, boolean serious, int payment) { + Player opponent = ComputerUtil.getOpponentFor(ai); + // test whether the human can kill the ai next turn + Combat combat = new Combat(opponent); + boolean containsAttacker = false; + for (Card att : opponent.getCreaturesInPlay()) { + if (ComputerUtilCombat.canAttackNextTurn(att, ai)) { + combat.addAttacker(att, ai); + containsAttacker = true; + } + } + if (!containsAttacker) { + return false; + } + AiBlockController block = new AiBlockController(ai); + block.assignBlockersForCombat(combat); + + // TODO predict other, noncombat sources of damage and add them to the "payment" variable. + // examples : Black Vise, The Rack, known direct damage spells in enemy hand, etc + // If added, might need a parameter to define whether we want to check all threats or combat threats. + + if ((serious) && (ComputerUtilCombat.lifeInSeriousDanger(ai, combat, payment))) { + return true; + } + if ((!serious) && (ComputerUtilCombat.lifeInDanger(ai, combat, payment))) { + return true; + } + return false; + + } } diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java index c699e3eec7f..f9522430688 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java @@ -382,6 +382,10 @@ public class ComputerUtilCombat { * @return a boolean. */ public static boolean lifeInDanger(final Player ai, final Combat combat) { + return lifeInDanger(ai, combat, 0); + } + + public static boolean lifeInDanger(final Player ai, final Combat combat, final int payment) { // life in danger only cares about the player's life. Not Planeswalkers' life if (ai.cantLose() || combat == null || combat.getAttackingPlayer() == ai) { return false; @@ -401,12 +405,12 @@ public class ComputerUtilCombat { // check for creatures that must be blocked final List attackers = combat.getAttackersOf(ai); - + final List threateningCommanders = getLifeThreateningCommanders(ai,combat); for (final Card attacker : attackers) { - final List blockers = combat.getBlockers(attacker); + final List blockers = combat.getBlockers(attacker); if (blockers.isEmpty()) { if (!attacker.getSVar("MustBeBlocked").equals("")) { @@ -429,7 +433,7 @@ public class ComputerUtilCombat { } } - if (ComputerUtilCombat.lifeThatWouldRemain(ai, combat) < Math.min(4, ai.getLife()) + if (ComputerUtilCombat.lifeThatWouldRemain(ai, combat) - payment < Math.min(4, ai.getLife()) && !ai.cantLoseForZeroOrLessLife()) { return true; } @@ -463,12 +467,16 @@ public class ComputerUtilCombat { * @return a boolean. */ public static boolean lifeInSeriousDanger(final Player ai, final Combat combat) { + return lifeInSeriousDanger(ai, combat, 0); + } + + public static boolean lifeInSeriousDanger(final Player ai, final Combat combat, final int payment) { // life in danger only cares about the player's life. Not about a // Planeswalkers life if (ai.cantLose() || combat == null) { return false; } - + final List threateningCommanders = ComputerUtilCombat.getLifeThreateningCommanders(ai, combat); // check for creatures that must be blocked @@ -488,7 +496,7 @@ public class ComputerUtilCombat { } } - if (ComputerUtilCombat.lifeThatWouldRemain(ai, combat) < 1 && !ai.cantLoseForZeroOrLessLife()) { + if (ComputerUtilCombat.lifeThatWouldRemain(ai, combat) - payment < 1 && !ai.cantLoseForZeroOrLessLife()) { return true; } diff --git a/forge-ai/src/main/java/forge/ai/ability/DrawAi.java b/forge-ai/src/main/java/forge/ai/ability/DrawAi.java index 9ffe3e22f16..2e5c7715823 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DrawAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DrawAi.java @@ -251,6 +251,20 @@ public class DrawAi extends SpellAbilityAi { } } + if (num != null && num.equals("ChosenX")) { + // Necrologia, Pay X Life : Draw X Cards + if (sa.getSVar("X").equals("XChoice")) { + // Draw up to max hand size but leave at least 3 in library + numCards = Math.min(computerMaxHandSize - computerHandSize, computerLibrarySize - 3); + // But no more than what's "safe" and doesn't risk a near death experience + // Maybe would be better to check for "serious danger" and take more risk? + while ((ComputerUtil.aiLifeInDanger(ai, false, numCards) && (numCards > 0))) { + numCards--; + } + sa.setSVar("ChosenX", Integer.toString(numCards)); + source.setSVar("ChosenX", Integer.toString(numCards)); + } + } // Logic for cards that require special handling if ("YawgmothsBargain".equals(logic)) { return SpecialCardAi.YawgmothsBargain.consider(ai, sa); diff --git a/forge-gui/res/cardsfolder/n/necrologia.txt b/forge-gui/res/cardsfolder/n/necrologia.txt index d1a9487f995..4cfbc6b3919 100644 --- a/forge-gui/res/cardsfolder/n/necrologia.txt +++ b/forge-gui/res/cardsfolder/n/necrologia.txt @@ -5,6 +5,5 @@ Text:Cast CARDNAME only during your end step.\r\n A:SP$ Draw | Cost$ 3 B B PayLife | NumCards$ ChosenX | Defined$ You | ActivationPhases$ End of Turn | PlayerTurn$ True | References$ X | SpellDescription$ Draw X cards. SVar:X:XChoice #ChosenX SVar created by Cost payment -SVar:RemAIDeck:True SVar:Picture:http://www.wizards.com/global/images/magic/general/necrologia.jpg Oracle:Cast Necrologia only during your end step.\nAs an additional cost to cast Necrologia, pay X life.\nDraw X cards.