Merge remote-tracking branch 'remotes/origin/AINecrologia' into PriceOfProgress

This commit is contained in:
Seravy
2018-02-14 21:34:24 +01:00
5 changed files with 65 additions and 6 deletions

View File

@@ -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);

View File

@@ -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;
}
}

View File

@@ -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<Card> attackers = combat.getAttackersOf(ai);
final List<Card> threateningCommanders = getLifeThreateningCommanders(ai,combat);
for (final Card attacker : attackers) {
final List<Card> blockers = combat.getBlockers(attacker);
final List<Card> 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<Card> 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;
}

View File

@@ -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);

View File

@@ -5,6 +5,5 @@ Text:Cast CARDNAME only during your end step.\r\n
A:SP$ Draw | Cost$ 3 B B PayLife<X> | 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.