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
This commit is contained in:
Agetian
2024-10-24 12:31:23 +03:00
committed by GitHub
parent e28d041ede
commit d92cacae7f
7 changed files with 36 additions and 21 deletions

View File

@@ -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()) {

View File

@@ -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<ZoneType> 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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.