- Refactored AI logic for cards like Goblin Dark-Dwellers and Snapcaster Mage.

- Rewired AI logic for Goblin Dark-Dwellers to work with the new card implementation.
This commit is contained in:
Agetian
2017-06-17 09:29:29 +00:00
parent 154e546de5
commit 2ca7597624
4 changed files with 62 additions and 37 deletions

View File

@@ -2636,4 +2636,34 @@ public class ComputerUtil {
return false; 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;
}
} }

View File

@@ -3,10 +3,12 @@ package forge.ai.ability;
import java.util.List; import java.util.List;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import forge.ai.AiController;
import forge.ai.AiPlayDecision; import forge.ai.AiPlayDecision;
import forge.ai.ComputerUtil; import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard; import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCost;
import forge.ai.PlayerControllerAi; import forge.ai.PlayerControllerAi;
import forge.ai.SpellAbilityAi; import forge.ai.SpellAbilityAi;
import forge.game.Game; import forge.game.Game;
@@ -68,7 +70,34 @@ public class PlayAi extends SpellAbilityAi {
*/ */
@Override @Override
protected boolean doTriggerAINoCost(final Player ai, final SpellAbility sa, final boolean mandatory) { 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.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; return false;
} }

View File

@@ -478,11 +478,7 @@ public class PumpAi extends PumpAiBase {
} }
if ("Snapcaster".equals(sa.getParam("AILogic"))) { if ("Snapcaster".equals(sa.getParam("AILogic"))) {
if (!doTargetSpellToPlayLogic(ai, list, sa, false)) { if (!ComputerUtil.targetPlayableSpellCard(ai, list, sa, false)) {
return false;
}
} else if ("PlaySpellForFree".equals(sa.getParam("AILogic"))) {
if (!doTargetSpellToPlayLogic(ai, list, sa, true)) {
return false; return false;
} }
} }
@@ -520,36 +516,6 @@ public class PumpAi extends PumpAiBase {
return true; return true;
} // pumpTgtAI() } // 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) { private boolean pumpMandatoryTarget(final Player ai, final SpellAbility sa) {
final Game game = ai.getGame(); final Game game = ai.getGame();
final TargetRestrictions tgt = sa.getTargetRestrictions(); final TargetRestrictions tgt = sa.getTargetRestrictions();

View File

@@ -4,6 +4,6 @@ Types:Creature Goblin
PT:4/4 PT:4/4
K:Menace 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. 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 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. 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.