diff --git a/forge-ai/src/main/java/forge/ai/ability/LifeLoseAi.java b/forge-ai/src/main/java/forge/ai/ability/LifeLoseAi.java index 72abec77dcc..1cabf2eedd2 100644 --- a/forge-ai/src/main/java/forge/ai/ability/LifeLoseAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/LifeLoseAi.java @@ -1,5 +1,9 @@ package forge.ai.ability; +import java.util.List; + +import com.google.common.base.Predicates; + import forge.ai.ComputerUtil; import forge.ai.ComputerUtilCost; import forge.ai.ComputerUtilMana; @@ -9,29 +13,37 @@ import forge.game.card.Card; import forge.game.cost.Cost; import forge.game.phase.PhaseType; import forge.game.player.Player; +import forge.game.player.PlayerCollection; +import forge.game.player.PlayerPredicates; import forge.game.spellability.SpellAbility; -import forge.game.spellability.TargetRestrictions; import forge.util.collect.FCollection; -import java.util.List; - public class LifeLoseAi extends SpellAbilityAi { + /* + * (non-Javadoc) + * + * @see forge.ai.SpellAbilityAi#chkAIDrawback(forge.game.spellability. + * SpellAbility, forge.game.player.Player) + */ @Override public boolean chkAIDrawback(SpellAbility sa, Player ai) { - final List tgtPlayers = sa.usesTargeting() && !sa.hasParam("Defined") - ? new FCollection(sa.getTargets().getTargetPlayers()) - : AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("Defined"), sa); + final PlayerCollection tgtPlayers = getPlayers(ai, sa); final Card source = sa.getHostCard(); final String amountStr = sa.getParam("LifeAmount"); int amount = 0; if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) { - // Set PayX here to maximum value. - final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai); - source.setSVar("PayX", Integer.toString(xPay)); - amount = xPay; + // something already set PayX + if (source.hasSVar("PayX")) { + amount = Integer.parseInt(source.getSVar("PayX")); + } else { + // Set PayX here to maximum value. + final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai); + source.setSVar("PayX", Integer.toString(xPay)); + amount = xPay; + } } else { amount = AbilityUtils.calculateAmount(source, amountStr, sa); } @@ -42,80 +54,84 @@ public class LifeLoseAi extends SpellAbilityAi { return true; } - /* (non-Javadoc) - * @see forge.card.abilityfactory.AbilityFactoryAlterLife.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility) + + /* + * (non-Javadoc) + * + * @see forge.ai.SpellAbilityAi#willPayCosts(forge.game.player.Player, + * forge.game.spellability.SpellAbility, forge.game.cost.Cost, + * forge.game.card.Card) */ @Override - protected boolean canPlayAI(Player ai, SpellAbility sa) { - - final Cost abCost = sa.getPayCosts(); - final Card source = sa.getHostCard(); - + protected boolean willPayCosts(Player ai, SpellAbility sa, Cost cost, Card source) { final String amountStr = sa.getParam("LifeAmount"); + int amount = 0; - // TODO handle proper calculation of X values based on Cost and what - // would be paid - int amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa); + if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) { + // Set PayX here to maximum value. + amount = ComputerUtilMana.determineLeftoverMana(sa, ai); + // source.setSVar("PayX", Integer.toString(amount)); + } else { + amount = AbilityUtils.calculateAmount(source, amountStr, sa); + } + + // special logic for checkLifeCost + if (!ComputerUtilCost.checkLifeCost(ai, cost, source, amount, sa)) { + return false; + } + + // other cost as the same + return super.willPayCosts(ai, sa, cost, source); + } + + /* + * (non-Javadoc) + * + * @see forge.ai.SpellAbilityAi#checkApiLogic(forge.game.player.Player, + * forge.game.spellability.SpellAbility) + */ + @Override + protected boolean checkApiLogic(Player ai, SpellAbility sa) { + final Card source = sa.getHostCard(); + final String amountStr = sa.getParam("LifeAmount"); + int amount = 0; if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) { // Set PayX here to maximum value. amount = ComputerUtilMana.determineLeftoverMana(sa, ai); source.setSVar("PayX", Integer.toString(amount)); + } else { + amount = AbilityUtils.calculateAmount(source, amountStr, sa); } if (amount <= 0) { return false; } - if (abCost != null) { - // AI currently disabled for these costs - if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, amount, sa)) { - return false; - } - - if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) { - return false; - } - - if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source)) { - return false; - } - - if (!ComputerUtilCost.checkRemoveCounterCost(abCost, source)) { - return false; - } - } - - Player opp = ai.getOpponent(); - - if (!opp.canLoseLife()) { - return false; - } - if (ComputerUtil.preventRunAwayActivations(sa)) { return false; } if (sa.usesTargeting()) { - sa.resetTargets(); - if (sa.canTarget(opp)) { - sa.getTargets().add(opp); - } else { + if (!doTgt(ai, sa, false)) { return false; } } - - if (amount >= opp.getLife()) { - return true; // killing the human should be done asap - } + final PlayerCollection tgtPlayers = getPlayers(ai, sa); if (ComputerUtil.playImmediately(ai, sa)) { return true; } + PlayerCollection filteredPlayer = tgtPlayers + .filter(Predicates.and(PlayerPredicates.isOpponentOf(ai), PlayerPredicates.lifeLessOrEqualTo(amount))); + // killing opponents asap + if (!filteredPlayer.isEmpty()) { + return true; + } + // Don't use loselife before main 2 if possible - if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2) - && !sa.hasParam("ActivationPhases") + if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2) && !sa.hasParam("ActivationPhases") && !ComputerUtil.castSpellInMain1(ai, sa)) { return false; } @@ -125,9 +141,7 @@ public class LifeLoseAi extends SpellAbilityAi { return false; } - if (SpellAbilityAi.isSorcerySpeed(sa) - || sa.hasParam("ActivationPhases") - || SpellAbilityAi.playReusable(ai, sa) + if (SpellAbilityAi.isSorcerySpeed(sa) || sa.hasParam("ActivationPhases") || SpellAbilityAi.playReusable(ai, sa) || ComputerUtil.activateForCost(sa, ai)) { return true; } @@ -135,16 +149,17 @@ public class LifeLoseAi extends SpellAbilityAi { return false; } + /* + * (non-Javadoc) + * + * @see forge.ai.SpellAbilityAi#doTriggerAINoCost(forge.game.player.Player, + * forge.game.spellability.SpellAbility, boolean) + */ @Override protected boolean doTriggerAINoCost(final Player ai, final SpellAbility sa, final boolean mandatory) { - final TargetRestrictions tgt = sa.getTargetRestrictions(); - if (tgt != null) { - if (sa.canTarget(ai.getOpponent())) { - sa.getTargets().add(ai.getOpponent()); - } else if (mandatory && sa.canTarget(ai)) { - sa.getTargets().add(ai); - } else { + if (sa.usesTargeting()) { + if (!doTgt(ai, sa, mandatory)) { return false; } } @@ -172,4 +187,50 @@ public class LifeLoseAi extends SpellAbilityAi { return true; } + + protected boolean doTgt(Player ai, SpellAbility sa, boolean mandatory) { + sa.resetTargets(); + PlayerCollection opps = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa)); + // try first to find Opponent that can lose life and lose the game + if (!opps.isEmpty()) { + for (Player opp : opps) { + if (opp.canLoseLife() && !opp.cantLose()) { + sa.getTargets().add(opp); + return true; + } + } + } + + // do that only if needed + if (mandatory) { + if (!opps.isEmpty()) { + // try another opponent even if it can't lose life + sa.getTargets().add(opps.getFirst()); + return true; + } + // try hit ally instead of itself + for (Player ally : ai.getAllies()) { + if (sa.canTarget(ally)) { + sa.getTargets().add(ally); + return true; + } + } + // need to hit itself + if (sa.canTarget(ai)) { + sa.getTargets().add(ai); + return true; + } + } + return false; + } + + protected PlayerCollection getPlayers(Player ai, SpellAbility sa) { + Iterable it; + if (sa.usesTargeting() && !sa.hasParam("Defined")) { + it = sa.getTargets().getTargetPlayers(); + } else { + it = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("Defined"), sa); + } + return new PlayerCollection(it); + } }