From d92cacae7f56034dc78fba73fe1473939c24e82e Mon Sep 17 00:00:00 2001 From: Agetian Date: Thu, 24 Oct 2024 12:31:23 +0300 Subject: [PATCH] Various improvements to AI-related logic (Canopy lands, Ugin's Labyrinth, Zuran Orb, Pyrite Spellbomb, Eladamri) (#6418) * - Improve AI for Canopy lands. - Improve AI for Ugin's Labyrinth. * - Pyrite Spellbomb: also don't sac immediately. * - Improve AI for Eladamri, Korvecdal * - Improve AI decision for Zuran Orb. * - Cleaner implementation for Eladamri AI - More generic implementation for SacToDraw AI that doesn't need a logic parameter * - Fix imports * - Cleaner implementation for Eladamri AI * - Suggested code tweak --- .../java/forge/ai/ability/ChangeZoneAi.java | 3 ++ .../java/forge/ai/ability/ChooseCardAi.java | 38 ++++++++++--------- .../main/java/forge/ai/ability/DrawAi.java | 7 ++++ .../java/forge/ai/ability/LifeGainAi.java | 3 ++ .../res/cardsfolder/e/eladamri_korvecdal.txt | 2 +- .../res/cardsfolder/u/ugins_labyrinth.txt | 2 +- forge-gui/res/cardsfolder/z/zuran_orb.txt | 2 +- 7 files changed, 36 insertions(+), 21 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java b/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java index 22b754f3dc3..83bbd6b2cc7 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java @@ -160,6 +160,9 @@ public class ChangeZoneAi extends SpellAbilityAi { return SpecialCardAi.MazesEnd.consider(aiPlayer, sa); } else if (aiLogic.equals("Pongify")) { return sa.isTargetNumberValid(); // Pre-targeted in checkAiLogic + } else if (aiLogic.equals("ReturnCastable")) { + return !sa.getHostCard().getExiledCards().isEmpty() + && ComputerUtilMana.canPayManaCost(sa.getHostCard().getExiledCards().getFirst().getFirstSpellAbility(), aiPlayer, 0, false); } } if (sa.isHidden()) { diff --git a/forge-ai/src/main/java/forge/ai/ability/ChooseCardAi.java b/forge-ai/src/main/java/forge/ai/ability/ChooseCardAi.java index 10c50ac98e2..721c183f635 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ChooseCardAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ChooseCardAi.java @@ -1,25 +1,12 @@ package forge.ai.ability; -import java.util.Collections; -import java.util.List; -import java.util.Map; - import com.google.common.base.Predicates; import com.google.common.collect.Iterables; - -import forge.ai.AiAttackController; -import forge.ai.ComputerUtilAbility; -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.card.Card; -import forge.game.card.CardCollection; -import forge.game.card.CardCollectionView; -import forge.game.card.CardLists; -import forge.game.card.CardPredicates; +import forge.game.card.*; import forge.game.card.CardPredicates.Presets; -import forge.game.card.CounterEnumType; import forge.game.combat.Combat; import forge.game.keyword.Keyword; import forge.game.phase.PhaseHandler; @@ -30,6 +17,10 @@ import forge.game.spellability.SpellAbility; import forge.game.zone.ZoneType; import forge.util.Aggregates; +import java.util.Collections; +import java.util.List; +import java.util.Map; + public class ChooseCardAi extends SpellAbilityAi { /** @@ -58,11 +49,15 @@ public class ChooseCardAi extends SpellAbilityAi { protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) { final Card host = sa.getHostCard(); final Game game = ai.getGame(); - ZoneType choiceZone = ZoneType.Battlefield; + + List choiceZone; if (sa.hasParam("ChoiceZone")) { - choiceZone = ZoneType.smartValueOf(sa.getParam("ChoiceZone")); + choiceZone = ZoneType.listValueOf(sa.getParam("ChoiceZone")); + } else { + choiceZone = Lists.newArrayList(ZoneType.Battlefield); } CardCollectionView choices = game.getCardsIn(choiceZone); + if (sa.hasParam("Choices")) { choices = CardLists.getValidCards(choices, sa.getParam("Choices"), host.getController(), host, sa); } @@ -129,6 +124,13 @@ public class ChooseCardAi extends SpellAbilityAi { ownChoices = CardLists.filter(choices, CardPredicates.isControlledByAnyOf(ai.getAllies())); } return !ownChoices.isEmpty(); + } else if (aiLogic.equals("GoodCreature")) { + for (Card choice : choices) { + if (choice.isCreature() && ComputerUtilCard.evaluateCreature(choice) >= 250) { + return true; + } + } + return false; } return true; } diff --git a/forge-ai/src/main/java/forge/ai/ability/DrawAi.java b/forge-ai/src/main/java/forge/ai/ability/DrawAi.java index 453f45ac6f5..9de4d7302fd 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DrawAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DrawAi.java @@ -81,6 +81,13 @@ public class DrawAi extends SpellAbilityAi { if (!canLoot(ai, sa)) { return false; } + + if (ComputerUtilCost.isSacrificeSelfCost(sa.getPayCosts())) { + // Canopy lands and other cards that sacrifice themselves to draw cards + return ai.getCardsIn(ZoneType.Hand).isEmpty() + || (sa.getHostCard().isLand() && ai.getLandsInPlay().size() >= 5); // TODO: make this configurable in the AI profile + } + return true; } diff --git a/forge-ai/src/main/java/forge/ai/ability/LifeGainAi.java b/forge-ai/src/main/java/forge/ai/ability/LifeGainAi.java index 1185df31edd..0b510d251e8 100644 --- a/forge-ai/src/main/java/forge/ai/ability/LifeGainAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/LifeGainAi.java @@ -42,6 +42,9 @@ public class LifeGainAi extends SpellAbilityAi { if (!lifeCritical) { // return super.willPayCosts(ai, sa, cost, source); + if ("CriticalOnly".equals(sa.getParam("AILogic"))) { + return false; + } if (!ComputerUtilCost.checkSacrificeCost(ai, cost, source, sa, false)) { return false; } diff --git a/forge-gui/res/cardsfolder/e/eladamri_korvecdal.txt b/forge-gui/res/cardsfolder/e/eladamri_korvecdal.txt index 2138799ecb0..fee1da6d5c8 100644 --- a/forge-gui/res/cardsfolder/e/eladamri_korvecdal.txt +++ b/forge-gui/res/cardsfolder/e/eladamri_korvecdal.txt @@ -4,7 +4,7 @@ Types:Legendary Creature Elf Warrior PT:3/3 S:Mode$ Continuous | Affected$ Card.TopLibrary+YouCtrl | AffectedZone$ Library | MayLookAt$ You | Description$ You may look at the top card of your library any time. S:Mode$ Continuous | Affected$ Creature.TopLibrary+YouCtrl+nonLand | AffectedZone$ Library | MayPlay$ True | Description$ You may cast creature spells from the top of your library. -A:AB$ ChooseCard | Cost$ G T tapXType<2/Creature> | ChoiceZone$ Hand,Library | PlayerTurn$ True | Reveal$ True | Choices$ Card.TopLibrary+YouOwn,Card.YouOwn+inZoneHand | SubAbility$ DBChangeZone | ChoiceTitle$ Reveal a card from your hand or the top of your library | SpellDescription$ Reveal a card from your hand or the top of your library. If you reveal a creature card this way, put it onto the battlefield. Activate only during your turn. +A:AB$ ChooseCard | Cost$ G T tapXType<2/Creature> | ChoiceZone$ Hand,Library | PlayerTurn$ True | Reveal$ True | Choices$ Card.TopLibrary+YouOwn,Card.YouOwn+inZoneHand | SubAbility$ DBChangeZone | ChoiceTitle$ Reveal a card from your hand or the top of your library | AILogic$ GoodCreature | SpellDescription$ Reveal a card from your hand or the top of your library. If you reveal a creature card this way, put it onto the battlefield. Activate only during your turn. SVar:DBChangeZone:DB$ ChangeZone | Defined$ ChosenCard | Origin$ Library,Hand | ConditionDefined$ ChosenCard | ConditionPresent$ Creature | Destination$ Battlefield | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearChosenCard$ True Oracle:You may look at the top of your library at any time.\nYou may cast creature spells from the top of your library.\n{G}, {T}, Tap two untapped creatures you control: Reveal a card from your hand or the top of your library. If you reveal a creature card this way, put it onto the battlefield. Activate only during your turn. diff --git a/forge-gui/res/cardsfolder/u/ugins_labyrinth.txt b/forge-gui/res/cardsfolder/u/ugins_labyrinth.txt index 9af78cf952d..155efa7b80a 100644 --- a/forge-gui/res/cardsfolder/u/ugins_labyrinth.txt +++ b/forge-gui/res/cardsfolder/u/ugins_labyrinth.txt @@ -4,7 +4,7 @@ Types:Land T:Mode$ ChangesZone | ValidCard$ Card.Self | Origin$ Any | Destination$ Battlefield | OptionalDecider$ You | Execute$ TrigExile | TriggerDescription$ Imprint — When CARDNAME enters, you may exile a colorless card with mana value 7 or greater from your hand. SVar:TrigExile:DB$ ChangeZone | Origin$ Hand | Destination$ Exile | ChangeType$ Card.Colorless+cmcGE7 | ChangeNum$ 1 A:AB$ Mana | Cost$ T | Produced$ C | Amount$ X | SpellDescription$ Add {C}. If a card is exiled with CARDNAME, add {C}{C} instead. -A:AB$ ChangeZone | Cost$ T | Defined$ ExiledWith | Origin$ Exile | Destination$ Hand | SpellDescription$ Return the exiled card to its owner's hand. +A:AB$ ChangeZone | Cost$ T | Defined$ ExiledWith | Origin$ Exile | Destination$ Hand | AILogic$ ReturnCastable | SpellDescription$ Return the exiled card to its owner's hand. SVar:X:Count$Compare Y GE1.2.1 SVar:Y:Count$ValidExile Card.ExiledWithSource Oracle:Imprint — When Ugin's Labyrinth enters, you may exile a colorless card with mana value 7 or greater from your hand.\n{T}: Add {C}. If a card is exiled with Ugin's Labyrinth, add {C}{C} instead.\n{T}: Return the exiled card to its owner's hand. diff --git a/forge-gui/res/cardsfolder/z/zuran_orb.txt b/forge-gui/res/cardsfolder/z/zuran_orb.txt index 9be1520a4cf..8d40776dbc4 100644 --- a/forge-gui/res/cardsfolder/z/zuran_orb.txt +++ b/forge-gui/res/cardsfolder/z/zuran_orb.txt @@ -1,6 +1,6 @@ Name:Zuran Orb ManaCost:0 Types:Artifact -A:AB$ GainLife | Cost$ Sac<1/Land> | LifeAmount$ 2 | SpellDescription$ You gain 2 life. +A:AB$ GainLife | Cost$ Sac<1/Land> | LifeAmount$ 2 | AILogic$ CriticalOnly | SpellDescription$ You gain 2 life. SVar:NonStackingEffect:True Oracle:Sacrifice a land: You gain 2 life.