diff --git a/forge-ai/src/main/java/forge/ai/AiBlockController.java b/forge-ai/src/main/java/forge/ai/AiBlockController.java index dd8d0f881641..b2852cc19bb9 100644 --- a/forge-ai/src/main/java/forge/ai/AiBlockController.java +++ b/forge-ai/src/main/java/forge/ai/AiBlockController.java @@ -1005,10 +1005,21 @@ private void clearBlockers(final Combat combat, final List possibleBlocker public void assignBlockersForCombat(final Combat combat) { assignBlockersForCombat(combat, null); } - public void assignBlockersForCombat(final Combat combat, final CardCollection exludedBlockers) { + public void assignBlockersForCombat(final Combat combat, final CardCollection excludedBlockers) { + assignBlockersForCombat(combat, excludedBlockers, null); + } + public void assignBlockersForCombat(final Combat combat, final CardCollection excludedBlockers, + final CardCollectionView additionalBlockers) { List possibleBlockers = ai.getCreaturesInPlay(); - if (exludedBlockers != null && !exludedBlockers.isEmpty()) { - possibleBlockers.removeAll(exludedBlockers); + if (additionalBlockers != null) { + for (Card c : additionalBlockers) { + if (!possibleBlockers.contains(c)) { + possibleBlockers.add(c); + } + } + } + if (excludedBlockers != null && !excludedBlockers.isEmpty()) { + possibleBlockers.removeAll(excludedBlockers); } attackers = sortPotentialAttackers(combat); assignBlockers(combat, possibleBlockers); @@ -1019,14 +1030,7 @@ public void assignBlockersForCombat(final Combat combat, final CardCollection ex * @param blockers blockers to add in addition to creatures already in play */ public void assignAdditionalBlockers(final Combat combat, CardCollectionView blockers) { - List possibleBlockers = ai.getCreaturesInPlay(); - for (Card c : blockers) { - if (!possibleBlockers.contains(c)) { - possibleBlockers.add(c); - } - } - attackers = sortPotentialAttackers(combat); - assignBlockers(combat, possibleBlockers); + assignBlockersForCombat(combat, null, blockers); } /** diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtil.java b/forge-ai/src/main/java/forge/ai/ComputerUtil.java index 2646a5458ec6..ff215874c337 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtil.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtil.java @@ -3175,10 +3175,17 @@ public static boolean aiLifeInDanger(Player ai, boolean serious, int payment) { return Integer.MIN_VALUE == AiCache.getCached("aiLifeInDanger", () -> predictNextCombatsRemainingLife(ai, serious, false, payment, null), List.of(AiCache::identity, Objects::equals, Objects::equals), ai, serious, payment); } + public static boolean aiLifeInDanger(Player ai, boolean serious, int payment, final CardCollectionView additionalBlockers) { + return Integer.MIN_VALUE == predictNextCombatsRemainingLife(ai, serious, false, payment, null, additionalBlockers, ai.getOpponents()); + } public static int predictNextCombatsRemainingLife(Player ai, boolean serious, boolean checkDiff, int payment, final CardCollection excludedBlockers) { return predictNextCombatsRemainingLife(ai, serious, checkDiff, payment, excludedBlockers, ai.getOpponents()); } public static int predictNextCombatsRemainingLife(Player ai, boolean serious, boolean checkDiff, int payment, final CardCollection excludedBlockers, final List opps) { + return predictNextCombatsRemainingLife(ai, serious, checkDiff, payment, excludedBlockers, null, opps); + } + private static int predictNextCombatsRemainingLife(Player ai, boolean serious, boolean checkDiff, int payment, + final CardCollection excludedBlockers, final CardCollectionView additionalBlockers, final List opps) { // life won't change int remainingLife = Integer.MAX_VALUE; @@ -3211,7 +3218,7 @@ public static int predictNextCombatsRemainingLife(Player ai, boolean serious, bo // TODO if it's next turn ignore mustBlockCards AiBlockController block = new AiBlockController(ai, false); // TODO for performance skip ahead to safer blocking approach (though probably only when not in checkDiff mode as that could lead to inflated prediction) - block.assignBlockersForCombat(combat, excludedBlockers); + block.assignBlockersForCombat(combat, excludedBlockers, additionalBlockers); // 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 diff --git a/forge-ai/src/main/java/forge/ai/SpecialCardAi.java b/forge-ai/src/main/java/forge/ai/SpecialCardAi.java index e11ea9dc7bce..9cce3e950a60 100644 --- a/forge-ai/src/main/java/forge/ai/SpecialCardAi.java +++ b/forge-ai/src/main/java/forge/ai/SpecialCardAi.java @@ -20,6 +20,7 @@ import com.google.common.collect.Lists; import forge.ai.ability.AnimateAi; import forge.ai.ability.FightAi; +import forge.ai.ability.TokenAi; import forge.card.ColorSet; import forge.card.MagicColor; import forge.card.mana.ManaCost; @@ -1976,6 +1977,33 @@ public static CardCollection targetBestCreature(final Player ai, final SpellAbil } } + // Welcome the Darkness + public static class WelcomeTheDarkness { + public static boolean consider(final Player ai, final SpellAbility sa, final int xValue) { + final int life = ai.getLife(); + final boolean acceptableX = switch (xValue) { + case 1, 2 -> xValue >= life; + case 3 -> life <= 5; + case 4 -> life <= 7; + case 5 -> life <= 12; + default -> xValue >= 6; + }; + if (!acceptableX) { + return false; + } + + final SpellAbility tokenSa = sa.findSubAbilityByType(ApiType.Token); + if (tokenSa == null) { + return false; + } + + final CardCollection additionalBlockers = new CardCollection(TokenAi.spawnToken(ai, tokenSa)); + final int resultingLife = xValue > life && !ai.canGainLife() ? life : xValue; + final int lifeReduction = life - resultingLife; + return !ComputerUtil.aiLifeInDanger(ai, true, lifeReduction, additionalBlockers); + } + } + // Ugin, the Spirit Dragon public static class UginTheSpiritDragon { public static boolean considerPWAbilityPriority(final Player ai, final SpellAbility sa, final ZoneType origin, CardCollectionView oppType, CardCollectionView computerType) { 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 2290a9714551..fe39a0a1f585 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DrawAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DrawAi.java @@ -315,6 +315,9 @@ private boolean targetAI(final Player ai, final SpellAbility sa, final boolean m if ("YawgmothsBargain".equals(logic)) { return SpecialCardAi.YawgmothsBargain.consider(ai, sa); } + if ("WelcomeTheDarkness".equals(logic) && !SpecialCardAi.WelcomeTheDarkness.consider(ai, sa, numCards)) { + return false; + } // Generic logic for all cards that do not need any special handling diff --git a/forge-gui/res/cardsfolder/w/welcome_the_darkness.txt b/forge-gui/res/cardsfolder/w/welcome_the_darkness.txt index d37a5185ff39..20ca2c50f895 100644 --- a/forge-gui/res/cardsfolder/w/welcome_the_darkness.txt +++ b/forge-gui/res/cardsfolder/w/welcome_the_darkness.txt @@ -1,7 +1,7 @@ Name:Welcome the Darkness ManaCost:X B Types:Instant -A:SP$ Draw | Cost$ XMin1 X B | NumCards$ X | SubAbility$ DBToken +A:SP$ Draw | Cost$ XMin1 X B | NumCards$ X | SubAbility$ DBToken | AILogic$ WelcomeTheDarkness SVar:DBToken:DB$ Token | TokenScript$ b_x_x_demon_flying | TokenOwner$ You | TokenPower$ X | TokenToughness$ X | SubAbility$ DBSetLife | SpellDescription$ X can't be 0. Draw X cards. Create an X/X black Demon creature token with flying. Your life total becomes X. You can't gain life for the rest of the game. SVar:DBSetLife:DB$ SetLife | Defined$ You | LifeAmount$ X | SubAbility$ DBEffect SVar:DBEffect:DB$ Effect | StaticAbilities$ CantGainLife | Duration$ Permanent