mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-17 19:28:01 +00:00
Merge branch 'ai-improvements' into 'master'
Various AI improvements. See merge request core-developers/forge!1066
This commit is contained in:
@@ -74,6 +74,8 @@ public enum AiProps { /** */
|
||||
DESTROY_IMMEDIATELY_UNBLOCKABLE_THRESHOLD ("2"), /** */
|
||||
DESTROY_IMMEDIATELY_UNBLOCKABLE_ONLY_IN_DNGR ("true"), /** */
|
||||
DESTROY_IMMEDIATELY_UNBLOCKABLE_LIFE_IN_DNGR ("5"), /** */
|
||||
AVOID_TARGETING_CREATS_THAT_WILL_DIE ("true"), /** */
|
||||
DONT_EVAL_KILLSPELLS_ON_STACK_WITH_PERMISSION ("true"), /** */
|
||||
PRIORITY_REDUCTION_FOR_STORM_SPELLS ("0"), /** */
|
||||
USE_BERSERK_AGGRESSIVELY ("false"), /** */
|
||||
MIN_COUNT_FOR_STORM_SPELLS ("0"), /** */
|
||||
|
||||
@@ -1770,7 +1770,68 @@ public class ComputerUtil {
|
||||
Iterables.addAll(threatened, ComputerUtil.predictThreatenedObjects(aiPlayer, saviour, topStack.getSubAbility()));
|
||||
return threatened;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if the specified creature will die this turn either from lethal damage in combat
|
||||
* or from a killing spell on stack.
|
||||
* TODO: This currently does not account for the fact that spells on stack can be countered, can be improved.
|
||||
*
|
||||
* @param creature
|
||||
* A creature to check
|
||||
* @return true if the creature dies according to current board position.
|
||||
*/
|
||||
public static boolean predictCreatureWillDieThisTurn(final Player ai, final Card creature) {
|
||||
final Game game = creature.getGame();
|
||||
|
||||
// a creature will die as a result of combat
|
||||
boolean willDieInCombat = game.getPhaseHandler().inCombat()
|
||||
&& ComputerUtilCombat.combatantWouldBeDestroyed(creature.getController(), creature, game.getCombat());
|
||||
|
||||
// a creature will [hopefully] die from a spell on stack
|
||||
boolean willDieFromSpell = false;
|
||||
boolean noStackCheck = false;
|
||||
AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
|
||||
if (aic.getBooleanProperty(AiProps.DONT_EVAL_KILLSPELLS_ON_STACK_WITH_PERMISSION)) {
|
||||
// See if permission is on stack and ignore this check if there is and the relevant AI flag is set
|
||||
// TODO: improve this so that this flag is not needed and the AI can properly evaluate spells in presence of counterspells.
|
||||
for (SpellAbilityStackInstance si : game.getStack()) {
|
||||
if (si.getSpellAbility(false).getApi() == ApiType.Counter) {
|
||||
noStackCheck = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
willDieFromSpell = !noStackCheck && ComputerUtil.predictThreatenedObjects(creature.getController(), null).contains(creature);
|
||||
|
||||
return willDieInCombat || willDieFromSpell;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of cards excluding any creatures that will die in active combat or from a spell on stack.
|
||||
* Works only on AI profiles which have AVOID_TARGETING_CREATS_THAT_WILL_DIE enabled, otherwise returns
|
||||
* the original list.
|
||||
*
|
||||
* @param ai
|
||||
* The AI player performing this evaluation
|
||||
* @param list
|
||||
* The list of cards to work with
|
||||
* @return a filtered list with no dying creatures in it
|
||||
*/
|
||||
public static CardCollection filterCreaturesThatWillDieThisTurn(final Player ai, final CardCollection list) {
|
||||
AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
|
||||
if (aic.getBooleanProperty(AiProps.AVOID_TARGETING_CREATS_THAT_WILL_DIE)) {
|
||||
// Try to avoid targeting creatures that are dead on board
|
||||
List<Card> willBeKilled = CardLists.filter(list, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(Card card) {
|
||||
return card.isCreature() && ComputerUtil.predictCreatureWillDieThisTurn(ai, card);
|
||||
}
|
||||
});
|
||||
list.removeAll(willBeKilled);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
public static boolean playImmediately(Player ai, SpellAbility sa) {
|
||||
final Card source = sa.getHostCard();
|
||||
final Zone zone = source.getZone();
|
||||
@@ -2779,6 +2840,10 @@ public class ComputerUtil {
|
||||
if (ab.getApi() == null) {
|
||||
// only API-based SAs are supported, other things may lead to a NPE (e.g. Ancestral Vision Suspend SA)
|
||||
continue;
|
||||
} else if (ab.getApi() == ApiType.Mana && "ManaRitual".equals(ab.getParam("AILogic"))) {
|
||||
// Mana Ritual cards are too complex for the AI to consider casting through a spell effect and will
|
||||
// lead to a stack overflow. Consider improving.
|
||||
continue;
|
||||
}
|
||||
SpellAbility abTest = withoutPayingManaCost ? ab.copyWithNoManaCost() : ab.copy();
|
||||
// at this point, we're assuming that card will be castable from whichever zone it's in by the AI player.
|
||||
|
||||
@@ -275,7 +275,7 @@ public class DamageDealAi extends DamageAiBase {
|
||||
final Game game = source.getGame();
|
||||
List<Card> hPlay = getTargetableCards(ai, sa, pl, tgt, activator, source, game);
|
||||
|
||||
List<Card> killables = CardLists.filter(hPlay, new Predicate<Card>() {
|
||||
CardCollection killables = CardLists.filter(hPlay, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
return c.getSVar("Targeting").equals("Dies")
|
||||
@@ -286,7 +286,10 @@ public class DamageDealAi extends DamageAiBase {
|
||||
});
|
||||
|
||||
// Filter AI-specific targets if provided
|
||||
killables = ComputerUtil.filterAITgts(sa, ai, new CardCollection(killables), true);
|
||||
killables = ComputerUtil.filterAITgts(sa, ai, killables, true);
|
||||
|
||||
// Try not to target anything which will already be dead by the time the spell resolves
|
||||
killables = ComputerUtil.filterCreaturesThatWillDieThisTurn(ai, killables);
|
||||
|
||||
Card targetCard = null;
|
||||
if (pl.isOpponentOf(ai) && activator.equals(ai) && !killables.isEmpty()) {
|
||||
|
||||
@@ -178,6 +178,8 @@ public class DestroyAi extends SpellAbilityAi {
|
||||
});
|
||||
}
|
||||
|
||||
// Try to avoid targeting creatures that are dead on board
|
||||
list = ComputerUtil.filterCreaturesThatWillDieThisTurn(ai, list);
|
||||
if (list.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
@@ -313,6 +315,9 @@ public class DestroyAi extends SpellAbilityAi {
|
||||
CardCollection list = CardLists.getTargetableCards(ai.getGame().getCardsIn(ZoneType.Battlefield), sa);
|
||||
list = CardLists.getValidCards(list, tgt.getValidTgts(), source.getController(), source, sa);
|
||||
|
||||
// Try to avoid targeting creatures that are dead on board
|
||||
list = ComputerUtil.filterCreaturesThatWillDieThisTurn(ai, list);
|
||||
|
||||
if (list.isEmpty() || list.size() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -175,6 +175,13 @@ public class PlayAi extends SpellAbilityAi {
|
||||
spell = (Spell) spell.copyWithDefinedCost(abCost);
|
||||
}
|
||||
if( AiPlayDecision.WillPlay == ((PlayerControllerAi)ai.getController()).getAi().canPlayFromEffectAI(spell, !isOptional, true)) {
|
||||
// Before accepting, see if the spell has a valid number of targets (it should at this point).
|
||||
// Proceeding past this point if the spell is not correctly targeted will result
|
||||
// in "Failed to add to stack" error and the card disappearing from the game completely.
|
||||
if (!spell.isTargetNumberValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,6 +66,14 @@ THRESHOLD_TOKEN_CHUMP_TO_SAVE_PLANESWALKER=135
|
||||
# If enabled, the AI will not bother chump blocking to protect a planeswalker unless lethal damage is threatened to it
|
||||
CHUMP_TO_SAVE_PLANESWALKER_ONLY_ON_LETHAL=true
|
||||
|
||||
# Options that allow the AI to attempt to optimize targeting for removal and damaging spells.
|
||||
# If enabled, the AI will try not to target a creature with a damaging spell or spot removal in case
|
||||
# this creature will die in current combat or to a spell which is currently on stack targeting it.
|
||||
AVOID_TARGETING_CREATS_THAT_WILL_DIE=true
|
||||
# If enabled, the AI will not evaluate the stack in case at least one counterspell is present on it,
|
||||
# since the current AI is not smart enough to predict whether a kill spell on stack is countered or not.
|
||||
DONT_EVAL_KILLSPELLS_ON_STACK_WITH_PERMISSION=true
|
||||
|
||||
# Only works when AI cheating is enabled in preferences, otherwise does nothing
|
||||
CHEAT_WITH_MANA_ON_SHUFFLE=true
|
||||
|
||||
|
||||
@@ -66,6 +66,14 @@ THRESHOLD_TOKEN_CHUMP_TO_SAVE_PLANESWALKER=135
|
||||
# If enabled, the AI will not bother chump blocking to protect a planeswalker unless lethal damage is threatened to it
|
||||
CHUMP_TO_SAVE_PLANESWALKER_ONLY_ON_LETHAL=true
|
||||
|
||||
# Options that allow the AI to attempt to optimize targeting for removal and damaging spells.
|
||||
# If enabled, the AI will try not to target a creature with a damaging spell or spot removal in case
|
||||
# this creature will die in current combat or to a spell which is currently on stack targeting it.
|
||||
AVOID_TARGETING_CREATS_THAT_WILL_DIE=true
|
||||
# If enabled, the AI will not evaluate the stack in case at least one counterspell is present on it,
|
||||
# since the current AI is not smart enough to predict whether a kill spell on stack is countered or not.
|
||||
DONT_EVAL_KILLSPELLS_ON_STACK_WITH_PERMISSION=true
|
||||
|
||||
# Only works when AI cheating is enabled in preferences, otherwise does nothing
|
||||
CHEAT_WITH_MANA_ON_SHUFFLE=true
|
||||
|
||||
|
||||
@@ -66,6 +66,14 @@ THRESHOLD_TOKEN_CHUMP_TO_SAVE_PLANESWALKER=135
|
||||
# If enabled, the AI will not bother chump blocking to protect a planeswalker unless lethal damage is threatened to it
|
||||
CHUMP_TO_SAVE_PLANESWALKER_ONLY_ON_LETHAL=false
|
||||
|
||||
# Options that allow the AI to attempt to optimize targeting for removal and damaging spells.
|
||||
# If enabled, the AI will try not to target a creature with a damaging spell or spot removal in case
|
||||
# this creature will die in current combat or to a spell which is currently on stack targeting it.
|
||||
AVOID_TARGETING_CREATS_THAT_WILL_DIE=true
|
||||
# If enabled, the AI will not evaluate the stack in case at least one counterspell is present on it,
|
||||
# since the current AI is not smart enough to predict whether a kill spell on stack is countered or not.
|
||||
DONT_EVAL_KILLSPELLS_ON_STACK_WITH_PERMISSION=false
|
||||
|
||||
# Only works when AI cheating is enabled in preferences, otherwise does nothing
|
||||
CHEAT_WITH_MANA_ON_SHUFFLE=true
|
||||
|
||||
@@ -219,4 +227,4 @@ AI_IN_DANGER_THRESHOLD=3
|
||||
# for each evaluation, introducing some unpredictability.
|
||||
AI_IN_DANGER_MAX_THRESHOLD=12
|
||||
|
||||
# <-- there are no options here at the moment -->
|
||||
# <-- there are no other experimental options here at the moment -->
|
||||
|
||||
@@ -66,6 +66,14 @@ THRESHOLD_TOKEN_CHUMP_TO_SAVE_PLANESWALKER=135
|
||||
# If enabled, the AI will not bother chump blocking to protect a planeswalker unless lethal damage is threatened to it
|
||||
CHUMP_TO_SAVE_PLANESWALKER_ONLY_ON_LETHAL=true
|
||||
|
||||
# Options that allow the AI to attempt to optimize targeting for removal and damaging spells.
|
||||
# If enabled, the AI will try not to target a creature with a damaging spell or spot removal in case
|
||||
# this creature will die in current combat or to a spell which is currently on stack targeting it.
|
||||
AVOID_TARGETING_CREATS_THAT_WILL_DIE=true
|
||||
# If enabled, the AI will not evaluate the stack in case at least one counterspell is present on it,
|
||||
# since the current AI is not smart enough to predict whether a kill spell on stack is countered or not.
|
||||
DONT_EVAL_KILLSPELLS_ON_STACK_WITH_PERMISSION=true
|
||||
|
||||
# Only works when AI cheating is enabled in preferences, otherwise does nothing
|
||||
CHEAT_WITH_MANA_ON_SHUFFLE=true
|
||||
|
||||
|
||||
@@ -9,6 +9,8 @@ SVar:DBPutCounter:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | Condition
|
||||
SVar:DBSac:DB$ Sacrifice | SacValid# Self | ConditionDefined$ Remembered | ConditionPresent$ Card.Creature | ConditionCompare$ EQ0 | SubAbility$ DBCleanup
|
||||
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
|
||||
A:AB$ PutCounter | Cost$ B G Discard<1/Creature> | CounterType$ P1P1 | CounterNum$ 1 | SpellDescription$ Put a +1/+1 counter on CARDNAME.
|
||||
SVar:NeedsToPlayVar:Z GE1
|
||||
SVar:Z:Count$ValidGraveyard Creature.YouCtrl
|
||||
AI:RemoveDeck:Random
|
||||
DeckNeeds:Ability$Graveyard
|
||||
DeckHas:Ability$Counters
|
||||
|
||||
@@ -6,5 +6,6 @@ SVar:DBChoose2:DB$ ChooseCard | Defined$ You | Choices$ Creature.YouOwn+cmcEQ2 |
|
||||
SVar:DBChoose3:DB$ ChooseCard | Defined$ You | Choices$ Creature.YouOwn+cmcEQ3 | ChoiceZone$ Graveyard | Amount$ 1 | SubAbility$ DBReturn | RememberChosen$ True | SpellDescription$ Choose a creature card with converted mana cost 3 in your graveyard.
|
||||
SVar:DBReturn:DB$ ChangeZoneAll | Origin$ Graveyard | Destination$ Battlefield | ChangeType$ Card.IsRemembered | SubAbility$ DBCleanup
|
||||
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
|
||||
SVar:NeedsToPlay:Creature.YouCtrl+inZoneGraveyard+cmcLE3
|
||||
SVar:NeedsToPlayVar:Z GE1
|
||||
SVar:Z:Count$ValidGraveyard Creature.YouCtrl+cmcLE3
|
||||
Oracle:Choose a creature card with converted mana cost 1 in your graveyard, then do the same for creature cards with converted mana costs 2 and 3. Return those cards to the battlefield.
|
||||
|
||||
Reference in New Issue
Block a user