Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 15 additions & 11 deletions forge-ai/src/main/java/forge/ai/AiBlockController.java
Original file line number Diff line number Diff line change
Expand Up @@ -1005,10 +1005,21 @@ private void clearBlockers(final Combat combat, final List<Card> 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<Card> 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);
Expand All @@ -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<Card> possibleBlockers = ai.getCreaturesInPlay();
for (Card c : blockers) {
if (!possibleBlockers.contains(c)) {
possibleBlockers.add(c);
}
}
attackers = sortPotentialAttackers(combat);
assignBlockers(combat, possibleBlockers);
assignBlockersForCombat(combat, null, blockers);
}

/**
Expand Down
9 changes: 8 additions & 1 deletion forge-ai/src/main/java/forge/ai/ComputerUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<Player> 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<Player> opps) {
// life won't change
int remainingLife = Integer.MAX_VALUE;

Expand Down Expand Up @@ -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
Expand Down
28 changes: 28 additions & 0 deletions forge-ai/src/main/java/forge/ai/SpecialCardAi.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down
3 changes: 3 additions & 0 deletions forge-ai/src/main/java/forge/ai/ability/DrawAi.java
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion forge-gui/res/cardsfolder/w/welcome_the_darkness.txt
Original file line number Diff line number Diff line change
@@ -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
Expand Down