mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-18 11:48:02 +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_THRESHOLD ("2"), /** */
|
||||||
DESTROY_IMMEDIATELY_UNBLOCKABLE_ONLY_IN_DNGR ("true"), /** */
|
DESTROY_IMMEDIATELY_UNBLOCKABLE_ONLY_IN_DNGR ("true"), /** */
|
||||||
DESTROY_IMMEDIATELY_UNBLOCKABLE_LIFE_IN_DNGR ("5"), /** */
|
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"), /** */
|
PRIORITY_REDUCTION_FOR_STORM_SPELLS ("0"), /** */
|
||||||
USE_BERSERK_AGGRESSIVELY ("false"), /** */
|
USE_BERSERK_AGGRESSIVELY ("false"), /** */
|
||||||
MIN_COUNT_FOR_STORM_SPELLS ("0"), /** */
|
MIN_COUNT_FOR_STORM_SPELLS ("0"), /** */
|
||||||
|
|||||||
@@ -1770,7 +1770,68 @@ public class ComputerUtil {
|
|||||||
Iterables.addAll(threatened, ComputerUtil.predictThreatenedObjects(aiPlayer, saviour, topStack.getSubAbility()));
|
Iterables.addAll(threatened, ComputerUtil.predictThreatenedObjects(aiPlayer, saviour, topStack.getSubAbility()));
|
||||||
return threatened;
|
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) {
|
public static boolean playImmediately(Player ai, SpellAbility sa) {
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final Zone zone = source.getZone();
|
final Zone zone = source.getZone();
|
||||||
@@ -2779,6 +2840,10 @@ public class ComputerUtil {
|
|||||||
if (ab.getApi() == null) {
|
if (ab.getApi() == null) {
|
||||||
// only API-based SAs are supported, other things may lead to a NPE (e.g. Ancestral Vision Suspend SA)
|
// only API-based SAs are supported, other things may lead to a NPE (e.g. Ancestral Vision Suspend SA)
|
||||||
continue;
|
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();
|
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.
|
// 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();
|
final Game game = source.getGame();
|
||||||
List<Card> hPlay = getTargetableCards(ai, sa, pl, tgt, activator, source, game);
|
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
|
@Override
|
||||||
public boolean apply(final Card c) {
|
public boolean apply(final Card c) {
|
||||||
return c.getSVar("Targeting").equals("Dies")
|
return c.getSVar("Targeting").equals("Dies")
|
||||||
@@ -286,7 +286,10 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Filter AI-specific targets if provided
|
// 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;
|
Card targetCard = null;
|
||||||
if (pl.isOpponentOf(ai) && activator.equals(ai) && !killables.isEmpty()) {
|
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()) {
|
if (list.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -313,6 +315,9 @@ public class DestroyAi extends SpellAbilityAi {
|
|||||||
CardCollection list = CardLists.getTargetableCards(ai.getGame().getCardsIn(ZoneType.Battlefield), sa);
|
CardCollection list = CardLists.getTargetableCards(ai.getGame().getCardsIn(ZoneType.Battlefield), sa);
|
||||||
list = CardLists.getValidCards(list, tgt.getValidTgts(), source.getController(), source, 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)) {
|
if (list.isEmpty() || list.size() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -175,6 +175,13 @@ public class PlayAi extends SpellAbilityAi {
|
|||||||
spell = (Spell) spell.copyWithDefinedCost(abCost);
|
spell = (Spell) spell.copyWithDefinedCost(abCost);
|
||||||
}
|
}
|
||||||
if( AiPlayDecision.WillPlay == ((PlayerControllerAi)ai.getController()).getAi().canPlayFromEffectAI(spell, !isOptional, true)) {
|
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;
|
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
|
# 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
|
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
|
# Only works when AI cheating is enabled in preferences, otherwise does nothing
|
||||||
CHEAT_WITH_MANA_ON_SHUFFLE=true
|
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
|
# 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
|
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
|
# Only works when AI cheating is enabled in preferences, otherwise does nothing
|
||||||
CHEAT_WITH_MANA_ON_SHUFFLE=true
|
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
|
# 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
|
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
|
# Only works when AI cheating is enabled in preferences, otherwise does nothing
|
||||||
CHEAT_WITH_MANA_ON_SHUFFLE=true
|
CHEAT_WITH_MANA_ON_SHUFFLE=true
|
||||||
|
|
||||||
@@ -219,4 +227,4 @@ AI_IN_DANGER_THRESHOLD=3
|
|||||||
# for each evaluation, introducing some unpredictability.
|
# for each evaluation, introducing some unpredictability.
|
||||||
AI_IN_DANGER_MAX_THRESHOLD=12
|
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
|
# 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
|
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
|
# Only works when AI cheating is enabled in preferences, otherwise does nothing
|
||||||
CHEAT_WITH_MANA_ON_SHUFFLE=true
|
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:DBSac:DB$ Sacrifice | SacValid# Self | ConditionDefined$ Remembered | ConditionPresent$ Card.Creature | ConditionCompare$ EQ0 | SubAbility$ DBCleanup
|
||||||
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
|
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.
|
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
|
AI:RemoveDeck:Random
|
||||||
DeckNeeds:Ability$Graveyard
|
DeckNeeds:Ability$Graveyard
|
||||||
DeckHas:Ability$Counters
|
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: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:DBReturn:DB$ ChangeZoneAll | Origin$ Graveyard | Destination$ Battlefield | ChangeType$ Card.IsRemembered | SubAbility$ DBCleanup
|
||||||
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
|
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.
|
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