From dc01a30136bb87ffa967d58f4c6eea18c0d9db8b Mon Sep 17 00:00:00 2001 From: Agetian Date: Fri, 27 Jan 2017 05:58:44 +0000 Subject: [PATCH] - More advanced Life Drain AI, now works with Soul Burn too, and also works correctly when the AI has other mana sources that do not provide black mana on the battlefield. --- .../main/java/forge/ai/ComputerUtilMana.java | 29 +++++++++++++++++++ .../java/forge/ai/ability/DamageDealAi.java | 29 +++++++++++++------ forge-gui/res/cardsfolder/s/soul_burn.txt | 2 +- 3 files changed, 50 insertions(+), 10 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java b/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java index 4fdc3a21197..ce0ed8fb8ba 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java @@ -12,6 +12,7 @@ import forge.card.ColorSet; import forge.card.MagicColor; import forge.card.mana.ManaAtom; import forge.card.mana.ManaCost; +import forge.card.mana.ManaCostParser; import forge.card.mana.ManaCostShard; import forge.game.Game; import forge.game.GameActionUtil; @@ -1295,6 +1296,34 @@ public class ComputerUtilMana { return 99; } + /** + *

+ * determineLeftoverMana. + *

+ * + * @param sa + * a {@link forge.game.spellability.SpellAbility} object. + * @param player + * a {@link forge.game.player.Player} object. + * @param shardColor + * a mana shard to specifically test for. + * @return a int. + * @since 1.0.15 + */ + public static int determineLeftoverMana(final SpellAbility sa, final Player player, final String shardColor) { + ManaCost origCost = sa.getPayCosts().getTotalMana(); + + String shardSurplus = shardColor; + for (int i = 1; i < 100; i++) { + ManaCost extra = new ManaCost(new ManaCostParser(shardSurplus)); + if (!canPayManaCost(new ManaCostBeingPaid(ManaCost.combine(origCost, extra)), sa, player)) { + return i - 1; + } + shardSurplus += " " + shardColor; + } + return 99; + } + // Returns basic mana abilities plus "reflected mana" abilities /** *

diff --git a/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java b/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java index 4a5e261629e..90dec2c7905 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java @@ -23,8 +23,10 @@ import forge.game.spellability.TargetChoices; import forge.game.spellability.TargetRestrictions; import forge.game.zone.ZoneType; import forge.util.Aggregates; +import java.util.HashMap; import java.util.List; +import java.util.Map; public class DamageDealAi extends DamageAiBase { @Override @@ -57,14 +59,14 @@ public class DamageDealAi extends DamageAiBase { } if (damage.equals("X")) { if (sa.getSVar(damage).equals("Count$xPaid")) { + // Life Drain + if ("XLifeDrain".equals(logic)) { + return doXLifeDrainLogic(ai, sa); + } + // Set PayX here to maximum value. dmg = ComputerUtilMana.determineLeftoverMana(sa, ai); source.setSVar("PayX", Integer.toString(dmg)); - - // Life Drain - if ("XLifeDrain".equals(logic)) { - return doXLifeDrainLogic(ai, sa, dmg); - } } else if (sa.getSVar(damage).equals("Count$CardsInYourHand") && source.getZone().is(ZoneType.Hand)) { dmg--; // the card will be spent casting the spell, so actual damage is 1 less } @@ -702,7 +704,7 @@ public class DamageDealAi extends DamageAiBase { return true; } - private boolean doXLifeDrainLogic(Player ai, SpellAbility sa, int origDmg) { + private boolean doXLifeDrainLogic(Player ai, SpellAbility sa) { Card source = sa.getHostCard(); // detect the top ability that actually targets in Drain Life and Soul Burn scripts @@ -711,10 +713,20 @@ public class DamageDealAi extends DamageAiBase { saTgt = saTgt.getParent(); } - // TODO: somehow account for the cost reduction? - int dmg = origDmg - saTgt.getPayCosts().getTotalMana().getCMC(); // otherwise AI incorrectly calculates mana it can afford Player opponent = ai.getOpponents().min(PlayerPredicates.compareByLife()); + // TODO: somehow account for the possible cost reduction? + int xColorRemaining = ComputerUtilMana.determineLeftoverMana(sa, ai, saTgt.getParam("XColor")); + int dmg = xColorRemaining - saTgt.getPayCosts().getTotalMana().getCMC(); // otherwise AI incorrectly calculates mana it can afford + + // set the color map for black X for the purpose of Soul Burn + // TODO: somehow generalize this calculation to allow other potential similar cards to function in the future + if ("Soul Burn".equals(source.getName())) { + Map xByColor = new HashMap<>(); + xByColor.put("B", dmg - ComputerUtilMana.determineLeftoverMana(sa, ai, "R")); + source.setXManaCostPaidByColor(xByColor); + } + if (dmg < 3 && dmg < opponent.getLife()) { return false; } @@ -739,7 +751,6 @@ public class DamageDealAi extends DamageAiBase { saTgt.resetTargets(); saTgt.getTargets().add(tgtCreature != null && dmg < opponent.getLife() ? tgtCreature : opponent); - // TODO: this currently does not work for Soul Burn because of xColorManaPaid (B/R) which the AI doesn't set source.setSVar("PayX", Integer.toString(dmg)); return true; } diff --git a/forge-gui/res/cardsfolder/s/soul_burn.txt b/forge-gui/res/cardsfolder/s/soul_burn.txt index 193dc2d73bf..4e1c23b3af8 100644 --- a/forge-gui/res/cardsfolder/s/soul_burn.txt +++ b/forge-gui/res/cardsfolder/s/soul_burn.txt @@ -3,7 +3,7 @@ ManaCost:X 2 B Types:Sorcery A:SP$ StoreSVar | Cost$ X 2 B | XColor$ BR | ValidTgts$ Creature,Player | TgtPrompt$ Select target creature or player | SVar$ Limit | Type$ Targeted | Expression$ CardToughness | SubAbility$ StoreTgtP | ConditionDefined$ Targeted | ConditionPresent$ Card.Creature | ConditionCompare$ GE1 | SpellDescription$ Spend only black and/or red mana on X. CARDNAME deals X damage to target creature or player. You gain life equal to the damage dealt, but not more than the amount of {B} spent on X, the player's life total before Soul Burn dealt damage, or the creature's toughness. SVar:StoreTgtP:DB$ StoreSVar | SVar$ Limit | Type$ Count | Expression$ TargetedLifeTotal | SubAbility$ DBDamage | ConditionDefined$ Targeted | ConditionPresent$ Card.Creature | ConditionCompare$ EQ0 -SVar:DBDamage:DB$ DealDamage | Defined$ Targeted | NumDmg$ X | SubAbility$ DBGainLife | References$ X +SVar:DBDamage:DB$ DealDamage | Defined$ Targeted | NumDmg$ X | SubAbility$ DBGainLife | References$ X | AILogic$ XLifeDrain SVar:X:Count$xPaid SVar:DBGainLife:DB$ GainLife | Defined$ You | LifeAmount$ DrainedLifeCard | References$ DrainedLifeCard SVar:DrainedLifeCard:SVar$BlackManaPaid/LimitMax.Limit