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