diff --git a/forge-ai/src/main/java/forge/ai/AiAttackController.java b/forge-ai/src/main/java/forge/ai/AiAttackController.java index 23e47dc733a..dffb3344d36 100644 --- a/forge-ai/src/main/java/forge/ai/AiAttackController.java +++ b/forge-ai/src/main/java/forge/ai/AiAttackController.java @@ -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 opponentsAttackers = new ArrayList(oppList); opponentsAttackers = CardLists.filter(opponentsAttackers, new Predicate() { @Override diff --git a/forge-ai/src/main/java/forge/ai/AiCardMemory.java b/forge-ai/src/main/java/forge/ai/AiCardMemory.java index 510c6834c48..be8593fdefe 100644 --- a/forge-ai/src/main/java/forge/ai/AiCardMemory.java +++ b/forge-ai/src/main/java/forge/ai/AiCardMemory.java @@ -44,20 +44,24 @@ public class AiCardMemory { private final Set memTrickAttackers; private final Set memHeldManaSources; private final Set memHeldManaSourcesForCombat; + private final Set memHeldManaSourcesForEnemyCombat; private final Set memAttachedThisTurn; private final Set memAnimatedThisTurn; private final Set memBouncedThisTurn; private final Set memActivatedThisTurn; + private final Set 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. diff --git a/forge-ai/src/main/java/forge/ai/AiController.java b/forge-ai/src/main/java/forge/ai/AiController.java index 077b151457f..a645ec1a81c 100644 --- a/forge-ai/src/main/java/forge/ai/AiController.java +++ b/forge-ai/src/main/java/forge/ai/AiController.java @@ -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: " diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java index 99b42a343f7..7b3975e8d8d 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java @@ -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 { diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java b/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java index 3011e56ba06..98df78bf24a 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java @@ -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; } diff --git a/forge-ai/src/main/java/forge/ai/ability/AttachAi.java b/forge-ai/src/main/java/forge/ai/ability/AttachAi.java index 2b71a67653e..4074512d7b0 100644 --- a/forge-ai/src/main/java/forge/ai/ability/AttachAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/AttachAi.java @@ -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") diff --git a/forge-ai/src/main/java/forge/ai/ability/FogAi.java b/forge-ai/src/main/java/forge/ai/ability/FogAi.java index 66f34652f0b..00a7386ea7d 100644 --- a/forge-ai/src/main/java/forge/ai/ability/FogAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/FogAi.java @@ -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 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)) { diff --git a/forge-gui/res/cardsfolder/a/awakening.txt b/forge-gui/res/cardsfolder/a/awakening.txt index 0c52bdff23e..89ff19f084a 100644 --- a/forge-gui/res/cardsfolder/a/awakening.txt +++ b/forge-gui/res/cardsfolder/a/awakening.txt @@ -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. diff --git a/forge-gui/res/cardsfolder/c/cho_mannos_blessing.txt b/forge-gui/res/cardsfolder/c/cho_mannos_blessing.txt index 6a5fe54791c..870d2ff486c 100644 --- a/forge-gui/res/cardsfolder/c/cho_mannos_blessing.txt +++ b/forge-gui/res/cardsfolder/c/cho_mannos_blessing.txt @@ -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. diff --git a/forge-gui/res/cardsfolder/f/flickering_ward.txt b/forge-gui/res/cardsfolder/f/flickering_ward.txt index 78040fe4b23..f2a01a3fd2e 100644 --- a/forge-gui/res/cardsfolder/f/flickering_ward.txt +++ b/forge-gui/res/cardsfolder/f/flickering_ward.txt @@ -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. diff --git a/forge-gui/res/cardsfolder/f/floating_shield.txt b/forge-gui/res/cardsfolder/f/floating_shield.txt index a14669460bb..a4dc1de9f3c 100644 --- a/forge-gui/res/cardsfolder/f/floating_shield.txt +++ b/forge-gui/res/cardsfolder/f/floating_shield.txt @@ -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. diff --git a/forge-gui/res/cardsfolder/p/pentarch_ward.txt b/forge-gui/res/cardsfolder/p/pentarch_ward.txt index b573ae3a13f..a5d0a3d3d89 100644 --- a/forge-gui/res/cardsfolder/p/pentarch_ward.txt +++ b/forge-gui/res/cardsfolder/p/pentarch_ward.txt @@ -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. diff --git a/forge-gui/res/cardsfolder/p/prophet_of_kruphix.txt b/forge-gui/res/cardsfolder/p/prophet_of_kruphix.txt index d5ce14ce9ef..e3e983edeb4 100644 --- a/forge-gui/res/cardsfolder/p/prophet_of_kruphix.txt +++ b/forge-gui/res/cardsfolder/p/prophet_of_kruphix.txt @@ -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. diff --git a/forge-gui/res/cardsfolder/w/ward_of_lights.txt b/forge-gui/res/cardsfolder/w/ward_of_lights.txt index b93c9b1c9e4..364d5fecc0b 100644 --- a/forge-gui/res/cardsfolder/w/ward_of_lights.txt +++ b/forge-gui/res/cardsfolder/w/ward_of_lights.txt @@ -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.