mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-14 01:38:13 +00:00
Refactor AiProfile handling (#9012)
Co-authored-by: tool4EvEr <tool4EvEr@>
This commit is contained in:
@@ -393,7 +393,7 @@ public class AiAttackController {
|
|||||||
if (ai.getController().isAI()) {
|
if (ai.getController().isAI()) {
|
||||||
PlayerControllerAi aic = ((PlayerControllerAi) ai.getController());
|
PlayerControllerAi aic = ((PlayerControllerAi) ai.getController());
|
||||||
pilotsNonAggroDeck = aic.pilotsNonAggroDeck();
|
pilotsNonAggroDeck = aic.pilotsNonAggroDeck();
|
||||||
playAggro = !pilotsNonAggroDeck || aic.getAi().getBooleanProperty(AiProps.PLAY_AGGRO);
|
playAggro = !pilotsNonAggroDeck || aic.getAi().getBoolProperty(AiProps.PLAY_AGGRO);
|
||||||
}
|
}
|
||||||
// TODO make switchable via AI property
|
// TODO make switchable via AI property
|
||||||
int thresholdMod = 0;
|
int thresholdMod = 0;
|
||||||
@@ -614,13 +614,7 @@ public class AiAttackController {
|
|||||||
|
|
||||||
// if true, the AI will attempt to identify which blockers will already be taken,
|
// if true, the AI will attempt to identify which blockers will already be taken,
|
||||||
// thus attempting to predict how many creatures with evasion can actively block
|
// thus attempting to predict how many creatures with evasion can actively block
|
||||||
boolean predictEvasion = false;
|
boolean predictEvasion = AiProfileUtil.getBoolProperty(ai, AiProps.COMBAT_ASSAULT_ATTACK_EVASION_PREDICTION);
|
||||||
if (ai.getController().isAI()) {
|
|
||||||
AiController aic = ((PlayerControllerAi) ai.getController()).getAi();
|
|
||||||
if (aic.getBooleanProperty(AiProps.COMBAT_ASSAULT_ATTACK_EVASION_PREDICTION)) {
|
|
||||||
predictEvasion = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
CardCollection accountedBlockers = new CardCollection(this.blockers);
|
CardCollection accountedBlockers = new CardCollection(this.blockers);
|
||||||
CardCollection categorizedAttackers = new CardCollection();
|
CardCollection categorizedAttackers = new CardCollection();
|
||||||
@@ -860,12 +854,12 @@ public class AiAttackController {
|
|||||||
AiController aic = ((PlayerControllerAi) ai.getController()).getAi();
|
AiController aic = ((PlayerControllerAi) ai.getController()).getAi();
|
||||||
simAI = aic.usesSimulation();
|
simAI = aic.usesSimulation();
|
||||||
if (!simAI) {
|
if (!simAI) {
|
||||||
playAggro = aic.getBooleanProperty(AiProps.PLAY_AGGRO);
|
playAggro = aic.getBoolProperty(AiProps.PLAY_AGGRO);
|
||||||
chanceToAttackToTrade = aic.getIntProperty(AiProps.CHANCE_TO_ATTACK_INTO_TRADE);
|
chanceToAttackToTrade = aic.getIntProperty(AiProps.CHANCE_TO_ATTACK_INTO_TRADE);
|
||||||
tradeIfTappedOut = aic.getBooleanProperty(AiProps.ATTACK_INTO_TRADE_WHEN_TAPPED_OUT);
|
tradeIfTappedOut = aic.getBoolProperty(AiProps.ATTACK_INTO_TRADE_WHEN_TAPPED_OUT);
|
||||||
extraChanceIfOppHasMana = aic.getIntProperty(AiProps.CHANCE_TO_ATKTRADE_WHEN_OPP_HAS_MANA);
|
extraChanceIfOppHasMana = aic.getIntProperty(AiProps.CHANCE_TO_ATKTRADE_WHEN_OPP_HAS_MANA);
|
||||||
tradeIfLowerLifePressure = aic.getBooleanProperty(AiProps.RANDOMLY_ATKTRADE_ONLY_ON_LOWER_LIFE_PRESSURE);
|
tradeIfLowerLifePressure = aic.getBoolProperty(AiProps.RANDOMLY_ATKTRADE_ONLY_ON_LOWER_LIFE_PRESSURE);
|
||||||
predictEvasion = aic.getBooleanProperty(AiProps.COMBAT_ATTRITION_ATTACK_EVASION_PREDICTION);
|
predictEvasion = aic.getBoolProperty(AiProps.COMBAT_ATTRITION_ATTACK_EVASION_PREDICTION);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1423,7 +1417,7 @@ public class AiAttackController {
|
|||||||
// Check if maybe we are too reckless in adding this attacker
|
// Check if maybe we are too reckless in adding this attacker
|
||||||
if (canKillAllDangerous) {
|
if (canKillAllDangerous) {
|
||||||
boolean avoidAttackingIntoBlock = ai.getController().isAI()
|
boolean avoidAttackingIntoBlock = ai.getController().isAI()
|
||||||
&& ((PlayerControllerAi) ai.getController()).getAi().getBooleanProperty(AiProps.TRY_TO_AVOID_ATTACKING_INTO_CERTAIN_BLOCK);
|
&& ((PlayerControllerAi) ai.getController()).getAi().getBoolProperty(AiProps.TRY_TO_AVOID_ATTACKING_INTO_CERTAIN_BLOCK);
|
||||||
boolean attackerWillDie = defPower >= attacker.getNetToughness();
|
boolean attackerWillDie = defPower >= attacker.getNetToughness();
|
||||||
boolean uselessAttack = !hasCombatEffect && !hasAttackEffect;
|
boolean uselessAttack = !hasCombatEffect && !hasAttackEffect;
|
||||||
boolean noContributionToAttack = attackers.size() <= defenders.size() || attacker.getNetPower() <= 0;
|
boolean noContributionToAttack = attackers.size() <= defenders.size() || attacker.getNetPower() <= 0;
|
||||||
|
|||||||
@@ -861,10 +861,9 @@ public class AiBlockController {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
AiController aic = ((PlayerControllerAi) ai.getController()).getAi();
|
final int evalThresholdToken = AiProfileUtil.getIntProperty(ai, AiProps.THRESHOLD_TOKEN_CHUMP_TO_SAVE_PLANESWALKER);
|
||||||
final int evalThresholdToken = aic.getIntProperty(AiProps.THRESHOLD_TOKEN_CHUMP_TO_SAVE_PLANESWALKER);
|
final int evalThresholdNonToken = AiProfileUtil.getIntProperty(ai, AiProps.THRESHOLD_NONTOKEN_CHUMP_TO_SAVE_PLANESWALKER);
|
||||||
final int evalThresholdNonToken = aic.getIntProperty(AiProps.THRESHOLD_NONTOKEN_CHUMP_TO_SAVE_PLANESWALKER);
|
final boolean onlyIfLethal = AiProfileUtil.getBoolProperty(ai, AiProps.CHUMP_TO_SAVE_PLANESWALKER_ONLY_ON_LETHAL);
|
||||||
final boolean onlyIfLethal = aic.getBooleanProperty(AiProps.CHUMP_TO_SAVE_PLANESWALKER_ONLY_ON_LETHAL);
|
|
||||||
|
|
||||||
if (evalThresholdToken > 0 || evalThresholdNonToken > 0) {
|
if (evalThresholdToken > 0 || evalThresholdNonToken > 0) {
|
||||||
// detect how much damage is threatened to each of the planeswalkers, see which ones would be
|
// detect how much damage is threatened to each of the planeswalkers, see which ones would be
|
||||||
@@ -1047,7 +1046,7 @@ public class AiBlockController {
|
|||||||
clearBlockers(combat, possibleBlockers);
|
clearBlockers(combat, possibleBlockers);
|
||||||
|
|
||||||
diff = (ai.getLife() * 2) - 5; // This is the minimal gain for an unnecessary trade
|
diff = (ai.getLife() * 2) - 5; // This is the minimal gain for an unnecessary trade
|
||||||
if (ai.getController().isAI() && diff > 0 && ((PlayerControllerAi) ai.getController()).getAi().getBooleanProperty(AiProps.PLAY_AGGRO)) {
|
if (diff > 0 && AiProfileUtil.getBoolProperty(ai, AiProps.PLAY_AGGRO)) {
|
||||||
diff = 0;
|
diff = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1284,9 +1283,9 @@ public class AiBlockController {
|
|||||||
AiController aic = ((PlayerControllerAi) ai.getController()).getAi();
|
AiController aic = ((PlayerControllerAi) ai.getController()).getAi();
|
||||||
// simulation must get same results or it may crash
|
// simulation must get same results or it may crash
|
||||||
if (!aic.usesSimulation()) {
|
if (!aic.usesSimulation()) {
|
||||||
enableRandomTrades = aic.getBooleanProperty(AiProps.ENABLE_RANDOM_FAVORABLE_TRADES_ON_BLOCK);
|
enableRandomTrades = aic.getBoolProperty(AiProps.ENABLE_RANDOM_FAVORABLE_TRADES_ON_BLOCK);
|
||||||
randomTradeIfBehindOnBoard = aic.getBooleanProperty(AiProps.RANDOMLY_TRADE_EVEN_WHEN_HAVE_LESS_CREATS);
|
randomTradeIfBehindOnBoard = aic.getBoolProperty(AiProps.RANDOMLY_TRADE_EVEN_WHEN_HAVE_LESS_CREATS);
|
||||||
randomTradeIfCreatInHand = aic.getBooleanProperty(AiProps.ALSO_TRADE_WHEN_HAVE_A_REPLACEMENT_CREAT);
|
randomTradeIfCreatInHand = aic.getBoolProperty(AiProps.ALSO_TRADE_WHEN_HAVE_A_REPLACEMENT_CREAT);
|
||||||
minRandomTradeChance = aic.getIntProperty(AiProps.MIN_CHANCE_TO_RANDOMLY_TRADE_ON_BLOCK);
|
minRandomTradeChance = aic.getIntProperty(AiProps.MIN_CHANCE_TO_RANDOMLY_TRADE_ON_BLOCK);
|
||||||
maxRandomTradeChance = aic.getIntProperty(AiProps.MAX_CHANCE_TO_RANDOMLY_TRADE_ON_BLOCK);
|
maxRandomTradeChance = aic.getIntProperty(AiProps.MAX_CHANCE_TO_RANDOMLY_TRADE_ON_BLOCK);
|
||||||
chanceModForEmbalm = aic.getIntProperty(AiProps.CHANCE_DECREASE_TO_TRADE_VS_EMBALM);
|
chanceModForEmbalm = aic.getIntProperty(AiProps.CHANCE_DECREASE_TO_TRADE_VS_EMBALM);
|
||||||
|
|||||||
@@ -764,7 +764,7 @@ public class AiController {
|
|||||||
return predictSpellToCastInMain2(exceptSA, true);
|
return predictSpellToCastInMain2(exceptSA, true);
|
||||||
}
|
}
|
||||||
private SpellAbility predictSpellToCastInMain2(ApiType exceptSA, boolean handOnly) {
|
private SpellAbility predictSpellToCastInMain2(ApiType exceptSA, boolean handOnly) {
|
||||||
if (!getBooleanProperty(AiProps.PREDICT_SPELLS_FOR_MAIN2)) {
|
if (!getBoolProperty(AiProps.PREDICT_SPELLS_FOR_MAIN2)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -930,7 +930,7 @@ public class AiController {
|
|||||||
final Card card = sa.getHostCard();
|
final Card card = sa.getHostCard();
|
||||||
|
|
||||||
// Trying to play a card that has Buyback without a Buyback cost, look for possible additional considerations
|
// Trying to play a card that has Buyback without a Buyback cost, look for possible additional considerations
|
||||||
if (getBooleanProperty(AiProps.TRY_TO_PRESERVE_BUYBACK_SPELLS)) {
|
if (getBoolProperty(AiProps.TRY_TO_PRESERVE_BUYBACK_SPELLS)) {
|
||||||
if (card.hasKeyword(Keyword.BUYBACK) && !sa.isBuyback() && !canPlaySpellWithoutBuyback(card, sa)) {
|
if (card.hasKeyword(Keyword.BUYBACK) && !sa.isBuyback() && !canPlaySpellWithoutBuyback(card, sa)) {
|
||||||
return AiPlayDecision.NeedsToPlayCriteriaNotMet;
|
return AiPlayDecision.NeedsToPlayCriteriaNotMet;
|
||||||
}
|
}
|
||||||
@@ -1299,27 +1299,13 @@ public class AiController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String getProperty(AiProps propName) {
|
public String getProperty(AiProps propName) {
|
||||||
return AiProfileUtil.getAIProp(getPlayer().getLobbyPlayer(), propName);
|
return AiProfileUtil.getProperty(getPlayer(), propName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getIntProperty(AiProps propName) {
|
public int getIntProperty(AiProps propName) {
|
||||||
String prop = AiProfileUtil.getAIProp(getPlayer().getLobbyPlayer(), propName);
|
return AiProfileUtil.getIntProperty(getPlayer(), propName);
|
||||||
|
|
||||||
if (prop == null || prop.isEmpty()) {
|
|
||||||
return Integer.parseInt(propName.getDefault());
|
|
||||||
}
|
|
||||||
|
|
||||||
return Integer.parseInt(prop);
|
|
||||||
}
|
}
|
||||||
|
public boolean getBoolProperty(AiProps propName) {
|
||||||
public boolean getBooleanProperty(AiProps propName) {
|
return AiProfileUtil.getBoolProperty(getPlayer(), propName);
|
||||||
String prop = AiProfileUtil.getAIProp(getPlayer().getLobbyPlayer(), propName);
|
|
||||||
|
|
||||||
if (prop == null || prop.isEmpty()) {
|
|
||||||
return Boolean.parseBoolean(propName.getDefault());
|
|
||||||
}
|
|
||||||
|
|
||||||
return Boolean.parseBoolean(prop);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public AiPlayDecision canPlayFromEffectAI(Spell spell, boolean mandatory, boolean withoutPayingManaCost) {
|
public AiPlayDecision canPlayFromEffectAI(Spell spell, boolean mandatory, boolean withoutPayingManaCost) {
|
||||||
@@ -1330,7 +1316,7 @@ public class AiController {
|
|||||||
|
|
||||||
final Card card = spell.getHostCard();
|
final Card card = spell.getHostCard();
|
||||||
if (spell instanceof SpellApiBased) {
|
if (spell instanceof SpellApiBased) {
|
||||||
boolean chance = false;
|
boolean chance;
|
||||||
if (withoutPayingManaCost) {
|
if (withoutPayingManaCost) {
|
||||||
chance = SpellApiToAi.Converter.get(spell).doTriggerNoCostWithSubs(player, spell, mandatory).willingToPlay();
|
chance = SpellApiToAi.Converter.get(spell).doTriggerNoCostWithSubs(player, spell, mandatory).willingToPlay();
|
||||||
} else {
|
} else {
|
||||||
@@ -1468,7 +1454,7 @@ public class AiController {
|
|||||||
CardCollection inHand = CardLists.filter(player.getCardsIn(ZoneType.Hand), CardPredicates.NON_LANDS);
|
CardCollection inHand = CardLists.filter(player.getCardsIn(ZoneType.Hand), CardPredicates.NON_LANDS);
|
||||||
CardCollectionView otb = player.getCardsIn(ZoneType.Battlefield);
|
CardCollectionView otb = player.getCardsIn(ZoneType.Battlefield);
|
||||||
|
|
||||||
if (getBooleanProperty(AiProps.HOLD_LAND_DROP_ONLY_IF_HAVE_OTHER_PERMS)) {
|
if (getBoolProperty(AiProps.HOLD_LAND_DROP_ONLY_IF_HAVE_OTHER_PERMS)) {
|
||||||
if (!otb.anyMatch(CardPredicates.NON_LANDS)) {
|
if (!otb.anyMatch(CardPredicates.NON_LANDS)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
package forge.ai;
|
package forge.ai;
|
||||||
|
|
||||||
import forge.LobbyPlayer;
|
import forge.LobbyPlayer;
|
||||||
|
import forge.game.player.Player;
|
||||||
import forge.util.Aggregates;
|
import forge.util.Aggregates;
|
||||||
import forge.util.FileUtil;
|
import forge.util.FileUtil;
|
||||||
import forge.util.TextUtil;
|
import forge.util.TextUtil;
|
||||||
@@ -113,6 +114,23 @@ public class AiProfileUtil {
|
|||||||
return profileMap;
|
return profileMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getProperty(final Player p, final AiProps propName) {
|
||||||
|
String prop = AiProfileUtil.getAIProp(p.getLobbyPlayer(), propName);
|
||||||
|
|
||||||
|
if (prop == null || prop.isEmpty()) {
|
||||||
|
// TODO if p is human try to predict some values from previous plays or something
|
||||||
|
return propName.getDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
return prop;
|
||||||
|
}
|
||||||
|
public static int getIntProperty(final Player p, final AiProps propName) {
|
||||||
|
return Integer.parseInt(getProperty(p, propName));
|
||||||
|
}
|
||||||
|
public static boolean getBoolProperty(final Player p, final AiProps propName) {
|
||||||
|
return Boolean.parseBoolean(getProperty(p, propName));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an AI property value for the current profile.
|
* Returns an AI property value for the current profile.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -345,27 +345,24 @@ public class ComputerUtil {
|
|||||||
return sacMeList.getFirst();
|
return sacMeList.getFirst();
|
||||||
} else {
|
} else {
|
||||||
// empty sacMeList, so get some viable average preference if the option is enabled
|
// empty sacMeList, so get some viable average preference if the option is enabled
|
||||||
if (ai.getController().isAI()) {
|
boolean enableDefaultPref = AiProfileUtil.getBoolProperty(ai, AiProps.SACRIFICE_DEFAULT_PREF_ENABLE);
|
||||||
AiController aic = ((PlayerControllerAi) ai.getController()).getAi();
|
if (enableDefaultPref) {
|
||||||
boolean enableDefaultPref = aic.getBooleanProperty(AiProps.SACRIFICE_DEFAULT_PREF_ENABLE);
|
int minCMC = AiProfileUtil.getIntProperty(ai, AiProps.SACRIFICE_DEFAULT_PREF_MIN_CMC);
|
||||||
if (enableDefaultPref) {
|
int maxCMC = AiProfileUtil.getIntProperty(ai, AiProps.SACRIFICE_DEFAULT_PREF_MAX_CMC);
|
||||||
int minCMC = aic.getIntProperty(AiProps.SACRIFICE_DEFAULT_PREF_MIN_CMC);
|
int maxCreatureEval = AiProfileUtil.getIntProperty(ai, AiProps.SACRIFICE_DEFAULT_PREF_MAX_CREATURE_EVAL);
|
||||||
int maxCMC = aic.getIntProperty(AiProps.SACRIFICE_DEFAULT_PREF_MAX_CMC);
|
boolean allowTokens = AiProfileUtil.getBoolProperty(ai, AiProps.SACRIFICE_DEFAULT_PREF_ALLOW_TOKENS);
|
||||||
int maxCreatureEval = aic.getIntProperty(AiProps.SACRIFICE_DEFAULT_PREF_MAX_CREATURE_EVAL);
|
List<String> dontSac = Arrays.asList("Black Lotus", "Mox Pearl", "Mox Jet", "Mox Emerald", "Mox Ruby", "Mox Sapphire", "Lotus Petal");
|
||||||
boolean allowTokens = aic.getBooleanProperty(AiProps.SACRIFICE_DEFAULT_PREF_ALLOW_TOKENS);
|
CardCollection allowList = CardLists.filter(typeList, card -> {
|
||||||
List<String> dontSac = Arrays.asList("Black Lotus", "Mox Pearl", "Mox Jet", "Mox Emerald", "Mox Ruby", "Mox Sapphire", "Lotus Petal");
|
if (card.isCreature() && ComputerUtilCard.evaluateCreature(card) > maxCreatureEval) {
|
||||||
CardCollection allowList = CardLists.filter(typeList, card -> {
|
return false;
|
||||||
if (card.isCreature() && ComputerUtilCard.evaluateCreature(card) > maxCreatureEval) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (allowTokens && card.isToken())
|
|
||||||
|| (card.getCMC() >= minCMC && card.getCMC() <= maxCMC && !dontSac.contains(card.getName()));
|
|
||||||
});
|
|
||||||
if (!allowList.isEmpty()) {
|
|
||||||
CardLists.sortByCmcDesc(allowList);
|
|
||||||
return allowList.getLast();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return (allowTokens && card.isToken())
|
||||||
|
|| (card.getCMC() >= minCMC && card.getCMC() <= maxCMC && !dontSac.contains(card.getName()));
|
||||||
|
});
|
||||||
|
if (!allowList.isEmpty()) {
|
||||||
|
CardLists.sortByCmcDesc(allowList);
|
||||||
|
return allowList.getLast();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1999,8 +1996,7 @@ public class ComputerUtil {
|
|||||||
else if ((threatApi == ApiType.Attach && (topStack.isCurse() || "Curse".equals(topStack.getParam("AILogic"))))
|
else if ((threatApi == ApiType.Attach && (topStack.isCurse() || "Curse".equals(topStack.getParam("AILogic"))))
|
||||||
&& (saviourApi == ApiType.Pump || saviourApi == ApiType.PumpAll
|
&& (saviourApi == ApiType.Pump || saviourApi == ApiType.PumpAll
|
||||||
|| saviourApi == ApiType.Protection || saviourApi == null)) {
|
|| saviourApi == ApiType.Protection || saviourApi == null)) {
|
||||||
AiController aic = aiPlayer.isAI() ? ((PlayerControllerAi)aiPlayer.getController()).getAi() : null;
|
boolean enableCurseAuraRemoval = AiProfileUtil.getBoolProperty(aiPlayer, AiProps.ACTIVELY_DESTROY_IMMEDIATELY_UNBLOCKABLE);
|
||||||
boolean enableCurseAuraRemoval = aic != null ? aic.getBooleanProperty(AiProps.ACTIVELY_DESTROY_IMMEDIATELY_UNBLOCKABLE) : false;
|
|
||||||
if (enableCurseAuraRemoval) {
|
if (enableCurseAuraRemoval) {
|
||||||
for (final Object o : objects) {
|
for (final Object o : objects) {
|
||||||
if (o instanceof Card c) {
|
if (o instanceof Card c) {
|
||||||
@@ -2041,17 +2037,14 @@ public class ComputerUtil {
|
|||||||
// a creature will [hopefully] die from a spell on stack
|
// a creature will [hopefully] die from a spell on stack
|
||||||
boolean willDieFromSpell = false;
|
boolean willDieFromSpell = false;
|
||||||
boolean noStackCheck = false;
|
boolean noStackCheck = false;
|
||||||
if (ai.getController().isAI()) {
|
if (AiProfileUtil.getBoolProperty(ai, AiProps.DONT_EVAL_KILLSPELLS_ON_STACK_WITH_PERMISSION)) {
|
||||||
AiController aic = ((PlayerControllerAi) ai.getController()).getAi();
|
// See if permission is on stack and ignore this check if there is and the relevant AI flag is set
|
||||||
if (aic.getBooleanProperty(AiProps.DONT_EVAL_KILLSPELLS_ON_STACK_WITH_PERMISSION)) {
|
// TODO: improve this so that this flag is not needed and the AI can properly evaluate spells in presence of counterspells.
|
||||||
// See if permission is on stack and ignore this check if there is and the relevant AI flag is set
|
for (SpellAbilityStackInstance si : game.getStack()) {
|
||||||
// TODO: improve this so that this flag is not needed and the AI can properly evaluate spells in presence of counterspells.
|
SpellAbility sa = si.getSpellAbility();
|
||||||
for (SpellAbilityStackInstance si : game.getStack()) {
|
if (sa.getApi() == ApiType.Counter) {
|
||||||
SpellAbility sa = si.getSpellAbility();
|
noStackCheck = true;
|
||||||
if (sa.getApi() == ApiType.Counter) {
|
break;
|
||||||
noStackCheck = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2080,8 +2073,7 @@ public class ComputerUtil {
|
|||||||
* @return a filtered list with no dying creatures in it
|
* @return a filtered list with no dying creatures in it
|
||||||
*/
|
*/
|
||||||
public static CardCollection filterCreaturesThatWillDieThisTurn(final Player ai, final CardCollection list, final SpellAbility excludeSa) {
|
public static CardCollection filterCreaturesThatWillDieThisTurn(final Player ai, final CardCollection list, final SpellAbility excludeSa) {
|
||||||
AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
|
if (AiProfileUtil.getBoolProperty(ai, AiProps.AVOID_TARGETING_CREATS_THAT_WILL_DIE)) {
|
||||||
if (aic.getBooleanProperty(AiProps.AVOID_TARGETING_CREATS_THAT_WILL_DIE)) {
|
|
||||||
// Try to avoid targeting creatures that are dead on board
|
// Try to avoid targeting creatures that are dead on board
|
||||||
List<Card> willBeKilled = CardLists.filter(list, card -> card.isCreature() && predictCreatureWillDieThisTurn(ai, card, excludeSa));
|
List<Card> willBeKilled = CardLists.filter(list, card -> card.isCreature() && predictCreatureWillDieThisTurn(ai, card, excludeSa));
|
||||||
list.removeAll(willBeKilled);
|
list.removeAll(willBeKilled);
|
||||||
@@ -2254,25 +2246,14 @@ public class ComputerUtil {
|
|||||||
boolean bottom = false;
|
boolean bottom = false;
|
||||||
|
|
||||||
// AI profile-based toggles
|
// AI profile-based toggles
|
||||||
int maxLandsToScryLandsToTop = 3;
|
int maxLandsToScryLandsToTop = AiProfileUtil.getIntProperty(player, AiProps.SCRY_NUM_LANDS_TO_STILL_NEED_MORE);
|
||||||
int minLandsToScryLandsAway = 8;
|
int minLandsToScryLandsAway = AiProfileUtil.getIntProperty(player, AiProps.SCRY_NUM_LANDS_TO_NOT_NEED_MORE);
|
||||||
int minCreatsToScryCreatsAway = 5;
|
int minCreatsToScryCreatsAway = AiProfileUtil.getIntProperty(player, AiProps.SCRY_NUM_CREATURES_TO_NOT_NEED_SUBPAR_ONES);
|
||||||
int minCreatEvalThreshold = 160; // just a bit higher than a baseline 2/2 creature or a 1/1 mana dork
|
int minCreatEvalThreshold = AiProfileUtil.getIntProperty(player, AiProps.SCRY_EVALTHR_TO_SCRY_AWAY_LOWCMC_CREATURE);
|
||||||
int lowCMCThreshold = 3;
|
int lowCMCThreshold = AiProfileUtil.getIntProperty(player, AiProps.SCRY_EVALTHR_CMC_THRESHOLD);
|
||||||
int maxCreatsToScryLowCMCAway = 3;
|
int maxCreatsToScryLowCMCAway = AiProfileUtil.getIntProperty(player, AiProps.SCRY_EVALTHR_CREATCOUNT_TO_SCRY_AWAY_LOWCMC);
|
||||||
boolean uncastablesToBottom = false;
|
boolean uncastablesToBottom = AiProfileUtil.getBoolProperty(player, AiProps.SCRY_IMMEDIATELY_UNCASTABLE_TO_BOTTOM);
|
||||||
int uncastableCMCThreshold = 1;
|
int uncastableCMCThreshold = AiProfileUtil.getIntProperty(player, AiProps.SCRY_IMMEDIATELY_UNCASTABLE_CMC_DIFF);
|
||||||
if (player.getController().isAI()) {
|
|
||||||
AiController aic = ((PlayerControllerAi)player.getController()).getAi();
|
|
||||||
maxLandsToScryLandsToTop = aic.getIntProperty(AiProps.SCRY_NUM_LANDS_TO_STILL_NEED_MORE);
|
|
||||||
minLandsToScryLandsAway = aic.getIntProperty(AiProps.SCRY_NUM_LANDS_TO_NOT_NEED_MORE);
|
|
||||||
minCreatsToScryCreatsAway = aic.getIntProperty(AiProps.SCRY_NUM_CREATURES_TO_NOT_NEED_SUBPAR_ONES);
|
|
||||||
minCreatEvalThreshold = aic.getIntProperty(AiProps.SCRY_EVALTHR_TO_SCRY_AWAY_LOWCMC_CREATURE);
|
|
||||||
lowCMCThreshold = aic.getIntProperty(AiProps.SCRY_EVALTHR_CMC_THRESHOLD);
|
|
||||||
maxCreatsToScryLowCMCAway = aic.getIntProperty(AiProps.SCRY_EVALTHR_CREATCOUNT_TO_SCRY_AWAY_LOWCMC);
|
|
||||||
uncastablesToBottom = aic.getBooleanProperty(AiProps.SCRY_IMMEDIATELY_UNCASTABLE_TO_BOTTOM);
|
|
||||||
uncastableCMCThreshold = aic.getIntProperty(AiProps.SCRY_IMMEDIATELY_UNCASTABLE_CMC_DIFF);
|
|
||||||
}
|
|
||||||
|
|
||||||
CardCollectionView allCards = player.getAllCards();
|
CardCollectionView allCards = player.getAllCards();
|
||||||
CardCollectionView cardsInHand = player.getCardsIn(ZoneType.Hand);
|
CardCollectionView cardsInHand = player.getCardsIn(ZoneType.Hand);
|
||||||
|
|||||||
@@ -1060,7 +1060,6 @@ public class ComputerUtilCard {
|
|||||||
|
|
||||||
public static boolean useRemovalNow(final SpellAbility sa, final Card c, final int dmg, ZoneType destination) {
|
public static boolean useRemovalNow(final SpellAbility sa, final Card c, final int dmg, ZoneType destination) {
|
||||||
final Player ai = sa.getActivatingPlayer();
|
final Player ai = sa.getActivatingPlayer();
|
||||||
final AiController aic = ((PlayerControllerAi) ai.getController()).getAi();
|
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
final PhaseHandler ph = game.getPhaseHandler();
|
final PhaseHandler ph = game.getPhaseHandler();
|
||||||
final PhaseType phaseType = ph.getPhase();
|
final PhaseType phaseType = ph.getPhase();
|
||||||
@@ -1207,7 +1206,7 @@ public class ComputerUtilCard {
|
|||||||
}
|
}
|
||||||
} else if (c.isPlaneswalker()) {
|
} else if (c.isPlaneswalker()) {
|
||||||
threat = 1;
|
threat = 1;
|
||||||
} else if (aic.getBooleanProperty(AiProps.ACTIVELY_DESTROY_ARTS_AND_NONAURA_ENCHS) && ((c.isArtifact() && !c.isCreature()) || (c.isEnchantment() && !c.isAura()))) {
|
} else if (AiProfileUtil.getBoolProperty(ai, AiProps.ACTIVELY_DESTROY_ARTS_AND_NONAURA_ENCHS) && ((c.isArtifact() && !c.isCreature()) || (c.isEnchantment() && !c.isAura()))) {
|
||||||
// non-creature artifacts and global enchantments with suspicious intrinsic abilities
|
// non-creature artifacts and global enchantments with suspicious intrinsic abilities
|
||||||
boolean priority = false;
|
boolean priority = false;
|
||||||
if (c.getOwner().isOpponentOf(ai) && c.getController().isOpponentOf(ai)) {
|
if (c.getOwner().isOpponentOf(ai) && c.getController().isOpponentOf(ai)) {
|
||||||
@@ -1311,7 +1310,7 @@ public class ComputerUtilCard {
|
|||||||
AiController aic = ((PlayerControllerAi) ai.getController()).getAi();
|
AiController aic = ((PlayerControllerAi) ai.getController()).getAi();
|
||||||
simAI = aic.usesSimulation();
|
simAI = aic.usesSimulation();
|
||||||
if (!simAI) {
|
if (!simAI) {
|
||||||
holdCombatTricks = aic.getBooleanProperty(AiProps.TRY_TO_HOLD_COMBAT_TRICKS_UNTIL_BLOCK);
|
holdCombatTricks = aic.getBoolProperty(AiProps.TRY_TO_HOLD_COMBAT_TRICKS_UNTIL_BLOCK);
|
||||||
chanceToHoldCombatTricks = aic.getIntProperty(AiProps.CHANCE_TO_HOLD_COMBAT_TRICKS_UNTIL_BLOCK);
|
chanceToHoldCombatTricks = aic.getIntProperty(AiProps.CHANCE_TO_HOLD_COMBAT_TRICKS_UNTIL_BLOCK);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1640,7 +1639,7 @@ public class ComputerUtilCard {
|
|||||||
// if we got here, Berserk will result in the pumped creature dying at EOT and the opponent will not lose
|
// if we got here, Berserk will result in the pumped creature dying at EOT and the opponent will not lose
|
||||||
// (other similar cards with AILogic$ Berserk that do not die only when attacking are excluded from consideration)
|
// (other similar cards with AILogic$ Berserk that do not die only when attacking are excluded from consideration)
|
||||||
if (ai.getController() instanceof PlayerControllerAi) {
|
if (ai.getController() instanceof PlayerControllerAi) {
|
||||||
boolean aggr = ((PlayerControllerAi) ai.getController()).getAi().getBooleanProperty(AiProps.USE_BERSERK_AGGRESSIVELY)
|
boolean aggr = ((PlayerControllerAi) ai.getController()).getAi().getBoolProperty(AiProps.USE_BERSERK_AGGRESSIVELY)
|
||||||
|| sa.hasParam("AtEOT");
|
|| sa.hasParam("AtEOT");
|
||||||
if (!aggr) {
|
if (!aggr) {
|
||||||
return false;
|
return false;
|
||||||
@@ -1907,17 +1906,10 @@ public class ComputerUtilCard {
|
|||||||
return oppCards;
|
return oppCards;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean enablePriorityRemoval = false;
|
boolean enablePriorityRemoval = AiProfileUtil.getBoolProperty(ai, AiProps.ACTIVELY_DESTROY_IMMEDIATELY_UNBLOCKABLE);
|
||||||
boolean priorityRemovalOnlyInDanger = false;
|
int priorityRemovalThreshold = AiProfileUtil.getIntProperty(ai, AiProps.DESTROY_IMMEDIATELY_UNBLOCKABLE_THRESHOLD);
|
||||||
int priorityRemovalThreshold = 0;
|
boolean priorityRemovalOnlyInDanger = AiProfileUtil.getBoolProperty(ai, AiProps.DESTROY_IMMEDIATELY_UNBLOCKABLE_ONLY_IN_DNGR);
|
||||||
int lifeInDanger = 5;
|
int lifeInDanger = AiProfileUtil.getIntProperty(ai, AiProps.DESTROY_IMMEDIATELY_UNBLOCKABLE_LIFE_IN_DNGR);
|
||||||
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) {
|
if (!enablePriorityRemoval) {
|
||||||
// Nothing to do here, the profile does not allow prioritizing
|
// Nothing to do here, the profile does not allow prioritizing
|
||||||
|
|||||||
@@ -443,13 +443,12 @@ public class ComputerUtilCombat {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int threshold = 0;
|
if (resultingPoison(ai, combat) > Math.max(7, ai.getPoisonCounters())) {
|
||||||
int maxTreshold = 0;
|
return true;
|
||||||
if (ai.getController().isAI()) {
|
|
||||||
threshold = ((PlayerControllerAi) ai.getController()).getAi().getIntProperty(AiProps.AI_IN_DANGER_THRESHOLD);
|
|
||||||
maxTreshold = ((PlayerControllerAi) ai.getController()).getAi().getIntProperty(AiProps.AI_IN_DANGER_MAX_THRESHOLD) - threshold;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int threshold = AiProfileUtil.getIntProperty(ai, AiProps.AI_IN_DANGER_THRESHOLD);
|
||||||
|
int maxTreshold = AiProfileUtil.getIntProperty(ai, AiProps.AI_IN_DANGER_MAX_THRESHOLD) - threshold;
|
||||||
int chance = MyRandom.getRandom().nextInt(80) + 5;
|
int chance = MyRandom.getRandom().nextInt(80) + 5;
|
||||||
while (maxTreshold > 0) {
|
while (maxTreshold > 0) {
|
||||||
if (MyRandom.getRandom().nextInt(100) < chance) {
|
if (MyRandom.getRandom().nextInt(100) < chance) {
|
||||||
@@ -458,10 +457,6 @@ public class ComputerUtilCombat {
|
|||||||
maxTreshold--;
|
maxTreshold--;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (resultingPoison(ai, combat) > Math.max(7, ai.getPoisonCounters())) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return !ai.cantLoseForZeroOrLessLife() && lifeThatWouldRemain(ai, combat) - payment < Math.min(threshold, ai.getLife());
|
return !ai.cantLoseForZeroOrLessLife() && lifeThatWouldRemain(ai, combat) - payment < Math.min(threshold, ai.getLife());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -356,7 +356,7 @@ public class ComputerUtilCost {
|
|||||||
final boolean beforeNextTurn = ai.getGame().getPhaseHandler().is(PhaseType.END_OF_TURN) && ai.getGame().getPhaseHandler().getNextTurn().equals(ai) && ComputerUtilCard.evaluateCreature(source) <= 150;
|
final boolean beforeNextTurn = ai.getGame().getPhaseHandler().is(PhaseType.END_OF_TURN) && ai.getGame().getPhaseHandler().getNextTurn().equals(ai) && ComputerUtilCard.evaluateCreature(source) <= 150;
|
||||||
final boolean creatureInDanger = ComputerUtil.predictCreatureWillDieThisTurn(ai, source, sourceAbility, false)
|
final boolean creatureInDanger = ComputerUtil.predictCreatureWillDieThisTurn(ai, source, sourceAbility, false)
|
||||||
&& !ComputerUtilCombat.willOpposingCreatureDieInCombat(ai, source, combat);
|
&& !ComputerUtilCombat.willOpposingCreatureDieInCombat(ai, source, combat);
|
||||||
final int lifeThreshold = ai.getController().isAI() ? (((PlayerControllerAi) ai.getController()).getAi().getIntProperty(AiProps.AI_IN_DANGER_THRESHOLD)) : 4;
|
final int lifeThreshold = AiProfileUtil.getIntProperty(ai, AiProps.AI_IN_DANGER_THRESHOLD);
|
||||||
final boolean aiInDanger = ai.getLife() <= lifeThreshold && ai.canLoseLife() && !ai.cantLoseForZeroOrLessLife();
|
final boolean aiInDanger = ai.getLife() <= lifeThreshold && ai.canLoseLife() && !ai.cantLoseForZeroOrLessLife();
|
||||||
if (creatureInDanger && !ComputerUtilCombat.isDangerousToSacInCombat(ai, source, combat)) {
|
if (creatureInDanger && !ComputerUtilCombat.isDangerousToSacInCombat(ai, source, combat)) {
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -139,9 +139,9 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean sbLimitedFormats = getAi().getBooleanProperty(AiProps.SIDEBOARDING_IN_LIMITED_FORMATS);
|
boolean sbLimitedFormats = getAi().getBoolProperty(AiProps.SIDEBOARDING_IN_LIMITED_FORMATS);
|
||||||
boolean sbSharedTypesOnly = getAi().getBooleanProperty(AiProps.SIDEBOARDING_SHARED_TYPE_ONLY);
|
boolean sbSharedTypesOnly = getAi().getBoolProperty(AiProps.SIDEBOARDING_SHARED_TYPE_ONLY);
|
||||||
boolean sbPlaneswalkerException = getAi().getBooleanProperty(AiProps.SIDEBOARDING_PLANESWALKER_EQ_CREATURE);
|
boolean sbPlaneswalkerException = getAi().getBoolProperty(AiProps.SIDEBOARDING_PLANESWALKER_EQ_CREATURE);
|
||||||
int sbChanceOnWin = getAi().getIntProperty(AiProps.SIDEBOARDING_CHANCE_ON_WIN);
|
int sbChanceOnWin = getAi().getIntProperty(AiProps.SIDEBOARDING_CHANCE_ON_WIN);
|
||||||
int sbChancePerCard = getAi().getIntProperty(AiProps.SIDEBOARDING_CHANCE_PER_CARD);
|
int sbChancePerCard = getAi().getIntProperty(AiProps.SIDEBOARDING_CHANCE_PER_CARD);
|
||||||
|
|
||||||
@@ -1372,7 +1372,7 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CardCollectionView cheatShuffle(CardCollectionView list) {
|
public CardCollectionView cheatShuffle(CardCollectionView list) {
|
||||||
return brains.getBooleanProperty(AiProps.CHEAT_WITH_MANA_ON_SHUFFLE) ? brains.cheatShuffle(list) : list;
|
return brains.getBoolProperty(AiProps.CHEAT_WITH_MANA_ON_SHUFFLE) ? brains.cheatShuffle(list) : list;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -1544,12 +1544,10 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Don't choose Tomb of Annihilation when life in danger unless we can win right away or can't lose for 0 life
|
// Don't choose Tomb of Annihilation when life in danger unless we can win right away or can't lose for 0 life
|
||||||
if (ai.getController().isAI()) { // FIXME: is this needed? Can simulation ever run this for a non-AI player?
|
int lifeInDanger = AiProfileUtil.getIntProperty(player, AiProps.AI_IN_DANGER_THRESHOLD);
|
||||||
int lifeInDanger = (((PlayerControllerAi) ai.getController()).getAi().getIntProperty(AiProps.AI_IN_DANGER_THRESHOLD));
|
if ((ai.getLife() <= lifeInDanger && !ai.cantLoseForZeroOrLessLife())
|
||||||
if ((ai.getLife() <= lifeInDanger && !ai.cantLoseForZeroOrLessLife())
|
&& !(ai.getLife() > 1 && ai.getWeakestOpponent().getLife() == 1)) {
|
||||||
&& !(ai.getLife() > 1 && ai.getWeakestOpponent().getLife() == 1)) {
|
dungeonNames.remove("Tomb of Annihilation");
|
||||||
dungeonNames.remove("Tomb of Annihilation");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -798,7 +798,7 @@ public class SpecialCardAi {
|
|||||||
public static class Intuition {
|
public static class Intuition {
|
||||||
public static CardCollection considerMultiple(final Player ai, final SpellAbility sa) {
|
public static CardCollection considerMultiple(final Player ai, final SpellAbility sa) {
|
||||||
if (ai.getController().isAI()) {
|
if (ai.getController().isAI()) {
|
||||||
if (!((PlayerControllerAi) ai.getController()).getAi().getBooleanProperty(AiProps.INTUITION_ALTERNATIVE_LOGIC)) {
|
if (!((PlayerControllerAi) ai.getController()).getAi().getBoolProperty(AiProps.INTUITION_ALTERNATIVE_LOGIC)) {
|
||||||
return new CardCollection(); // fall back to standard ChangeZoneAi considerations
|
return new CardCollection(); // fall back to standard ChangeZoneAi considerations
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,11 +73,8 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flash logic
|
boolean advancedFlash = AiProfileUtil.getBoolProperty(ai, AiProps.FLASH_ENABLE_ADVANCED_LOGIC);
|
||||||
boolean advancedFlash = false;
|
|
||||||
if (ai.getController().isAI()) {
|
|
||||||
advancedFlash = ((PlayerControllerAi)ai.getController()).getAi().getBooleanProperty(AiProps.FLASH_ENABLE_ADVANCED_LOGIC);
|
|
||||||
}
|
|
||||||
if ((source.hasKeyword(Keyword.FLASH) || (!ai.canCastSorcery() && sa.canCastTiming(ai)))
|
if ((source.hasKeyword(Keyword.FLASH) || (!ai.canCastSorcery() && sa.canCastTiming(ai)))
|
||||||
&& source.isAura() && advancedFlash && !doAdvancedFlashAuraLogic(ai, sa, sa.getTargetCard())) {
|
&& source.isAura() && advancedFlash && !doAdvancedFlashAuraLogic(ai, sa, sa.getTargetCard())) {
|
||||||
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
@@ -108,9 +105,8 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
Card source = sa.getHostCard();
|
Card source = sa.getHostCard();
|
||||||
Game game = ai.getGame();
|
Game game = ai.getGame();
|
||||||
Combat combat = game.getCombat();
|
Combat combat = game.getCombat();
|
||||||
AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
|
|
||||||
|
|
||||||
if (!aic.getBooleanProperty(AiProps.FLASH_USE_BUFF_AURAS_AS_COMBAT_TRICKS)) {
|
if (!AiProfileUtil.getBoolProperty(ai, AiProps.FLASH_USE_BUFF_AURAS_AS_COMBAT_TRICKS)) {
|
||||||
// Currently this only works with buff auras, so if the relevant toggle is disabled, just return true
|
// Currently this only works with buff auras, so if the relevant toggle is disabled, just return true
|
||||||
// for instant speed use. To be improved later.
|
// for instant speed use. To be improved later.
|
||||||
return true;
|
return true;
|
||||||
@@ -190,9 +186,9 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int chanceToCastAtEOT = aic.getIntProperty(AiProps.FLASH_BUFF_AURA_CHANCE_CAST_AT_EOT);
|
int chanceToCastAtEOT = AiProfileUtil.getIntProperty(ai, AiProps.FLASH_BUFF_AURA_CHANCE_CAST_AT_EOT);
|
||||||
int chanceToCastEarly = aic.getIntProperty(AiProps.FLASH_BUFF_AURA_CHANCE_TO_CAST_EARLY);
|
int chanceToCastEarly = AiProfileUtil.getIntProperty(ai, AiProps.FLASH_BUFF_AURA_CHANCE_TO_CAST_EARLY);
|
||||||
int chanceToRespondToStack = aic.getIntProperty(AiProps.FLASH_BUFF_AURA_CHANCE_TO_RESPOND_TO_STACK);
|
int chanceToRespondToStack = AiProfileUtil.getIntProperty(ai, AiProps.FLASH_BUFF_AURA_CHANCE_TO_RESPOND_TO_STACK);
|
||||||
|
|
||||||
boolean hasFloatMana = ai.getManaPool().totalMana() > 0;
|
boolean hasFloatMana = ai.getManaPool().totalMana() > 0;
|
||||||
boolean willDiscardNow = game.getPhaseHandler().is(PhaseType.END_OF_TURN, ai)
|
boolean willDiscardNow = game.getPhaseHandler().is(PhaseType.END_OF_TURN, ai)
|
||||||
@@ -912,7 +908,7 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
if (sa.getHostCard().getAttachedTo() != null && sa.getHostCard().getAttachedTo().isCreature()
|
if (sa.getHostCard().getAttachedTo() != null && sa.getHostCard().getAttachedTo().isCreature()
|
||||||
&& sa.getPayCosts() != null && sa.getPayCosts().hasSpecificCostType(CostSacrifice.class)) {
|
&& sa.getPayCosts() != null && sa.getPayCosts().hasSpecificCostType(CostSacrifice.class)) {
|
||||||
final int oldEvalRating = ComputerUtilCard.evaluateCreature(sa.getHostCard().getAttachedTo());
|
final int oldEvalRating = ComputerUtilCard.evaluateCreature(sa.getHostCard().getAttachedTo());
|
||||||
final int threshold = ai.isAI() ? ((PlayerControllerAi)ai.getController()).getAi().getIntProperty(AiProps.SAC_TO_REATTACH_TARGET_EVAL_THRESHOLD) : Integer.MAX_VALUE;
|
final int threshold = AiProfileUtil.getIntProperty(ai, AiProps.SAC_TO_REATTACH_TARGET_EVAL_THRESHOLD);
|
||||||
prefList = CardLists.filter(prefList, c -> {
|
prefList = CardLists.filter(prefList, c -> {
|
||||||
if (!c.isCreature()) {
|
if (!c.isCreature()) {
|
||||||
return false;
|
return false;
|
||||||
@@ -1388,7 +1384,7 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// make sure to prioritize casting spells in main 2 (creatures, other equipment, etc.) rather than moving equipment around
|
// make sure to prioritize casting spells in main 2 (creatures, other equipment, etc.) rather than moving equipment around
|
||||||
boolean decideMoveFromUseless = uselessCreature && aic.getBooleanProperty(AiProps.PRIORITIZE_MOVE_EQUIPMENT_IF_USELESS);
|
boolean decideMoveFromUseless = uselessCreature && aic.getBoolProperty(AiProps.PRIORITIZE_MOVE_EQUIPMENT_IF_USELESS);
|
||||||
|
|
||||||
if (!decideMoveFromUseless && AiCardMemory.isMemorySetEmpty(aiPlayer, AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_MAIN2)) {
|
if (!decideMoveFromUseless && AiCardMemory.isMemorySetEmpty(aiPlayer, AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_MAIN2)) {
|
||||||
SpellAbility futureSpell = aic.predictSpellToCastInMain2(ApiType.Attach);
|
SpellAbility futureSpell = aic.predictSpellToCastInMain2(ApiType.Attach);
|
||||||
|
|||||||
@@ -1300,15 +1300,9 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
// Reload planeswalkers
|
// Reload planeswalkers
|
||||||
else if (!aiPlaneswalkers.isEmpty() && (sa.getHostCard().isSorcery() || !game.getPhaseHandler().isPlayerTurn(ai))) {
|
else if (!aiPlaneswalkers.isEmpty() && (sa.getHostCard().isSorcery() || !game.getPhaseHandler().isPlayerTurn(ai))) {
|
||||||
int maxLoyaltyToConsider = 2;
|
int maxLoyaltyToConsider = AiProfileUtil.getIntProperty(ai, AiProps.BLINK_RELOAD_PLANESWALKER_MAX_LOYALTY);
|
||||||
int loyaltyDiff = 2;
|
int loyaltyDiff = AiProfileUtil.getIntProperty(ai, AiProps.BLINK_RELOAD_PLANESWALKER_LOYALTY_DIFF);
|
||||||
int chance = 30;
|
int chance = AiProfileUtil.getIntProperty(ai, AiProps.BLINK_RELOAD_PLANESWALKER_CHANCE);
|
||||||
if (ai.getController().isAI()) {
|
|
||||||
AiController aic = ((PlayerControllerAi) ai.getController()).getAi();
|
|
||||||
maxLoyaltyToConsider = aic.getIntProperty(AiProps.BLINK_RELOAD_PLANESWALKER_MAX_LOYALTY);
|
|
||||||
loyaltyDiff = aic.getIntProperty(AiProps.BLINK_RELOAD_PLANESWALKER_LOYALTY_DIFF);
|
|
||||||
chance = aic.getIntProperty(AiProps.BLINK_RELOAD_PLANESWALKER_CHANCE);
|
|
||||||
}
|
|
||||||
if (MyRandom.percentTrue(chance)) {
|
if (MyRandom.percentTrue(chance)) {
|
||||||
aiPlaneswalkers.sort(CardPredicates.compareByCounterType(CounterEnumType.LOYALTY));
|
aiPlaneswalkers.sort(CardPredicates.compareByCounterType(CounterEnumType.LOYALTY));
|
||||||
for (Card pw : aiPlaneswalkers) {
|
for (Card pw : aiPlaneswalkers) {
|
||||||
@@ -1670,15 +1664,9 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
if (card.hasCounters()) {
|
if (card.hasCounters()) {
|
||||||
if (card.isPlaneswalker()) {
|
if (card.isPlaneswalker()) {
|
||||||
int maxLoyaltyToConsider = 2;
|
int maxLoyaltyToConsider = AiProfileUtil.getIntProperty(decider, AiProps.BLINK_RELOAD_PLANESWALKER_MAX_LOYALTY);
|
||||||
int loyaltyDiff = 2;
|
int loyaltyDiff = AiProfileUtil.getIntProperty(decider, AiProps.BLINK_RELOAD_PLANESWALKER_LOYALTY_DIFF);
|
||||||
int chance = 30;
|
int chance = AiProfileUtil.getIntProperty(decider, AiProps.BLINK_RELOAD_PLANESWALKER_CHANCE);
|
||||||
if (decider.getController().isAI()) {
|
|
||||||
AiController aic = ((PlayerControllerAi) decider.getController()).getAi();
|
|
||||||
maxLoyaltyToConsider = aic.getIntProperty(AiProps.BLINK_RELOAD_PLANESWALKER_MAX_LOYALTY);
|
|
||||||
loyaltyDiff = aic.getIntProperty(AiProps.BLINK_RELOAD_PLANESWALKER_LOYALTY_DIFF);
|
|
||||||
chance = aic.getIntProperty(AiProps.BLINK_RELOAD_PLANESWALKER_CHANCE);
|
|
||||||
}
|
|
||||||
if (MyRandom.percentTrue(chance)) {
|
if (MyRandom.percentTrue(chance)) {
|
||||||
int curLoyalty = card.getCounters(CounterEnumType.LOYALTY);
|
int curLoyalty = card.getCounters(CounterEnumType.LOYALTY);
|
||||||
int freshLoyalty = Integer.parseInt(card.getCurrentState().getBaseLoyalty());
|
int freshLoyalty = Integer.parseInt(card.getCurrentState().getBaseLoyalty());
|
||||||
|
|||||||
@@ -140,17 +140,15 @@ public class ChangeZoneAllAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
computerType = new CardCollection();
|
computerType = new CardCollection();
|
||||||
}
|
}
|
||||||
int creatureEvalThreshold = 200; // value difference (in evaluateCreatureList units)
|
|
||||||
int nonCreatureEvalThreshold = 3; // CMC difference
|
int creatureEvalThreshold; // value difference (in evaluateCreatureList units)
|
||||||
if (ai.getController().isAI()) {
|
int nonCreatureEvalThreshold; // CMC difference
|
||||||
AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
|
if (destination == ZoneType.Hand) {
|
||||||
if (destination == ZoneType.Hand) {
|
creatureEvalThreshold = AiProfileUtil.getIntProperty(ai, AiProps.BOUNCE_ALL_TO_HAND_CREAT_EVAL_DIFF);
|
||||||
creatureEvalThreshold = aic.getIntProperty(AiProps.BOUNCE_ALL_TO_HAND_CREAT_EVAL_DIFF);
|
nonCreatureEvalThreshold = AiProfileUtil.getIntProperty(ai, AiProps.BOUNCE_ALL_TO_HAND_NONCREAT_EVAL_DIFF);
|
||||||
nonCreatureEvalThreshold = aic.getIntProperty(AiProps.BOUNCE_ALL_TO_HAND_NONCREAT_EVAL_DIFF);
|
} else {
|
||||||
} else {
|
creatureEvalThreshold = AiProfileUtil.getIntProperty(ai, AiProps.BOUNCE_ALL_ELSEWHERE_CREAT_EVAL_DIFF);
|
||||||
creatureEvalThreshold = aic.getIntProperty(AiProps.BOUNCE_ALL_ELSEWHERE_CREAT_EVAL_DIFF);
|
nonCreatureEvalThreshold = AiProfileUtil.getIntProperty(ai, AiProps.BOUNCE_ALL_ELSEWHERE_NONCREAT_EVAL_DIFF);
|
||||||
nonCreatureEvalThreshold = aic.getIntProperty(AiProps.BOUNCE_ALL_ELSEWHERE_NONCREAT_EVAL_DIFF);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// mass zone change for creatures: if in dire danger, do it; otherwise, only do it if the opponent's
|
// mass zone change for creatures: if in dire danger, do it; otherwise, only do it if the opponent's
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ public class CopySpellAbilityAi extends SpellAbilityAi {
|
|||||||
@Override
|
@Override
|
||||||
protected AiAbilityDecision checkApiLogic(Player aiPlayer, SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(Player aiPlayer, SpellAbility sa) {
|
||||||
Game game = aiPlayer.getGame();
|
Game game = aiPlayer.getGame();
|
||||||
int chance = ((PlayerControllerAi)aiPlayer.getController()).getAi().getIntProperty(AiProps.CHANCE_TO_COPY_OWN_SPELL_WHILE_ON_STACK);
|
int chance = AiProfileUtil.getIntProperty(aiPlayer, AiProps.CHANCE_TO_COPY_OWN_SPELL_WHILE_ON_STACK);
|
||||||
int diff = ((PlayerControllerAi)aiPlayer.getController()).getAi().getIntProperty(AiProps.ALWAYS_COPY_SPELL_IF_CMC_DIFF);
|
int diff = AiProfileUtil.getIntProperty(aiPlayer, AiProps.ALWAYS_COPY_SPELL_IF_CMC_DIFF);
|
||||||
String logic = sa.getParamOrDefault("AILogic", "");
|
String logic = sa.getParamOrDefault("AILogic", "");
|
||||||
|
|
||||||
if (game.getStack().isEmpty()) {
|
if (game.getStack().isEmpty()) {
|
||||||
|
|||||||
@@ -148,18 +148,16 @@ public class CounterAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Specific constraints for the AI to use/not use counterspells against specific groups of spells
|
// Specific constraints for the AI to use/not use counterspells against specific groups of spells
|
||||||
// (specified in the AI profile)
|
boolean ctrCmc0ManaPerms = AiProfileUtil.getBoolProperty(ai, AiProps.ALWAYS_COUNTER_CMC_0_MANA_MAKING_PERMS);
|
||||||
AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
|
boolean ctrDamageSpells = AiProfileUtil.getBoolProperty(ai, AiProps.ALWAYS_COUNTER_DAMAGE_SPELLS);
|
||||||
boolean ctrCmc0ManaPerms = aic.getBooleanProperty(AiProps.ALWAYS_COUNTER_CMC_0_MANA_MAKING_PERMS);
|
boolean ctrRemovalSpells = AiProfileUtil.getBoolProperty(ai, AiProps.ALWAYS_COUNTER_REMOVAL_SPELLS);
|
||||||
boolean ctrDamageSpells = aic.getBooleanProperty(AiProps.ALWAYS_COUNTER_DAMAGE_SPELLS);
|
boolean ctrPumpSpells = AiProfileUtil.getBoolProperty(ai, AiProps.ALWAYS_COUNTER_PUMP_SPELLS);
|
||||||
boolean ctrRemovalSpells = aic.getBooleanProperty(AiProps.ALWAYS_COUNTER_REMOVAL_SPELLS);
|
boolean ctrAuraSpells = AiProfileUtil.getBoolProperty(ai, AiProps.ALWAYS_COUNTER_AURAS);
|
||||||
boolean ctrPumpSpells = aic.getBooleanProperty(AiProps.ALWAYS_COUNTER_PUMP_SPELLS);
|
boolean ctrOtherCounters = AiProfileUtil.getBoolProperty(ai, AiProps.ALWAYS_COUNTER_OTHER_COUNTERSPELLS);
|
||||||
boolean ctrAuraSpells = aic.getBooleanProperty(AiProps.ALWAYS_COUNTER_AURAS);
|
int ctrChanceCMC1 = AiProfileUtil.getIntProperty(ai, AiProps.CHANCE_TO_COUNTER_CMC_1);
|
||||||
boolean ctrOtherCounters = aic.getBooleanProperty(AiProps.ALWAYS_COUNTER_OTHER_COUNTERSPELLS);
|
int ctrChanceCMC2 = AiProfileUtil.getIntProperty(ai, AiProps.CHANCE_TO_COUNTER_CMC_2);
|
||||||
int ctrChanceCMC1 = aic.getIntProperty(AiProps.CHANCE_TO_COUNTER_CMC_1);
|
int ctrChanceCMC3 = AiProfileUtil.getIntProperty(ai, AiProps.CHANCE_TO_COUNTER_CMC_3);
|
||||||
int ctrChanceCMC2 = aic.getIntProperty(AiProps.CHANCE_TO_COUNTER_CMC_2);
|
String ctrNamed = AiProfileUtil.getProperty(ai, AiProps.ALWAYS_COUNTER_SPELLS_FROM_NAMED_CARDS);
|
||||||
int ctrChanceCMC3 = aic.getIntProperty(AiProps.CHANCE_TO_COUNTER_CMC_3);
|
|
||||||
String ctrNamed = aic.getProperty(AiProps.ALWAYS_COUNTER_SPELLS_FROM_NAMED_CARDS);
|
|
||||||
boolean dontCounter = false;
|
boolean dontCounter = false;
|
||||||
|
|
||||||
if (tgtCMC == 1 && !MyRandom.percentTrue(ctrChanceCMC1)) {
|
if (tgtCMC == 1 && !MyRandom.percentTrue(ctrChanceCMC1)) {
|
||||||
@@ -170,7 +168,7 @@ public class CounterAi extends SpellAbilityAi {
|
|||||||
dontCounter = true;
|
dontCounter = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tgtSA != null && tgtCMC < aic.getIntProperty(AiProps.MIN_SPELL_CMC_TO_COUNTER)) {
|
if (tgtSA != null && tgtCMC < AiProfileUtil.getIntProperty(ai, AiProps.MIN_SPELL_CMC_TO_COUNTER)) {
|
||||||
dontCounter = true;
|
dontCounter = true;
|
||||||
Card tgtSource = tgtSA.getHostCard();
|
Card tgtSource = tgtSA.getHostCard();
|
||||||
if ((tgtSource != null && tgtCMC == 0 && tgtSource.isPermanent() && !tgtSource.getManaAbilities().isEmpty() && ctrCmc0ManaPerms)
|
if ((tgtSource != null && tgtCMC == 0 && tgtSource.isPermanent() && !tgtSource.getManaAbilities().isEmpty() && ctrCmc0ManaPerms)
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ public class CountersProliferateAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
final CounterType poison = CounterEnumType.POISON;
|
final CounterType poison = CounterEnumType.POISON;
|
||||||
|
|
||||||
boolean aggroAI = (((PlayerControllerAi) ai.getController()).getAi()).getBooleanProperty(AiProps.PLAY_AGGRO);
|
boolean aggroAI = AiProfileUtil.getBoolProperty(ai, AiProps.PLAY_AGGRO);
|
||||||
// because countertype can't be chosen anymore, only look for poison counters
|
// because countertype can't be chosen anymore, only look for poison counters
|
||||||
for (final Player p : IterableUtil.filter(options, Player.class)) {
|
for (final Player p : IterableUtil.filter(options, Player.class)) {
|
||||||
if (p.isOpponentOf(ai)) {
|
if (p.isOpponentOf(ai)) {
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ public class CountersPutAi extends CountersAi {
|
|||||||
final boolean isClockwork = "True".equals(sa.getParam("UpTo")) && "Self".equals(sa.getParam("Defined"))
|
final boolean isClockwork = "True".equals(sa.getParam("UpTo")) && "Self".equals(sa.getParam("Defined"))
|
||||||
&& "P1P0".equals(sa.getParam("CounterType")) && "Count$xPaid".equals(source.getSVar("X"))
|
&& "P1P0".equals(sa.getParam("CounterType")) && "Count$xPaid".equals(source.getSVar("X"))
|
||||||
&& sa.hasParam("MaxFromEffect");
|
&& sa.hasParam("MaxFromEffect");
|
||||||
boolean playAggro = ((PlayerControllerAi) ai.getController()).getAi().getBooleanProperty(AiProps.PLAY_AGGRO);
|
boolean playAggro = AiProfileUtil.getBoolProperty(ai, AiProps.PLAY_AGGRO);
|
||||||
|
|
||||||
if ("ExistingCounter".equals(type)) {
|
if ("ExistingCounter".equals(type)) {
|
||||||
final boolean eachExisting = sa.hasParam("EachExistingCounter");
|
final boolean eachExisting = sa.hasParam("EachExistingCounter");
|
||||||
@@ -219,10 +219,8 @@ public class CountersPutAi extends CountersAi {
|
|||||||
} else if ("PayEnergy".equals(logic)) {
|
} else if ("PayEnergy".equals(logic)) {
|
||||||
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
} else if ("PayEnergyConservatively".equals(logic)) {
|
} else if ("PayEnergyConservatively".equals(logic)) {
|
||||||
boolean onlyInCombat = ai.getController().isAI()
|
boolean onlyInCombat = AiProfileUtil.getBoolProperty(ai, AiProps.CONSERVATIVE_ENERGY_PAYMENT_ONLY_IN_COMBAT);
|
||||||
&& ((PlayerControllerAi) ai.getController()).getAi().getBooleanProperty(AiProps.CONSERVATIVE_ENERGY_PAYMENT_ONLY_IN_COMBAT);
|
boolean onlyDefensive = AiProfileUtil.getBoolProperty(ai, AiProps.CONSERVATIVE_ENERGY_PAYMENT_ONLY_DEFENSIVELY);
|
||||||
boolean onlyDefensive = ai.getController().isAI()
|
|
||||||
&& ((PlayerControllerAi) ai.getController()).getAi().getBooleanProperty(AiProps.CONSERVATIVE_ENERGY_PAYMENT_ONLY_DEFENSIVELY);
|
|
||||||
|
|
||||||
if (playAggro) {
|
if (playAggro) {
|
||||||
// aggro profiles ignore conservative play for this AI logic
|
// aggro profiles ignore conservative play for this AI logic
|
||||||
|
|||||||
@@ -108,16 +108,13 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
dmg = ComputerUtilCost.getMaxXValue(sa, ai, sa.isTrigger());
|
dmg = ComputerUtilCost.getMaxXValue(sa, ai, sa.isTrigger());
|
||||||
|
|
||||||
// Try not to waste spells like Blaze or Fireball on early targets, try to do more damage with them if possible
|
// Try not to waste spells like Blaze or Fireball on early targets, try to do more damage with them if possible
|
||||||
if (ai.getController().isAI()) {
|
int holdChance = AiProfileUtil.getIntProperty(ai, AiProps.HOLD_X_DAMAGE_SPELLS_FOR_MORE_DAMAGE_CHANCE);
|
||||||
AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
|
if (MyRandom.percentTrue(holdChance)) {
|
||||||
int holdChance = aic.getIntProperty(AiProps.HOLD_X_DAMAGE_SPELLS_FOR_MORE_DAMAGE_CHANCE);
|
int threshold = AiProfileUtil.getIntProperty(ai, AiProps.HOLD_X_DAMAGE_SPELLS_THRESHOLD);
|
||||||
if (MyRandom.percentTrue(holdChance)) {
|
boolean inDanger = ComputerUtil.aiLifeInDanger(ai, false, 0);
|
||||||
int threshold = aic.getIntProperty(AiProps.HOLD_X_DAMAGE_SPELLS_THRESHOLD);
|
boolean isLethal = sa.usesTargeting() && sa.getTargetRestrictions().canTgtPlayer() && dmg >= ai.getWeakestOpponent().getLife() && !ai.getWeakestOpponent().cantLoseForZeroOrLessLife();
|
||||||
boolean inDanger = ComputerUtil.aiLifeInDanger(ai, false, 0);
|
if (dmg < threshold && ai.getGame().getPhaseHandler().getTurn() / 2 < threshold && !inDanger && !isLethal) {
|
||||||
boolean isLethal = sa.usesTargeting() && sa.getTargetRestrictions().canTgtPlayer() && dmg >= ai.getWeakestOpponent().getLife() && !ai.getWeakestOpponent().cantLoseForZeroOrLessLife();
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
if (dmg < threshold && ai.getGame().getPhaseHandler().getTurn() / 2 < threshold && !inDanger && !isLethal) {
|
|
||||||
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1086,7 +1083,7 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Game game = ai.getGame();
|
Game game = ai.getGame();
|
||||||
int chance = ((PlayerControllerAi)ai.getController()).getAi().getIntProperty(AiProps.CHANCE_TO_CHAIN_TWO_DAMAGE_SPELLS);
|
int chance = AiProfileUtil.getIntProperty(ai, AiProps.CHANCE_TO_CHAIN_TWO_DAMAGE_SPELLS);
|
||||||
|
|
||||||
if (chance > 0 && (ComputerUtilCombat.lifeInDanger(ai, game.getCombat()) || ComputerUtil.aiLifeInDanger(ai, true, 0))) {
|
if (chance > 0 && (ComputerUtilCombat.lifeInDanger(ai, game.getCombat()) || ComputerUtil.aiLifeInDanger(ai, true, 0))) {
|
||||||
chance = 100; // in danger, do it even if normally the chance is low (unless chaining is completely disabled)
|
chance = 100; // in danger, do it even if normally the chance is low (unless chaining is completely disabled)
|
||||||
|
|||||||
@@ -409,12 +409,11 @@ public class DestroyAi extends SpellAbilityAi {
|
|||||||
int oppLandsOTB = tgtPlayer.getLandsInPlay().size();
|
int oppLandsOTB = tgtPlayer.getLandsInPlay().size();
|
||||||
|
|
||||||
// AI profile-dependent properties
|
// AI profile-dependent properties
|
||||||
AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
|
int amountNoTempoCheck = AiProfileUtil.getIntProperty(ai, AiProps.STRIPMINE_MIN_LANDS_OTB_FOR_NO_TEMPO_CHECK);
|
||||||
int amountNoTempoCheck = aic.getIntProperty(AiProps.STRIPMINE_MIN_LANDS_OTB_FOR_NO_TEMPO_CHECK);
|
int amountNoTimingCheck = AiProfileUtil.getIntProperty(ai, AiProps.STRIPMINE_MIN_LANDS_FOR_NO_TIMING_CHECK);
|
||||||
int amountNoTimingCheck = aic.getIntProperty(AiProps.STRIPMINE_MIN_LANDS_FOR_NO_TIMING_CHECK);
|
int amountLandsInHand = AiProfileUtil.getIntProperty(ai, AiProps.STRIPMINE_MIN_LANDS_IN_HAND_TO_ACTIVATE);
|
||||||
int amountLandsInHand = aic.getIntProperty(AiProps.STRIPMINE_MIN_LANDS_IN_HAND_TO_ACTIVATE);
|
int amountLandsToManalock = AiProfileUtil.getIntProperty(ai, AiProps.STRIPMINE_MAX_LANDS_TO_ATTEMPT_MANALOCKING);
|
||||||
int amountLandsToManalock = aic.getIntProperty(AiProps.STRIPMINE_MAX_LANDS_TO_ATTEMPT_MANALOCKING);
|
boolean highPriorityIfNoLandDrop = AiProfileUtil.getBoolProperty(ai, AiProps.STRIPMINE_HIGH_PRIORITY_ON_SKIPPED_LANDDROP);
|
||||||
boolean highPriorityIfNoLandDrop = aic.getBooleanProperty(AiProps.STRIPMINE_HIGH_PRIORITY_ON_SKIPPED_LANDDROP);
|
|
||||||
|
|
||||||
// if the opponent didn't play a land and has few lands OTB, might be worth mana-locking him
|
// if the opponent didn't play a land and has few lands OTB, might be worth mana-locking him
|
||||||
PhaseHandler ph = ai.getGame().getPhaseHandler();
|
PhaseHandler ph = ai.getGame().getPhaseHandler();
|
||||||
|
|||||||
@@ -264,7 +264,7 @@ public class DrawAi extends SpellAbilityAi {
|
|||||||
if (sa.getPayCosts().hasSpecificCostType(CostPayLife.class)) {
|
if (sa.getPayCosts().hasSpecificCostType(CostPayLife.class)) {
|
||||||
// [Necrologia, Pay X Life : Draw X Cards]
|
// [Necrologia, Pay X Life : Draw X Cards]
|
||||||
// Don't draw more than what's "safe" and don't risk a near death experience
|
// Don't draw more than what's "safe" and don't risk a near death experience
|
||||||
boolean aggroAI = (((PlayerControllerAi) ai.getController()).getAi()).getBooleanProperty(AiProps.PLAY_AGGRO);
|
boolean aggroAI = AiProfileUtil.getBoolProperty(ai, AiProps.PLAY_AGGRO);
|
||||||
while (ComputerUtil.aiLifeInDanger(ai, aggroAI, numCards) && numCards > 0) {
|
while (ComputerUtil.aiLifeInDanger(ai, aggroAI, numCards) && numCards > 0) {
|
||||||
numCards--;
|
numCards--;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ public class EndureAi extends SpellAbilityAi {
|
|||||||
return new AiAbilityDecision(0, AiPlayDecision.AnotherTime);
|
return new AiAbilityDecision(0, AiPlayDecision.AnotherTime);
|
||||||
}
|
}
|
||||||
int curLife = aiPlayer.getLife();
|
int curLife = aiPlayer.getLife();
|
||||||
int dangerLife = (((PlayerControllerAi) aiPlayer.getController()).getAi().getIntProperty(AiProps.AI_IN_DANGER_THRESHOLD));
|
int dangerLife = AiProfileUtil.getIntProperty(aiPlayer, AiProps.AI_IN_DANGER_THRESHOLD);
|
||||||
if (curLife <= dangerLife) {
|
if (curLife <= dangerLife) {
|
||||||
return new AiAbilityDecision(0, AiPlayDecision.CantAffordX);
|
return new AiAbilityDecision(0, AiPlayDecision.CantAffordX);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,14 +37,8 @@ public class ExploreAi extends SpellAbilityAi {
|
|||||||
CardCollection landsOTB = CardLists.filter(cardsOTB, CardPredicates.LANDS_PRODUCING_MANA);
|
CardCollection landsOTB = CardLists.filter(cardsOTB, CardPredicates.LANDS_PRODUCING_MANA);
|
||||||
CardCollection landsInHand = CardLists.filter(cardsInHand, CardPredicates.LANDS_PRODUCING_MANA);
|
CardCollection landsInHand = CardLists.filter(cardsInHand, CardPredicates.LANDS_PRODUCING_MANA);
|
||||||
|
|
||||||
int maxCMCDiff = 1;
|
int maxCMCDiff = AiProfileUtil.getIntProperty(ai, AiProps.EXPLORE_MAX_CMC_DIFF_TO_PUT_IN_GRAVEYARD);
|
||||||
int numLandsToStillNeedMore = 2;
|
int numLandsToStillNeedMore = AiProfileUtil.getIntProperty(ai, AiProps.EXPLORE_NUM_LANDS_TO_STILL_NEED_MORE);
|
||||||
|
|
||||||
if (ai.getController().isAI()) {
|
|
||||||
AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
|
|
||||||
maxCMCDiff = aic.getIntProperty(AiProps.EXPLORE_MAX_CMC_DIFF_TO_PUT_IN_GRAVEYARD);
|
|
||||||
numLandsToStillNeedMore = aic.getIntProperty(AiProps.EXPLORE_NUM_LANDS_TO_STILL_NEED_MORE);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (landsInHand.isEmpty() && landsOTB.size() <= numLandsToStillNeedMore) {
|
if (landsInHand.isEmpty() && landsOTB.size() <= numLandsToStillNeedMore) {
|
||||||
// We need more lands to improve our mana base, explore away the non-lands
|
// We need more lands to improve our mana base, explore away the non-lands
|
||||||
|
|||||||
@@ -93,9 +93,9 @@ public class LifeExchangeVariantAi extends SpellAbilityAi {
|
|||||||
if (game.getCombat().isUnblocked(source) && def.canLoseLife() && aiLife >= def.getLife() && source.getNetPower() < def.getLife()) {
|
if (game.getCombat().isUnblocked(source) && def.canLoseLife() && aiLife >= def.getLife() && source.getNetPower() < def.getLife()) {
|
||||||
// Unblocked Evra which can deal lethal damage
|
// Unblocked Evra which can deal lethal damage
|
||||||
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
} else if (ai.getController().isAI() && aiLife > source.getNetPower() && source.hasKeyword(Keyword.LIFELINK)) {
|
} else if (aiLife > source.getNetPower() && source.hasKeyword(Keyword.LIFELINK)) {
|
||||||
int dangerMin = (((PlayerControllerAi) ai.getController()).getAi().getIntProperty(AiProps.AI_IN_DANGER_THRESHOLD));
|
int dangerMin = AiProfileUtil.getIntProperty(ai, AiProps.AI_IN_DANGER_THRESHOLD);
|
||||||
int dangerMax = (((PlayerControllerAi) ai.getController()).getAi().getIntProperty(AiProps.AI_IN_DANGER_MAX_THRESHOLD));
|
int dangerMax = AiProfileUtil.getIntProperty(ai, AiProps.AI_IN_DANGER_MAX_THRESHOLD);
|
||||||
int dangerDiff = dangerMax - dangerMin;
|
int dangerDiff = dangerMax - dangerMin;
|
||||||
int lifeInDanger = dangerDiff <= 0 ? dangerMin : MyRandom.getRandom().nextInt(dangerDiff) + dangerMin;
|
int lifeInDanger = dangerDiff <= 0 ? dangerMin : MyRandom.getRandom().nextInt(dangerDiff) + dangerMin;
|
||||||
if (source.getNetPower() >= lifeInDanger && ai.canGainLife() && ComputerUtil.lifegainPositive(ai, source)) {
|
if (source.getNetPower() >= lifeInDanger && ai.canGainLife() && ComputerUtil.lifegainPositive(ai, source)) {
|
||||||
|
|||||||
@@ -65,10 +65,7 @@ public class PermanentCreatureAi extends PermanentAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Flash logic
|
// Flash logic
|
||||||
boolean advancedFlash = false;
|
boolean advancedFlash = AiProfileUtil.getBoolProperty(ai, AiProps.FLASH_ENABLE_ADVANCED_LOGIC);
|
||||||
if (ai.getController().isAI()) {
|
|
||||||
advancedFlash = ((PlayerControllerAi)ai.getController()).getAi().getBooleanProperty(AiProps.FLASH_ENABLE_ADVANCED_LOGIC);
|
|
||||||
}
|
|
||||||
if (card.hasKeyword(Keyword.FLASH) || (!ai.canCastSorcery() && sa.canCastTiming(ai) && !sa.isCastFromPlayEffect())) {
|
if (card.hasKeyword(Keyword.FLASH) || (!ai.canCastSorcery() && sa.canCastTiming(ai) && !sa.isCastFromPlayEffect())) {
|
||||||
if (advancedFlash) {
|
if (advancedFlash) {
|
||||||
return doAdvancedFlashLogic(card, ai, sa);
|
return doAdvancedFlashLogic(card, ai, sa);
|
||||||
@@ -135,11 +132,11 @@ public class PermanentCreatureAi extends PermanentAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int chanceToObeyAmbushAI = aic.getIntProperty(AiProps.FLASH_CHANCE_TO_OBEY_AMBUSHAI);
|
int chanceToObeyAmbushAI = AiProfileUtil.getIntProperty(ai, AiProps.FLASH_CHANCE_TO_OBEY_AMBUSHAI);
|
||||||
int chanceToAddBlocker = aic.getIntProperty(AiProps.FLASH_CHANCE_TO_CAST_AS_VALUABLE_BLOCKER);
|
int chanceToAddBlocker = AiProfileUtil.getIntProperty(ai, AiProps.FLASH_CHANCE_TO_CAST_AS_VALUABLE_BLOCKER);
|
||||||
int chanceToCastForETB = aic.getIntProperty(AiProps.FLASH_CHANCE_TO_CAST_DUE_TO_ETB_EFFECTS);
|
int chanceToCastForETB = AiProfileUtil.getIntProperty(ai, AiProps.FLASH_CHANCE_TO_CAST_DUE_TO_ETB_EFFECTS);
|
||||||
int chanceToRespondToStack = aic.getIntProperty(AiProps.FLASH_CHANCE_TO_RESPOND_TO_STACK_WITH_ETB);
|
int chanceToRespondToStack = AiProfileUtil.getIntProperty(ai, AiProps.FLASH_CHANCE_TO_RESPOND_TO_STACK_WITH_ETB);
|
||||||
int chanceToProcETBBeforeMain1 = aic.getIntProperty(AiProps.FLASH_CHANCE_TO_CAST_FOR_ETB_BEFORE_MAIN1);
|
int chanceToProcETBBeforeMain1 = AiProfileUtil.getIntProperty(ai, AiProps.FLASH_CHANCE_TO_CAST_FOR_ETB_BEFORE_MAIN1);
|
||||||
boolean canCastAtOppTurn = true;
|
boolean canCastAtOppTurn = true;
|
||||||
for (Card c : ai.getGame().getCardsIn(ZoneType.Battlefield)) {
|
for (Card c : ai.getGame().getCardsIn(ZoneType.Battlefield)) {
|
||||||
for (StaticAbility s : c.getStaticAbilities()) {
|
for (StaticAbility s : c.getStaticAbilities()) {
|
||||||
|
|||||||
@@ -100,13 +100,8 @@ public class RearrangeTopOfLibraryAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int uncastableCMCThreshold = 2;
|
int minLandsToScryLandsAway = AiProfileUtil.getIntProperty(player, AiProps.SCRY_NUM_LANDS_TO_NOT_NEED_MORE);
|
||||||
int minLandsToScryLandsAway = 4;
|
int uncastableCMCThreshold = AiProfileUtil.getIntProperty(player, AiProps.SCRY_IMMEDIATELY_UNCASTABLE_CMC_DIFF);
|
||||||
if (player.getController().isAI()) {
|
|
||||||
AiController aic = ((PlayerControllerAi)player.getController()).getAi();
|
|
||||||
minLandsToScryLandsAway = aic.getIntProperty(AiProps.SCRY_NUM_LANDS_TO_NOT_NEED_MORE);
|
|
||||||
uncastableCMCThreshold = aic.getIntProperty(AiProps.SCRY_IMMEDIATELY_UNCASTABLE_CMC_DIFF);
|
|
||||||
}
|
|
||||||
|
|
||||||
int landsOTB = CardLists.count(p.getCardsIn(ZoneType.Battlefield), CardPredicates.LANDS_PRODUCING_MANA);
|
int landsOTB = CardLists.count(p.getCardsIn(ZoneType.Battlefield), CardPredicates.LANDS_PRODUCING_MANA);
|
||||||
int cmc = top.isSplitCard() ? Math.min(top.getCMC(Card.SplitCMCMode.LeftSplitCMC), top.getCMC(Card.SplitCMCMode.RightSplitCMC))
|
int cmc = top.isSplitCard() ? Math.min(top.getCMC(Card.SplitCMCMode.LeftSplitCMC), top.getCMC(Card.SplitCMCMode.RightSplitCMC))
|
||||||
|
|||||||
@@ -29,14 +29,13 @@ public class RollPlanarDiceAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean willRollOnPlane(Player ai, Card plane) {
|
private boolean willRollOnPlane(Player ai, Card plane) {
|
||||||
AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
|
|
||||||
boolean decideToRoll = false;
|
boolean decideToRoll = false;
|
||||||
boolean rollInMain1 = false;
|
boolean rollInMain1 = false;
|
||||||
String modeName = "never";
|
String modeName = "never";
|
||||||
int maxActivations = aic.getIntProperty(AiProps.DEFAULT_MAX_PLANAR_DIE_ROLLS_PER_TURN);
|
int maxActivations = AiProfileUtil.getIntProperty(ai, AiProps.DEFAULT_MAX_PLANAR_DIE_ROLLS_PER_TURN);
|
||||||
int chance = aic.getIntProperty(AiProps.DEFAULT_PLANAR_DIE_ROLL_CHANCE);
|
int chance = AiProfileUtil.getIntProperty(ai, AiProps.DEFAULT_PLANAR_DIE_ROLL_CHANCE);
|
||||||
int hesitationChance = aic.getIntProperty(AiProps.PLANAR_DIE_ROLL_HESITATION_CHANCE);
|
int hesitationChance = AiProfileUtil.getIntProperty(ai, AiProps.PLANAR_DIE_ROLL_HESITATION_CHANCE);
|
||||||
int minTurnToRoll = aic.getIntProperty(AiProps.DEFAULT_MIN_TURN_TO_ROLL_PLANAR_DIE);
|
int minTurnToRoll = AiProfileUtil.getIntProperty(ai, AiProps.DEFAULT_MIN_TURN_TO_ROLL_PLANAR_DIE);
|
||||||
|
|
||||||
if (plane.hasSVar("AIRollPlanarDieParams")) {
|
if (plane.hasSVar("AIRollPlanarDieParams")) {
|
||||||
String[] params = plane.getSVar("AIRollPlanarDieParams").toLowerCase().trim().split("\\|");
|
String[] params = plane.getSVar("AIRollPlanarDieParams").toLowerCase().trim().split("\\|");
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ public class SurveilAi extends SpellAbilityAi {
|
|||||||
// Only Surveil for life when at decent amount of life remaining
|
// Only Surveil for life when at decent amount of life remaining
|
||||||
final Cost cost = sa.getPayCosts();
|
final Cost cost = sa.getPayCosts();
|
||||||
if (cost != null && cost.hasSpecificCostType(CostPayLife.class)) {
|
if (cost != null && cost.hasSpecificCostType(CostPayLife.class)) {
|
||||||
final int maxLife = ((PlayerControllerAi)ai.getController()).getAi().getIntProperty(AiProps.SURVEIL_LIFEPERC_AFTER_PAYING_LIFE);
|
final int maxLife = AiProfileUtil.getIntProperty(ai, AiProps.SURVEIL_LIFEPERC_AFTER_PAYING_LIFE);
|
||||||
if (!ComputerUtilCost.checkLifeCost(ai, cost, sa.getHostCard(), ai.getStartingLife() * maxLife / 100, sa)) {
|
if (!ComputerUtilCost.checkLifeCost(ai, cost, sa.getHostCard(), ai.getStartingLife() * maxLife / 100, sa)) {
|
||||||
return new AiAbilityDecision(0, AiPlayDecision.CostNotAcceptable);
|
return new AiAbilityDecision(0, AiPlayDecision.CostNotAcceptable);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,8 +30,7 @@ public class TapAi extends TapAiBase {
|
|||||||
// Cast it if it's a sorcery.
|
// Cast it if it's a sorcery.
|
||||||
} else if (phase.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
} else if (phase.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||||
// Aggro Brains are willing to use TapEffects aggressively instead of defensively
|
// Aggro Brains are willing to use TapEffects aggressively instead of defensively
|
||||||
AiController aic = ((PlayerControllerAi) ai.getController()).getAi();
|
if (!AiProfileUtil.getBoolProperty(ai, AiProps.PLAY_AGGRO)) {
|
||||||
if (!aic.getBooleanProperty(AiProps.PLAY_AGGRO)) {
|
|
||||||
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -186,16 +186,9 @@ public class TokenAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
double chance = 1.0F; // 100%
|
double chance = (double)AiProfileUtil.getIntProperty(ai, AiProps.TOKEN_GENERATION_ABILITY_CHANCE) / 100;
|
||||||
boolean alwaysFromPW = true;
|
boolean alwaysFromPW = AiProfileUtil.getBoolProperty(ai, AiProps.TOKEN_GENERATION_ALWAYS_IF_FROM_PLANESWALKER);
|
||||||
boolean alwaysOnOppAttack = true;
|
boolean alwaysOnOppAttack = AiProfileUtil.getBoolProperty(ai, AiProps.TOKEN_GENERATION_ALWAYS_IF_OPP_ATTACKS);
|
||||||
|
|
||||||
if (ai.getController().isAI()) {
|
|
||||||
AiController aic = ((PlayerControllerAi) ai.getController()).getAi();
|
|
||||||
chance = (double)aic.getIntProperty(AiProps.TOKEN_GENERATION_ABILITY_CHANCE) / 100;
|
|
||||||
alwaysFromPW = aic.getBooleanProperty(AiProps.TOKEN_GENERATION_ALWAYS_IF_FROM_PLANESWALKER);
|
|
||||||
alwaysOnOppAttack = aic.getBooleanProperty(AiProps.TOKEN_GENERATION_ALWAYS_IF_OPP_ATTACKS);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sa.isPwAbility() && alwaysFromPW) {
|
if (sa.isPwAbility() && alwaysFromPW) {
|
||||||
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import forge.item.PaperCardPredicates;
|
|||||||
import forge.util.Localizer;
|
import forge.util.Localizer;
|
||||||
import forge.util.PredicateString.StringOp;
|
import forge.util.PredicateString.StringOp;
|
||||||
|
|
||||||
public class VentureEffect extends SpellAbilityEffect {
|
public class VentureEffect extends SpellAbilityEffect {
|
||||||
|
|
||||||
private Card getDungeonCard(SpellAbility sa, Player player, Map<AbilityKey, Object> moveParams) {
|
private Card getDungeonCard(SpellAbility sa, Player player, Map<AbilityKey, Object> moveParams) {
|
||||||
final Game game = player.getGame();
|
final Game game = player.getGame();
|
||||||
@@ -44,7 +44,7 @@ public class VentureEffect extends SpellAbilityEffect {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
List<PaperCard> dungeonCards = null;
|
List<PaperCard> dungeonCards;
|
||||||
if (sa.hasParam("Dungeon")) {
|
if (sa.hasParam("Dungeon")) {
|
||||||
String dungeonType = sa.getParam("Dungeon");
|
String dungeonType = sa.getParam("Dungeon");
|
||||||
Predicate<CardRules> rulesPredicate = CardRulesPredicates.IS_DUNGEON.and(CardRulesPredicates.subType(StringOp.EQUALS, dungeonType));
|
Predicate<CardRules> rulesPredicate = CardRulesPredicates.IS_DUNGEON.and(CardRulesPredicates.subType(StringOp.EQUALS, dungeonType));
|
||||||
|
|||||||
Reference in New Issue
Block a user