diff --git a/forge-ai/src/main/java/forge/ai/AiProps.java b/forge-ai/src/main/java/forge/ai/AiProps.java index fde316c8604..2808454513f 100644 --- a/forge-ai/src/main/java/forge/ai/AiProps.java +++ b/forge-ai/src/main/java/forge/ai/AiProps.java @@ -69,6 +69,10 @@ public enum AiProps { /** */ ALWAYS_COUNTER_AURAS ("true"), /** */ ALWAYS_COUNTER_SPELLS_FROM_NAMED_CARDS (""), /** */ ACTIVELY_DESTROY_ARTS_AND_NONAURA_ENCHS ("true"), /** */ + ACTIVELY_DESTROY_IMMEDIATELY_UNBLOCKABLE ("false"), /** */ + DESTROY_IMMEDIATELY_UNBLOCKABLE_THRESHOLD ("2"), /** */ + DESTROY_IMMEDIATELY_UNBLOCKABLE_ONLY_IN_DNGR ("true"), /** */ + DESTROY_IMMEDIATELY_UNBLOCKABLE_LIFE_IN_DNGR ("5"), /** */ PRIORITY_REDUCTION_FOR_STORM_SPELLS ("0"), /** */ USE_BERSERK_AGGRESSIVELY ("false"), /** */ MIN_COUNT_FOR_STORM_SPELLS ("0"), /** */ diff --git a/forge-ai/src/main/java/forge/ai/ability/DestroyAi.java b/forge-ai/src/main/java/forge/ai/ability/DestroyAi.java index f0d6939227d..59f427e49d4 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DestroyAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DestroyAi.java @@ -5,6 +5,7 @@ import forge.ai.*; import forge.game.ability.AbilityUtils; import forge.game.ability.ApiType; import forge.game.card.*; +import forge.game.combat.CombatUtil; import forge.game.cost.Cost; import forge.game.cost.CostPart; import forge.game.cost.CostSacrifice; @@ -115,6 +116,9 @@ public class DestroyAi extends SpellAbilityAi { } } list = CardLists.getNotKeyword(list, "Indestructible"); + if (CardLists.getNotType(list, "Creature").isEmpty()) { + list = prioritizeCreaturesWorthDestroyingNow(ai, list); + } if (!SpellAbilityAi.playReusable(ai, sa)) { list = CardLists.filter(list, new Predicate() { @Override @@ -291,6 +295,9 @@ public class DestroyAi extends SpellAbilityAi { CardCollection preferred = CardLists.getNotKeyword(list, "Indestructible"); preferred = CardLists.filterControlledBy(preferred, ai.getOpponents()); + if (CardLists.getNotType(preferred, "Creature").isEmpty()) { + preferred = prioritizeCreaturesWorthDestroyingNow(ai, preferred); + } // If NoRegen is not set, filter out creatures that have a // regeneration shield @@ -444,4 +451,51 @@ public class DestroyAi extends SpellAbilityAi { return tempoCheck; } } + + public static CardCollection prioritizeCreaturesWorthDestroyingNow(final Player ai, final CardCollection oppCards) { + if (!CardLists.getNotType(oppCards, "Creature").isEmpty()) { + // non-creatures were passed, nothing to do here + return oppCards; + } + + boolean enablePriorityRemoval = false; + boolean priorityRemovalOnlyInDanger = false; + int priorityRemovalThreshold = 0; + int lifeInDanger = 5; + if (ai.getController().isAI()) { + AiController aic = ((PlayerControllerAi)ai.getController()).getAi(); + enablePriorityRemoval = aic.getBooleanProperty(AiProps.ACTIVELY_DESTROY_IMMEDIATELY_UNBLOCKABLE); + priorityRemovalThreshold = aic.getIntProperty(AiProps.DESTROY_IMMEDIATELY_UNBLOCKABLE_THRESHOLD); + priorityRemovalOnlyInDanger = aic.getBooleanProperty(AiProps.DESTROY_IMMEDIATELY_UNBLOCKABLE_ONLY_IN_DNGR); + lifeInDanger = aic.getIntProperty(AiProps.DESTROY_IMMEDIATELY_UNBLOCKABLE_LIFE_IN_DNGR); + } + + if (!enablePriorityRemoval) { + // Nothing to do here, the profile does not allow prioritizing + return oppCards; + } + + CardCollection aiCreats = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES); + + CardCollection priorityCards = new CardCollection(); + for (Card atk : oppCards) { + if (ComputerUtilCard.isUselessCreature(atk.getController(), atk)) { + continue; + } + for (Card blk : aiCreats) { + if (!CombatUtil.canBlock(atk, blk, true)) { + boolean threat = atk.getNetCombatDamage() >= ai.getLife() - lifeInDanger; + if (!priorityRemovalOnlyInDanger || threat) { + priorityCards.add(atk); + } + } + } + } + + if (!priorityCards.isEmpty() && priorityCards.size() <= priorityRemovalThreshold) { + return priorityCards; + } + + return oppCards; + } } diff --git a/forge-gui/res/ai/Cautious.ai b/forge-gui/res/ai/Cautious.ai index d1a0da2613d..7985486efeb 100644 --- a/forge-gui/res/ai/Cautious.ai +++ b/forge-gui/res/ai/Cautious.ai @@ -91,6 +91,18 @@ RESERVE_MANA_FOR_MAIN2_CHANCE=100 # If enabled, the AI will target artifacts and non-aura enchantments with removal aggressively ACTIVELY_DESTROY_ARTS_AND_NONAURA_ENCHS=true +# If enabled, the AI will prioritize removing creatures that it can't block at all (e.g. Flying in absence of flying +# blockers). This is a master toggle. If disabled, the following three options do nothing. +ACTIVELY_DESTROY_IMMEDIATELY_UNBLOCKABLE=false +# How many unblockable creatures can there be when the AI should still consider prioritizing something (when there are +# too many, probably it's just worth going for the biggest threat overall instead) +DESTROY_IMMEDIATELY_UNBLOCKABLE_THRESHOLD=2 +# Only consider prioritizing the destruction of immediately unblockable creatures if life is in danger +DESTROY_IMMEDIATELY_UNBLOCKABLE_ONLY_IN_DNGR=true +# How much life can there remain after a swing of the immediately unblockable creature for it to be considered dangerous +# for the purpose of previous option +DESTROY_IMMEDIATELY_UNBLOCKABLE_LIFE_IN_DNGR=5 + # Permission timings MIN_SPELL_CMC_TO_COUNTER=2 CHANCE_TO_COUNTER_CMC_1=0 diff --git a/forge-gui/res/ai/Default.ai b/forge-gui/res/ai/Default.ai index 28ea9b09e5e..e8fd3709b3a 100644 --- a/forge-gui/res/ai/Default.ai +++ b/forge-gui/res/ai/Default.ai @@ -91,6 +91,18 @@ RESERVE_MANA_FOR_MAIN2_CHANCE=100 # If enabled, the AI will target artifacts and non-aura enchantments with removal aggressively ACTIVELY_DESTROY_ARTS_AND_NONAURA_ENCHS=true +# If enabled, the AI will prioritize removing creatures that it can't block at all (e.g. Flying in absence of flying +# blockers). This is a master toggle. If disabled, the following three options do nothing. +ACTIVELY_DESTROY_IMMEDIATELY_UNBLOCKABLE=false +# How many unblockable creatures can there be when the AI should still consider prioritizing something (when there are +# too many, probably it's just worth going for the biggest threat overall instead) +DESTROY_IMMEDIATELY_UNBLOCKABLE_THRESHOLD=2 +# Only consider prioritizing the destruction of immediately unblockable creatures if life is in danger +DESTROY_IMMEDIATELY_UNBLOCKABLE_ONLY_IN_DNGR=true +# How much life can there remain after a swing of the immediately unblockable creature for it to be considered dangerous +# for the purpose of previous option +DESTROY_IMMEDIATELY_UNBLOCKABLE_LIFE_IN_DNGR=5 + # Permission timings MIN_SPELL_CMC_TO_COUNTER=0 CHANCE_TO_COUNTER_CMC_1=30 diff --git a/forge-gui/res/ai/Experimental.ai b/forge-gui/res/ai/Experimental.ai index e54bb925384..17863cf0729 100644 --- a/forge-gui/res/ai/Experimental.ai +++ b/forge-gui/res/ai/Experimental.ai @@ -29,7 +29,7 @@ USE_BERSERK_AGGRESSIVELY=true TRY_TO_HOLD_COMBAT_TRICKS_UNTIL_BLOCK=true # Chance to hold combat tricks until blockers are declared. If -1 is specified, this chance is not used, and the standard # evaluation for offensive pump buff is used instead. -CHANCE_TO_HOLD_COMBAT_TRICKS_UNTIL_BLOCK=65 +CHANCE_TO_HOLD_COMBAT_TRICKS_UNTIL_BLOCK=75 # Trade blocking preferences (enabling these will make the AI trade more aggressively when considering blocks, # but only with creatures that are worse in abilities and have lower or the same power as the attacker). Note @@ -91,6 +91,18 @@ RESERVE_MANA_FOR_MAIN2_CHANCE=100 # If enabled, the AI will target artifacts and non-aura enchantments with removal aggressively ACTIVELY_DESTROY_ARTS_AND_NONAURA_ENCHS=true +# If enabled, the AI will prioritize removing creatures that it can't block at all (e.g. Flying in absence of flying +# blockers). This is a master toggle. If disabled, the following three options do nothing. +ACTIVELY_DESTROY_IMMEDIATELY_UNBLOCKABLE=true +# How many unblockable creatures can there be when the AI should still consider prioritizing something (when there are +# too many, probably it's just worth going for the biggest threat overall instead) +DESTROY_IMMEDIATELY_UNBLOCKABLE_THRESHOLD=3 +# Only consider prioritizing the destruction of immediately unblockable creatures if life is in danger +DESTROY_IMMEDIATELY_UNBLOCKABLE_ONLY_IN_DNGR=false +# How much life can there remain after a swing of the immediately unblockable creature for it to be considered dangerous +# for the purpose of previous option +DESTROY_IMMEDIATELY_UNBLOCKABLE_LIFE_IN_DNGR=5 + # Permission timings MIN_SPELL_CMC_TO_COUNTER=2 CHANCE_TO_COUNTER_CMC_1=30 diff --git a/forge-gui/res/ai/Reckless.ai b/forge-gui/res/ai/Reckless.ai index 6bb65394dcd..e6958bf21e0 100644 --- a/forge-gui/res/ai/Reckless.ai +++ b/forge-gui/res/ai/Reckless.ai @@ -91,6 +91,18 @@ RESERVE_MANA_FOR_MAIN2_CHANCE=100 # If enabled, the AI will target artifacts and non-aura enchantments with removal aggressively ACTIVELY_DESTROY_ARTS_AND_NONAURA_ENCHS=true +# If enabled, the AI will prioritize removing creatures that it can't block at all (e.g. Flying in absence of flying +# blockers). This is a master toggle. If disabled, the following three options do nothing. +ACTIVELY_DESTROY_IMMEDIATELY_UNBLOCKABLE=false +# How many unblockable creatures can there be when the AI should still consider prioritizing something (when there are +# too many, probably it's just worth going for the biggest threat overall instead) +DESTROY_IMMEDIATELY_UNBLOCKABLE_THRESHOLD=2 +# Only consider prioritizing the destruction of immediately unblockable creatures if life is in danger +DESTROY_IMMEDIATELY_UNBLOCKABLE_ONLY_IN_DNGR=true +# How much life can there remain after a swing of the immediately unblockable creature for it to be considered dangerous +# for the purpose of previous option +DESTROY_IMMEDIATELY_UNBLOCKABLE_LIFE_IN_DNGR=5 + # Permission timings MIN_SPELL_CMC_TO_COUNTER=0 CHANCE_TO_COUNTER_CMC_1=80