From e3e97cd2e61647b62ef8502829847582013cb91b Mon Sep 17 00:00:00 2001 From: Agetian Date: Fri, 20 Jan 2017 17:41:43 +0000 Subject: [PATCH] - Added global options to control the min CMC the AI would use permission against (disabled in both default profiles at the moment). - Implemented some simple AI for Daze and Force of Will such that the AI does not waste them so much on every single opportunity (quite basic, needs further expansion). - Fixed Force of Will disappearing from the AI card pool when it was trying to cast it without having a valid blue card to exile in hand. - Marked Daze and Force of Will as AI-playable (should at least be more or less on par with other permission for the most part). --- forge-ai/src/main/java/forge/ai/AiProps.java | 1 + .../src/main/java/forge/ai/SpecialCardAi.java | 29 +++++++++++++++++ .../main/java/forge/ai/ability/CounterAi.java | 31 +++++++++++++++++-- forge-gui/res/ai/Default.ai | 3 +- forge-gui/res/ai/Reckless.ai | 3 +- forge-gui/res/cardsfolder/d/daze.txt | 3 +- forge-gui/res/cardsfolder/f/force_of_will.txt | 3 +- 7 files changed, 65 insertions(+), 8 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/AiProps.java b/forge-ai/src/main/java/forge/ai/AiProps.java index 1b87cc27d84..7226b176670 100644 --- a/forge-ai/src/main/java/forge/ai/AiProps.java +++ b/forge-ai/src/main/java/forge/ai/AiProps.java @@ -35,6 +35,7 @@ public enum AiProps { /** */ PREDICT_SPELLS_FOR_MAIN2 ("true"), /** */ RESERVE_MANA_FOR_MAIN2_CHANCE ("0"), /** */ PLAY_AGGRO ("false"), /** */ + MIN_SPELL_CMC_TO_COUNTER ("0"), /** */ ACTIVELY_DESTROY_ARTS_AND_NONAURA_ENCHS ("false"); /** */ private final String strDefaultVal; diff --git a/forge-ai/src/main/java/forge/ai/SpecialCardAi.java b/forge-ai/src/main/java/forge/ai/SpecialCardAi.java index ed082e56762..5aae0ce2859 100644 --- a/forge-ai/src/main/java/forge/ai/SpecialCardAi.java +++ b/forge-ai/src/main/java/forge/ai/SpecialCardAi.java @@ -32,6 +32,7 @@ import forge.game.card.CardFactoryUtil; import forge.game.card.CardLists; import forge.game.card.CardPredicates; import forge.game.card.CounterType; +import forge.game.cost.CostPart; import forge.game.mana.ManaCostBeingPaid; import forge.game.phase.PhaseHandler; import forge.game.phase.PhaseType; @@ -187,6 +188,34 @@ public class SpecialCardAi { } } + // Force of Will + public static class ForceOfWill { + public static boolean consider(Player ai, SpellAbility sa) { + CardCollection blueCards = CardLists.filter(ai.getCardsIn(ZoneType.Hand), CardPredicates.isColor(MagicColor.BLUE)); + + boolean isExileMode = false; + for (CostPart c : sa.getPayCosts().getCostParts()) { + if (c.toString().contains("Exile")) { + isExileMode = true; // the AI is trying to go for the "exile and pay life" alt cost + break; + } + } + + if (isExileMode) { + if (blueCards.size() < 2) { + // Need to have something else in hand that is blue in addition to Force of Will itself, + // otherwise the AI will fail to play the card and the card will disappear from the pool + return false; + } else if (CardLists.filter(blueCards, Predicates.or(CardPredicates.hasCMC(0), CardPredicates.hasCMC(1), CardPredicates.hasCMC(2), CardPredicates.hasCMC(3))).isEmpty()) { + // We probably need a low-CMC card to exile to it, exiling a higher CMC spell may be suboptimal + // since the AI does not prioritize/value cards vs. permission at the moment. + return false; + } + } + + return true; + } + } // Living Death (and possibly other similar cards using AILogic LivingDeath) public static class LivingDeath { public static boolean consider(Player ai, SpellAbility sa) { diff --git a/forge-ai/src/main/java/forge/ai/ability/CounterAi.java b/forge-ai/src/main/java/forge/ai/ability/CounterAi.java index 8ddf5c872df..7723e46451e 100644 --- a/forge-ai/src/main/java/forge/ai/ability/CounterAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/CounterAi.java @@ -1,19 +1,26 @@ package forge.ai.ability; +import forge.ai.AiProps; import java.util.Iterator; import forge.ai.ComputerUtilCost; import forge.ai.ComputerUtilMana; +import forge.ai.PlayerControllerAi; +import forge.ai.SpecialCardAi; import forge.ai.SpellAbilityAi; +import forge.card.MagicColor; import forge.game.Game; import forge.game.ability.AbilityUtils; import forge.game.card.Card; import forge.game.card.CardFactoryUtil; +import forge.game.card.CardLists; +import forge.game.card.CardPredicates; import forge.game.cost.Cost; import forge.game.player.Player; import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbilityStackInstance; import forge.game.spellability.TargetRestrictions; +import forge.game.zone.ZoneType; import forge.util.MyRandom; import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.Pair; @@ -26,6 +33,8 @@ public class CounterAi extends SpellAbilityAi { final Cost abCost = sa.getPayCosts(); final Card source = sa.getHostCard(); final Game game = ai.getGame(); + int tgtCMC = 0; + if (game.getStack().isEmpty()) { return false; } @@ -40,6 +49,12 @@ public class CounterAi extends SpellAbilityAi { } } + if ("Force of Will".equals(source.getName())) { + if (!SpecialCardAi.ForceOfWill.consider(ai, sa)) { + return false; + } + } + final TargetRestrictions tgt = sa.getTargetRestrictions(); if (tgt != null) { @@ -61,6 +76,10 @@ public class CounterAi extends SpellAbilityAi { sa.resetTargets(); if (sa.canTargetSpellAbility(topSA)) { sa.getTargets().add(topSA); + if (topSA.getPayCosts().getTotalMana() != null) { + tgtCMC = topSA.getPayCosts().getTotalMana().getCMC(); + tgtCMC += topSA.getPayCosts().getTotalMana().countX() > 0 ? 3 : 0; // TODO: somehow determine the value of X paid and account for it? + } } else { return false; } @@ -110,11 +129,19 @@ public class CounterAi extends SpellAbilityAi { String logic = sa.getParam("AILogic"); if ("Never".equals(logic)) { return false; + } else if (logic.startsWith("MinCMC.")) { + int minCMC = Integer.parseInt(logic.substring(7)); + if (tgtCMC < minCMC) { + return false; + } } } - - + // if minimum CMC to use counterspells against is specified in the AI profile, obey it + if (tgtCMC < ((PlayerControllerAi)ai.getController()).getAi().getIntProperty(AiProps.MIN_SPELL_CMC_TO_COUNTER)) { + return false; + } + return toReturn; } diff --git a/forge-gui/res/ai/Default.ai b/forge-gui/res/ai/Default.ai index cfe5d9609c2..5b9efdb93df 100644 --- a/forge-gui/res/ai/Default.ai +++ b/forge-gui/res/ai/Default.ai @@ -8,6 +8,7 @@ MOVE_EQUIPMENT_TO_BETTER_CREATURES=from_useless_only PRIORITIZE_MOVE_EQUIPMENT_IF_USELESS=true PREDICT_SPELLS_FOR_MAIN2=true RESERVE_MANA_FOR_MAIN2_CHANCE=100 -ACTIVELY_DESTROY_ARTS_AND_NONAURA_ENCHS=false +ACTIVELY_DESTROY_ARTS_AND_NONAURA_ENCHS=true +MIN_SPELL_CMC_TO_COUNTER=0 PLAY_AGGRO=false diff --git a/forge-gui/res/ai/Reckless.ai b/forge-gui/res/ai/Reckless.ai index 5f68167e17f..3aeb32995a1 100644 --- a/forge-gui/res/ai/Reckless.ai +++ b/forge-gui/res/ai/Reckless.ai @@ -8,5 +8,6 @@ MOVE_EQUIPMENT_TO_BETTER_CREATURES=always PRIORITIZE_MOVE_EQUIPMENT_IF_USELESS=false PREDICT_SPELLS_FOR_MAIN2=true RESERVE_MANA_FOR_MAIN2_CHANCE=100 -ACTIVELY_DESTROY_ARTS_AND_NONAURA_ENCHS=false +ACTIVELY_DESTROY_ARTS_AND_NONAURA_ENCHS=true +MIN_SPELL_CMC_TO_COUNTER=0 PLAY_AGGRO=true diff --git a/forge-gui/res/cardsfolder/d/daze.txt b/forge-gui/res/cardsfolder/d/daze.txt index cf3aa85c5e0..0c32616d97a 100644 --- a/forge-gui/res/cardsfolder/d/daze.txt +++ b/forge-gui/res/cardsfolder/d/daze.txt @@ -2,7 +2,6 @@ Name:Daze ManaCost:1 U Types:Instant A:SP$ Counter | Cost$ 1 U | TargetType$ Spell | TgtPrompt$ Select target spell | ValidTgts$ Card | UnlessCost$ 1 | SpellDescription$ Counter target spell unless its controller pays {1}. -A:SP$ Counter | Cost$ Return<1/Island> | TargetType$ Spell | TgtPrompt$ Select target spell | ValidTgts$ Card | UnlessCost$ 1 | CostDesc$ You may return an Island you control to its owner's hand | SpellDescription$ rather than pay CARDNAME's mana cost. -SVar:RemAIDeck:True +A:SP$ Counter | Cost$ Return<1/Island> | TargetType$ Spell | TgtPrompt$ Select target spell | ValidTgts$ Card | UnlessCost$ 1 | AILogic$ MinCMC.4 | CostDesc$ You may return an Island you control to its owner's hand | SpellDescription$ rather than pay CARDNAME's mana cost. SVar:Picture:http://www.wizards.com/global/images/magic/general/daze.jpg Oracle:You may return an Island you control to its owner's hand rather than pay Daze's mana cost.\nCounter target spell unless its controller pays {1}. diff --git a/forge-gui/res/cardsfolder/f/force_of_will.txt b/forge-gui/res/cardsfolder/f/force_of_will.txt index 61d41e524b6..ee5d9b53b3d 100644 --- a/forge-gui/res/cardsfolder/f/force_of_will.txt +++ b/forge-gui/res/cardsfolder/f/force_of_will.txt @@ -1,8 +1,7 @@ Name:Force of Will ManaCost:3 U U Types:Instant -A:SP$ Counter | Cost$ 3 U U | TargetType$ Spell | TgtPrompt$ Select target spell | ValidTgts$ Card | Destination$ Graveyard | SpellDescription$ Counter target spell. +A:SP$ Counter | Cost$ 3 U U | TargetType$ Spell | TgtPrompt$ Select target spell | ValidTgts$ Card | Destination$ Graveyard | AILogic$ MinCMC.4 | SpellDescription$ Counter target spell. SVar:AltCost:Cost$ PayLife<1> ExileFromHand<1/Card.Blue> | Description$ You may pay 1 life and exile a blue card from your hand rather than pay CARDNAME's mana cost. -SVar:RemAIDeck:True SVar:Picture:http://www.wizards.com/global/images/magic/general/force_of_will.jpg Oracle:You may pay 1 life and exile a blue card from your hand rather than pay Force of Will's mana cost.\nCounter target spell.