diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtil.java b/forge-ai/src/main/java/forge/ai/ComputerUtil.java index 51fb11e8607..1958d3e7a60 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtil.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtil.java @@ -2636,4 +2636,34 @@ public class ComputerUtil { return false; } + + public static boolean targetPlayableSpellCard(final Player ai, CardCollection options, final SpellAbility sa, final boolean withoutPayingManaCost) { + // determine and target a card with a SA that the AI can afford and will play + AiController aic = ((PlayerControllerAi) ai.getController()).getAi(); + Card targetSpellCard = null; + for (Card c : options) { + for (SpellAbility ab : c.getSpellAbilities()) { + if (ab.getApi() == null) { + // only API-based SAs are supported, other things may lead to a NPE (e.g. Ancestral Vision Suspend SA) + continue; + } + SpellAbility abTest = withoutPayingManaCost ? ab.copyWithNoManaCost() : ab.copy(); + // at this point, we're assuming that card will be castable from whichever zone it's in by the AI player. + abTest.setActivatingPlayer(ai); + abTest.getRestrictions().setZone(c.getZone().getZoneType()); + final boolean play = AiPlayDecision.WillPlay == aic.canPlaySa(abTest); + final boolean pay = ComputerUtilCost.canPayCost(abTest, ai); + if (play && pay) { + targetSpellCard = c; + break; + } + } + } + if (targetSpellCard == null) { + return false; + } else { + sa.getTargets().add(targetSpellCard); + } + return true; + } } diff --git a/forge-ai/src/main/java/forge/ai/ability/PlayAi.java b/forge-ai/src/main/java/forge/ai/ability/PlayAi.java index 62bf2358668..a79cd56540c 100644 --- a/forge-ai/src/main/java/forge/ai/ability/PlayAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/PlayAi.java @@ -3,10 +3,12 @@ package forge.ai.ability; import java.util.List; import com.google.common.base.Predicate; +import forge.ai.AiController; import forge.ai.AiPlayDecision; import forge.ai.ComputerUtil; import forge.ai.ComputerUtilCard; +import forge.ai.ComputerUtilCost; import forge.ai.PlayerControllerAi; import forge.ai.SpellAbilityAi; import forge.game.Game; @@ -68,7 +70,34 @@ public class PlayAi extends SpellAbilityAi { */ @Override protected boolean doTriggerAINoCost(final Player ai, final SpellAbility sa, final boolean mandatory) { + final Card source = sa.getHostCard(); + final Game game = ai.getGame(); + + // general logic (no AILogic specified) if (sa.usesTargeting()) { + if (!sa.hasParam("AILogic")) { + return false; + } + + CardCollection cards = null; + final TargetRestrictions tgt = sa.getTargetRestrictions(); + if (tgt != null) { + ZoneType zone = tgt.getZone().get(0); + cards = CardLists.getValidCards(game.getCardsIn(zone), tgt.getValidTgts(), ai, source, sa); + if (cards.isEmpty()) { + return false; + } + } else if (!sa.hasParam("Valid")) { + cards = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa); + if (cards.isEmpty()) { + return false; + } + } + + if ("PlayWithNoManaCost".equals(sa.getParam("AILogic"))) { + return ComputerUtil.targetPlayableSpellCard(ai, cards, sa, true); + } + return false; } diff --git a/forge-ai/src/main/java/forge/ai/ability/PumpAi.java b/forge-ai/src/main/java/forge/ai/ability/PumpAi.java index 0d7e0d75aa8..903baba1159 100644 --- a/forge-ai/src/main/java/forge/ai/ability/PumpAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/PumpAi.java @@ -478,15 +478,11 @@ public class PumpAi extends PumpAiBase { } if ("Snapcaster".equals(sa.getParam("AILogic"))) { - if (!doTargetSpellToPlayLogic(ai, list, sa, false)) { - return false; - } - } else if ("PlaySpellForFree".equals(sa.getParam("AILogic"))) { - if (!doTargetSpellToPlayLogic(ai, list, sa, true)) { + if (!ComputerUtil.targetPlayableSpellCard(ai, list, sa, false)) { return false; } } - + while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(source, sa)) { Card t = null; // boolean goodt = false; @@ -520,36 +516,6 @@ public class PumpAi extends PumpAiBase { return true; } // pumpTgtAI() - private boolean doTargetSpellToPlayLogic(final Player ai, CardCollection options, final SpellAbility sa, final boolean withoutPayingManaCost) { - // determine and target a card with a SA that the AI can afford and will play - AiController aic = ((PlayerControllerAi) ai.getController()).getAi(); - Card targetSpellCard = null; - for (Card c : options) { - for (SpellAbility ab : c.getSpellAbilities()) { - if (ab.getApi() == null) { - // only API-based SAs are supported, other things may lead to a NPE (e.g. Ancestral Vision Suspend SA) - continue; - } - SpellAbility abTest = withoutPayingManaCost ? ab.copyWithNoManaCost() : ab.copy(); - // at this point, we're assuming that card will be castable from whichever zone it's in by the AI player. - abTest.setActivatingPlayer(ai); - abTest.getRestrictions().setZone(c.getZone().getZoneType()); - final boolean play = AiPlayDecision.WillPlay == aic.canPlaySa(abTest); - final boolean pay = ComputerUtilCost.canPayCost(abTest, ai); - if (play && pay) { - targetSpellCard = c; - break; - } - } - } - if (targetSpellCard == null) { - return false; - } else { - sa.getTargets().add(targetSpellCard); - } - return true; - } - private boolean pumpMandatoryTarget(final Player ai, final SpellAbility sa) { final Game game = ai.getGame(); final TargetRestrictions tgt = sa.getTargetRestrictions(); diff --git a/forge-gui/res/cardsfolder/g/goblin_dark_dwellers.txt b/forge-gui/res/cardsfolder/g/goblin_dark_dwellers.txt index 3e0f2280a4f..344363a0450 100644 --- a/forge-gui/res/cardsfolder/g/goblin_dark_dwellers.txt +++ b/forge-gui/res/cardsfolder/g/goblin_dark_dwellers.txt @@ -4,6 +4,6 @@ Types:Creature Goblin PT:4/4 K:Menace T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigPlay | TriggerDescription$ When CARDNAME enters the battlefield, you may cast target instant or sorcery card with converted mana cost 3 or less from your graveyard without paying its mana cost. If that card would be put into your graveyard this turn, exile it instead. -SVar:TrigPlay:DB$ Play | TgtZone$ Graveyard | ValidTgts$ Instant.YouCtrl+cmcLE3,Sorcery.YouCtrl+cmcLE3 | TgtPrompt$ Choose target instant or sorcery card with converted mana cost 3 or less from your graveyard | WithoutManaCost$ True | Optional$ True | ReplaceGraveyard$ Exile +SVar:TrigPlay:DB$ Play | TgtZone$ Graveyard | ValidTgts$ Instant.YouCtrl+cmcLE3,Sorcery.YouCtrl+cmcLE3 | TgtPrompt$ Choose target instant or sorcery card with converted mana cost 3 or less from your graveyard | WithoutManaCost$ True | Optional$ True | ReplaceGraveyard$ Exile | AILogic$ PlayWithNoManaCost SVar:Picture:http://www.wizards.com/global/images/magic/general/goblin_dark_dwellers.jpg Oracle:Menace\nWhen Goblin Dark-Dwellers enters the battlefield, you may cast target instant or sorcery card with converted mana cost 3 or less from your graveyard without paying its mana cost. If that card would be put into your graveyard this turn, exile it instead.