mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-17 11:18:01 +00:00
Merge branch 'seravy-ai-fixes' into 'master'
Reformatted/revised AI updates from Seravy: Fog AI, Protection from chosen color AI See merge request core-developers/forge!503
This commit is contained in:
@@ -38,6 +38,7 @@ import forge.game.trigger.TriggerType;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.Expressions;
|
||||
import forge.util.MyRandom;
|
||||
import forge.util.TextUtil;
|
||||
import forge.util.collect.FCollectionView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -263,6 +264,34 @@ public class AiAttackController {
|
||||
if (ai.getGame().getPhaseHandler().getNextTurn().equals(ai)) {
|
||||
return attackers;
|
||||
}
|
||||
// no need to block (already holding mana to cast fog next turn)
|
||||
if (!AiCardMemory.isMemorySetEmpty(ai, AiCardMemory.MemorySet.CHOSEN_FOG_EFFECT)) {
|
||||
// Don't send the card that'll do the fog effect to attack, it's unsafe!
|
||||
if (attackers.contains(AiCardMemory.MemorySet.CHOSEN_FOG_EFFECT)) {
|
||||
attackers.remove(AiCardMemory.MemorySet.CHOSEN_FOG_EFFECT);
|
||||
}
|
||||
return attackers;
|
||||
}
|
||||
|
||||
// no need to block if an effect is in play which untaps all creatures (pseudo-Vigilance akin to
|
||||
// Awakening or Prophet of Kruphix)
|
||||
for (Card card : ai.getGame().getCardsIn(ZoneType.Battlefield)) {
|
||||
boolean untapsEachTurn = card.hasSVar("UntapsEachTurn");
|
||||
boolean untapsEachOtherTurn = card.hasSVar("UntapsEachOtherPlayerTurn");
|
||||
|
||||
if (untapsEachTurn || untapsEachOtherTurn) {
|
||||
String affected = untapsEachTurn ? card.getSVar("UntapsEachTurn")
|
||||
: card.getSVar("UntapsEachOtherPlayerTurn");
|
||||
|
||||
for (String aff : TextUtil.split(affected, ',')) {
|
||||
if (aff.equals("Creature")
|
||||
&& (untapsEachTurn || (untapsEachOtherTurn && ai.equals(card.getController())))) {
|
||||
return attackers;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<Card> opponentsAttackers = new ArrayList<Card>(oppList);
|
||||
opponentsAttackers = CardLists.filter(opponentsAttackers, new Predicate<Card>() {
|
||||
@Override
|
||||
|
||||
@@ -44,20 +44,24 @@ public class AiCardMemory {
|
||||
private final Set<Card> memTrickAttackers;
|
||||
private final Set<Card> memHeldManaSources;
|
||||
private final Set<Card> memHeldManaSourcesForCombat;
|
||||
private final Set<Card> memHeldManaSourcesForEnemyCombat;
|
||||
private final Set<Card> memAttachedThisTurn;
|
||||
private final Set<Card> memAnimatedThisTurn;
|
||||
private final Set<Card> memBouncedThisTurn;
|
||||
private final Set<Card> memActivatedThisTurn;
|
||||
private final Set<Card> memChosenFogEffect;
|
||||
|
||||
public AiCardMemory() {
|
||||
this.memMandatoryAttackers = new HashSet<>();
|
||||
this.memHeldManaSources = new HashSet<>();
|
||||
this.memHeldManaSourcesForCombat = new HashSet<>();
|
||||
this.memHeldManaSourcesForEnemyCombat = new HashSet<>();
|
||||
this.memAttachedThisTurn = new HashSet<>();
|
||||
this.memAnimatedThisTurn = new HashSet<>();
|
||||
this.memBouncedThisTurn = new HashSet<>();
|
||||
this.memActivatedThisTurn = new HashSet<>();
|
||||
this.memTrickAttackers = new HashSet<>();
|
||||
this.memChosenFogEffect = new HashSet<>();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -70,10 +74,12 @@ public class AiCardMemory {
|
||||
TRICK_ATTACKERS,
|
||||
HELD_MANA_SOURCES_FOR_MAIN2,
|
||||
HELD_MANA_SOURCES_FOR_DECLBLK,
|
||||
HELD_MANA_SOURCES_FOR_ENEMY_DECLBLK,
|
||||
ATTACHED_THIS_TURN,
|
||||
ANIMATED_THIS_TURN,
|
||||
BOUNCED_THIS_TURN,
|
||||
ACTIVATED_THIS_TURN,
|
||||
CHOSEN_FOG_EFFECT,
|
||||
//REVEALED_CARDS // stub, not linked to AI code yet
|
||||
}
|
||||
|
||||
@@ -87,6 +93,8 @@ public class AiCardMemory {
|
||||
return memHeldManaSources;
|
||||
case HELD_MANA_SOURCES_FOR_DECLBLK:
|
||||
return memHeldManaSourcesForCombat;
|
||||
case HELD_MANA_SOURCES_FOR_ENEMY_DECLBLK:
|
||||
return memHeldManaSourcesForEnemyCombat;
|
||||
case ATTACHED_THIS_TURN:
|
||||
return memAttachedThisTurn;
|
||||
case ANIMATED_THIS_TURN:
|
||||
@@ -95,6 +103,8 @@ public class AiCardMemory {
|
||||
return memBouncedThisTurn;
|
||||
case ACTIVATED_THIS_TURN:
|
||||
return memActivatedThisTurn;
|
||||
case CHOSEN_FOG_EFFECT:
|
||||
return memChosenFogEffect;
|
||||
//case REVEALED_CARDS:
|
||||
// return memRevealedCards;
|
||||
default:
|
||||
@@ -267,10 +277,12 @@ public class AiCardMemory {
|
||||
clearMemorySet(MemorySet.TRICK_ATTACKERS);
|
||||
clearMemorySet(MemorySet.HELD_MANA_SOURCES_FOR_MAIN2);
|
||||
clearMemorySet(MemorySet.HELD_MANA_SOURCES_FOR_DECLBLK);
|
||||
clearMemorySet(MemorySet.HELD_MANA_SOURCES_FOR_ENEMY_DECLBLK);
|
||||
clearMemorySet(MemorySet.ATTACHED_THIS_TURN);
|
||||
clearMemorySet(MemorySet.ANIMATED_THIS_TURN);
|
||||
clearMemorySet(MemorySet.BOUNCED_THIS_TURN);
|
||||
clearMemorySet(MemorySet.ACTIVATED_THIS_TURN);
|
||||
clearMemorySet(MemorySet.CHOSEN_FOG_EFFECT);
|
||||
}
|
||||
|
||||
// Static functions to simplify access to AI card memory of a given AI player.
|
||||
|
||||
@@ -581,10 +581,10 @@ public class AiController {
|
||||
}
|
||||
|
||||
public void reserveManaSources(SpellAbility sa) {
|
||||
reserveManaSources(sa, PhaseType.MAIN2);
|
||||
reserveManaSources(sa, PhaseType.MAIN2, false);
|
||||
}
|
||||
|
||||
public void reserveManaSources(SpellAbility sa, PhaseType phaseType) {
|
||||
public void reserveManaSources(SpellAbility sa, PhaseType phaseType, boolean enemy) {
|
||||
ManaCostBeingPaid cost = ComputerUtilMana.calculateManaCost(sa, true, 0);
|
||||
CardCollection manaSources = ComputerUtilMana.getManaSourcesToPayCost(cost, sa, player);
|
||||
|
||||
@@ -595,7 +595,8 @@ public class AiController {
|
||||
memSet = AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_MAIN2;
|
||||
break;
|
||||
case COMBAT_DECLARE_BLOCKERS:
|
||||
memSet = AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_DECLBLK;
|
||||
memSet = enemy ? AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_ENEMY_DECLBLK
|
||||
: AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_DECLBLK;
|
||||
break;
|
||||
default:
|
||||
System.out.println("Warning: unsupported mana reservation phase specified for reserveManaSources: "
|
||||
|
||||
@@ -1512,7 +1512,7 @@ public class ComputerUtilCard {
|
||||
// Reserve the mana until Declare Blockers such that the AI doesn't tap out before having a chance to use
|
||||
// the combat trick
|
||||
if (ai.getController().isAI()) {
|
||||
((PlayerControllerAi) ai.getController()).getAi().reserveManaSources(sa, PhaseType.COMBAT_DECLARE_BLOCKERS);
|
||||
((PlayerControllerAi) ai.getController()).getAi().reserveManaSources(sa, PhaseType.COMBAT_DECLARE_BLOCKERS, false);
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
|
||||
@@ -865,9 +865,12 @@ public class ComputerUtilMana {
|
||||
// For combat tricks, always obey mana reservation
|
||||
if (curPhase == PhaseType.COMBAT_DECLARE_BLOCKERS || curPhase == PhaseType.CLEANUP) {
|
||||
AiCardMemory.clearMemorySet(ai, AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_DECLBLK);
|
||||
}
|
||||
else {
|
||||
if (AiCardMemory.isRememberedCard(ai, sourceCard, AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_DECLBLK)) {
|
||||
} else if (!(ai.getGame().getPhaseHandler().isPlayerTurn(ai)) && (curPhase == PhaseType.COMBAT_DECLARE_BLOCKERS || curPhase == PhaseType.CLEANUP)) {
|
||||
AiCardMemory.clearMemorySet(ai, AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_ENEMY_DECLBLK);
|
||||
AiCardMemory.clearMemorySet(ai, AiCardMemory.MemorySet.CHOSEN_FOG_EFFECT);
|
||||
} else {
|
||||
if ((AiCardMemory.isRememberedCard(ai, sourceCard, AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_DECLBLK)) ||
|
||||
(AiCardMemory.isRememberedCard(ai, sourceCard, AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_ENEMY_DECLBLK))) {
|
||||
// This mana source is held elsewhere for a combat trick.
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1311,6 +1311,22 @@ public class AttachAi extends SpellAbilityAi {
|
||||
if (!CardUtil.isStackingKeyword(keyword) && card.hasKeyword(keyword)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't play if would choose a color the target is already protected from
|
||||
if (sa.getHostCard().hasSVar("ChosenProtection")) {
|
||||
CardCollectionView oppAllCards = ai.getOpponents().getCardsIn(ZoneType.Battlefield);
|
||||
String cc = ComputerUtilCard.getMostProminentColor(oppAllCards);
|
||||
if (card.hasKeyword("Protection from " + cc.toLowerCase())) {
|
||||
return false;
|
||||
}
|
||||
// Also don't play if it would destroy own Aura
|
||||
for (Card c : card.getEnchantedBy(false)) {
|
||||
if ((c.getController().equals(ai)) && (c.isOfColor(cc))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final boolean evasive = (keyword.equals("Unblockable") || keyword.equals("Fear")
|
||||
|| keyword.equals("Intimidate") || keyword.equals("Shadow")
|
||||
|| keyword.equals("Flying") || keyword.equals("Horsemanship")
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCombat;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.ai.*;
|
||||
import forge.game.Game;
|
||||
import forge.game.GameObject;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.phase.PhaseType;
|
||||
@@ -12,6 +11,8 @@ import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.util.Aggregates;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class FogAi extends SpellAbilityAi {
|
||||
|
||||
/* (non-Javadoc)
|
||||
@@ -20,6 +21,33 @@ public class FogAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
final Game game = ai.getGame();
|
||||
final Card hostCard = sa.getHostCard();
|
||||
|
||||
// Don't cast it, if the effect is already in place
|
||||
if (game.getPhaseHandler().isPreventCombatDamageThisTurn()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// if card would be destroyed, react and use immediately if it's not own turn
|
||||
if ((AiCardMemory.isRememberedCard(ai, hostCard, AiCardMemory.MemorySet.CHOSEN_FOG_EFFECT))
|
||||
&& (!game.getStack().isEmpty())
|
||||
&& (!game.getPhaseHandler().isPlayerTurn(sa.getActivatingPlayer()))) {
|
||||
final List<GameObject> objects = ComputerUtil.predictThreatenedObjects(ai, null);
|
||||
if (objects.contains(hostCard)) {
|
||||
AiCardMemory.clearMemorySet(ai, AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_ENEMY_DECLBLK);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Reserve mana to cast this card if it will be likely needed
|
||||
if (((game.getPhaseHandler().isPlayerTurn(sa.getActivatingPlayer()))
|
||||
|| (game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)))
|
||||
&& (AiCardMemory.isMemorySetEmpty(ai, AiCardMemory.MemorySet.CHOSEN_FOG_EFFECT))
|
||||
&& (ComputerUtil.aiLifeInDanger(ai, false, 0))) {
|
||||
((PlayerControllerAi) ai.getController()).getAi().reserveManaSources(sa, PhaseType.COMBAT_DECLARE_BLOCKERS, true);
|
||||
AiCardMemory.rememberCard(ai, hostCard, AiCardMemory.MemorySet.CHOSEN_FOG_EFFECT);
|
||||
}
|
||||
|
||||
// AI should only activate this during Human's Declare Blockers phase
|
||||
if (game.getPhaseHandler().isPlayerTurn(sa.getActivatingPlayer())) {
|
||||
return false;
|
||||
@@ -33,11 +61,6 @@ public class FogAi extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't cast it, if the effect is already in place
|
||||
if (game.getPhaseHandler().isPreventCombatDamageThisTurn()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ("SeriousDamage".equals(sa.getParam("AILogic")) && game.getCombat() != null) {
|
||||
int dmg = 0;
|
||||
for (Card atk : game.getCombat().getAttackersOf(ai)) {
|
||||
|
||||
@@ -3,6 +3,7 @@ ManaCost:2 G G
|
||||
Types:Enchantment
|
||||
T:Mode$ Phase | Phase$ Upkeep | TriggerZones$ Battlefield | Execute$ TrigUntapAll | TriggerDescription$ At the beginning of each upkeep, untap all creatures and lands.
|
||||
SVar:TrigUntapAll:DB$UntapAll | ValidCards$ Creature,Land | SpellDescription$ untap all creatures and lands.
|
||||
SVar:UntapsEachTurn:Creature,Land
|
||||
SVar:RemRandomDeck:True
|
||||
SVar:Picture:http://www.wizards.com/global/images/magic/general/awakening.jpg
|
||||
Oracle:At the beginning of each upkeep, untap all creatures and lands.
|
||||
|
||||
@@ -8,4 +8,5 @@ SVar:ChooseColor:DB$ ChooseColor | Defined$ You | AILogic$ MostProminentInHumanD
|
||||
A:SP$ Attach | Cost$ W W | ValidTgts$ Creature | AILogic$ Pump
|
||||
S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddKeyword$ Protection:Card.ChosenColor:Protection from ChosenColor:Card.CardUID_HostCardUID | Description$ Enchanted creature has protection from the chosen color. This effect doesn't remove CARDNAME.
|
||||
SVar:Picture:http://www.wizards.com/global/images/magic/general/cho_mannos_blessing.jpg
|
||||
SVar:ChosenProtection:True
|
||||
Oracle:Flash\nEnchant creature\nAs Cho-Manno's Blessing enters the battlefield, choose a color.\nEnchanted creature has protection from the chosen color. This effect doesn't remove Cho-Manno's Blessing.
|
||||
|
||||
@@ -8,5 +8,6 @@ A:SP$ Attach | Cost$ W | ValidTgts$ Creature | AILogic$ Pump
|
||||
S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddKeyword$ Protection:Card.ChosenColor:Protection from ChosenColor:Card.CardUID_HostCardUID | Description$ Enchanted creature has protection from the chosen color. This effect doesn't remove CARDNAME.
|
||||
A:AB$ ChangeZone | Cost$ W | Origin$ Battlefield | Destination$ Hand | SpellDescription$ Return CARDNAME to its owner's hand.
|
||||
SVar:RemAIDeck:True
|
||||
SVar:ChosenProtection:True
|
||||
SVar:Picture:http://www.wizards.com/global/images/magic/general/flickering_ward.jpg
|
||||
Oracle:Enchant creature\nAs Flickering Ward enters the battlefield, choose a color.\nEnchanted creature has protection from the chosen color. This effect doesn't remove Flickering Ward.\n{W}: Return Flickering Ward to its owner's hand.
|
||||
|
||||
@@ -8,5 +8,6 @@ A:SP$ Attach | Cost$ 2 W | ValidTgts$ Creature | AILogic$ Pump
|
||||
S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddKeyword$ Protection:Card.ChosenColor:Protection from ChosenColor:Card.CardUID_HostCardUID | Description$ Enchanted creature has protection from the chosen color. This effect doesn't remove CARDNAME.
|
||||
A:AB$ Protection | Cost$ Sac<1/CARDNAME> | ValidTgts$ Creature | TgtPrompt$ Select target creature | Gains$ ChosenColor | SpellDescription$ Target creature gains protection from the chosen color until end of turn.
|
||||
SVar:RemAIDeck:True
|
||||
SVar:ChosenProtection:True
|
||||
SVar:Picture:http://www.wizards.com/global/images/magic/general/floating_shield.jpg
|
||||
Oracle:Enchant creature\nAs Floating Shield enters the battlefield, choose a color.\nEnchanted creature has protection from the chosen color. This effect doesn't remove Floating Shield.\nSacrifice Floating Shield: Target creature gains protection from the chosen color until end of turn.
|
||||
|
||||
@@ -9,4 +9,5 @@ SVar:TrigDraw:DB$Draw | Defined$ You | NumCards$ 1
|
||||
A:SP$ Attach | Cost$ 2 W | ValidTgts$ Creature | AILogic$ Pump
|
||||
S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddKeyword$ Protection:Card.ChosenColor:Protection from ChosenColor:Card.CardUID_HostCardUID | Description$ Enchanted creature has protection from the chosen color. This effect doesn't remove CARDNAME.
|
||||
SVar:Picture:http://www.wizards.com/global/images/magic/general/pentarch_ward.jpg
|
||||
SVar:ChosenProtection:True
|
||||
Oracle:Enchant creature\nAs Pentarch Ward enters the battlefield, choose a color.\nWhen Pentarch Ward enters the battlefield, draw a card.\nEnchanted creature has protection from the chosen color. This effect doesn't remove Pentarch Ward.
|
||||
|
||||
@@ -4,5 +4,6 @@ Types:Creature Human Wizard
|
||||
PT:2/3
|
||||
S:Mode$ Continuous | Affected$ Creature.YouCtrl,Land.YouCtrl | AddHiddenKeyword$ CARDNAME untaps during each other player's untap step. | Description$ Untap all creatures and lands you control during each other player's untap step.
|
||||
S:Mode$ Continuous | Affected$ Creature.YouCtrl | AddHiddenKeyword$ Flash | AffectedZone$ Exile,Graveyard,Hand,Library,Command | Description$ You may cast creature spells as though they had flash.
|
||||
SVar:UntapsEachOtherPlayerTurn:Creature,Land
|
||||
SVar:Picture:http://www.wizards.com/global/images/magic/general/prophet_of_kruphix.jpg
|
||||
Oracle:Untap all creatures and lands you control during each other player's untap step.\nYou may cast creature spells as though they had flash.
|
||||
|
||||
@@ -11,5 +11,6 @@ SVar:ChooseColor:DB$ ChooseColor | Defined$ You | SpellDescription$ As CARDNAME
|
||||
A:SP$ Attach | Cost$ W W | ValidTgts$ Creature | AILogic$ Pump
|
||||
S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddKeyword$ Protection:Card.ChosenColor:Protection from ChosenColor:Card.CardUID_HostCardUID | Description$ Enchanted creature has protection from the chosen color. This effect doesn't remove CARDNAME.
|
||||
SVar:RemAIDeck:True
|
||||
SVar:ChosenProtection:True
|
||||
SVar:Picture:http://www.wizards.com/global/images/magic/general/ward_of_lights.jpg
|
||||
Oracle:You may cast Ward of Lights as though it had flash. If you cast it any time a sorcery couldn't have been cast, the controller of the permanent it becomes sacrifices it at the beginning of the next cleanup step.\nEnchant creature\nAs Ward of Lights enters the battlefield, choose a color.\nEnchanted creature has protection from the chosen color. This effect doesn't remove Ward of Lights.
|
||||
|
||||
Reference in New Issue
Block a user