mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-17 19:28:01 +00:00
- Refactored the card-specific AIs implemented so far to its own class SpecialCardAi, will consider moving other card-specific hardcoded logic there later.
- Implemented rudimentary AI logic for Necropotence and Yawgmoth's Bargain, could use improvement but should be fine in typical situations. - Promoted Necropotence and Yawgmoth's Bargain from RemAIDeck to RemRandomDeck. - Propagated the SA parameter to checkLifeCost (and closed the previous TODO associated with that), was also necessary to support the Necro AI logic.
This commit is contained in:
1
.gitattributes
vendored
1
.gitattributes
vendored
@@ -32,6 +32,7 @@ forge-ai/src/main/java/forge/ai/CreatureEvaluator.java -text
|
|||||||
forge-ai/src/main/java/forge/ai/GameState.java -text
|
forge-ai/src/main/java/forge/ai/GameState.java -text
|
||||||
forge-ai/src/main/java/forge/ai/LobbyPlayerAi.java -text
|
forge-ai/src/main/java/forge/ai/LobbyPlayerAi.java -text
|
||||||
forge-ai/src/main/java/forge/ai/PlayerControllerAi.java -text
|
forge-ai/src/main/java/forge/ai/PlayerControllerAi.java -text
|
||||||
|
forge-ai/src/main/java/forge/ai/SpecialCardAi.java -text
|
||||||
forge-ai/src/main/java/forge/ai/SpellAbilityAi.java -text
|
forge-ai/src/main/java/forge/ai/SpellAbilityAi.java -text
|
||||||
forge-ai/src/main/java/forge/ai/SpellApiToAi.java -text
|
forge-ai/src/main/java/forge/ai/SpellApiToAi.java -text
|
||||||
forge-ai/src/main/java/forge/ai/ability/ActivateAbilityAi.java -text
|
forge-ai/src/main/java/forge/ai/ability/ActivateAbilityAi.java -text
|
||||||
|
|||||||
@@ -751,7 +751,7 @@ public class ComputerUtil {
|
|||||||
if (controller == ai) {
|
if (controller == ai) {
|
||||||
final Cost abCost = sa.getPayCosts();
|
final Cost abCost = sa.getPayCosts();
|
||||||
if (abCost != null) {
|
if (abCost != null) {
|
||||||
if (!ComputerUtilCost.checkLifeCost(controller, abCost, c, 4, null)) {
|
if (!ComputerUtilCost.checkLifeCost(controller, abCost, c, 4, sa)) {
|
||||||
continue; // Won't play ability
|
continue; // Won't play ability
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -195,8 +195,7 @@ public class ComputerUtilCost {
|
|||||||
* @param sourceAbility TODO
|
* @param sourceAbility TODO
|
||||||
* @return true, if successful
|
* @return true, if successful
|
||||||
*/
|
*/
|
||||||
public static boolean checkLifeCost(final Player ai, final Cost cost, final Card source, final int remainingLife, SpellAbility sourceAbility) {
|
public static boolean checkLifeCost(final Player ai, final Cost cost, final Card source, int remainingLife, SpellAbility sourceAbility) {
|
||||||
// TODO - Pass in SA for everything else that calls this function
|
|
||||||
if (cost == null) {
|
if (cost == null) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -209,6 +208,11 @@ public class ComputerUtilCost {
|
|||||||
amount = AbilityUtils.calculateAmount(source, payLife.getAmount(), sourceAbility);
|
amount = AbilityUtils.calculateAmount(source, payLife.getAmount(), sourceAbility);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check if there's override for the remainingLife threshold
|
||||||
|
if (sourceAbility != null && sourceAbility.hasParam("AIMinLifeThreshold")) {
|
||||||
|
remainingLife = Integer.parseInt(sourceAbility.getParam("AIMinLifeThreshold"));
|
||||||
|
}
|
||||||
|
|
||||||
if (ai.getLife() - amount < remainingLife && !ai.cantLoseForZeroOrLessLife()) {
|
if (ai.getLife() - amount < remainingLife && !ai.cantLoseForZeroOrLessLife()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1166,7 +1166,7 @@ public class ComputerUtilMana {
|
|||||||
|
|
||||||
// don't kill yourself
|
// don't kill yourself
|
||||||
final Cost abCost = m.getPayCosts();
|
final Cost abCost = m.getPayCosts();
|
||||||
if (!ComputerUtilCost.checkLifeCost(ai, abCost, sourceCard, 1, null)) {
|
if (!ComputerUtilCost.checkLifeCost(ai, abCost, sourceCard, 1, m)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
256
forge-ai/src/main/java/forge/ai/SpecialCardAi.java
Normal file
256
forge-ai/src/main/java/forge/ai/SpecialCardAi.java
Normal file
@@ -0,0 +1,256 @@
|
|||||||
|
/*
|
||||||
|
* Forge: Play Magic: the Gathering.
|
||||||
|
* Copyright (C) 2011 Forge Team
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package forge.ai;
|
||||||
|
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import forge.card.MagicColor;
|
||||||
|
import forge.card.mana.ManaCost;
|
||||||
|
import forge.game.Game;
|
||||||
|
import forge.game.card.Card;
|
||||||
|
import forge.game.card.CardCollectionView;
|
||||||
|
import forge.game.card.CardFactoryUtil;
|
||||||
|
import forge.game.card.CardLists;
|
||||||
|
import forge.game.card.CardPredicates;
|
||||||
|
import forge.game.phase.PhaseHandler;
|
||||||
|
import forge.game.phase.PhaseType;
|
||||||
|
import forge.game.player.Player;
|
||||||
|
import forge.game.player.PlayerPredicates;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
import forge.game.zone.ZoneType;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Special logic for individual cards
|
||||||
|
*
|
||||||
|
* Specific methods for each card that requires special handling are stored in inner classes
|
||||||
|
* Each class should have a name based on the name of the card and ideally preceded with a
|
||||||
|
* single-line comment with the full English card name to make searching for them easier.
|
||||||
|
*
|
||||||
|
* Class methods should return "true" if they are successful and have completed their task in full,
|
||||||
|
* otherwise should return "false" to signal that the AI should not use the card under current
|
||||||
|
* circumstances. A good convention to follow is to call the method "consider" if it's the only
|
||||||
|
* method necessary, or considerXXXX if several methods do different tasks, and use at least two
|
||||||
|
* mandatory parameters (Player ai, SpellAbility sa, in this order) and, if necessary, additional
|
||||||
|
* parameters later.
|
||||||
|
*
|
||||||
|
* If this class ends up being busy, consider splitting it into individual classes, each in its
|
||||||
|
* own file, inside its own package, for example, forge.ai.cards.
|
||||||
|
*/
|
||||||
|
public class SpecialCardAi {
|
||||||
|
|
||||||
|
// Donate
|
||||||
|
public static class Donate {
|
||||||
|
public static boolean considerTargetingOpponent(Player ai, SpellAbility sa) {
|
||||||
|
final Card donateTarget = ComputerUtil.getCardPreference(ai, sa.getHostCard(), "DonateMe", CardLists.filter(
|
||||||
|
ai.getCardsIn(ZoneType.Battlefield).threadSafeIterable(), CardPredicates.hasSVar("DonateMe")));
|
||||||
|
if (donateTarget != null) {
|
||||||
|
// first filter for opponents which can be targeted by SA
|
||||||
|
final Iterable<Player> oppList = Iterables.filter(ai.getOpponents(),
|
||||||
|
PlayerPredicates.isTargetableBy(sa));
|
||||||
|
|
||||||
|
// filter for player who does not have donate target already
|
||||||
|
Iterable<Player> oppTarget = Iterables.filter(oppList,
|
||||||
|
PlayerPredicates.isNotCardInPlay(donateTarget.getName()));
|
||||||
|
// fall back to previous list
|
||||||
|
if (Iterables.isEmpty(oppTarget)) {
|
||||||
|
oppTarget = oppList;
|
||||||
|
}
|
||||||
|
|
||||||
|
// select player with less lands on the field (helpful for Illusions of Grandeur and probably Pacts too)
|
||||||
|
Player opp = Collections.min(Lists.newArrayList(oppTarget),
|
||||||
|
PlayerPredicates.compareByZoneSize(ZoneType.Battlefield, CardPredicates.Presets.LANDS));
|
||||||
|
|
||||||
|
if (opp != null) {
|
||||||
|
sa.resetTargets();
|
||||||
|
sa.getTargets().add(opp);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// No targets found to donate, so do nothing.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean considerDonatingPermanent(Player ai, SpellAbility sa) {
|
||||||
|
Card donateTarget = ComputerUtil.getCardPreference(ai, sa.getHostCard(), "DonateMe", CardLists.filter(ai.getCardsIn(ZoneType.Battlefield).threadSafeIterable(), CardPredicates.hasSVar("DonateMe")));
|
||||||
|
if (donateTarget != null) {
|
||||||
|
sa.resetTargets();
|
||||||
|
sa.getTargets().add(donateTarget);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should never get here because targetOpponent, called before targetPermanentToDonate, should already have made the AI bail
|
||||||
|
System.err.println("Warning: Donate AI failed at SpecialCardAi.Donate#targetPermanentToDonate despite successfully targeting an opponent first.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Necropotence
|
||||||
|
public static class Necropotence {
|
||||||
|
public static boolean consider(Player ai, SpellAbility sa) {
|
||||||
|
Game game = ai.getGame();
|
||||||
|
int computerHandSize = ai.getZone(ZoneType.Hand).size();
|
||||||
|
int maxHandSize = ai.getMaxHandSize();
|
||||||
|
|
||||||
|
if (CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.nameEquals("Yawgmoth's Bargain")).size() > 0) {
|
||||||
|
// Prefer Yawgmoth's Bargain because AI is generally better with it
|
||||||
|
|
||||||
|
// TODO: in presence of bad effects which deal damage when a card is drawn, probably better to prefer Necropotence instead?
|
||||||
|
// (not sure how to detect the presence of such effects yet)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
PhaseHandler ph = game.getPhaseHandler();
|
||||||
|
|
||||||
|
int exiledWithNecro = 1; // start with 1 because if this succeeds, one extra card will be exiled with Necro
|
||||||
|
for (Card c : ai.getCardsIn(ZoneType.Exile)) {
|
||||||
|
if (c.getExiledWith() != null && "Necropotence".equals(c.getExiledWith().getName()) && c.isFaceDown()) {
|
||||||
|
exiledWithNecro++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Any other bad effects like that?
|
||||||
|
boolean blackViseOTB = CardLists.filter(game.getCardsIn(ZoneType.Battlefield), CardPredicates.nameEquals("Black Vise")).size() > 0;
|
||||||
|
|
||||||
|
if (!ph.isPlayerTurn(ai) && ph.is(PhaseType.MAIN2)
|
||||||
|
&& ai.getSpellsCastLastTurn() == 0
|
||||||
|
&& ai.getSpellsCastThisTurn() == 0
|
||||||
|
&& ai.getLandsPlayedLastTurn() == 0) {
|
||||||
|
// We're in a situation when we have nothing castable in hand, something needs to be done
|
||||||
|
if (!blackViseOTB) {
|
||||||
|
// exile-loot +1 card when a max hand size, hoping to get a workable spell or land
|
||||||
|
return computerHandSize + exiledWithNecro - 1 == maxHandSize;
|
||||||
|
} else {
|
||||||
|
// Loot to 7 in presence of Black Vise, hoping to find what to do
|
||||||
|
// NOTE: can still currently get theoretically locked with 7 uncastable spells. Loot to 8 instead?
|
||||||
|
return computerHandSize + exiledWithNecro <= maxHandSize;
|
||||||
|
}
|
||||||
|
} else if (blackViseOTB && computerHandSize + exiledWithNecro - 1 >= 4) {
|
||||||
|
// try not to overdraw in presence of Black Vise
|
||||||
|
return false;
|
||||||
|
} else if (computerHandSize + exiledWithNecro - 1 >= maxHandSize) {
|
||||||
|
// Only draw until we reach max hand size
|
||||||
|
return false;
|
||||||
|
} else if (!ph.isPlayerTurn(ai) || !ph.is(PhaseType.MAIN2)) {
|
||||||
|
// Only activate in AI's own turn (sans the exception above)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nykthos, Shrine to Nyx
|
||||||
|
public static class NykthosShrineToNyx {
|
||||||
|
public static boolean consider(Player ai, SpellAbility sa) {
|
||||||
|
Game game = ai.getGame();
|
||||||
|
PhaseHandler ph = game.getPhaseHandler();
|
||||||
|
if (!ph.isPlayerTurn(ai) || ph.getPhase().isBefore(PhaseType.MAIN2)) {
|
||||||
|
// TODO: currently limited to Main 2, somehow improve to let the AI use this SA at other time?
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String prominentColor = ComputerUtilCard.getMostProminentColor(ai.getCardsIn(ZoneType.Battlefield));
|
||||||
|
int devotion = CardFactoryUtil.xCount(sa.getHostCard(), "Count$Devotion." + prominentColor);
|
||||||
|
int activationCost = sa.getPayCosts().getTotalMana().getCMC() + (sa.getPayCosts().hasTapCost() ? 1 : 0);
|
||||||
|
|
||||||
|
// do not use this SA if devotion to most prominent color is less than its own activation cost + 1 (to actually get advantage)
|
||||||
|
if (devotion < activationCost + 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
final CardCollectionView cards = ai.getCardsIn(new ZoneType[] {ZoneType.Hand, ZoneType.Battlefield, ZoneType.Command});
|
||||||
|
List<SpellAbility> all = ComputerUtilAbility.getSpellAbilities(cards, ai);
|
||||||
|
|
||||||
|
int numManaSrcs = CardLists.filter(ComputerUtilMana.getAvailableMana(ai, true), CardPredicates.Presets.UNTAPPED).size();
|
||||||
|
|
||||||
|
for (final SpellAbility testSa : ComputerUtilAbility.getOriginalAndAltCostAbilities(all, ai)) {
|
||||||
|
ManaCost cost = testSa.getPayCosts().getTotalMana();
|
||||||
|
byte colorProfile = cost.getColorProfile();
|
||||||
|
|
||||||
|
if ((cost.getCMC() == 0)) {
|
||||||
|
// no mana cost, no need to activate this SA then (additional mana not needed)
|
||||||
|
continue;
|
||||||
|
} else if (colorProfile != 0 && (cost.getColorProfile() & MagicColor.fromName(prominentColor)) == 0) {
|
||||||
|
// does not feature prominent color, won't be able to pay for it with SA activated for this color
|
||||||
|
continue;
|
||||||
|
} else if ((testSa.getPayCosts().getTotalMana().getCMC() > devotion + numManaSrcs - activationCost)) {
|
||||||
|
// the cost may be too high even if we activate this SA
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (testSa.getHostCard().getName().equals(sa.getHostCard().getName())) {
|
||||||
|
// prevent infinitely recursing own ability when testing AI play decision
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
testSa.setActivatingPlayer(ai);
|
||||||
|
if (((PlayerControllerAi)ai.getController()).getAi().canPlaySa(testSa) == AiPlayDecision.WillPlay) {
|
||||||
|
// the AI is willing to play the spell
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false; // haven't found anything to play with the excess generated mana
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Yawgmoth's Bargain
|
||||||
|
public static class YawgmothsBargain {
|
||||||
|
public static boolean consider(Player ai, SpellAbility sa) {
|
||||||
|
Game game = ai.getGame();
|
||||||
|
PhaseHandler ph = game.getPhaseHandler();
|
||||||
|
|
||||||
|
int computerHandSize = ai.getZone(ZoneType.Hand).size();
|
||||||
|
int maxHandSize = ai.getMaxHandSize();
|
||||||
|
|
||||||
|
// TODO: Any other bad effects like that?
|
||||||
|
boolean blackViseOTB = CardLists.filter(game.getCardsIn(ZoneType.Battlefield), CardPredicates.nameEquals("Black Vise")).size() > 0;
|
||||||
|
|
||||||
|
// TODO: Consider effects like "whenever a player draws a card, he loses N life" (e.g. Nekusar, the Mindraiser),
|
||||||
|
// and effects that draw an additional card whenever a card is drawn.
|
||||||
|
|
||||||
|
if (!ph.isPlayerTurn(ai) && ph.is(PhaseType.END_OF_TURN)
|
||||||
|
&& ai.getSpellsCastLastTurn() == 0
|
||||||
|
&& ai.getSpellsCastThisTurn() == 0
|
||||||
|
&& ai.getLandsPlayedLastTurn() == 0) {
|
||||||
|
// We're in a situation when we have nothing castable in hand, something needs to be done
|
||||||
|
if (!blackViseOTB) {
|
||||||
|
// draw +1 card when a max hand size, hoping to draw a workable spell or land
|
||||||
|
return computerHandSize == maxHandSize;
|
||||||
|
} else {
|
||||||
|
// draw cards hoping to draw answers even in presence of Black Vise if there's no valid play
|
||||||
|
// TODO: maybe limit to 1 or 2 cards at a time?
|
||||||
|
return computerHandSize + 1 <= maxHandSize; // currently draws to 7 cards
|
||||||
|
}
|
||||||
|
} else if (blackViseOTB && computerHandSize + 1 > 4) {
|
||||||
|
// try not to overdraw in presence of Black Vise
|
||||||
|
return false;
|
||||||
|
} else if (computerHandSize + 1 > maxHandSize) {
|
||||||
|
// Only draw until we reach max hand size
|
||||||
|
return false;
|
||||||
|
} else if (!ph.isPlayerTurn(ai)) {
|
||||||
|
// Only activate in AI's own turn (sans the exception above)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -86,7 +86,7 @@ public abstract class SpellAbilityAi {
|
|||||||
* Evaluated costs are: life, discard, sacrifice and counter-removal
|
* Evaluated costs are: life, discard, sacrifice and counter-removal
|
||||||
*/
|
*/
|
||||||
protected boolean willPayCosts(final Player ai, final SpellAbility sa, final Cost cost, final Card source) {
|
protected boolean willPayCosts(final Player ai, final SpellAbility sa, final Cost cost, final Card source) {
|
||||||
if (!ComputerUtilCost.checkLifeCost(ai, cost, source, 4, null)) {
|
if (!ComputerUtilCost.checkLifeCost(ai, cost, source, 4, sa)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!ComputerUtilCost.checkDiscardCost(ai, cost, source)) {
|
if (!ComputerUtilCost.checkDiscardCost(ai, cost, source)) {
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source)) {
|
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, null)) {
|
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -137,6 +137,9 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (aiLogic.equals("Necropotence")) {
|
||||||
|
return SpecialCardAi.Necropotence.consider(aiPlayer, sa);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
String origin = null;
|
String origin = null;
|
||||||
if (sa.hasParam("Origin")) {
|
if (sa.hasParam("Origin")) {
|
||||||
@@ -245,7 +248,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, null)) {
|
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -654,7 +657,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, null)) {
|
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ public class ChangeZoneAllAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
if (abCost != null) {
|
if (abCost != null) {
|
||||||
// AI currently disabled for these costs
|
// AI currently disabled for these costs
|
||||||
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, null)) {
|
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import forge.ai.ComputerUtilAbility;
|
|||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.ai.ComputerUtilMana;
|
import forge.ai.ComputerUtilMana;
|
||||||
import forge.ai.PlayerControllerAi;
|
import forge.ai.PlayerControllerAi;
|
||||||
|
import forge.ai.SpecialCardAi;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.card.MagicColor;
|
import forge.card.MagicColor;
|
||||||
import forge.card.mana.ManaCost;
|
import forge.card.mana.ManaCost;
|
||||||
@@ -41,53 +42,7 @@ public class ChooseColorAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ("Nykthos, Shrine to Nyx".equals(source.getName())) {
|
if ("Nykthos, Shrine to Nyx".equals(source.getName())) {
|
||||||
PhaseHandler ph = game.getPhaseHandler();
|
return SpecialCardAi.NykthosShrineToNyx.consider(ai, sa);
|
||||||
if (!ph.isPlayerTurn(ai) || ph.getPhase().isBefore(PhaseType.MAIN2)) {
|
|
||||||
// TODO: currently limited to Main 2, somehow improve to let the AI use this SA at other time?
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
String prominentColor = ComputerUtilCard.getMostProminentColor(ai.getCardsIn(ZoneType.Battlefield));
|
|
||||||
int devotion = CardFactoryUtil.xCount(source, "Count$Devotion." + prominentColor);
|
|
||||||
int activationCost = sa.getPayCosts().getTotalMana().getCMC() + (sa.getPayCosts().hasTapCost() ? 1 : 0);
|
|
||||||
|
|
||||||
// do not use this SA if devotion to most prominent color is less than its own activation cost + 1 (to actually get advantage)
|
|
||||||
if (devotion < activationCost + 1) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
final CardCollectionView cards = ai.getCardsIn(new ZoneType[] {ZoneType.Hand, ZoneType.Battlefield, ZoneType.Command});
|
|
||||||
List<SpellAbility> all = ComputerUtilAbility.getSpellAbilities(cards, ai);
|
|
||||||
|
|
||||||
int numManaSrcs = CardLists.filter(ComputerUtilMana.getAvailableMana(ai, true), CardPredicates.Presets.UNTAPPED).size();
|
|
||||||
|
|
||||||
for (final SpellAbility testSa : ComputerUtilAbility.getOriginalAndAltCostAbilities(all, ai)) {
|
|
||||||
ManaCost cost = testSa.getPayCosts().getTotalMana();
|
|
||||||
byte colorProfile = cost.getColorProfile();
|
|
||||||
|
|
||||||
if ((cost.getCMC() == 0)) {
|
|
||||||
// no mana cost, no need to activate this SA then (additional mana not needed)
|
|
||||||
continue;
|
|
||||||
} else if (colorProfile != 0 && (cost.getColorProfile() & MagicColor.fromName(prominentColor)) == 0) {
|
|
||||||
// does not feature prominent color, won't be able to pay for it with SA activated for this color
|
|
||||||
continue;
|
|
||||||
} else if ((testSa.getPayCosts().getTotalMana().getCMC() > devotion + numManaSrcs - activationCost)) {
|
|
||||||
// the cost may be too high even if we activate this SA
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (testSa.getHostCard().getName().equals(source.getName())) {
|
|
||||||
// prevent infinitely recursing own ability when testing AI play decision
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
testSa.setActivatingPlayer(ai);
|
|
||||||
if (((PlayerControllerAi)ai.getController()).getAi().canPlaySa(testSa) == AiPlayDecision.WillPlay) {
|
|
||||||
// the AI is willing to play the spell
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false; // haven't found anything to play with the excess generated mana
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ("Oona, Queen of the Fae".equals(source.getName())) {
|
if ("Oona, Queen of the Fae".equals(source.getName())) {
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ public class ChooseSourceAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
if (abCost != null) {
|
if (abCost != null) {
|
||||||
// AI currently disabled for these costs
|
// AI currently disabled for these costs
|
||||||
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, null)) {
|
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ public class CounterAi extends SpellAbilityAi {
|
|||||||
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source)) {
|
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, null)) {
|
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ public class CountersPutAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
if (abCost != null) {
|
if (abCost != null) {
|
||||||
// AI currently disabled for these costs
|
// AI currently disabled for these costs
|
||||||
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, null)) {
|
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ public class CountersPutAllAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
if (abCost != null) {
|
if (abCost != null) {
|
||||||
// AI currently disabled for these costs
|
// AI currently disabled for these costs
|
||||||
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 8, null)) {
|
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 8, sa)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
if (abCost != null) {
|
if (abCost != null) {
|
||||||
// AI currently disabled for these costs
|
// AI currently disabled for these costs
|
||||||
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, null)) {
|
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ public class DamageAllAi extends SpellAbilityAi {
|
|||||||
final Cost abCost = sa.getPayCosts();
|
final Cost abCost = sa.getPayCosts();
|
||||||
if (abCost != null) {
|
if (abCost != null) {
|
||||||
// AI currently disabled for some costs
|
// AI currently disabled for some costs
|
||||||
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, null)) {
|
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// temporarily disabled until better AI
|
// temporarily disabled until better AI
|
||||||
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, null)) {
|
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ public class DamagePreventAi extends SpellAbilityAi {
|
|||||||
final Cost cost = sa.getPayCosts();
|
final Cost cost = sa.getPayCosts();
|
||||||
|
|
||||||
// temporarily disabled until better AI
|
// temporarily disabled until better AI
|
||||||
if (!ComputerUtilCost.checkLifeCost(ai, cost, hostCard, 4, null)) {
|
if (!ComputerUtilCost.checkLifeCost(ai, cost, hostCard, 4, sa)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ public class DamagePreventAllAi extends SpellAbilityAi {
|
|||||||
final Cost cost = sa.getPayCosts();
|
final Cost cost = sa.getPayCosts();
|
||||||
|
|
||||||
// temporarily disabled until better AI
|
// temporarily disabled until better AI
|
||||||
if (!ComputerUtilCost.checkLifeCost(ai, cost, hostCard, 4, null)) {
|
if (!ComputerUtilCost.checkLifeCost(ai, cost, hostCard, 4, sa)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ public class DebuffAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ComputerUtilCost.checkLifeCost(ai, cost, source, 40, null)) {
|
if (!ComputerUtilCost.checkLifeCost(ai, cost, source, 40, sa)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ public class DestroyAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, null)) {
|
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ public class DestroyAllAi extends SpellAbilityAi {
|
|||||||
if (abCost != null) {
|
if (abCost != null) {
|
||||||
// AI currently disabled for some costs
|
// AI currently disabled for some costs
|
||||||
|
|
||||||
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, null)) {
|
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ public class DiscardAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, null)) {
|
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import forge.game.cost.Cost;
|
|||||||
import forge.game.cost.CostDiscard;
|
import forge.game.cost.CostDiscard;
|
||||||
import forge.game.cost.CostPart;
|
import forge.game.cost.CostPart;
|
||||||
import forge.game.cost.PaymentDecision;
|
import forge.game.cost.PaymentDecision;
|
||||||
|
import forge.game.phase.PhaseHandler;
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.player.PlayerActionConfirmMode;
|
import forge.game.player.PlayerActionConfirmMode;
|
||||||
@@ -60,7 +61,7 @@ public class DrawAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, null)) {
|
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,7 +120,8 @@ public class DrawAi extends SpellAbilityAi {
|
|||||||
|| game.getPhaseHandler().getPhase().isBefore(PhaseType.END_OF_TURN))
|
|| game.getPhaseHandler().getPhase().isBefore(PhaseType.END_OF_TURN))
|
||||||
&& !sa.hasParam("PlayerTurn") && !SpellAbilityAi.isSorcerySpeed(sa)
|
&& !sa.hasParam("PlayerTurn") && !SpellAbilityAi.isSorcerySpeed(sa)
|
||||||
&& ai.getCardsIn(ZoneType.Hand).size() > 1
|
&& ai.getCardsIn(ZoneType.Hand).size() > 1
|
||||||
&& !ComputerUtil.activateForCost(sa, ai)) {
|
&& !ComputerUtil.activateForCost(sa, ai)
|
||||||
|
&& !"YawgmothsBargain".equals(sa.getParam("AILogic"))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -221,9 +223,18 @@ public class DrawAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
//if (n)
|
//if (n)
|
||||||
|
|
||||||
|
|
||||||
|
// Logic for cards that require special handling
|
||||||
|
if (sa.hasParam("AILogic")) {
|
||||||
|
if ("YawgmothsBargain".equals(sa.getParam("AILogic"))) {
|
||||||
|
return SpecialCardAi.YawgmothsBargain.consider(ai, sa);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generic logic for all cards that do not need any special handling
|
||||||
|
|
||||||
// TODO: if xPaid and one of the below reasons would fail, instead of
|
// TODO: if xPaid and one of the below reasons would fail, instead of
|
||||||
// bailing reduce toPay amount to acceptable level
|
// bailing reduce toPay amount to acceptable level
|
||||||
|
|
||||||
if (tgt != null) {
|
if (tgt != null) {
|
||||||
// ability is targeted
|
// ability is targeted
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ public class LifeGainAi extends SpellAbilityAi {
|
|||||||
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source, false)) {
|
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source, false)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, null)) {
|
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ public class LifeLoseAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
if (abCost != null) {
|
if (abCost != null) {
|
||||||
// AI currently disabled for these costs
|
// AI currently disabled for these costs
|
||||||
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, amount, null)) {
|
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, amount, sa)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ public class PoisonAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
if (abCost != null) {
|
if (abCost != null) {
|
||||||
// AI currently disabled for these costs
|
// AI currently disabled for these costs
|
||||||
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 1, null)) {
|
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 1, sa)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ public class ProtectAllAi extends SpellAbilityAi {
|
|||||||
final Cost cost = sa.getPayCosts();
|
final Cost cost = sa.getPayCosts();
|
||||||
|
|
||||||
// temporarily disabled until better AI
|
// temporarily disabled until better AI
|
||||||
if (!ComputerUtilCost.checkLifeCost(ai, cost, hostCard, 4, null)) {
|
if (!ComputerUtilCost.checkLifeCost(ai, cost, hostCard, 4, sa)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import forge.ai.ComputerUtilCard;
|
|||||||
import forge.ai.ComputerUtilCost;
|
import forge.ai.ComputerUtilCost;
|
||||||
import forge.ai.ComputerUtilMana;
|
import forge.ai.ComputerUtilMana;
|
||||||
import forge.ai.PlayerControllerAi;
|
import forge.ai.PlayerControllerAi;
|
||||||
|
import forge.ai.SpecialCardAi;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
@@ -277,38 +278,8 @@ public class PumpAi extends PumpAiBase {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} else if (sa.hasParam("AILogic") && sa.getParam("AILogic").startsWith("Donate")) {
|
} else if (sa.hasParam("AILogic") && sa.getParam("AILogic").startsWith("Donate")) {
|
||||||
final Card donateTarget = ComputerUtil.getCardPreference(ai, sa.getHostCard(), "DonateMe", CardLists.filter(
|
// Donate step 1 - try to target an opponent, preferably one who does not have a donate target yet
|
||||||
ai.getCardsIn(ZoneType.Battlefield).threadSafeIterable(), CardPredicates.hasSVar("DonateMe")));
|
return SpecialCardAi.Donate.considerTargetingOpponent(ai, sa);
|
||||||
if (donateTarget != null) {
|
|
||||||
// Donate, step 1 - target the opponent.
|
|
||||||
if (sa.getParam("AILogic").equals("DonateTargetPlayer")) {
|
|
||||||
// first filter for opponents which can be targeted by SA
|
|
||||||
final Iterable<Player> oppList = Iterables.filter(ai.getOpponents(),
|
|
||||||
PlayerPredicates.isTargetableBy(sa));
|
|
||||||
|
|
||||||
// filter for player with does not have donate target
|
|
||||||
// already
|
|
||||||
Iterable<Player> oppTarget = Iterables.filter(oppList,
|
|
||||||
PlayerPredicates.isNotCardInPlay(donateTarget.getName()));
|
|
||||||
// fall back to previous list
|
|
||||||
if (Iterables.isEmpty(oppTarget)) {
|
|
||||||
oppTarget = oppList;
|
|
||||||
}
|
|
||||||
|
|
||||||
// select player with less lands on the field
|
|
||||||
Player opp = Collections.min(Lists.newArrayList(oppTarget),
|
|
||||||
PlayerPredicates.compareByZoneSize(ZoneType.Battlefield, CardPredicates.Presets.LANDS));
|
|
||||||
|
|
||||||
if (opp != null) {
|
|
||||||
sa.resetTargets();
|
|
||||||
sa.getTargets().add(opp);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// No targets found to donate, so do nothing.
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||||
@@ -449,12 +420,8 @@ public class PumpAi extends PumpAiBase {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else if (sa.getParam("AILogic").equals("DonateTargetPerm")) {
|
} else if (sa.getParam("AILogic").equals("DonateTargetPerm")) {
|
||||||
// Donate, step 2 - target a donatable permanent.
|
// Donate step 2 - target a donatable permanent.
|
||||||
Card donateTarget = ComputerUtil.getCardPreference(ai, sa.getHostCard(), "DonateMe", CardLists.filter(ai.getCardsIn(ZoneType.Battlefield).threadSafeIterable(), CardPredicates.hasSVar("DonateMe")));
|
return SpecialCardAi.Donate.considerDonatingPermanent(ai, sa);
|
||||||
if (donateTarget != null) {
|
|
||||||
sa.getTargets().add(donateTarget);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (isFight) {
|
if (isFight) {
|
||||||
return FightAi.canFightAi(ai, sa, attack, defense);
|
return FightAi.canFightAi(ai, sa, attack, defense);
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ public class SacrificeAllAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
if (abCost != null) {
|
if (abCost != null) {
|
||||||
// AI currently disabled for some costs
|
// AI currently disabled for some costs
|
||||||
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, null)) {
|
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ Types:Enchantment
|
|||||||
S:Mode$ Continuous | Affected$ You | AddKeyword$ Skip your draw step. | Description$ Skip your draw step.
|
S:Mode$ Continuous | Affected$ You | AddKeyword$ Skip your draw step. | Description$ Skip your draw step.
|
||||||
T:Mode$ Discarded | ValidCard$ Card.YouCtrl | TriggerZones$ Battlefield | Execute$ TrigChange | TriggerDescription$ Whenever you discard a card, exile that card from your graveyard.
|
T:Mode$ Discarded | ValidCard$ Card.YouCtrl | TriggerZones$ Battlefield | Execute$ TrigChange | TriggerDescription$ Whenever you discard a card, exile that card from your graveyard.
|
||||||
SVar:TrigChange:AB$ChangeZone | Cost$ 0 | Defined$ TriggeredCard | Origin$ Graveyard | Destination$ Exile
|
SVar:TrigChange:AB$ChangeZone | Cost$ 0 | Defined$ TriggeredCard | Origin$ Graveyard | Destination$ Exile
|
||||||
A:AB$ ChangeZone | Cost$ PayLife<1> | Defined$ TopOfLibrary | Origin$ Library | Destination$ Exile | ExileFaceDown$ True | RememberChanged$ True | SubAbility$ DelayedReturn | SpellDescription$ Exile the top card of your library face down. Put that card into your hand at the beginning of your next end step.
|
A:AB$ ChangeZone | Cost$ PayLife<1> | Defined$ TopOfLibrary | Origin$ Library | Destination$ Exile | ExileFaceDown$ True | RememberChanged$ True | SubAbility$ DelayedReturn | AILogic$ Necropotence | AIMinLifeThreshold$ 1 | SpellDescription$ Exile the top card of your library face down. Put that card into your hand at the beginning of your next end step.
|
||||||
SVar:DelayedReturn:DB$ DelayedTrigger | Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You | Execute$ TrigReturn | RememberObjects$ Remembered | TriggerDescription$ Put the exiled card into your hand. | SubAbility$ DBCleanup
|
SVar:DelayedReturn:DB$ DelayedTrigger | Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You | Execute$ TrigReturn | RememberObjects$ Remembered | TriggerDescription$ Put the exiled card into your hand. | SubAbility$ DBCleanup
|
||||||
SVar:TrigReturn:DB$ ChangeZone | Origin$ Exile | Destination$ Hand | Defined$ DelayTriggerRemembered
|
SVar:TrigReturn:DB$ ChangeZone | Origin$ Exile | Destination$ Hand | Defined$ DelayTriggerRemembered
|
||||||
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
|
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
|
||||||
SVar:RemAIDeck:True
|
SVar:RemRandomDeck:True
|
||||||
SVar:Picture:http://www.wizards.com/global/images/magic/general/necropotence.jpg
|
SVar:Picture:http://www.wizards.com/global/images/magic/general/necropotence.jpg
|
||||||
Oracle:Skip your draw step.\nWhenever you discard a card, exile that card from your graveyard.\nPay 1 life: Exile the top card of your library face down. Put that card into your hand at the beginning of your next end step.
|
Oracle:Skip your draw step.\nWhenever you discard a card, exile that card from your graveyard.\nPay 1 life: Exile the top card of your library face down. Put that card into your hand at the beginning of your next end step.
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ Name:Yawgmoth's Bargain
|
|||||||
ManaCost:4 B B
|
ManaCost:4 B B
|
||||||
Types:Enchantment
|
Types:Enchantment
|
||||||
S:Mode$ Continuous | Affected$ You | AddKeyword$ Skip your draw step. | Description$ Skip your draw step.
|
S:Mode$ Continuous | Affected$ You | AddKeyword$ Skip your draw step. | Description$ Skip your draw step.
|
||||||
A:AB$ Draw | Cost$ PayLife<1> | NumCards$ 1 | SpellDescription$ Draw a card.
|
A:AB$ Draw | Cost$ PayLife<1> | NumCards$ 1 | AILogic$ YawgmothsBargain | AIMinLifeThreshold$ 1 | SpellDescription$ Draw a card.
|
||||||
SVar:RemAIDeck:True
|
SVar:RemRandomDeck:True
|
||||||
SVar:Picture:http://www.wizards.com/global/images/magic/general/yawgmoths_bargain.jpg
|
SVar:Picture:http://www.wizards.com/global/images/magic/general/yawgmoths_bargain.jpg
|
||||||
Oracle:Skip your draw step.\nPay 1 life: Draw a card.
|
Oracle:Skip your draw step.\nPay 1 life: Draw a card.
|
||||||
|
|||||||
Reference in New Issue
Block a user