mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-20 20:58:03 +00:00
- Explicitly marked cards that use a re-entry mechanism for testing playability via canPlaySa such that they don't recursively call each other and thus risk getting into an infinite loop (fixes potential check combinations of e.g. various mana rituals + Yawgmoth's Will in AI's hand and other things like that, which would otherwise cause a stack overflow).
- Some simplification in the AICardMemory interface, allowing to use static methods to simplify calls.
This commit is contained in:
@@ -585,9 +585,8 @@ public class AiAttackController {
|
||||
// Cards that are remembered to attack anyway (e.g. temporarily stolen creatures)
|
||||
if (ai.getController() instanceof PlayerControllerAi) {
|
||||
// Only do this if |ai| is actually an AI - as we could be trying to predict how the human will attack.
|
||||
AiCardMemory aiMemory = ((PlayerControllerAi) ai.getController()).getAi().getCardMemory();
|
||||
for (Card attacker : this.attackers) {
|
||||
if (aiMemory.isRememberedCard(attacker, AiCardMemory.MemorySet.MANDATORY_ATTACKERS)) {
|
||||
if (AiCardMemory.isRememberedCard(ai, attacker, AiCardMemory.MemorySet.MANDATORY_ATTACKERS)) {
|
||||
combat.addAttacker(attacker, defender);
|
||||
attackersLeft.remove(attacker);
|
||||
}
|
||||
|
||||
@@ -44,12 +44,14 @@ public class AiCardMemory {
|
||||
private final Set<Card> memHeldManaSources;
|
||||
private final Set<Card> memAttachedThisTurn;
|
||||
private final Set<Card> memAnimatedThisTurn;
|
||||
private final Set<Card> memTestedCanPlayThisTurn;
|
||||
|
||||
public AiCardMemory() {
|
||||
this.memMandatoryAttackers = new HashSet<>();
|
||||
this.memHeldManaSources = new HashSet<>();
|
||||
this.memAttachedThisTurn = new HashSet<>();
|
||||
this.memAnimatedThisTurn = new HashSet<>();
|
||||
this.memTestedCanPlayThisTurn = new HashSet<>();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -62,6 +64,7 @@ public class AiCardMemory {
|
||||
HELD_MANA_SOURCES,
|
||||
ATTACHED_THIS_TURN,
|
||||
ANIMATED_THIS_TURN,
|
||||
TESTED_CANPLAY_THIS_PHASE,
|
||||
//REVEALED_CARDS // stub, not linked to AI code yet
|
||||
}
|
||||
|
||||
@@ -75,6 +78,8 @@ public class AiCardMemory {
|
||||
return memAttachedThisTurn;
|
||||
case ANIMATED_THIS_TURN:
|
||||
return memAnimatedThisTurn;
|
||||
case TESTED_CANPLAY_THIS_PHASE:
|
||||
return memTestedCanPlayThisTurn;
|
||||
//case REVEALED_CARDS:
|
||||
// return memRevealedCards;
|
||||
default:
|
||||
@@ -247,5 +252,23 @@ public class AiCardMemory {
|
||||
clearMemorySet(MemorySet.HELD_MANA_SOURCES);
|
||||
clearMemorySet(MemorySet.ATTACHED_THIS_TURN);
|
||||
clearMemorySet(MemorySet.ANIMATED_THIS_TURN);
|
||||
clearMemorySet(MemorySet.TESTED_CANPLAY_THIS_PHASE);
|
||||
}
|
||||
|
||||
// Static functions to simplify access to AI card memory of a given AI player.
|
||||
public static void rememberCard(Player ai, Card c, MemorySet set) {
|
||||
((PlayerControllerAi)ai.getController()).getAi().getCardMemory().rememberCard(c, set);
|
||||
}
|
||||
public static void forgetCard(Player ai, Card c, MemorySet set) {
|
||||
((PlayerControllerAi)ai.getController()).getAi().getCardMemory().forgetCard(c, set);
|
||||
}
|
||||
public static boolean isRememberedCard(Player ai, Card c, MemorySet set) {
|
||||
return ((PlayerControllerAi)ai.getController()).getAi().getCardMemory().isRememberedCard(c, set);
|
||||
}
|
||||
public static void clearMemorySet(Player ai, MemorySet set) {
|
||||
((PlayerControllerAi)ai.getController()).getAi().getCardMemory().clearMemorySet(set);
|
||||
}
|
||||
public static boolean isMemorySetEmpty(Player ai, MemorySet set) {
|
||||
return ((PlayerControllerAi)ai.getController()).getAi().getCardMemory().isMemorySetEmpty(set);
|
||||
}
|
||||
}
|
||||
@@ -607,7 +607,7 @@ public class AiController {
|
||||
ManaCostBeingPaid cost = ComputerUtilMana.calculateManaCost(sa, true, 0);
|
||||
CardCollection manaSources = ComputerUtilMana.getManaSourcesToPayCost(cost, sa, player);
|
||||
for (Card c : manaSources) {
|
||||
((PlayerControllerAi)player.getController()).getAi().getCardMemory().rememberCard(c, AiCardMemory.MemorySet.HELD_MANA_SOURCES);
|
||||
AiCardMemory.rememberCard(player, c, AiCardMemory.MemorySet.HELD_MANA_SOURCES);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -800,10 +800,10 @@ public class ComputerUtilMana {
|
||||
|
||||
PhaseType curPhase = ai.getGame().getPhaseHandler().getPhase();
|
||||
if (curPhase == PhaseType.MAIN2 || curPhase == PhaseType.CLEANUP) {
|
||||
aic.getCardMemory().clearMemorySet(AiCardMemory.MemorySet.HELD_MANA_SOURCES);
|
||||
AiCardMemory.clearMemorySet(ai, AiCardMemory.MemorySet.HELD_MANA_SOURCES);
|
||||
}
|
||||
else {
|
||||
if (aic.getCardMemory().isRememberedCard(sourceCard, AiCardMemory.MemorySet.HELD_MANA_SOURCES)) {
|
||||
if (AiCardMemory.isRememberedCard(ai, sourceCard, AiCardMemory.MemorySet.HELD_MANA_SOURCES)) {
|
||||
// This mana source is held elsewhere for a Main Phase 2 spell.
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -359,8 +359,8 @@ public class SpecialCardAi {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (testSa.getHostCard().getName().equals(sa.getHostCard().getName())) {
|
||||
// prevent infinitely recursing own ability when testing AI play decision
|
||||
if (testSa.getHostCard().getName().equals(sa.getHostCard().getName()) || testSa.hasParam("AIRecursiveTestAbility")) {
|
||||
// prevent infinitely recursing abilities that are susceptible to reentry
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -544,8 +544,9 @@ public class SpecialCardAi {
|
||||
// cut short considering to play counterspells via Yawgmoth's Will
|
||||
continue;
|
||||
}
|
||||
if (ab.getHostCard().getName().equals(sa.getHostCard().getName())) {
|
||||
// prevent infinitely recursing own ability when testing AI play decision
|
||||
|
||||
if (ab.getHostCard().getName().equals(sa.getHostCard().getName()) || ab.hasParam("AIRecursiveTestAbility")) {
|
||||
// prevent infinitely recursing abilities that are susceptible to reentry
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -610,12 +610,10 @@ public class AnimateAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
private void rememberAnimatedThisTurn(Player ai, Card c) {
|
||||
AiCardMemory mem = ((PlayerControllerAi)ai.getController()).getAi().getCardMemory();
|
||||
mem.rememberCard(c, AiCardMemory.MemorySet.ANIMATED_THIS_TURN);
|
||||
AiCardMemory.rememberCard(ai, c, AiCardMemory.MemorySet.ANIMATED_THIS_TURN);
|
||||
}
|
||||
|
||||
public static boolean isAnimatedThisTurn(Player ai, Card c) {
|
||||
AiCardMemory mem = ((PlayerControllerAi)ai.getController()).getAi().getCardMemory();
|
||||
return mem.isRememberedCard(c, AiCardMemory.MemorySet.ANIMATED_THIS_TURN);
|
||||
return AiCardMemory.isRememberedCard(ai, c, AiCardMemory.MemorySet.ANIMATED_THIS_TURN);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1095,7 +1095,7 @@ public class AttachAi extends SpellAbilityAi {
|
||||
// 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);
|
||||
|
||||
if (!decideMoveFromUseless && aic.getCardMemory().isMemorySetEmpty(AiCardMemory.MemorySet.HELD_MANA_SOURCES)) {
|
||||
if (!decideMoveFromUseless && AiCardMemory.isMemorySetEmpty(aiPlayer, AiCardMemory.MemorySet.HELD_MANA_SOURCES)) {
|
||||
SpellAbility futureSpell = aic.predictSpellToCastInMain2(ApiType.Attach);
|
||||
if (futureSpell != null && futureSpell.getHostCard() != null) {
|
||||
aic.reserveManaSourcesForMain2(futureSpell);
|
||||
@@ -1103,12 +1103,12 @@ public class AttachAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
// avoid randomly moving the equipment back and forth between several creatures in one turn
|
||||
if (aic.getCardMemory().isRememberedCard(sa.getHostCard(), AiCardMemory.MemorySet.ATTACHED_THIS_TURN)) {
|
||||
if (AiCardMemory.isRememberedCard(aiPlayer, sa.getHostCard(), AiCardMemory.MemorySet.ATTACHED_THIS_TURN)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
aic.getCardMemory().rememberCard(sa.getHostCard(), AiCardMemory.MemorySet.ATTACHED_THIS_TURN);
|
||||
AiCardMemory.rememberCard(aiPlayer, sa.getHostCard(), AiCardMemory.MemorySet.ATTACHED_THIS_TURN);
|
||||
|
||||
if (c == null && mandatory) {
|
||||
CardLists.shuffle(list);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.base.Predicates;
|
||||
import forge.ai.AiCardMemory;
|
||||
import forge.ai.AiPlayDecision;
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilAbility;
|
||||
@@ -10,6 +11,7 @@ import forge.ai.SpellAbilityAi;
|
||||
import forge.card.MagicColor;
|
||||
import forge.card.mana.ManaCost;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardLists;
|
||||
@@ -63,7 +65,7 @@ public class ManaEffectAi extends SpellAbilityAi {
|
||||
*/
|
||||
@Override
|
||||
protected boolean checkPhaseRestrictions(Player ai, SpellAbility sa, PhaseHandler ph, String logic) {
|
||||
if ("ManaRitual".equals(logic)) {
|
||||
if (logic.startsWith("ManaRitual")) {
|
||||
return ph.is(PhaseType.MAIN2, ai) || ph.is(PhaseType.MAIN1, ai);
|
||||
}
|
||||
return super.checkPhaseRestrictions(ai, sa, ph, logic);
|
||||
@@ -110,7 +112,9 @@ public class ManaEffectAi extends SpellAbilityAi {
|
||||
|
||||
CardCollection manaSources = ComputerUtilMana.getAvailableMana(ai, true);
|
||||
int numManaSrcs = manaSources.size();
|
||||
int manaReceived = AbilityUtils.calculateAmount(host, sa.getParam("Amount"), sa);
|
||||
int manaReceived = sa.hasParam("Amount") ? AbilityUtils.calculateAmount(host, sa.getParam("Amount"), sa) : 1;
|
||||
manaReceived *= sa.getParam("Produced").split(" ").length;
|
||||
|
||||
int selfCost = sa.getPayCosts().getCostMana() != null ? sa.getPayCosts().getCostMana().getMana().getCMC() : 0;
|
||||
byte producedColor = MagicColor.fromName(sa.getParam("Produced"));
|
||||
int searchCMC = numManaSrcs - selfCost + manaReceived;
|
||||
@@ -139,8 +143,8 @@ public class ManaEffectAi extends SpellAbilityAi {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (testSa.getHostCard().getName().equals(host.getName())) {
|
||||
// prevent infinitely recursing own ability when testing AI play decision
|
||||
if (testSa.getHostCard().equals(host) || testSa.hasParam("AIRecursiveTestAbility")) {
|
||||
// prevent infinitely recursing mana ritual and other abilities with reentry
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user