- 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).
This commit is contained in:
Agetian
2017-01-20 17:41:43 +00:00
parent bf98c77eb7
commit e3e97cd2e6
7 changed files with 65 additions and 8 deletions

View File

@@ -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;

View File

@@ -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) {

View File

@@ -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;
}