diff --git a/forge-ai/src/main/java/forge/ai/SpecialCardAi.java b/forge-ai/src/main/java/forge/ai/SpecialCardAi.java index 8be07ea2417..8faff40f3fd 100644 --- a/forge-ai/src/main/java/forge/ai/SpecialCardAi.java +++ b/forge-ai/src/main/java/forge/ai/SpecialCardAi.java @@ -38,6 +38,8 @@ import forge.game.phase.PhaseType; import forge.game.player.Player; import forge.game.player.PlayerPredicates; import forge.game.spellability.SpellAbility; +import forge.game.spellability.SpellAbilityRestriction; +import forge.game.spellability.SpellPermanent; import forge.game.zone.ZoneType; import forge.util.Aggregates; import java.util.Collections; @@ -459,4 +461,58 @@ public class SpecialCardAi { } } + // Yawgmoth's Will (can potentially be expanded for other broadly similar effects too) + public static class YawgmothsWill { + public static boolean consider(Player ai, SpellAbility sa) { + CardCollectionView cardsInGY = ai.getCardsIn(ZoneType.Graveyard); + if (cardsInGY.size() == 0) { + return false; + } + + int minManaAdj = 2; // we want the AI to have some spare mana for possible other spells to cast + float minCastableInGY = 3.0f; // we want the AI to have several castable cards in GY before attempting this effect + List saList = ComputerUtilAbility.getSpellAbilities(cardsInGY, ai); + int selfCMC = sa.getPayCosts().getCostMana().getMana().getCMC(); + + float numCastable = 0.0f; + for (SpellAbility ab : saList) { + final Card src = ab.getHostCard(); + + if (ab.getApi() == ApiType.Counter) { + // cut short considering to play counterspells via Yawgmoth's Will + continue; + } + if (ab.getHostCard().getName().equals(sa.getHostCard().getName())) { + // prevent infinitely recursing own ability when testing AI play decision + continue; + } + + // check to see if the AI is willing to play this card + final SpellAbility testAb = ab.copy(); + testAb.getRestrictions().setZone(ZoneType.Graveyard); + testAb.setActivatingPlayer(ai); + + boolean willPlayAb = ((PlayerControllerAi)ai.getController()).getAi().canPlaySa(testAb) == AiPlayDecision.WillPlay; + + // Land drops are generally made by the AI in main 1 before casting spells, so testing for them is iffy. + if (!src.getType().isLand() && willPlayAb) { + int CMC = ab.getPayCosts().getTotalMana() != null ? ab.getPayCosts().getTotalMana().getCMC() : 0; + int Xcount = ab.getPayCosts().getTotalMana() != null ? ab.getPayCosts().getTotalMana().countX() : 0; + + if ((Xcount == 0 && CMC == 0) || ComputerUtilMana.canPayManaCost(ab, ai, selfCMC + minManaAdj)) { + if (src.isInstant() || src.isSorcery()) { + // instants and sorceries are one-shot, so only treat them as 1/2 value for the purpose of meeting minimum + // castable cards in graveyard requirements + numCastable += 0.5f; + } else { + numCastable += 1.0f; + } + } + } + } + + return numCastable >= minCastableInGY; + } + } + } diff --git a/forge-ai/src/main/java/forge/ai/ability/EffectAi.java b/forge-ai/src/main/java/forge/ai/ability/EffectAi.java index af67fa452d8..6cada65aea3 100644 --- a/forge-ai/src/main/java/forge/ai/ability/EffectAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/EffectAi.java @@ -7,7 +7,9 @@ import forge.ai.ComputerUtil; import forge.ai.ComputerUtilAbility; import forge.ai.ComputerUtilCard; import forge.ai.ComputerUtilCombat; +import forge.ai.ComputerUtilDeck; import forge.ai.ComputerUtilMana; +import forge.ai.SpecialCardAi; import forge.ai.SpellAbilityAi; import forge.ai.SpellApiToAi; import forge.game.Game; @@ -216,34 +218,8 @@ public class EffectAi extends SpellAbilityAi { // for DamageDeal sub-abilities (eg. Wild Slash, Skullcrack) SpellAbility burn = sa.getSubAbility(); return SpellApiToAi.Converter.get(burn.getApi()).canPlayAIWithSubs(ai, burn); - } else if (logic.equals("CastFromGraveyardUntilEOT")) { - // Effects that allow you to play stuff from graveyard until end of turn (e.g. Yawgmoth's Will) - CardCollectionView cardsInGY = ai.getCardsIn(ZoneType.Graveyard); - if (cardsInGY.size() == 0) { - return false; - } - - int minManaAdj = 2; // we want the AI to have some spare mana for possible other spells to cast from GY/hand - int minCastableInGY = 3; // we want the AI to have several castable cards in GY before attempting an effect - List saList = ComputerUtilAbility.getSpellAbilities(cardsInGY, ai); - int selfCMC = sa.getPayCosts().getCostMana().getMana().getCMC(); - - // Currently limited to considering nonland permanents, since instants and sorceries may be very contextual - // and land drops are generally made by the AI in main 1 before casting spells, so testing for them is iffy. - int numCastable = 0; - for (SpellAbility ab : saList) { - final Card src = ab.getHostCard(); - if (ab instanceof SpellPermanent && !src.getType().isLand()) { - int CMC = ab.getPayCosts().getTotalMana() != null ? ab.getPayCosts().getTotalMana().getCMC() : 0; - int Xcount = ab.getPayCosts().getTotalMana() != null ? ab.getPayCosts().getTotalMana().countX() : 0; - - if ((Xcount == 0 && CMC == 0) || ComputerUtilMana.canPayManaCost(ab, ai, selfCMC + minManaAdj)) { - numCastable++; - } - } - } - - return numCastable >= minCastableInGY; + } else if (logic.equals("YawgmothsWill")) { + return SpecialCardAi.YawgmothsWill.consider(ai, sa); } } else { //no AILogic return false; diff --git a/forge-gui/res/cardsfolder/y/yawgmoths_will.txt b/forge-gui/res/cardsfolder/y/yawgmoths_will.txt index 3829977672e..698ac0d235f 100644 --- a/forge-gui/res/cardsfolder/y/yawgmoths_will.txt +++ b/forge-gui/res/cardsfolder/y/yawgmoths_will.txt @@ -1,7 +1,7 @@ Name:Yawgmoth's Will ManaCost:2 B Types:Sorcery -A:SP$ Effect | Cost$ 2 B | Name$ Yawgmoth's Will Effect | ReplacementEffects$ GraveToExile | StaticAbilities$ STPlay | SVars$ Exile | AILogic$ CastFromGraveyardUntilEOT | SpellDescription$ Until end of turn, you may play cards from your graveyard. If a card would be put into your graveyard from anywhere this turn, exile that card instead. +A:SP$ Effect | Cost$ 2 B | Name$ Yawgmoth's Will Effect | ReplacementEffects$ GraveToExile | StaticAbilities$ STPlay | SVars$ Exile | AILogic$ YawgmothsWill | SpellDescription$ Until end of turn, you may play cards from your graveyard. If a card would be put into your graveyard from anywhere this turn, exile that card instead. SVar:STPlay:Mode$ Continuous | EffectZone$ Command | Affected$ Card.YouCtrl | AffectedZone$ Graveyard | MayPlay$ True | Description$ You may play cards from your graveyard. SVar:GraveToExile:Event$ Moved | ActiveZones$ Command | Destination$ Graveyard | ValidCard$ Card.nonToken+YouOwn | ReplaceWith$ Exile | Description$ If a card would be put into your graveyard from anywhere, exile it instead. SVar:Exile:AB$ ChangeZone | Cost$ 0 | Hidden$ True | Origin$ All | Destination$ Exile | Defined$ ReplacedCard