From 31d887301b8bb2418080cb35e5f739534b27c7b0 Mon Sep 17 00:00:00 2001 From: Agetian Date: Thu, 29 Nov 2018 15:02:19 +0300 Subject: [PATCH] - Improved the AI logic for MustBlock a little. - Enabled several cards for the AI. - Implemented Sisters of Stone Death's 3rd ability similar to Tomb of the Dusk Rose which the AI can properly work with (seems functionally identical). - Fixed the AI logic spec for Tomb of the Dusk Rose. --- .../java/forge/ai/ability/MustBlockAi.java | 103 +++++++++++++----- forge-gui/res/cardsfolder/l/lurking_arynx.txt | 1 - .../res/cardsfolder/m/matsu_tribe_decoy.txt | 2 +- ...ofane_procession_tomb_of_the_dusk_rose.txt | 3 +- .../res/cardsfolder/r/rampant_elephant.txt | 2 - .../cardsfolder/s/sisters_of_stone_death.txt | 5 +- forge-gui/res/cardsfolder/t/tangle_angler.txt | 3 +- .../res/cardsfolder/t/trumpeting_armodon.txt | 1 - 8 files changed, 80 insertions(+), 40 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/ability/MustBlockAi.java b/forge-ai/src/main/java/forge/ai/ability/MustBlockAi.java index 79c3cbbad0e..306566477c1 100644 --- a/forge-ai/src/main/java/forge/ai/ability/MustBlockAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/MustBlockAi.java @@ -2,15 +2,16 @@ package forge.ai.ability; import com.google.common.base.Predicate; -import forge.ai.ComputerUtil; -import forge.ai.ComputerUtilCard; -import forge.ai.ComputerUtilCombat; -import forge.ai.SpellAbilityAi; +import com.google.common.collect.Lists; +import forge.ai.*; +import forge.game.Game; import forge.game.ability.AbilityUtils; import forge.game.card.Card; import forge.game.card.CardLists; import forge.game.card.CardPredicates; +import forge.game.combat.Combat; import forge.game.combat.CombatUtil; +import forge.game.phase.PhaseHandler; import forge.game.phase.PhaseType; import forge.game.player.Player; import forge.game.spellability.SpellAbility; @@ -23,14 +24,45 @@ public class MustBlockAi extends SpellAbilityAi { @Override protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) { - // disabled for the AI until he/she can make decisions about who to make - // block + final Card source = sa.getHostCard(); + final Game game = aiPlayer.getGame(); + final Combat combat = game.getCombat(); + final PhaseHandler ph = game.getPhaseHandler(); + final boolean onlyLethal = !"AllowNonLethal".equals(sa.getParam("AILogic")); + + if (combat == null || !combat.isAttacking(source)) { + return false; + } else if (AiCardMemory.isRememberedCard(aiPlayer, source, AiCardMemory.MemorySet.ACTIVATED_THIS_TURN)) { + // The AI can meaningfully do it only to one creature per card yet, trying to do it to multiple cards + // may result in overextending and losing the attacker + return false; + } + + final TargetRestrictions abTgt = sa.getTargetRestrictions(); + final List list = determineGoodBlockers(source, aiPlayer, combat.getDefenderPlayerByAttacker(source), sa, onlyLethal,false); + + if (!list.isEmpty()) { + final Card blocker = ComputerUtilCard.getBestCreatureAI(list); + if (blocker == null) { + return false; + } + sa.getTargets().add(blocker); + AiCardMemory.rememberCard(aiPlayer, source, AiCardMemory.MemorySet.ACTIVATED_THIS_TURN); + return true; + } + return false; } @Override public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) { - return false; + if (sa.hasParam("DefinedAttacker")) { + // The AI can't handle "target creature blocks another target creature" abilities yet + return false; + } + + // Otherwise it's a standard targeted "target creature blocks CARDNAME" ability, so use the main canPlayAI code path + return canPlayAI(aiPlayer, sa); } @Override @@ -62,27 +94,7 @@ public class MustBlockAi extends SpellAbilityAi { boolean chance = false; if (abTgt != null) { - List list = CardLists.filter(ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES); - list = CardLists.getTargetableCards(list, sa); - list = CardLists.getValidCards(list, abTgt.getValidTgts(), source.getController(), source, sa); - list = CardLists.filter(list, new Predicate() { - @Override - public boolean apply(final Card c) { - boolean tapped = c.isTapped(); - c.setTapped(false); - if (!CombatUtil.canBlock(definedAttacker, c)) { - return false; - } - if (ComputerUtilCombat.canDestroyAttacker(ai, definedAttacker, c, null, false)) { - return false; - } - if (!ComputerUtilCombat.canDestroyBlocker(ai, c, definedAttacker, null, false)) { - return false; - } - c.setTapped(tapped); - return true; - } - }); + final List list = determineGoodBlockers(definedAttacker, ai, ComputerUtil.getOpponentFor(ai), sa, true,true); if (list.isEmpty()) { return false; } @@ -98,4 +110,39 @@ public class MustBlockAi extends SpellAbilityAi { return chance; } + + private List determineGoodBlockers(Card attacker, Player ai, Player defender, SpellAbility sa, boolean onlyLethal, boolean testTapped) { + final Card source = sa.getHostCard(); + final TargetRestrictions abTgt = sa.getTargetRestrictions(); + + List list = Lists.newArrayList(); + list = CardLists.filter(defender.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES); + list = CardLists.getTargetableCards(list, sa); + list = CardLists.getValidCards(list, abTgt.getValidTgts(), source.getController(), source, sa); + list = CardLists.filter(list, new Predicate() { + @Override + public boolean apply(final Card c) { + boolean tapped = c.isTapped(); + if (testTapped) { + c.setTapped(false); + } + if (!CombatUtil.canBlock(attacker, c)) { + return false; + } + if (ComputerUtilCombat.canDestroyAttacker(ai, attacker, c, null, false)) { + return false; + } + if (onlyLethal && !ComputerUtilCombat.canDestroyBlocker(ai, c, attacker, null, false)) { + return false; + } + if (testTapped) { + c.setTapped(tapped); + } + return true; + } + + }); + + return list; + } } diff --git a/forge-gui/res/cardsfolder/l/lurking_arynx.txt b/forge-gui/res/cardsfolder/l/lurking_arynx.txt index 9498d2f6353..6acacbc0584 100644 --- a/forge-gui/res/cardsfolder/l/lurking_arynx.txt +++ b/forge-gui/res/cardsfolder/l/lurking_arynx.txt @@ -4,6 +4,5 @@ Types:Creature Cat Beast PT:3/5 A:AB$ MustBlock | Cost$ 2 G | ValidTgts$ Creature | CheckSVar$ FormidableTest | SVarCompare$ GE8 | References$ FormidableTest | PrecostDesc$ Formidable — | TgtPrompt$ Select target creature that must block this creature this turn | SpellDescription$ Target creature blocks CARDNAME this turn if able. Activate this ability only if creatures you control have total power 8 or greater. SVar:FormidableTest:Count$SumPower_Creature.YouCtrl -AI:RemoveDeck:All SVar:Picture:http://www.wizards.com/global/images/magic/general/lurking_arynx.jpg Oracle:Formidable — {2}{G}: Target creature blocks Lurking Arynx this turn if able. Activate this ability only if creatures you control have total power 8 or greater. diff --git a/forge-gui/res/cardsfolder/m/matsu_tribe_decoy.txt b/forge-gui/res/cardsfolder/m/matsu_tribe_decoy.txt index dcb5193cb12..66fabaec30f 100644 --- a/forge-gui/res/cardsfolder/m/matsu_tribe_decoy.txt +++ b/forge-gui/res/cardsfolder/m/matsu_tribe_decoy.txt @@ -2,7 +2,7 @@ Name:Matsu-Tribe Decoy ManaCost:2 G Types:Creature Snake Warrior PT:1/3 -A:AB$ MustBlock | Cost$ 2 G | ValidTgts$ Creature | TgtPrompt$ Select target creature | SpellDescription$ Target creature blocks CARDNAME this turn if able. +A:AB$ MustBlock | Cost$ 2 G | ValidTgts$ Creature | TgtPrompt$ Select target creature | AILogic$ AllowNonLethal | SpellDescription$ Target creature blocks CARDNAME this turn if able. T:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Creature | CombatDamage$ True | TriggerZones$ Battlefield | Execute$ TrigTap | TriggerDescription$ Whenever CARDNAME deals combat damage to a creature, tap that creature and it doesn't untap during its controller's next untap step. SVar:TrigTap:DB$ Tap | Defined$ TriggeredTarget | SubAbility$ DBPump SVar:DBPump:DB$ Pump | Defined$ TriggeredTarget | KW$ HIDDEN This card doesn't untap during your next untap step. | Permanent$ True | IsCurse$ True diff --git a/forge-gui/res/cardsfolder/p/profane_procession_tomb_of_the_dusk_rose.txt b/forge-gui/res/cardsfolder/p/profane_procession_tomb_of_the_dusk_rose.txt index 2b2dc12afa6..5c2e1124067 100644 --- a/forge-gui/res/cardsfolder/p/profane_procession_tomb_of_the_dusk_rose.txt +++ b/forge-gui/res/cardsfolder/p/profane_procession_tomb_of_the_dusk_rose.txt @@ -7,14 +7,13 @@ AlternateMode:DoubleFaced SVar:Picture:http://www.wizards.com/global/images/magic/general/profane_procession.jpg Oracle:{3}{W}{B}: Exile target creature. Then if there are three or more cards exiled with Profane Procession, transform it. -//Not sure if ExiledWithSource and IsRemembered persist through transformation, but for this card, it's absolutely vital that they do. ALTERNATE Name:Tomb of the Dusk Rose ManaCost:no cost Types:Legendary Land A:AB$ Mana | Cost$ T | Produced$ Any | Amount$ 1 | SpellDescription$ Add one mana of any color. -A:AB$ ChooseCard | Cost$ 2 W B T | Choices$ Creature.IsRemembered+ExiledWithSource | ChoiceZone$ Exile | SubAbility$ DBChangeZone | SpellDescription$ Put a creature card exiled with this permanent onto the battlefield under your control. +A:AB$ ChooseCard | Cost$ 2 W B T | Choices$ Creature.IsRemembered+ExiledWithSource | ChoiceZone$ Exile | SubAbility$ DBChangeZone | AILogic$ AtLeast1 | Mandatory$ True | SpellDescription$ Put a creature card exiled with this permanent onto the battlefield under your control. SVar:DBChangeZone:DB$ ChangeZone | Defined$ ChosenCard | Origin$ Exile | Destination$ Battlefield | ChangeType$ Creature.IsRemembered+ExiledWithSource | ChangeNum$ 1 | GainControl$ True SVar:Picture:http://www.wizards.com/global/images/magic/general/tomb_of_the_dusk_rose.jpg Oracle:(Transforms from Profane Procession.)\n{T}: Add one mana of any color.\n{2}{W}{B},{T}: Put a creature card exiled with this permanent onto the battlefield under your control. \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/r/rampant_elephant.txt b/forge-gui/res/cardsfolder/r/rampant_elephant.txt index 62851a275c6..175a4ad8d1e 100644 --- a/forge-gui/res/cardsfolder/r/rampant_elephant.txt +++ b/forge-gui/res/cardsfolder/r/rampant_elephant.txt @@ -3,7 +3,5 @@ ManaCost:3 W Types:Creature Elephant PT:2/2 A:AB$ MustBlock | Cost$ G | ValidTgts$ Creature | TgtPrompt$ Select target creature | SpellDescription$ Target creature blocks CARDNAME this turn if able. -AI:RemoveDeck:All -AI:RemoveDeck:Random SVar:Picture:http://www.wizards.com/global/images/magic/general/rampant_elephant.jpg Oracle:{G}: Target creature blocks Rampant Elephant this turn if able. diff --git a/forge-gui/res/cardsfolder/s/sisters_of_stone_death.txt b/forge-gui/res/cardsfolder/s/sisters_of_stone_death.txt index aec478cc53a..3e326007894 100644 --- a/forge-gui/res/cardsfolder/s/sisters_of_stone_death.txt +++ b/forge-gui/res/cardsfolder/s/sisters_of_stone_death.txt @@ -4,12 +4,11 @@ Types:Legendary Creature Gorgon PT:7/5 A:AB$ MustBlock | Cost$ G | ValidTgts$ Creature | TgtPrompt$ Select target creature | SpellDescription$ Target creature blocks CARDNAME this turn if able. A:AB$ ChangeZone | Cost$ B G | Origin$ Battlefield | Destination$ Exile | ValidTgts$ Creature.blockingSource,Creature.blockedBySource | TgtPrompt$ Select target creature blocking Sisters of Stone Death | RememberTargets$ True | SpellDescription$ Exile target creature blocking or blocked by CARDNAME. -A:AB$ ChangeZone | Cost$ 2 B | Origin$ Exile | Destination$ Battlefield | ChangeType$ Creature.IsRemembered+ExiledWithSource | Hidden$ True | GainControl$ True | SpellDescription$ Put a creature card exiled with CARDNAME onto the battlefield under your control. +A:AB$ ChooseCard | Cost$ 2 B | Choices$ Creature.IsRemembered+ExiledWithSource | ChoiceZone$ Exile | AILogic$ AtLeast1 | Mandatory$ True | SubAbility$ DBChangeZone | SpellDescription$ Put a creature card exiled with CARDNAME onto the battlefield under your control. +SVar:DBChangeZone:DB$ ChangeZone | Defined$ ChosenCard | Origin$ Exile | Destination$ Battlefield | ChangeType$ Creature.IsRemembered+ExiledWithSource | ChangeNum$ 1 | GainControl$ True T:Mode$ ChangesZone | Origin$ Exile | Destination$ Any | Static$ True | ValidCard$ Card.IsRemembered+ExiledWithSource | Execute$ DBForget SVar:DBForget:DB$ Pump | ForgetImprinted$ TriggeredCard T:Mode$ ChangesZone | ValidCard$ Card.Self | Origin$ Battlefield | Destination$ Any | Execute$ DBCleanup | Static$ True | Secondary$ True | TriggerDescription$ Forget all remembered cards. SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True -AI:RemoveDeck:All -AI:RemoveDeck:Random SVar:Picture:http://www.wizards.com/global/images/magic/general/sisters_of_stone_death.jpg Oracle:{G}: Target creature blocks Sisters of Stone Death this turn if able.\n{B}{G}: Exile target creature blocking or blocked by Sisters of Stone Death.\n{2}{B}: Put a creature card exiled with Sisters of Stone Death onto the battlefield under your control. diff --git a/forge-gui/res/cardsfolder/t/tangle_angler.txt b/forge-gui/res/cardsfolder/t/tangle_angler.txt index e409e592c9a..89dc8dcb781 100644 --- a/forge-gui/res/cardsfolder/t/tangle_angler.txt +++ b/forge-gui/res/cardsfolder/t/tangle_angler.txt @@ -3,7 +3,6 @@ ManaCost:3 G Types:Creature Horror PT:1/5 K:Infect -A:AB$ MustBlock | Cost$ G | ValidTgts$ Creature | TgtPrompt$ Select target creature | SpellDescription$ Target creature blocks CARDNAME this turn if able. -AI:RemoveDeck:All +A:AB$ MustBlock | Cost$ G | ValidTgts$ Creature | TgtPrompt$ Select target creature | AILogic$ AllowNonLethal | SpellDescription$ Target creature blocks CARDNAME this turn if able. SVar:Picture:http://www.wizards.com/global/images/magic/general/tangle_angler.jpg Oracle:Infect (This creature deals damage to creatures in the form of -1/-1 counters and to players in the form of poison counters.)\n{G}: Target creature blocks Tangle Angler this turn if able. diff --git a/forge-gui/res/cardsfolder/t/trumpeting_armodon.txt b/forge-gui/res/cardsfolder/t/trumpeting_armodon.txt index 85fbb34e52e..fb533f42605 100644 --- a/forge-gui/res/cardsfolder/t/trumpeting_armodon.txt +++ b/forge-gui/res/cardsfolder/t/trumpeting_armodon.txt @@ -3,6 +3,5 @@ ManaCost:3 G Types:Creature Elephant PT:3/3 A:AB$ MustBlock | Cost$ 1 G | ValidTgts$ Creature | TgtPrompt$ Select target creature | SpellDescription$ Target creature blocks CARDNAME this turn if able. -AI:RemoveDeck:All SVar:Picture:http://www.wizards.com/global/images/magic/general/trumpeting_armodon.jpg Oracle:{1}{G}: Target creature blocks Trumpeting Armodon this turn if able.