mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-20 04:38:00 +00:00
AI framework to improve sacrificing endangered cards + several AI hints (Stoneforge Mystic, Atog, others) and improvements. (#4014)
* - Implement a fallback mechanism in case getting a card by name and edition fails for whatever reason. * - Patch up pulling a card without filters in All Card Variants mode. * - Sacrifice creatures when they're endangered (currently works for AF LifeGain, LifeLose, and any AFs that do not have phase-based AI restrictions or other factors that will prevent instant speed activation) * - Tweaks to the requirements for the AI. - Some AI enablement. * - Account for non-creature endangered objects * - Flag Wall of Limbs as RemAIDeck for now. * - Support for AF PutCounter. * - Clean up. * - Logic fix for AF PutCounter. * - Clean up. * - Logic tweak/fix for AF Pump. * - Another slight tweak. * - Some AI hint fixes/additions. * - Some AI hint fixes/additions. * - Improve timing for AF LifeGain/LifeLose. * - AI profile option for default SacCost AI preference. * - Default Sacrifice AI preference master toggle. * - Stoneforge Mystic AI hint. * - For now, keep the default pref SacCost toggle to the Experimental AI and at minimum values (too extreme for general use). * - AI hint: Cryptbreaker * - Don't auto-sac creatures that evaluate above a given threshold, sac smaller CMC first * - Lower the priority of cards that have a self-sacrifice activated ability * - Revert the evaluation modification until a better solution is found. * - AI hint for Hallowed Moonlight. * - AI hint for Winds of Abandon (AI casts the non-overloaded version in Main 1, so cast the other one in Main 1 as well to be able to prioritize/choose) * - AI logic for The One Ring. * - Some logic tweaks/fixes. * - Winds of Abandon: use the AI logic hint like other similar non-permanent spells. * - Fix logic for default sacrifice priorities. - Mark P9 Mox, Black Lotus, and Lotus Petal cards as bad for AI sacrifice. * - More logic fixes. * - One more logic fix. * - Revert the AIDontSacrifice hint for now. * - Revert Tinker as well * - Limit LifeLoseAi sac logic to threatened cards. * - Logic tweak. * - Logic tweak. * - Simplify check (part already checked above). * - Some more minor cleanup. * - AI shouldn't sacrifice things mid-combat in presence of Trample or Banding because of altered combat rules (likely to backfire/result in a misplay) - Minor cleanup. * - A [hacky] way to make the AI understand Anticognition and Bring the Ending. * - Fix imports. * - Avoid a crash by ensuring that the AI parameter indeed points to an AI player (and not e.g. predicting/simulating human decisions at the moment) * - Do not try to sacrifice a card in an attempt to regenerate it * - Clean up for AiController mustRespond * - Suppress recursive checkSacrificeCost when called from the predictive code. - Trample only matters for the attacking side when checking for threatened card SacCost requirements * - Naming convention. * - NPE guard. * - Recommended tweaks and fixes. * - Don't override X payment for a triggered ability (e.g. Spiteful Banditry) * - A better attempt at handling X inside trigger code. * - Process AI logic for EffectAi from triggered abilities. * - Improve Black Lotus AI by handling it as if it were a Mana Ritual card when processing ManaEffectAi. * - AI property guarded check + meaningful default for potential non-AI calls
This commit is contained in:
@@ -1537,7 +1537,14 @@ public class AiController {
|
|||||||
top = game.getStack().peekAbility();
|
top = game.getStack().peekAbility();
|
||||||
}
|
}
|
||||||
final boolean topOwnedByAI = top != null && top.getActivatingPlayer().equals(player);
|
final boolean topOwnedByAI = top != null && top.getActivatingPlayer().equals(player);
|
||||||
final boolean mustRespond = top != null && top.hasParam("AIRespondsToOwnAbility");
|
|
||||||
|
// Must respond: cases where the AI should respond to its own triggers or other abilities (need to add negative stuff to be countered here)
|
||||||
|
boolean mustRespond = false;
|
||||||
|
if (top != null) {
|
||||||
|
mustRespond = top.hasParam("AIRespondsToOwnAbility"); // Forced combos (currently defined for Sensei's Divining Top)
|
||||||
|
mustRespond |= top.isTrigger() && top.getTrigger().getKeyword() != null
|
||||||
|
&& top.getTrigger().getKeyword().getKeyword() == Keyword.EVOKE; // Evoke sacrifice trigger
|
||||||
|
}
|
||||||
|
|
||||||
if (topOwnedByAI) {
|
if (topOwnedByAI) {
|
||||||
// AI's own spell: should probably let my stuff resolve first, but may want to copy the SA or respond to it
|
// AI's own spell: should probably let my stuff resolve first, but may want to copy the SA or respond to it
|
||||||
|
|||||||
@@ -134,7 +134,12 @@ public enum AiProps { /** */
|
|||||||
FLASH_BUFF_AURA_CHANCE_TO_RESPOND_TO_STACK("100"),
|
FLASH_BUFF_AURA_CHANCE_TO_RESPOND_TO_STACK("100"),
|
||||||
BLINK_RELOAD_PLANESWALKER_CHANCE("30"), /** */
|
BLINK_RELOAD_PLANESWALKER_CHANCE("30"), /** */
|
||||||
BLINK_RELOAD_PLANESWALKER_MAX_LOYALTY("2"), /** */
|
BLINK_RELOAD_PLANESWALKER_MAX_LOYALTY("2"), /** */
|
||||||
BLINK_RELOAD_PLANESWALKER_LOYALTY_DIFF("2"); /** */
|
BLINK_RELOAD_PLANESWALKER_LOYALTY_DIFF("2"),
|
||||||
|
SACRIFICE_DEFAULT_PREF_ENABLE("true"),
|
||||||
|
SACRIFICE_DEFAULT_PREF_MIN_CMC("0"),
|
||||||
|
SACRIFICE_DEFAULT_PREF_MAX_CMC("2"),
|
||||||
|
SACRIFICE_DEFAULT_PREF_ALLOW_TOKENS("true"),
|
||||||
|
SACRIFICE_DEFAULT_PREF_MAX_CREATURE_EVAL("135");
|
||||||
// Experimental features, must be promoted or removed after extensive testing and, ideally, defaulting
|
// Experimental features, must be promoted or removed after extensive testing and, ideally, defaulting
|
||||||
// <-- There are no experimental options here -->
|
// <-- There are no experimental options here -->
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import forge.game.cost.*;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
@@ -65,13 +66,6 @@ import forge.game.card.CounterEnumType;
|
|||||||
import forge.game.card.CounterType;
|
import forge.game.card.CounterType;
|
||||||
import forge.game.combat.Combat;
|
import forge.game.combat.Combat;
|
||||||
import forge.game.combat.CombatUtil;
|
import forge.game.combat.CombatUtil;
|
||||||
import forge.game.cost.Cost;
|
|
||||||
import forge.game.cost.CostDiscard;
|
|
||||||
import forge.game.cost.CostExile;
|
|
||||||
import forge.game.cost.CostPart;
|
|
||||||
import forge.game.cost.CostPayment;
|
|
||||||
import forge.game.cost.CostPutCounter;
|
|
||||||
import forge.game.cost.CostSacrifice;
|
|
||||||
import forge.game.keyword.Keyword;
|
import forge.game.keyword.Keyword;
|
||||||
import forge.game.phase.PhaseHandler;
|
import forge.game.phase.PhaseHandler;
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
@@ -433,12 +427,41 @@ public class ComputerUtil {
|
|||||||
final CardCollection sacMeList = CardLists.filter(typeList, new Predicate<Card>() {
|
final CardCollection sacMeList = CardLists.filter(typeList, new Predicate<Card>() {
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(final Card c) {
|
public boolean apply(final Card c) {
|
||||||
return c.hasSVar("SacMe") && Integer.parseInt(c.getSVar("SacMe")) == priority;
|
return (c.hasSVar("SacMe") && Integer.parseInt(c.getSVar("SacMe")) == priority)
|
||||||
|
|| (priority == 1 && shouldSacrificeThreatenedCard(ai, c, sa));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (!sacMeList.isEmpty()) {
|
if (!sacMeList.isEmpty()) {
|
||||||
CardLists.shuffle(sacMeList);
|
CardLists.shuffle(sacMeList);
|
||||||
return sacMeList.get(0);
|
return sacMeList.getFirst();
|
||||||
|
} else {
|
||||||
|
// empty sacMeList, so get some viable average preference if the option is enabled
|
||||||
|
if (ai.getController().isAI()) {
|
||||||
|
AiController aic = ((PlayerControllerAi) ai.getController()).getAi();
|
||||||
|
boolean enableDefaultPref = aic.getBooleanProperty(AiProps.SACRIFICE_DEFAULT_PREF_ENABLE);
|
||||||
|
if (enableDefaultPref) {
|
||||||
|
int minCMC = aic.getIntProperty(AiProps.SACRIFICE_DEFAULT_PREF_MIN_CMC);
|
||||||
|
int maxCMC = aic.getIntProperty(AiProps.SACRIFICE_DEFAULT_PREF_MAX_CMC);
|
||||||
|
int maxCreatureEval = aic.getIntProperty(AiProps.SACRIFICE_DEFAULT_PREF_MAX_CREATURE_EVAL);
|
||||||
|
boolean allowTokens = aic.getBooleanProperty(AiProps.SACRIFICE_DEFAULT_PREF_ALLOW_TOKENS);
|
||||||
|
List<String> dontSac = Arrays.asList("Black Lotus", "Mox Pearl", "Mox Jet", "Mox Emerald", "Mox Ruby", "Mox Sapphire", "Lotus Petal");
|
||||||
|
CardCollection allowList = CardLists.filter(typeList, new Predicate<Card>() {
|
||||||
|
@Override
|
||||||
|
public boolean apply(Card card) {
|
||||||
|
if (card.isCreature() && ComputerUtilCard.evaluateCreature(card) > maxCreatureEval) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (allowTokens && card.isToken())
|
||||||
|
|| (card.getCMC() >= minCMC && card.getCMC() <= maxCMC && !dontSac.contains(card.getName()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!allowList.isEmpty()) {
|
||||||
|
CardLists.sortByCmcDesc(allowList);
|
||||||
|
return allowList.getLast();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1435,6 +1458,8 @@ public class ComputerUtil {
|
|||||||
if (type.equals("CARDNAME")) {
|
if (type.equals("CARDNAME")) {
|
||||||
if (source.getSVar("SacMe").equals("6")) {
|
if (source.getSVar("SacMe").equals("6")) {
|
||||||
return true;
|
return true;
|
||||||
|
} else if (shouldSacrificeThreatenedCard(ai, source, sa)) {
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -1444,6 +1469,8 @@ public class ComputerUtil {
|
|||||||
for (Card c : typeList) {
|
for (Card c : typeList) {
|
||||||
if (c.getSVar("SacMe").equals("6")) {
|
if (c.getSVar("SacMe").equals("6")) {
|
||||||
return true;
|
return true;
|
||||||
|
} else if (shouldSacrificeThreatenedCard(ai, c, sa)) {
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1810,6 +1837,13 @@ public class ComputerUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (saviourApi == ApiType.Pump || saviourApi == ApiType.PumpAll) {
|
if (saviourApi == ApiType.Pump || saviourApi == ApiType.PumpAll) {
|
||||||
|
if (saviour.usesTargeting() && !saviour.canTarget(c)) {
|
||||||
|
continue;
|
||||||
|
} else if (saviour.getPayCosts() != null && saviour.getPayCosts().hasSpecificCostType(CostSacrifice.class)
|
||||||
|
&& (!ComputerUtilCost.isSacrificeSelfCost(saviour.getPayCosts())) || c == source) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
boolean canSave = ComputerUtilCombat.predictDamageTo(c, dmg - toughness, source, false) < ComputerUtilCombat.getDamageToKill(c, false);
|
boolean canSave = ComputerUtilCombat.predictDamageTo(c, dmg - toughness, source, false) < ComputerUtilCombat.getDamageToKill(c, false);
|
||||||
if ((!topStack.usesTargeting() && !grantIndestructible && !canSave)
|
if ((!topStack.usesTargeting() && !grantIndestructible && !canSave)
|
||||||
|| (!grantIndestructible && !grantShroud && !canSave)) {
|
|| (!grantIndestructible && !grantShroud && !canSave)) {
|
||||||
@@ -1818,6 +1852,13 @@ public class ComputerUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (saviourApi == ApiType.PutCounter || saviourApi == ApiType.PutCounterAll) {
|
if (saviourApi == ApiType.PutCounter || saviourApi == ApiType.PutCounterAll) {
|
||||||
|
if (saviour.usesTargeting() && !saviour.canTarget(c)) {
|
||||||
|
continue;
|
||||||
|
} else if (saviour.getPayCosts() != null && saviour.getPayCosts().hasSpecificCostType(CostSacrifice.class)
|
||||||
|
&& (!ComputerUtilCost.isSacrificeSelfCost(saviour.getPayCosts())) || c == source) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
boolean canSave = ComputerUtilCombat.predictDamageTo(c, dmg - toughness, source, false) < ComputerUtilCombat.getDamageToKill(c, false);
|
boolean canSave = ComputerUtilCombat.predictDamageTo(c, dmg - toughness, source, false) < ComputerUtilCombat.getDamageToKill(c, false);
|
||||||
if (!canSave) {
|
if (!canSave) {
|
||||||
continue;
|
continue;
|
||||||
@@ -2038,25 +2079,35 @@ public class ComputerUtil {
|
|||||||
* @return true if the creature dies according to current board position.
|
* @return true if the creature dies according to current board position.
|
||||||
*/
|
*/
|
||||||
public static boolean predictCreatureWillDieThisTurn(final Player ai, final Card creature, final SpellAbility excludeSa) {
|
public static boolean predictCreatureWillDieThisTurn(final Player ai, final Card creature, final SpellAbility excludeSa) {
|
||||||
|
return predictCreatureWillDieThisTurn(ai, creature, excludeSa, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean predictCreatureWillDieThisTurn(final Player ai, final Card creature, final SpellAbility excludeSa, final boolean nonCombatOnly) {
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
|
|
||||||
// a creature will [hopefully] die from a spell on stack
|
// a creature will [hopefully] die from a spell on stack
|
||||||
boolean willDieFromSpell = false;
|
boolean willDieFromSpell = false;
|
||||||
boolean noStackCheck = false;
|
boolean noStackCheck = false;
|
||||||
AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
|
if (ai.getController().isAI()) {
|
||||||
if (aic.getBooleanProperty(AiProps.DONT_EVAL_KILLSPELLS_ON_STACK_WITH_PERMISSION)) {
|
AiController aic = ((PlayerControllerAi) ai.getController()).getAi();
|
||||||
// See if permission is on stack and ignore this check if there is and the relevant AI flag is set
|
if (aic.getBooleanProperty(AiProps.DONT_EVAL_KILLSPELLS_ON_STACK_WITH_PERMISSION)) {
|
||||||
// TODO: improve this so that this flag is not needed and the AI can properly evaluate spells in presence of counterspells.
|
// See if permission is on stack and ignore this check if there is and the relevant AI flag is set
|
||||||
for (SpellAbilityStackInstance si : game.getStack()) {
|
// TODO: improve this so that this flag is not needed and the AI can properly evaluate spells in presence of counterspells.
|
||||||
SpellAbility sa = si.getSpellAbility();
|
for (SpellAbilityStackInstance si : game.getStack()) {
|
||||||
if (sa.getApi() == ApiType.Counter) {
|
SpellAbility sa = si.getSpellAbility();
|
||||||
noStackCheck = true;
|
if (sa.getApi() == ApiType.Counter) {
|
||||||
break;
|
noStackCheck = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
willDieFromSpell = !noStackCheck && predictThreatenedObjects(creature.getController(), excludeSa).contains(creature);
|
willDieFromSpell = !noStackCheck && predictThreatenedObjects(creature.getController(), excludeSa).contains(creature);
|
||||||
|
|
||||||
|
if (nonCombatOnly) {
|
||||||
|
return willDieFromSpell;
|
||||||
|
}
|
||||||
|
|
||||||
// a creature will die as a result of combat
|
// a creature will die as a result of combat
|
||||||
boolean willDieInCombat = !willDieFromSpell && game.getPhaseHandler().inCombat()
|
boolean willDieInCombat = !willDieFromSpell && game.getPhaseHandler().inCombat()
|
||||||
&& ComputerUtilCombat.combatantWouldBeDestroyed(creature.getController(), creature, game.getCombat());
|
&& ComputerUtilCombat.combatantWouldBeDestroyed(creature.getController(), creature, game.getCombat());
|
||||||
@@ -3257,5 +3308,20 @@ public class ComputerUtil {
|
|||||||
List<ReplacementEffect> list = c.getGame().getReplacementHandler().getReplacementList(ReplacementType.Moved, repParams, ReplacementLayer.CantHappen);
|
List<ReplacementEffect> list = c.getGame().getReplacementHandler().getReplacementList(ReplacementType.Moved, repParams, ReplacementLayer.CantHappen);
|
||||||
return !list.isEmpty();
|
return !list.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean shouldSacrificeThreatenedCard(Player ai, Card c, SpellAbility sa) {
|
||||||
|
if (!ai.getController().isAI()) {
|
||||||
|
return false; // only makes sense for actual AI decisions
|
||||||
|
} else if (sa != null && sa.getApi() == ApiType.Regenerate && sa.getHostCard().equals(c)) {
|
||||||
|
return false; // no use in sacrificing a card in an attempt to regenerate it
|
||||||
|
}
|
||||||
|
ComputerUtilCost.setSuppressRecursiveSacCostCheck(true);
|
||||||
|
Game game = ai.getGame();
|
||||||
|
Combat combat = game.getCombat();
|
||||||
|
boolean isThreatened = (c.isCreature() && ComputerUtil.predictCreatureWillDieThisTurn(ai, c, sa, false)
|
||||||
|
&& (!ComputerUtilCombat.willOpposingCreatureDieInCombat(ai, c, combat) && !ComputerUtilCombat.isDangerousToSacInCombat(ai, c, combat)))
|
||||||
|
|| (!c.isCreature() && ComputerUtil.predictThreatenedObjects(ai, sa).contains(c));
|
||||||
|
ComputerUtilCost.setSuppressRecursiveSacCostCheck(false);
|
||||||
|
return isThreatened;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2583,4 +2583,43 @@ public class ComputerUtilCombat {
|
|||||||
}
|
}
|
||||||
return totalLifeLinkDamage;
|
return totalLifeLinkDamage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean willOpposingCreatureDieInCombat(final Player ai, final Card combatant, final Combat combat) {
|
||||||
|
if (combat != null) {
|
||||||
|
if (combat.isBlocking(combatant)) {
|
||||||
|
for (Card atk : combat.getAttackersBlockedBy(combatant)) {
|
||||||
|
if (ComputerUtilCombat.combatantWouldBeDestroyed(ai, atk, combat)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (combat.isBlocked(combatant)) {
|
||||||
|
for (Card blk : combat.getBlockers(combatant)) {
|
||||||
|
if (ComputerUtilCombat.combatantWouldBeDestroyed(ai, blk, combat)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isDangerousToSacInCombat(final Player ai, final Card combatant, final Combat combat) {
|
||||||
|
if (combat != null) {
|
||||||
|
if (combat.isBlocking(combatant)) {
|
||||||
|
if (combatant.hasKeyword(Keyword.BANDING)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
for (Card atk : combat.getAttackersBlockedBy(combatant)) {
|
||||||
|
if (atk.hasKeyword(Keyword.TRAMPLE)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (combat.isBlocked(combatant)) {
|
||||||
|
if (combatant.hasKeyword(Keyword.BANDING)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,10 @@ import java.util.Set;
|
|||||||
|
|
||||||
|
|
||||||
public class ComputerUtilCost {
|
public class ComputerUtilCost {
|
||||||
|
private static boolean suppressRecursiveSacCostCheck = false;
|
||||||
|
public static void setSuppressRecursiveSacCostCheck(boolean shouldSuppress) {
|
||||||
|
suppressRecursiveSacCostCheck = shouldSuppress;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check add m1 m1 counter cost.
|
* Check add m1 m1 counter cost.
|
||||||
@@ -344,6 +348,10 @@ public class ComputerUtilCost {
|
|||||||
}
|
}
|
||||||
for (final CostPart part : cost.getCostParts()) {
|
for (final CostPart part : cost.getCostParts()) {
|
||||||
if (part instanceof CostSacrifice) {
|
if (part instanceof CostSacrifice) {
|
||||||
|
if (suppressRecursiveSacCostCheck) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
final CostSacrifice sac = (CostSacrifice) part;
|
final CostSacrifice sac = (CostSacrifice) part;
|
||||||
final int amount = AbilityUtils.calculateAmount(source, sac.getAmount(), sourceAbility);
|
final int amount = AbilityUtils.calculateAmount(source, sac.getAmount(), sourceAbility);
|
||||||
|
|
||||||
@@ -358,11 +366,13 @@ public class ComputerUtilCost {
|
|||||||
}
|
}
|
||||||
if (source.isCreature()) {
|
if (source.isCreature()) {
|
||||||
// e.g. Sakura Tribe-Elder
|
// e.g. Sakura Tribe-Elder
|
||||||
|
final Combat combat = ai.getGame().getCombat();
|
||||||
final boolean beforeNextTurn = ai.getGame().getPhaseHandler().is(PhaseType.END_OF_TURN) && ai.getGame().getPhaseHandler().getNextTurn().equals(ai);
|
final boolean beforeNextTurn = ai.getGame().getPhaseHandler().is(PhaseType.END_OF_TURN) && ai.getGame().getPhaseHandler().getNextTurn().equals(ai);
|
||||||
final boolean creatureInDanger = ComputerUtil.predictCreatureWillDieThisTurn(ai, source, sourceAbility);
|
final boolean creatureInDanger = ComputerUtil.predictCreatureWillDieThisTurn(ai, source, sourceAbility, false)
|
||||||
final int lifeThreshold = (((PlayerControllerAi) ai.getController()).getAi().getIntProperty(AiProps.AI_IN_DANGER_THRESHOLD));
|
&& !ComputerUtilCombat.willOpposingCreatureDieInCombat(ai, source, combat);
|
||||||
|
final int lifeThreshold = ai.getController().isAI() ? (((PlayerControllerAi) ai.getController()).getAi().getIntProperty(AiProps.AI_IN_DANGER_THRESHOLD)) : 4;
|
||||||
final boolean aiInDanger = ai.getLife() <= lifeThreshold && ai.canLoseLife() && !ai.cantLoseForZeroOrLessLife();
|
final boolean aiInDanger = ai.getLife() <= lifeThreshold && ai.canLoseLife() && !ai.cantLoseForZeroOrLessLife();
|
||||||
if (creatureInDanger) {
|
if (creatureInDanger && !ComputerUtilCombat.isDangerousToSacInCombat(ai, source, combat)) {
|
||||||
return true;
|
return true;
|
||||||
} else if (aiInDanger || !beforeNextTurn) {
|
} else if (aiInDanger || !beforeNextTurn) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -1610,6 +1610,22 @@ public class SpecialCardAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The One Ring
|
||||||
|
public static class TheOneRing {
|
||||||
|
public static boolean consider(final Player ai, final SpellAbility sa) {
|
||||||
|
if (!ai.canLoseLife() || ai.cantLoseForZeroOrLessLife()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
AiController aic = ((PlayerControllerAi) ai.getController()).getAi();
|
||||||
|
int lifeInDanger = aic.getIntProperty(AiProps.AI_IN_DANGER_THRESHOLD);
|
||||||
|
int numCtrs = sa.getHostCard().getCounters(CounterEnumType.BURDEN);
|
||||||
|
|
||||||
|
return ai.getLife() > numCtrs + 1 && ai.getLife() > lifeInDanger
|
||||||
|
&& ai.getMaxHandSize() >= ai.getCardsIn(ZoneType.Hand).size() + numCtrs + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// The Scarab God
|
// The Scarab God
|
||||||
public static class TheScarabGod {
|
public static class TheScarabGod {
|
||||||
public static boolean consider(final Player ai, final SpellAbility sa) {
|
public static boolean consider(final Player ai, final SpellAbility sa) {
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.*;
|
||||||
import forge.ai.SpecialCardAi;
|
|
||||||
import forge.ai.SpellAbilityAi;
|
|
||||||
import forge.game.GameEntity;
|
import forge.game.GameEntity;
|
||||||
|
import forge.game.ability.AbilityUtils;
|
||||||
|
import forge.game.ability.ApiType;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CardCollection;
|
import forge.game.card.CardCollection;
|
||||||
import forge.game.card.CardLists;
|
import forge.game.card.CardLists;
|
||||||
@@ -15,6 +13,9 @@ import forge.game.combat.Combat;
|
|||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.player.PlayerActionConfirmMode;
|
import forge.game.player.PlayerActionConfirmMode;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
|
import forge.util.Expressions;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
public class BranchAi extends SpellAbilityAi {
|
public class BranchAi extends SpellAbilityAi {
|
||||||
/* (non-Javadoc)
|
/* (non-Javadoc)
|
||||||
@@ -25,6 +26,37 @@ public class BranchAi extends SpellAbilityAi {
|
|||||||
final String aiLogic = sa.getParamOrDefault("AILogic", "");
|
final String aiLogic = sa.getParamOrDefault("AILogic", "");
|
||||||
if ("GrislySigil".equals(aiLogic)) {
|
if ("GrislySigil".equals(aiLogic)) {
|
||||||
return SpecialCardAi.GrislySigil.consider(aiPlayer, sa);
|
return SpecialCardAi.GrislySigil.consider(aiPlayer, sa);
|
||||||
|
} else if ("BranchCounter".equals(aiLogic)) {
|
||||||
|
// TODO: this might need expanding/tweaking if more cards are added with different SA setups
|
||||||
|
SpellAbility top = ComputerUtilAbility.getTopSpellAbilityOnStack(aiPlayer.getGame(), sa);
|
||||||
|
if (top == null || !sa.canTarget(top)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Card host = sa.getHostCard();
|
||||||
|
|
||||||
|
// pre-target the object to calculate the branch condition SVar, then clean up before running the real check
|
||||||
|
sa.getTargets().add(top);
|
||||||
|
int value = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("BranchConditionSVar"), sa);
|
||||||
|
sa.resetTargets();
|
||||||
|
|
||||||
|
String branchCompare = sa.getParamOrDefault("BranchConditionSVarCompare", "GE1");
|
||||||
|
String operator = branchCompare.substring(0, 2);
|
||||||
|
String operand = branchCompare.substring(2);
|
||||||
|
final int operandValue = AbilityUtils.calculateAmount(host, operand, sa);
|
||||||
|
boolean conditionMet = Expressions.compare(value, operator, operandValue);
|
||||||
|
|
||||||
|
SpellAbility falseSub = sa.getAdditionalAbility("FalseSubAbility"); // this ability has the UnlessCost part
|
||||||
|
boolean willPlay = false;
|
||||||
|
if (!conditionMet && falseSub.hasParam("UnlessCost")) {
|
||||||
|
// FIXME: We're emulating the UnlessCost on the SA to run the proper checks.
|
||||||
|
// This is hacky, but it works. Perhaps a cleaner way exists?
|
||||||
|
sa.getMapParams().put("UnlessCost", falseSub.getParam("UnlessCost"));
|
||||||
|
willPlay = SpellApiToAi.Converter.get(ApiType.Counter).canPlayAIWithSubs(aiPlayer, sa);
|
||||||
|
sa.getMapParams().remove("UnlessCost");
|
||||||
|
} else {
|
||||||
|
willPlay = SpellApiToAi.Converter.get(ApiType.Counter).canPlayAIWithSubs(aiPlayer, sa);
|
||||||
|
}
|
||||||
|
return willPlay;
|
||||||
} else if ("TgtAttacker".equals(aiLogic)) {
|
} else if ("TgtAttacker".equals(aiLogic)) {
|
||||||
final Combat combat = aiPlayer.getGame().getCombat();
|
final Combat combat = aiPlayer.getGame().getCombat();
|
||||||
if (combat == null || combat.getAttackingPlayer() != aiPlayer) {
|
if (combat == null || combat.getAttackingPlayer() != aiPlayer) {
|
||||||
|
|||||||
@@ -768,6 +768,8 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
if (aiLogic.equals("SurvivalOfTheFittest") || aiLogic.equals("AtOppEOT")) {
|
if (aiLogic.equals("SurvivalOfTheFittest") || aiLogic.equals("AtOppEOT")) {
|
||||||
return ph.getNextTurn().equals(ai) && ph.is(PhaseType.END_OF_TURN);
|
return ph.getNextTurn().equals(ai) && ph.is(PhaseType.END_OF_TURN);
|
||||||
|
} else if (aiLogic.equals("Main1") && ph.is(PhaseType.MAIN1, ai)) {
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.isHidden()) {
|
if (sa.isHidden()) {
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ public class ChangeZoneAllAi extends SpellAbilityAi {
|
|||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
final ZoneType destination = ZoneType.smartValueOf(sa.getParam("Destination"));
|
final ZoneType destination = ZoneType.smartValueOf(sa.getParam("Destination"));
|
||||||
final ZoneType origin = ZoneType.listValueOf(sa.getParam("Origin")).get(0);
|
final ZoneType origin = ZoneType.listValueOf(sa.getParam("Origin")).get(0);
|
||||||
|
final String aiLogic = sa.getParamOrDefault("AILogic" ,"");
|
||||||
|
|
||||||
if (abCost != null) {
|
if (abCost != null) {
|
||||||
// AI currently disabled for these costs
|
// AI currently disabled for these costs
|
||||||
@@ -52,7 +53,7 @@ public class ChangeZoneAllAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source, sa)) {
|
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source, sa)) {
|
||||||
boolean aiLogicAllowsDiscard = sa.hasParam("AILogic") && sa.getParam("AILogic").startsWith("DiscardAll");
|
boolean aiLogicAllowsDiscard = aiLogic.startsWith("DiscardAll");
|
||||||
|
|
||||||
if (!aiLogicAllowsDiscard) {
|
if (!aiLogicAllowsDiscard) {
|
||||||
return false;
|
return false;
|
||||||
@@ -86,16 +87,16 @@ public class ChangeZoneAllAi extends SpellAbilityAi {
|
|||||||
oppType = AbilityUtils.filterListByType(oppType, sa.getParam("ChangeType"), sa);
|
oppType = AbilityUtils.filterListByType(oppType, sa.getParam("ChangeType"), sa);
|
||||||
computerType = AbilityUtils.filterListByType(computerType, sa.getParam("ChangeType"), sa);
|
computerType = AbilityUtils.filterListByType(computerType, sa.getParam("ChangeType"), sa);
|
||||||
|
|
||||||
if ("LivingDeath".equals(sa.getParam("AILogic"))) {
|
if ("LivingDeath".equals(aiLogic)) {
|
||||||
// Living Death AI
|
// Living Death AI
|
||||||
return SpecialCardAi.LivingDeath.consider(ai, sa);
|
return SpecialCardAi.LivingDeath.consider(ai, sa);
|
||||||
} else if ("Timetwister".equals(sa.getParam("AILogic"))) {
|
} else if ("Timetwister".equals(aiLogic)) {
|
||||||
// Timetwister AI
|
// Timetwister AI
|
||||||
return SpecialCardAi.Timetwister.consider(ai, sa);
|
return SpecialCardAi.Timetwister.consider(ai, sa);
|
||||||
} else if ("RetDiscardedThisTurn".equals(sa.getParam("AILogic"))) {
|
} else if ("RetDiscardedThisTurn".equals(aiLogic)) {
|
||||||
// e.g. Shadow of the Grave
|
// e.g. Shadow of the Grave
|
||||||
return ai.getNumDiscardedThisTurn() > 0 && ai.getGame().getPhaseHandler().is(PhaseType.END_OF_TURN);
|
return ai.getNumDiscardedThisTurn() > 0 && ai.getGame().getPhaseHandler().is(PhaseType.END_OF_TURN);
|
||||||
} else if ("ExileGraveyards".equals(sa.getParam("AILogic"))) {
|
} else if ("ExileGraveyards".equals(aiLogic)) {
|
||||||
for (Player opp : ai.getOpponents()) {
|
for (Player opp : ai.getOpponents()) {
|
||||||
CardCollectionView cardsGY = opp.getCardsIn(ZoneType.Graveyard);
|
CardCollectionView cardsGY = opp.getCardsIn(ZoneType.Graveyard);
|
||||||
CardCollection creats = CardLists.filter(cardsGY, CardPredicates.Presets.CREATURES);
|
CardCollection creats = CardLists.filter(cardsGY, CardPredicates.Presets.CREATURES);
|
||||||
@@ -105,7 +106,7 @@ public class ChangeZoneAllAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
} else if ("ManifestCreatsFromGraveyard".equals(sa.getParam("AILogic"))) {
|
} else if ("ManifestCreatsFromGraveyard".equals(aiLogic)) {
|
||||||
PlayerCollection players = ai.getOpponents();
|
PlayerCollection players = ai.getOpponents();
|
||||||
players.add(ai);
|
players.add(ai);
|
||||||
int maxSize = 1;
|
int maxSize = 1;
|
||||||
@@ -217,7 +218,7 @@ public class ChangeZoneAllAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Don't cast during main1?
|
// Don't cast during main1?
|
||||||
if (game.getPhaseHandler().is(PhaseType.MAIN1, ai)) {
|
if (game.getPhaseHandler().is(PhaseType.MAIN1, ai) && !aiLogic.equals("Main1")) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else if (origin.equals(ZoneType.Graveyard)) {
|
} else if (origin.equals(ZoneType.Graveyard)) {
|
||||||
@@ -245,15 +246,13 @@ public class ChangeZoneAllAi extends SpellAbilityAi {
|
|||||||
&& !ComputerUtil.isPlayingReanimator(ai);
|
&& !ComputerUtil.isPlayingReanimator(ai);
|
||||||
}
|
}
|
||||||
} else if (origin.equals(ZoneType.Exile)) {
|
} else if (origin.equals(ZoneType.Exile)) {
|
||||||
String logic = sa.getParam("AILogic");
|
if (aiLogic.startsWith("DiscardAllAndRetExiled")) {
|
||||||
|
|
||||||
if (logic != null && logic.startsWith("DiscardAllAndRetExiled")) {
|
|
||||||
int numExiledWithSrc = CardLists.filter(ai.getCardsIn(ZoneType.Exile), CardPredicates.isExiledWith(source)).size();
|
int numExiledWithSrc = CardLists.filter(ai.getCardsIn(ZoneType.Exile), CardPredicates.isExiledWith(source)).size();
|
||||||
int curHandSize = ai.getCardsIn(ZoneType.Hand).size();
|
int curHandSize = ai.getCardsIn(ZoneType.Hand).size();
|
||||||
|
|
||||||
// minimum card advantage unless the hand will be fully reloaded
|
// minimum card advantage unless the hand will be fully reloaded
|
||||||
int minAdv = logic.contains(".minAdv") ? Integer.parseInt(logic.substring(logic.indexOf(".minAdv") + 7)) : 0;
|
int minAdv = aiLogic.contains(".minAdv") ? Integer.parseInt(aiLogic.substring(aiLogic.indexOf(".minAdv") + 7)) : 0;
|
||||||
boolean noDiscard = logic.contains(".noDiscard");
|
boolean noDiscard = aiLogic.contains(".noDiscard");
|
||||||
|
|
||||||
if (numExiledWithSrc > curHandSize || (noDiscard && numExiledWithSrc > 0)) {
|
if (numExiledWithSrc > curHandSize || (noDiscard && numExiledWithSrc > 0)) {
|
||||||
if (ComputerUtil.predictThreatenedObjects(ai, sa, true).contains(source)) {
|
if (ComputerUtil.predictThreatenedObjects(ai, sa, true).contains(source)) {
|
||||||
|
|||||||
@@ -308,8 +308,10 @@ public class CountersPutAi extends CountersAi {
|
|||||||
} else if (logic.equals("ChargeToBestCMC")) {
|
} else if (logic.equals("ChargeToBestCMC")) {
|
||||||
return doChargeToCMCLogic(ai, sa);
|
return doChargeToCMCLogic(ai, sa);
|
||||||
} else if (logic.equals("ChargeToBestOppControlledCMC")) {
|
} else if (logic.equals("ChargeToBestOppControlledCMC")) {
|
||||||
return doChargeToOppCtrlCMCLogic(ai, sa);
|
return doChargeToOppCtrlCMCLogic(ai, sa);
|
||||||
}
|
} else if (logic.equals("TheOneRing")) {
|
||||||
|
return SpecialCardAi.TheOneRing.consider(ai, sa);
|
||||||
|
}
|
||||||
|
|
||||||
if (!sa.metConditions() && sa.getSubAbility() == null) {
|
if (!sa.metConditions() && sa.getSubAbility() == null) {
|
||||||
return false;
|
return false;
|
||||||
@@ -440,6 +442,9 @@ public class CountersPutAi extends CountersAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final boolean hasSacCost = abCost.hasSpecificCostType(CostSacrifice.class);
|
||||||
|
final boolean sacSelf = ComputerUtilCost.isSacrificeSelfCost(abCost);
|
||||||
|
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
if (!ai.getGame().getStack().isEmpty() && !isSorcerySpeed(sa, ai)) {
|
if (!ai.getGame().getStack().isEmpty() && !isSorcerySpeed(sa, ai)) {
|
||||||
// only evaluates case where all tokens are placed on a single target
|
// only evaluates case where all tokens are placed on a single target
|
||||||
@@ -453,15 +458,15 @@ public class CountersPutAi extends CountersAi {
|
|||||||
sa.addDividedAllocation(c, amount);
|
sa.addDividedAllocation(c, amount);
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
return false;
|
if (!hasSacCost) { // for Sacrifice costs, evaluate further to see if it's worth using the ability before the card dies
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
|
|
||||||
final boolean sacSelf = ComputerUtilCost.isSacrificeSelfCost(abCost);
|
|
||||||
|
|
||||||
if (sa.isCurse()) {
|
if (sa.isCurse()) {
|
||||||
list = ai.getOpponents().getCardsIn(ZoneType.Battlefield);
|
list = ai.getOpponents().getCardsIn(ZoneType.Battlefield);
|
||||||
} else {
|
} else {
|
||||||
@@ -474,6 +479,8 @@ public class CountersPutAi extends CountersAi {
|
|||||||
// don't put the counter on the dead creature
|
// don't put the counter on the dead creature
|
||||||
if (sacSelf && c.equals(source)) {
|
if (sacSelf && c.equals(source)) {
|
||||||
return false;
|
return false;
|
||||||
|
} else if (hasSacCost && !ComputerUtil.shouldSacrificeThreatenedCard(ai, c, sa)) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
if ("NoCounterOfType".equals(sa.getParam("AILogic"))) {
|
if ("NoCounterOfType".equals(sa.getParam("AILogic"))) {
|
||||||
for (String ctrType : types) {
|
for (String ctrType : types) {
|
||||||
@@ -628,7 +635,7 @@ public class CountersPutAi extends CountersAi {
|
|||||||
}
|
}
|
||||||
// Instant +1/+1
|
// Instant +1/+1
|
||||||
if (type.equals("P1P1") && !isSorcerySpeed(sa, ai)) {
|
if (type.equals("P1P1") && !isSorcerySpeed(sa, ai)) {
|
||||||
if (!(ph.getNextTurn() == ai && ph.is(PhaseType.END_OF_TURN) && abCost.isReusuableResource())) {
|
if (!hasSacCost && !(ph.getNextTurn() == ai && ph.is(PhaseType.END_OF_TURN) && abCost.isReusuableResource())) {
|
||||||
return false; // only if next turn and cost is reusable
|
return false; // only if next turn and cost is reusable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -274,7 +274,8 @@ public class DamageAllAi extends SpellAbilityAi {
|
|||||||
final String damage = sa.getParam("NumDmg");
|
final String damage = sa.getParam("NumDmg");
|
||||||
int dmg;
|
int dmg;
|
||||||
|
|
||||||
if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) {
|
if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")
|
||||||
|
&& sa.getPayCosts() != null && sa.getPayCosts().hasXInAnyCostPart()) {
|
||||||
// Set PayX here to maximum value.
|
// Set PayX here to maximum value.
|
||||||
dmg = ComputerUtilCost.getMaxXValue(sa, ai, true);
|
dmg = ComputerUtilCost.getMaxXValue(sa, ai, true);
|
||||||
sa.setXManaCostPaid(dmg);
|
sa.setXManaCostPaid(dmg);
|
||||||
|
|||||||
@@ -752,7 +752,7 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
int minTgts = tgt.getMinTargets(source, sa);
|
int minTgts = tgt.getMinTargets(source, sa);
|
||||||
if (tcs.size() < minTgts || tcs.size() == 0) {
|
if (tcs.size() < minTgts || tcs.size() == 0) {
|
||||||
if (mandatory) {
|
if (mandatory) {
|
||||||
// Sanity check: if there are any legal non-owned targets after the check (which may happen for complex cards like Searing Blaze),
|
// Sanity check: if there are any legal non-owned targets after the check (which may happen for complex cards like Rift Bolt),
|
||||||
// choose a random opponent's target before forcing targeting of own stuff
|
// choose a random opponent's target before forcing targeting of own stuff
|
||||||
List<GameEntity> allTgtEntities = sa.getTargetRestrictions().getAllCandidates(sa, true);
|
List<GameEntity> allTgtEntities = sa.getTargetRestrictions().getAllCandidates(sa, true);
|
||||||
for (GameEntity ent : allTgtEntities) {
|
for (GameEntity ent : allTgtEntities) {
|
||||||
|
|||||||
@@ -35,11 +35,7 @@ import forge.game.ability.ApiType;
|
|||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CounterEnumType;
|
import forge.game.card.CounterEnumType;
|
||||||
import forge.game.card.CounterType;
|
import forge.game.card.CounterType;
|
||||||
import forge.game.cost.Cost;
|
import forge.game.cost.*;
|
||||||
import forge.game.cost.CostDiscard;
|
|
||||||
import forge.game.cost.CostPart;
|
|
||||||
import forge.game.cost.CostPayLife;
|
|
||||||
import forge.game.cost.PaymentDecision;
|
|
||||||
import forge.game.phase.PhaseHandler;
|
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;
|
||||||
@@ -148,9 +144,15 @@ public class DrawAi extends SpellAbilityAi {
|
|||||||
return !ai.getGame().getStack().isEmpty() && ai.getGame().getStack().peekAbility().getHostCard().equals(sa.getHostCard());
|
return !ai.getGame().getStack().isEmpty() && ai.getGame().getStack().peekAbility().getHostCard().equals(sa.getHostCard());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sacrificing a creature in response to something dangerous is generally good in any phase
|
||||||
|
boolean isSacCost = false;
|
||||||
|
if (sa.getPayCosts() != null && sa.getPayCosts().hasSpecificCostType(CostSacrifice.class)) {
|
||||||
|
isSacCost = true;
|
||||||
|
}
|
||||||
|
|
||||||
// Don't use draw abilities before main 2 if possible
|
// Don't use draw abilities before main 2 if possible
|
||||||
if (ph.getPhase().isBefore(PhaseType.MAIN2) && !sa.hasParam("ActivationPhases")
|
if (ph.getPhase().isBefore(PhaseType.MAIN2) && !sa.hasParam("ActivationPhases")
|
||||||
&& !ComputerUtil.castSpellInMain1(ai, sa)) {
|
&& !ComputerUtil.castSpellInMain1(ai, sa) && !isSacCost) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -396,10 +396,17 @@ public class EffectAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
|
protected boolean doTriggerAINoCost(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
|
||||||
|
if (sa.hasParam("AILogic")) {
|
||||||
|
if (canPlayAI(aiPlayer, sa)) {
|
||||||
|
return true; // if false, fall through further to do the mandatory stuff
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// E.g. Nova Pentacle
|
// E.g. Nova Pentacle
|
||||||
if (sa.usesTargeting() && !sa.getTargetRestrictions().canTgtPlayer()) {
|
if (sa.usesTargeting() && !sa.getTargetRestrictions().canTgtPlayer()) {
|
||||||
// try to target the opponent's best targetable permanent, if able
|
// try to target the opponent's best targetable permanent, if able
|
||||||
CardCollection oppPerms = CardLists.getValidCards(aiPlayer.getOpponents().getCardsIn(sa.getTargetRestrictions().getZone()), sa.getTargetRestrictions().getValidTgts(), aiPlayer, sa.getHostCard(), sa);
|
CardCollection oppPerms = CardLists.getValidCards(aiPlayer.getOpponents().getCardsIn(sa.getTargetRestrictions().getZone()), sa.getTargetRestrictions().getValidTgts(), aiPlayer, sa.getHostCard(), sa);
|
||||||
|
oppPerms = CardLists.filter(oppPerms, card -> sa.canTarget(card));
|
||||||
if (!oppPerms.isEmpty()) {
|
if (!oppPerms.isEmpty()) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
sa.getTargets().add(ComputerUtilCard.getBestAI(oppPerms));
|
sa.getTargets().add(ComputerUtilCard.getBestAI(oppPerms));
|
||||||
@@ -409,6 +416,7 @@ public class EffectAi extends SpellAbilityAi {
|
|||||||
if (mandatory) {
|
if (mandatory) {
|
||||||
// try to target the AI's worst targetable permanent, if able
|
// try to target the AI's worst targetable permanent, if able
|
||||||
CardCollection aiPerms = CardLists.getValidCards(aiPlayer.getCardsIn(sa.getTargetRestrictions().getZone()), sa.getTargetRestrictions().getValidTgts(), aiPlayer, sa.getHostCard(), sa);
|
CardCollection aiPerms = CardLists.getValidCards(aiPlayer.getCardsIn(sa.getTargetRestrictions().getZone()), sa.getTargetRestrictions().getValidTgts(), aiPlayer, sa.getHostCard(), sa);
|
||||||
|
aiPerms = CardLists.filter(aiPerms, card -> sa.canTarget(card));
|
||||||
if (!aiPerms.isEmpty()) {
|
if (!aiPerms.isEmpty()) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
sa.getTargets().add(ComputerUtilCard.getWorstAI(aiPerms));
|
sa.getTargets().add(ComputerUtilCard.getWorstAI(aiPerms));
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ public class LifeGainAi extends SpellAbilityAi {
|
|||||||
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
|
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
final int life = ai.getLife();
|
final int life = ai.getLife();
|
||||||
|
final String aiLogic = sa.getParamOrDefault("AILogic", "");
|
||||||
boolean activateForCost = ComputerUtil.activateForCost(sa, ai);
|
boolean activateForCost = ComputerUtil.activateForCost(sa, ai);
|
||||||
|
|
||||||
boolean lifeCritical = life <= 5;
|
boolean lifeCritical = life <= 5;
|
||||||
@@ -103,9 +104,15 @@ public class LifeGainAi extends SpellAbilityAi {
|
|||||||
if (!ph.is(PhaseType.COMBAT_DECLARE_BLOCKERS)) { return false; }
|
if (!ph.is(PhaseType.COMBAT_DECLARE_BLOCKERS)) { return false; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sacrificing in response to something dangerous is generally good in any phase
|
||||||
|
boolean isSacCost = false;
|
||||||
|
if (sa.getPayCosts() != null && sa.getPayCosts().hasSpecificCostType(CostSacrifice.class)) {
|
||||||
|
isSacCost = true;
|
||||||
|
}
|
||||||
|
|
||||||
// Don't use lifegain before main 2 if possible
|
// Don't use lifegain before main 2 if possible
|
||||||
if (!lifeCritical && ph.getPhase().isBefore(PhaseType.MAIN2) && !sa.hasParam("ActivationPhases")
|
if (!lifeCritical && ph.getPhase().isBefore(PhaseType.MAIN2) && !sa.hasParam("ActivationPhases")
|
||||||
&& !ComputerUtil.castSpellInMain1(ai, sa)) {
|
&& !ComputerUtil.castSpellInMain1(ai, sa) && !aiLogic.contains("AnyPhase") && !isSacCost) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,6 +131,7 @@ public class LifeGainAi extends SpellAbilityAi {
|
|||||||
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
|
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
||||||
|
final String aiLogic = sa.getParamOrDefault("AILogic", "");
|
||||||
|
|
||||||
final int life = ai.getLife();
|
final int life = ai.getLife();
|
||||||
final String amountStr = sa.getParam("LifeAmount");
|
final String amountStr = sa.getParam("LifeAmount");
|
||||||
@@ -185,7 +193,11 @@ public class LifeGainAi extends SpellAbilityAi {
|
|||||||
|| sa.getSubAbility() != null || playReusable(ai, sa)) {
|
|| sa.getSubAbility() != null || playReusable(ai, sa)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (sa.getPayCosts() != null && sa.getPayCosts().hasSpecificCostType(CostSacrifice.class)) {
|
||||||
|
return true; // sac costs should be performed at Instant speed when able
|
||||||
|
}
|
||||||
|
|
||||||
// Save instant-speed life-gain unless it is really worth it
|
// Save instant-speed life-gain unless it is really worth it
|
||||||
final float value = 0.9f * lifeAmount / life;
|
final float value = 0.9f * lifeAmount / life;
|
||||||
if (value < 0.2f) {
|
if (value < 0.2f) {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import forge.ai.SpellAbilityAi;
|
|||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.cost.Cost;
|
import forge.game.cost.Cost;
|
||||||
|
import forge.game.cost.CostSacrifice;
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.player.PlayerCollection;
|
import forge.game.player.PlayerCollection;
|
||||||
@@ -96,6 +97,7 @@ public class LifeLoseAi extends SpellAbilityAi {
|
|||||||
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
|
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final String amountStr = sa.getParam("LifeAmount");
|
final String amountStr = sa.getParam("LifeAmount");
|
||||||
|
final String aiLogic = sa.getParamOrDefault("AILogic", "");
|
||||||
int amount = 0;
|
int amount = 0;
|
||||||
|
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
@@ -133,9 +135,15 @@ public class LifeLoseAi extends SpellAbilityAi {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sacrificing a creature in response to something dangerous is generally good in any phase
|
||||||
|
boolean isSacCost = false;
|
||||||
|
if (sa.getPayCosts() != null && sa.getPayCosts().hasSpecificCostType(CostSacrifice.class)) {
|
||||||
|
isSacCost = true;
|
||||||
|
}
|
||||||
|
|
||||||
// Don't use loselife before main 2 if possible
|
// Don't use loselife before main 2 if possible
|
||||||
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2) && !sa.hasParam("ActivationPhases")
|
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2) && !sa.hasParam("ActivationPhases")
|
||||||
&& !ComputerUtil.castSpellInMain1(ai, sa) && !"AnyPhase".equals(sa.getParam("AILogic"))) {
|
&& !ComputerUtil.castSpellInMain1(ai, sa) && !aiLogic.contains("AnyPhase") && !isSacCost) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,14 +6,7 @@ import java.util.List;
|
|||||||
import com.google.common.base.Predicates;
|
import com.google.common.base.Predicates;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
|
|
||||||
import forge.ai.AiPlayDecision;
|
import forge.ai.*;
|
||||||
import forge.ai.ComputerUtil;
|
|
||||||
import forge.ai.ComputerUtilAbility;
|
|
||||||
import forge.ai.ComputerUtilCard;
|
|
||||||
import forge.ai.ComputerUtilCost;
|
|
||||||
import forge.ai.ComputerUtilMana;
|
|
||||||
import forge.ai.PlayerControllerAi;
|
|
||||||
import forge.ai.SpellAbilityAi;
|
|
||||||
import forge.card.ColorSet;
|
import forge.card.ColorSet;
|
||||||
import forge.card.MagicColor;
|
import forge.card.MagicColor;
|
||||||
import forge.card.mana.ManaAtom;
|
import forge.card.mana.ManaAtom;
|
||||||
@@ -50,7 +43,7 @@ public class ManaEffectAi extends SpellAbilityAi {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkAiLogic(Player ai, SpellAbility sa, String aiLogic) {
|
protected boolean checkAiLogic(Player ai, SpellAbility sa, String aiLogic) {
|
||||||
if (aiLogic.startsWith("ManaRitual")) {
|
if (aiLogic.startsWith("ManaRitual") || aiLogic.startsWith("BlackLotus")) {
|
||||||
return doManaRitualLogic(ai, sa, false);
|
return doManaRitualLogic(ai, sa, false);
|
||||||
} else if ("Always".equals(aiLogic)) {
|
} else if ("Always".equals(aiLogic)) {
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -299,3 +299,16 @@ MOJHOSTO_CHANCE_TO_PREFER_JHOIRA_OVER_MOMIR=50
|
|||||||
# attempt this either in its upkeep or its draw phase or main 1).
|
# attempt this either in its upkeep or its draw phase or main 1).
|
||||||
MOJHOSTO_CHANCE_TO_USE_JHOIRA_COPY_INSTANT=15
|
MOJHOSTO_CHANCE_TO_USE_JHOIRA_COPY_INSTANT=15
|
||||||
|
|
||||||
|
# Master toggle for the following options setting the default AIPreference:SacCost handling.
|
||||||
|
SACRIFICE_DEFAULT_PREF_ENABLE=false
|
||||||
|
# If a card has no card-specific AIPreference for the Sacrifice cost (AIPreference:SacCost), the AI will still
|
||||||
|
# consider the sacrifice of a matching card if its mana value (CMC) matches the specified minimum
|
||||||
|
SACRIFICE_DEFAULT_PREF_MIN_CMC=0
|
||||||
|
# If a card has no card-specific AIPreference for the Sacrifice cost (AIPreference:SacCost), the AI will still
|
||||||
|
# consider the sacrifice of a matching card if its mana value (CMC) matches the specified maximum
|
||||||
|
SACRIFICE_DEFAULT_PREF_MAX_CMC=1
|
||||||
|
# If a card has no card-specific AIPreference for the Sacrifice cost (AIPreference:SacCost), the AI will still
|
||||||
|
# consider the sacrifice of a matching card is a token
|
||||||
|
SACRIFICE_DEFAULT_PREF_ALLOW_TOKENS=true
|
||||||
|
# A creature should evaluate to no more than this much to be considered for default SacCost preference
|
||||||
|
SACRIFICE_DEFAULT_PREF_MAX_CREATURE_EVAL=135
|
||||||
@@ -299,3 +299,17 @@ MOJHOSTO_CHANCE_TO_PREFER_JHOIRA_OVER_MOMIR=50
|
|||||||
# The chance that the AI will activate Jhoira's copy random instant ability (per phase, the AI will generally
|
# The chance that the AI will activate Jhoira's copy random instant ability (per phase, the AI will generally
|
||||||
# attempt this either in its upkeep or its draw phase or main 1).
|
# attempt this either in its upkeep or its draw phase or main 1).
|
||||||
MOJHOSTO_CHANCE_TO_USE_JHOIRA_COPY_INSTANT=20
|
MOJHOSTO_CHANCE_TO_USE_JHOIRA_COPY_INSTANT=20
|
||||||
|
|
||||||
|
# Master toggle for the following options setting the default AIPreference:SacCost handling.
|
||||||
|
SACRIFICE_DEFAULT_PREF_ENABLE=false
|
||||||
|
# If a card has no card-specific AIPreference for the Sacrifice cost (AIPreference:SacCost), the AI will still
|
||||||
|
# consider the sacrifice of a matching card if its mana value (CMC) matches the specified minimum
|
||||||
|
SACRIFICE_DEFAULT_PREF_MIN_CMC=0
|
||||||
|
# If a card has no card-specific AIPreference for the Sacrifice cost (AIPreference:SacCost), the AI will still
|
||||||
|
# consider the sacrifice of a matching card if its mana value (CMC) matches the specified maximum
|
||||||
|
SACRIFICE_DEFAULT_PREF_MAX_CMC=2
|
||||||
|
# If a card has no card-specific AIPreference for the Sacrifice cost (AIPreference:SacCost), the AI will still
|
||||||
|
# consider the sacrifice of a matching card is a token
|
||||||
|
SACRIFICE_DEFAULT_PREF_ALLOW_TOKENS=true
|
||||||
|
# A creature should evaluate to no more than this much to be considered for default SacCost preference
|
||||||
|
SACRIFICE_DEFAULT_PREF_MAX_CREATURE_EVAL=135
|
||||||
@@ -300,8 +300,22 @@ MOJHOSTO_CHANCE_TO_PREFER_JHOIRA_OVER_MOMIR=50
|
|||||||
# attempt this either in its upkeep or its draw phase or main 1).
|
# attempt this either in its upkeep or its draw phase or main 1).
|
||||||
MOJHOSTO_CHANCE_TO_USE_JHOIRA_COPY_INSTANT=20
|
MOJHOSTO_CHANCE_TO_USE_JHOIRA_COPY_INSTANT=20
|
||||||
|
|
||||||
|
# Master toggle for the following options setting the default AIPreference:SacCost handling.
|
||||||
|
SACRIFICE_DEFAULT_PREF_ENABLE=true
|
||||||
|
# If a card has no card-specific AIPreference for the Sacrifice cost (AIPreference:SacCost), the AI will still
|
||||||
|
# consider the sacrifice of a matching card if its mana value (CMC) matches the specified minimum
|
||||||
|
SACRIFICE_DEFAULT_PREF_MIN_CMC=0
|
||||||
|
# If a card has no card-specific AIPreference for the Sacrifice cost (AIPreference:SacCost), the AI will still
|
||||||
|
# consider the sacrifice of a matching card if its mana value (CMC) matches the specified maximum
|
||||||
|
SACRIFICE_DEFAULT_PREF_MAX_CMC=1
|
||||||
|
# If a card has no card-specific AIPreference for the Sacrifice cost (AIPreference:SacCost), the AI will still
|
||||||
|
# consider the sacrifice of a matching card is a token
|
||||||
|
SACRIFICE_DEFAULT_PREF_ALLOW_TOKENS=true
|
||||||
|
# A creature should evaluate to no more than this much to be considered for default SacCost preference
|
||||||
|
SACRIFICE_DEFAULT_PREF_MAX_CREATURE_EVAL=135
|
||||||
|
|
||||||
# -- Experimental feature toggles which only exist until the testing procedure for the relevant --
|
# -- Experimental feature toggles which only exist until the testing procedure for the relevant --
|
||||||
# -- features is over. These toggles will be removed later, or may be reintroduced under a --
|
# -- features is over. These toggles will be removed later, or may be reintroduced under a --
|
||||||
# -- different name if necessary --
|
# -- different name if necessary --
|
||||||
|
|
||||||
# <-- there are no experimental options here at the moment -->
|
# <-- there are no experimental options here at the moment -->
|
||||||
@@ -300,3 +300,16 @@ MOJHOSTO_CHANCE_TO_PREFER_JHOIRA_OVER_MOMIR=50
|
|||||||
# attempt this either in its upkeep or its draw phase or main 1).
|
# attempt this either in its upkeep or its draw phase or main 1).
|
||||||
MOJHOSTO_CHANCE_TO_USE_JHOIRA_COPY_INSTANT=20
|
MOJHOSTO_CHANCE_TO_USE_JHOIRA_COPY_INSTANT=20
|
||||||
|
|
||||||
|
# Master toggle for the following options setting the default AIPreference:SacCost handling.
|
||||||
|
SACRIFICE_DEFAULT_PREF_ENABLE=false
|
||||||
|
# If a card has no card-specific AIPreference for the Sacrifice cost (AIPreference:SacCost), the AI will still
|
||||||
|
# consider the sacrifice of a matching card if its mana value (CMC) matches the specified minimum
|
||||||
|
SACRIFICE_DEFAULT_PREF_MIN_CMC=0
|
||||||
|
# If a card has no card-specific AIPreference for the Sacrifice cost (AIPreference:SacCost), the AI will still
|
||||||
|
# consider the sacrifice of a matching card if its mana value (CMC) matches the specified maximum
|
||||||
|
SACRIFICE_DEFAULT_PREF_MAX_CMC=3
|
||||||
|
# If a card has no card-specific AIPreference for the Sacrifice cost (AIPreference:SacCost), the AI will still
|
||||||
|
# consider the sacrifice of a matching card is a token
|
||||||
|
SACRIFICE_DEFAULT_PREF_ALLOW_TOKENS=true
|
||||||
|
# A creature should evaluate to no more than this much to be considered for default SacCost preference
|
||||||
|
SACRIFICE_DEFAULT_PREF_MAX_CREATURE_EVAL=135
|
||||||
@@ -6,6 +6,5 @@ A:SP$ Attach | Cost$ 2 W | ValidTgts$ Land | AILogic$ Pump
|
|||||||
S:Mode$ Continuous | Affected$ Land.AttachedBy | AddAbility$ GainLife | AddSVar$ AnimalBoneyardX | Description$ Enchanted land has "{T}, Sacrifice a creature: You gain life equal to the sacrificed creature's toughness."
|
S:Mode$ Continuous | Affected$ Land.AttachedBy | AddAbility$ GainLife | AddSVar$ AnimalBoneyardX | Description$ Enchanted land has "{T}, Sacrifice a creature: You gain life equal to the sacrificed creature's toughness."
|
||||||
SVar:GainLife:AB$ GainLife | Cost$ T Sac<1/Creature> | LifeAmount$ AnimalBoneyardX | SpellDescription$ You gain life equal to the sacrificed creature's toughness.
|
SVar:GainLife:AB$ GainLife | Cost$ T Sac<1/Creature> | LifeAmount$ AnimalBoneyardX | SpellDescription$ You gain life equal to the sacrificed creature's toughness.
|
||||||
SVar:AnimalBoneyardX:Sacrificed$CardToughness
|
SVar:AnimalBoneyardX:Sacrificed$CardToughness
|
||||||
AI:RemoveDeck:All
|
|
||||||
SVar:NonStackingAttachEffect:True
|
SVar:NonStackingAttachEffect:True
|
||||||
Oracle:Enchant land\nEnchanted land has "{T}, Sacrifice a creature: You gain life equal to the sacrificed creature's toughness."
|
Oracle:Enchant land\nEnchanted land has "{T}, Sacrifice a creature: You gain life equal to the sacrificed creature's toughness."
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
Name:Anticognition
|
Name:Anticognition
|
||||||
ManaCost:1 U
|
ManaCost:1 U
|
||||||
Types:Instant
|
Types:Instant
|
||||||
A:SP$ Pump | Cost$ 1 U | IsCurse$ True | TargetType$ Spell | TgtZone$ Stack | TgtPrompt$ Select target creature or planeswalker spell | ValidTgts$ Creature,Planeswalker | SubAbility$ DBBranch | StackDescription$ SpellDescription | SpellDescription$ Counter target creature or planeswalker spell unless its controller pays {2}. If an opponent has eight or more cards in their graveyard, instead counter that spell, then scry 2.
|
A:SP$ Branch | BranchConditionSVar$ X | TargetType$ Spell | TgtZone$ Stack | ValidTgts$ Creature,Planeswalker | BranchConditionSVarCompare$ GE8 | TrueSubAbility$ CounterScry | FalseSubAbility$ CounterUnless | AILogic$ BranchCounter | SpellDescription$ Counter target creature or planeswalker spell unless its controller pays {2}. If an opponent has eight or more cards in their graveyard, instead counter that spell, then scry 2.
|
||||||
SVar:DBBranch:DB$ Branch | BranchConditionSVar$ X | BranchConditionSVarCompare$ GE8 | TrueSubAbility$ CounterScry | FalseSubAbility$ CounterUnless | StackDescription$ None
|
|
||||||
SVar:CounterUnless:DB$ Counter | Defined$ Targeted | UnlessCost$ 2
|
SVar:CounterUnless:DB$ Counter | Defined$ Targeted | UnlessCost$ 2
|
||||||
SVar:CounterScry:DB$ Counter | Defined$ Targeted | SubAbility$ DBScry
|
SVar:CounterScry:DB$ Counter | Defined$ Targeted | SubAbility$ DBScry
|
||||||
SVar:DBScry:DB$ Scry | ScryNum$ 2
|
SVar:DBScry:DB$ Scry | ScryNum$ 2
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ Types:Legendary Enchantment
|
|||||||
A:AB$ Draw | Cost$ 1 B PayLife<2> | SpellDescription$ Draw a card.
|
A:AB$ Draw | Cost$ 1 B PayLife<2> | SpellDescription$ Draw a card.
|
||||||
T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | OptionalDecider$ You | Execute$ DBTransform | LifeTotal$ You | LifeAmount$ LE5 | TriggerDescription$ At the beginning of your upkeep, if you have 5 or less life, you may transform CARDNAME.
|
T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | OptionalDecider$ You | Execute$ DBTransform | LifeTotal$ You | LifeAmount$ LE5 | TriggerDescription$ At the beginning of your upkeep, if you have 5 or less life, you may transform CARDNAME.
|
||||||
SVar:DBTransform:DB$ SetState | Defined$ Self | Mode$ Transform
|
SVar:DBTransform:DB$ SetState | Defined$ Self | Mode$ Transform
|
||||||
AI:RemoveDeck:All
|
AI:RemoveDeck:Random
|
||||||
AlternateMode:DoubleFaced
|
AlternateMode:DoubleFaced
|
||||||
Oracle:{1}{B}, Pay 2 life: Draw a card.\nAt the beginning of your upkeep, if you have 5 or less life, you may transform Arguel's Blood Fast.
|
Oracle:{1}{B}, Pay 2 life: Draw a card.\nAt the beginning of your upkeep, if you have 5 or less life, you may transform Arguel's Blood Fast.
|
||||||
|
|
||||||
|
|||||||
@@ -5,4 +5,5 @@ PT:1/2
|
|||||||
A:AB$ Pump | Cost$ Sac<1/Artifact> | Defined$ Self | NumAtt$ 2 | NumDef$ 2 | SpellDescription$ CARDNAME gets +2/+2 until end of turn.
|
A:AB$ Pump | Cost$ Sac<1/Artifact> | Defined$ Self | NumAtt$ 2 | NumDef$ 2 | SpellDescription$ CARDNAME gets +2/+2 until end of turn.
|
||||||
DeckNeeds:Type$Artifact
|
DeckNeeds:Type$Artifact
|
||||||
DeckHas:Ability$Sacrifice
|
DeckHas:Ability$Sacrifice
|
||||||
|
SVar:AIPreference:SacCost$Artifact.token,Artifact.cmcEQ0+nonLegendary+notnamedBlack Lotus,Artifact.cmcEQ1,Artifact.cmcEQ2,Artifact.cmcEQ3
|
||||||
Oracle:Sacrifice an artifact: Atog gets +2/+2 until end of turn.
|
Oracle:Sacrifice an artifact: Atog gets +2/+2 until end of turn.
|
||||||
|
|||||||
@@ -2,6 +2,6 @@ Name:Bone Splinters
|
|||||||
ManaCost:B
|
ManaCost:B
|
||||||
Types:Sorcery
|
Types:Sorcery
|
||||||
A:SP$ Destroy | Cost$ B Sac<1/Creature> | ValidTgts$ Creature | TgtPrompt$ Select target creature | SpellDescription$ Destroy target creature.
|
A:SP$ Destroy | Cost$ B Sac<1/Creature> | ValidTgts$ Creature | TgtPrompt$ Select target creature | SpellDescription$ Destroy target creature.
|
||||||
SVar:AICostPreference:SacCost$Creature.Token,Creature.cmcLE2
|
SVar:AIPreference:SacCost$Creature.Token,Creature.cmcLE2
|
||||||
AI:RemoveDeck:Random
|
AI:RemoveDeck:Random
|
||||||
Oracle:As an additional cost to cast this spell, sacrifice a creature.\nDestroy target creature.
|
Oracle:As an additional cost to cast this spell, sacrifice a creature.\nDestroy target creature.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
Name:Bring the Ending
|
Name:Bring the Ending
|
||||||
ManaCost:1 U
|
ManaCost:1 U
|
||||||
Types:Instant
|
Types:Instant
|
||||||
A:SP$ Branch | BranchConditionSVar$ X | TargetType$ Spell | TgtZone$ Stack | ValidTgts$ Card | BranchConditionSVarCompare$ GE3 | TrueSubAbility$ Counter | FalseSubAbility$ CounterUnless | SpellDescription$ Counter target spell unless its controller pays {2}. Corrupted — Counter that spell instead if its controller has three or more poison counters.
|
A:SP$ Branch | BranchConditionSVar$ X | TargetType$ Spell | TgtZone$ Stack | ValidTgts$ Card | BranchConditionSVarCompare$ GE3 | TrueSubAbility$ Counter | FalseSubAbility$ CounterUnless | AILogic$ BranchCounter | SpellDescription$ Counter target spell unless its controller pays {2}. Corrupted — Counter that spell instead if its controller has three or more poison counters.
|
||||||
SVar:CounterUnless:DB$ Counter | Defined$ Targeted | UnlessCost$ 2
|
SVar:CounterUnless:DB$ Counter | Defined$ Targeted | UnlessCost$ 2
|
||||||
SVar:Counter:DB$ Counter | Defined$ Targeted
|
SVar:Counter:DB$ Counter | Defined$ Targeted
|
||||||
SVar:X:TargetedController$PoisonCounters
|
SVar:X:TargetedController$PoisonCounters
|
||||||
|
|||||||
@@ -3,6 +3,6 @@ ManaCost:4 B B
|
|||||||
Types:Creature Phyrexian Horror
|
Types:Creature Phyrexian Horror
|
||||||
PT:6/3
|
PT:6/3
|
||||||
A:AB$ Regenerate | Cost$ B Sac<1/Creature> | SpellDescription$ Regenerate CARDNAME.
|
A:AB$ Regenerate | Cost$ B Sac<1/Creature> | SpellDescription$ Regenerate CARDNAME.
|
||||||
SVar:AIPreferences:SacCost$Creature.token,Creature.cmcLE5+powerLE3+toughnessLE4
|
SVar:AIPreference:SacCost$Creature.token,Creature.cmcLE5+powerLE3+toughnessLE4
|
||||||
AI:RemoveDeck:Random
|
AI:RemoveDeck:Random
|
||||||
Oracle:{B}, Sacrifice a creature: Regenerate Corrupted Harvester.
|
Oracle:{B}, Sacrifice a creature: Regenerate Corrupted Harvester.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ ManaCost:B
|
|||||||
Types:Creature Zombie
|
Types:Creature Zombie
|
||||||
PT:1/1
|
PT:1/1
|
||||||
A:AB$ Token | Cost$ 1 B T Discard<1/Card> | TokenAmount$ 1 | TokenScript$ b_2_2_zombie | TokenOwner$ You | SpellDescription$ Create a 2/2 black Zombie creature token.
|
A:AB$ Token | Cost$ 1 B T Discard<1/Card> | TokenAmount$ 1 | TokenScript$ b_2_2_zombie | TokenOwner$ You | SpellDescription$ Create a 2/2 black Zombie creature token.
|
||||||
A:AB$ Draw | Cost$ tapXType<3/Zombie> | NumCards$ 1 | SpellDescription$ You draw a card and you lose 1 life. | SubAbility$ DBLoseLife
|
A:AB$ Draw | Cost$ tapXType<3/Zombie> | NumCards$ 1 | AILogic$ AtOppEOT | SpellDescription$ You draw a card and you lose 1 life. | SubAbility$ DBLoseLife
|
||||||
SVar:DBLoseLife:DB$ LoseLife | LifeAmount$ 1
|
SVar:DBLoseLife:DB$ LoseLife | LifeAmount$ 1
|
||||||
SVar:AIPreference:DiscardCost$Card
|
SVar:AIPreference:DiscardCost$Card
|
||||||
DeckNeeds:Type$Zombie
|
DeckNeeds:Type$Zombie
|
||||||
|
|||||||
@@ -4,5 +4,4 @@ Types:Land
|
|||||||
A:AB$ GainLife | Cost$ T Sac<1/Creature> | LifeAmount$ X | SpellDescription$ You gain life equal to the sacrificed creature's toughness.
|
A:AB$ GainLife | Cost$ T Sac<1/Creature> | LifeAmount$ X | SpellDescription$ You gain life equal to the sacrificed creature's toughness.
|
||||||
SVar:X:Sacrificed$CardToughness
|
SVar:X:Sacrificed$CardToughness
|
||||||
DeckHas:Ability$Sacrifice
|
DeckHas:Ability$Sacrifice
|
||||||
AI:RemoveDeck:All
|
|
||||||
Oracle:{T}, Sacrifice a creature: You gain life equal to the sacrificed creature's toughness.
|
Oracle:{T}, Sacrifice a creature: You gain life equal to the sacrificed creature's toughness.
|
||||||
|
|||||||
@@ -4,5 +4,4 @@ Types:Creature Human Cleric
|
|||||||
PT:1/1
|
PT:1/1
|
||||||
A:AB$ GainLife | Cost$ 1 Sac<1/Creature> | Defined$ You | LifeAmount$ X | SpellDescription$ You gain life equal to the sacrificed creature's toughness.
|
A:AB$ GainLife | Cost$ 1 Sac<1/Creature> | Defined$ You | LifeAmount$ X | SpellDescription$ You gain life equal to the sacrificed creature's toughness.
|
||||||
SVar:X:Sacrificed$CardToughness
|
SVar:X:Sacrificed$CardToughness
|
||||||
AI:RemoveDeck:All
|
|
||||||
Oracle:{1}, Sacrifice a creature: You gain life equal to the sacrificed creature's toughness.
|
Oracle:{1}, Sacrifice a creature: You gain life equal to the sacrificed creature's toughness.
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
Name:Hallowed Moonlight
|
Name:Hallowed Moonlight
|
||||||
ManaCost:1 W
|
ManaCost:1 W
|
||||||
Types:Instant
|
Types:Instant
|
||||||
A:SP$ Effect | Cost$ 1 W | ReplacementEffects$ ReplaceExile | SubAbility$ DBDraw | SpellDescription$ Until end of turn, if a creature would enter the battlefield and it wasn't cast, exile it instead. Draw a card.
|
A:SP$ Effect | Cost$ 1 W | ReplacementEffects$ ReplaceExile | SubAbility$ DBDraw | AILogic$ NonCastCreature | SpellDescription$ Until end of turn, if a creature would enter the battlefield and it wasn't cast, exile it instead. Draw a card.
|
||||||
SVar:ReplaceExile:Event$ Moved | ActiveZones$ Command | Destination$ Battlefield | ValidCard$ Creature.wasNotCast | ReplaceWith$ DBExile | Description$ If a creature would enter the battlefield and it wasn't cast, exile it instead.
|
SVar:ReplaceExile:Event$ Moved | ActiveZones$ Command | Destination$ Battlefield | ValidCard$ Creature.wasNotCast | ReplaceWith$ DBExile | Description$ If a creature would enter the battlefield and it wasn't cast, exile it instead.
|
||||||
SVar:DBExile:DB$ ChangeZone | Hidden$ True | Origin$ All | Destination$ Exile | Defined$ ReplacedCard
|
SVar:DBExile:DB$ ChangeZone | Hidden$ True | Origin$ All | Destination$ Exile | Defined$ ReplacedCard
|
||||||
SVar:DBDraw:DB$ Draw | NumCards$ 1
|
SVar:DBDraw:DB$ Draw | NumCards$ 1
|
||||||
AI:RemoveDeck:All
|
|
||||||
Oracle:Until end of turn, if a creature would enter the battlefield and it wasn't cast, exile it instead.\nDraw a card.
|
Oracle:Until end of turn, if a creature would enter the battlefield and it wasn't cast, exile it instead.\nDraw a card.
|
||||||
|
|||||||
@@ -3,5 +3,4 @@ ManaCost:no cost
|
|||||||
Types:Land
|
Types:Land
|
||||||
A:AB$ Mana | Cost$ T | Produced$ C | SpellDescription$ Add {C}.
|
A:AB$ Mana | Cost$ T | Produced$ C | SpellDescription$ Add {C}.
|
||||||
A:AB$ GainLife | Cost$ T Sac<1/Creature> | LifeAmount$ 1 | SpellDescription$ You gain 1 life.
|
A:AB$ GainLife | Cost$ T Sac<1/Creature> | LifeAmount$ 1 | SpellDescription$ You gain 1 life.
|
||||||
AI:RemoveDeck:All
|
|
||||||
Oracle:{T}: Add {C}.\n{T}, Sacrifice a creature: You gain 1 life.
|
Oracle:{T}: Add {C}.\n{T}, Sacrifice a creature: You gain 1 life.
|
||||||
|
|||||||
@@ -3,6 +3,6 @@ ManaCost:3 R R
|
|||||||
Types:Creature Cyclops
|
Types:Creature Cyclops
|
||||||
PT:5/4
|
PT:5/4
|
||||||
A:AB$ DealDamage | Cost$ 1 Sac<1/Creature.Other/another creature> | ValidTgts$ Any | NumDmg$ 1 | SpellDescription$ CARDNAME deals 1 damage to any target.
|
A:AB$ DealDamage | Cost$ 1 Sac<1/Creature.Other/another creature> | ValidTgts$ Any | NumDmg$ 1 | SpellDescription$ CARDNAME deals 1 damage to any target.
|
||||||
SVar:AICostPreference:SacCost$Creature.Token,Creature.cmcLE2
|
SVar:AIPreference:SacCost$Creature.Token,Creature.cmcLE2
|
||||||
AI:RemoveDeck:Random
|
AI:RemoveDeck:Random
|
||||||
Oracle:{1}, Sacrifice another creature: Hurler Cyclops deals 1 damage to any target.
|
Oracle:{1}, Sacrifice another creature: Hurler Cyclops deals 1 damage to any target.
|
||||||
@@ -6,5 +6,4 @@ K:Defender
|
|||||||
A:AB$ GainLife | Cost$ 1 G Sac<1/Creature.Other/another creature> | LifeAmount$ X | SubAbility$ DBCleanup | SpellDescription$ You gain life equal to the sacrificed creature's toughness.
|
A:AB$ GainLife | Cost$ 1 G Sac<1/Creature.Other/another creature> | LifeAmount$ X | SubAbility$ DBCleanup | SpellDescription$ You gain life equal to the sacrificed creature's toughness.
|
||||||
SVar:X:Sacrificed$CardToughness
|
SVar:X:Sacrificed$CardToughness
|
||||||
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
|
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
|
||||||
AI:RemoveDeck:All
|
|
||||||
Oracle:Defender\n{1}{G}, Sacrifice another creature: You gain life equal to the sacrificed creature's toughness.
|
Oracle:Defender\n{1}{G}, Sacrifice another creature: You gain life equal to the sacrificed creature's toughness.
|
||||||
|
|||||||
@@ -4,5 +4,4 @@ Types:Legendary Land
|
|||||||
A:AB$ Mana | Cost$ T | Produced$ C | SpellDescription$ Add {C}.
|
A:AB$ Mana | Cost$ T | Produced$ C | SpellDescription$ Add {C}.
|
||||||
A:AB$ GainLife | Cost$ 3 T Sac<1/Creature> | LifeAmount$ X | SpellDescription$ You gain life equal to the sacrificed creature's toughness.
|
A:AB$ GainLife | Cost$ 3 T Sac<1/Creature> | LifeAmount$ X | SpellDescription$ You gain life equal to the sacrificed creature's toughness.
|
||||||
SVar:X:Sacrificed$CardToughness
|
SVar:X:Sacrificed$CardToughness
|
||||||
AI:RemoveDeck:All
|
|
||||||
Oracle:{T}: Add {C}.\n{3}, {T}, Sacrifice a creature: You gain life equal to the sacrificed creature's toughness.
|
Oracle:{T}: Add {C}.\n{3}, {T}, Sacrifice a creature: You gain life equal to the sacrificed creature's toughness.
|
||||||
|
|||||||
@@ -3,5 +3,5 @@ ManaCost:B
|
|||||||
Types:Sorcery
|
Types:Sorcery
|
||||||
S:Mode$ Continuous | CharacteristicDefining$ True | AddKeyword$ Flash | IsPresent$ Permanent.YouCtrl+hasKeywordFlash | Description$ CARDNAME has flash as long as you control a permanent with flash.
|
S:Mode$ Continuous | CharacteristicDefining$ True | AddKeyword$ Flash | IsPresent$ Permanent.YouCtrl+hasKeywordFlash | Description$ CARDNAME has flash as long as you control a permanent with flash.
|
||||||
A:SP$ Destroy | Cost$ B Sac<1/Creature> | ValidTgts$ Creature | TgtPrompt$ Select target creature | SpellDescription$ Destroy target creature.
|
A:SP$ Destroy | Cost$ B Sac<1/Creature> | ValidTgts$ Creature | TgtPrompt$ Select target creature | SpellDescription$ Destroy target creature.
|
||||||
SVar:AICostPreference:SacCost$Creature.Token,Creature.cmcLE2
|
SVar:AIPreference:SacCost$Creature.Token,Creature.cmcLE2
|
||||||
Oracle:This spell has flash as long as you control a permanent with flash.\nAs an additional cost to cast this spell, sacrifice a creature.\nDestroy target creature.
|
Oracle:This spell has flash as long as you control a permanent with flash.\nAs an additional cost to cast this spell, sacrifice a creature.\nDestroy target creature.
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ T:Mode$ ChangesZone | ValidCard$ Card.wasCastByYou+Self | Destination$ Battlefie
|
|||||||
SVar:TrigPump:DB$ Pump | Defined$ You | Duration$ UntilYourNextTurn | KW$ Protection from everything
|
SVar:TrigPump:DB$ Pump | Defined$ You | Duration$ UntilYourNextTurn | KW$ Protection from everything
|
||||||
T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | Execute$ TrigLoseLife | TriggerZones$ Battlefield | TriggerDescription$ At the beginning of your upkeep, you lose 1 life for each burden counter on CARDNAME.
|
T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | Execute$ TrigLoseLife | TriggerZones$ Battlefield | TriggerDescription$ At the beginning of your upkeep, you lose 1 life for each burden counter on CARDNAME.
|
||||||
SVar:TrigLoseLife:DB$ LoseLife | LifeAmount$ X
|
SVar:TrigLoseLife:DB$ LoseLife | LifeAmount$ X
|
||||||
A:AB$ PutCounter | Cost$ 1 T | Defined$ Self | CounterType$ BURDEN | CounterNum$ 1 | SubAbility$ DBDraw | SpellDescription$ Put a burden counter on CARDNAME, then draw a card for each burden counter on CARDNAME.
|
A:AB$ PutCounter | Cost$ 1 T | Defined$ Self | CounterType$ BURDEN | CounterNum$ 1 | SubAbility$ DBDraw | AILogic$ TheOneRing | SpellDescription$ Put a burden counter on CARDNAME, then draw a card for each burden counter on CARDNAME.
|
||||||
SVar:DBDraw:DB$ Draw | Defined$ You | NumCards$ X
|
SVar:DBDraw:DB$ Draw | Defined$ You | NumCards$ X
|
||||||
SVar:X:Count$CardCounters.BURDEN
|
SVar:X:Count$CardCounters.BURDEN
|
||||||
Oracle:Indestructible\nWhen The One Ring enters the battlefield, if you cast it, you gain protection from everything until your next turn.\nAt the beginning of your upkeep, you lose 1 life for each burden counter on The One Ring.\n{1}, {T}: Put a burden counter on The One Ring, then draw a card for each burden counter on The One Ring.
|
Oracle:Indestructible\nWhen The One Ring enters the battlefield, if you cast it, you gain protection from everything until your next turn.\nAt the beginning of your upkeep, you lose 1 life for each burden counter on The One Ring.\n{1}, {T}: Put a burden counter on The One Ring, then draw a card for each burden counter on The One Ring.
|
||||||
|
|||||||
@@ -4,5 +4,5 @@ Types:Creature Kor Artificer
|
|||||||
PT:1/2
|
PT:1/2
|
||||||
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChange | OptionalDecider$ You | TriggerDescription$ When CARDNAME enters the battlefield, you may search your library for an Equipment card, reveal it, put it into your hand, then shuffle.
|
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChange | OptionalDecider$ You | TriggerDescription$ When CARDNAME enters the battlefield, you may search your library for an Equipment card, reveal it, put it into your hand, then shuffle.
|
||||||
SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Card.Equipment | ChangeNum$ 1 | ShuffleNonMandatory$ True
|
SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Card.Equipment | ChangeNum$ 1 | ShuffleNonMandatory$ True
|
||||||
A:AB$ ChangeZone | Cost$ 1 W T | Origin$ Hand | Destination$ Battlefield | ChangeType$ Equipment | ChangeNum$ 1 | SpellDescription$ You may put an Equipment card from your hand onto the battlefield.
|
A:AB$ ChangeZone | Cost$ 1 W T | Origin$ Hand | Destination$ Battlefield | ChangeType$ Equipment | ChangeNum$ 1 | AILogic$ Main1 | SpellDescription$ You may put an Equipment card from your hand onto the battlefield.
|
||||||
Oracle:When Stoneforge Mystic enters the battlefield, you may search your library for an Equipment card, reveal it, put it into your hand, then shuffle.\n{1}{W}, {T}: You may put an Equipment card from your hand onto the battlefield.
|
Oracle:When Stoneforge Mystic enters the battlefield, you may search your library for an Equipment card, reveal it, put it into your hand, then shuffle.\n{1}{W}, {T}: You may put an Equipment card from your hand onto the battlefield.
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ T:Mode$ ChangesZone | ValidCard$ Card.wasCastByYou+Self | Destination$ Battlefie
|
|||||||
SVar:TrigPump:DB$ Pump | Defined$ You | Duration$ UntilYourNextTurn | KW$ Protection from everything
|
SVar:TrigPump:DB$ Pump | Defined$ You | Duration$ UntilYourNextTurn | KW$ Protection from everything
|
||||||
T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | Execute$ TrigLoseLife | TriggerZones$ Battlefield | TriggerDescription$ At the beginning of your upkeep, you lose 1 life for each burden counter on CARDNAME.
|
T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | Execute$ TrigLoseLife | TriggerZones$ Battlefield | TriggerDescription$ At the beginning of your upkeep, you lose 1 life for each burden counter on CARDNAME.
|
||||||
SVar:TrigLoseLife:DB$ LoseLife | LifeAmount$ X
|
SVar:TrigLoseLife:DB$ LoseLife | LifeAmount$ X
|
||||||
A:AB$ PutCounter | Cost$ T | Defined$ Self | CounterType$ BURDEN | CounterNum$ 1 | SubAbility$ DBDraw | SpellDescription$ Put a burden counter on CARDNAME, then draw a card for each burden counter on CARDNAME.
|
A:AB$ PutCounter | Cost$ T | Defined$ Self | CounterType$ BURDEN | CounterNum$ 1 | SubAbility$ DBDraw | AILogic$ TheOneRing | SpellDescription$ Put a burden counter on CARDNAME, then draw a card for each burden counter on CARDNAME.
|
||||||
SVar:DBDraw:DB$ Draw | Defined$ You | NumCards$ X
|
SVar:DBDraw:DB$ Draw | Defined$ You | NumCards$ X
|
||||||
SVar:X:Count$CardCounters.BURDEN
|
SVar:X:Count$CardCounters.BURDEN
|
||||||
Oracle:Indestructible\nWhen The One Ring enters the battlefield, if you cast it, you gain protection from everything until your next turn.\nAt the beginning of your upkeep, you lose 1 life for each burden counter on The One Ring.\n{T}: Put a burden counter on The One Ring, then draw a card for each burden counter on The One Ring.
|
Oracle:Indestructible\nWhen The One Ring enters the battlefield, if you cast it, you gain protection from everything until your next turn.\nAt the beginning of your upkeep, you lose 1 life for each burden counter on The One Ring.\n{T}: Put a burden counter on The One Ring, then draw a card for each burden counter on The One Ring.
|
||||||
|
|||||||
@@ -4,5 +4,4 @@ Types:Enchantment
|
|||||||
A:AB$ GainLife | Cost$ 1 B Sac<1/Creature> | Defined$ You | LifeAmount$ 1 | SubAbility$ DBDraw | SpellDescription$ You gain 1 life and draw a card.
|
A:AB$ GainLife | Cost$ 1 B Sac<1/Creature> | Defined$ You | LifeAmount$ 1 | SubAbility$ DBDraw | SpellDescription$ You gain 1 life and draw a card.
|
||||||
SVar:DBDraw:DB$ Draw | NumCards$ 1
|
SVar:DBDraw:DB$ Draw | NumCards$ 1
|
||||||
SVar:NonStackingEffect:True
|
SVar:NonStackingEffect:True
|
||||||
AI:RemoveDeck:All
|
|
||||||
Oracle:{1}{B}, Sacrifice a creature: You gain 1 life and draw a card.
|
Oracle:{1}{B}, Sacrifice a creature: You gain 1 life and draw a card.
|
||||||
|
|||||||
@@ -7,6 +7,6 @@ T:Mode$ LifeGained | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ Tri
|
|||||||
SVar:TrigPutCounter:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ 1
|
SVar:TrigPutCounter:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ 1
|
||||||
A:AB$ LoseLife | Cost$ 5 B B Sac<1/CARDNAME> | LifeAmount$ X | ValidTgts$ Player | TgtPrompt$ Select a player | SpellDescription$ Target player loses X life, where X is CARDNAME's power.
|
A:AB$ LoseLife | Cost$ 5 B B Sac<1/CARDNAME> | LifeAmount$ X | ValidTgts$ Player | TgtPrompt$ Select a player | SpellDescription$ Target player loses X life, where X is CARDNAME's power.
|
||||||
SVar:X:Sacrificed$CardPower
|
SVar:X:Sacrificed$CardPower
|
||||||
AI:RemoveDeck:All
|
|
||||||
AI:RemoveDeck:Random
|
AI:RemoveDeck:Random
|
||||||
|
AI:RemoveDeck:All
|
||||||
Oracle:Defender (This creature can't attack.)\nWhenever you gain life, put a +1/+1 counter on Wall of Limbs.\n{5}{B}{B}, Sacrifice Wall of Limbs: Target player loses X life, where X is Wall of Limbs's power.
|
Oracle:Defender (This creature can't attack.)\nWhenever you gain life, put a +1/+1 counter on Wall of Limbs.\n{5}{B}{B}, Sacrifice Wall of Limbs: Target player loses X life, where X is Wall of Limbs's power.
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ Name:Winds of Abandon
|
|||||||
ManaCost:1 W
|
ManaCost:1 W
|
||||||
Types:Sorcery
|
Types:Sorcery
|
||||||
A:SP$ ChangeZone | Cost$ 1 W | Origin$ Battlefield | Destination$ Exile | ValidTgts$ Creature.YouDontCtrl | TgtPrompt$ Select target creature you don't control | SubAbility$ DBGetLandsAll | RememberLKI$ True | SpellDescription$ Exile target creature you don't control. For each creature exiled this way, its controller searches their library for a basic land card. Those players put those cards onto the battlefield tapped, then shuffle.
|
A:SP$ ChangeZone | Cost$ 1 W | Origin$ Battlefield | Destination$ Exile | ValidTgts$ Creature.YouDontCtrl | TgtPrompt$ Select target creature you don't control | SubAbility$ DBGetLandsAll | RememberLKI$ True | SpellDescription$ Exile target creature you don't control. For each creature exiled this way, its controller searches their library for a basic land card. Those players put those cards onto the battlefield tapped, then shuffle.
|
||||||
A:SP$ ChangeZoneAll | Cost$ 4 W W | ChangeType$ Creature.YouDontCtrl | Origin$ Battlefield | Destination$ Exile | RememberLKI$ True | SubAbility$ DBGetLandsAll | PrecostDesc$ Overload | CostDesc$ {4}{W}{W} | NonBasicSpell$ True | SpellDescription$ (You may cast this spell for its overload cost. If you do, change its text by replacing all instances of "target" with "each.")
|
A:SP$ ChangeZoneAll | Cost$ 4 W W | ChangeType$ Creature.YouDontCtrl | Origin$ Battlefield | Destination$ Exile | RememberLKI$ True | SubAbility$ DBGetLandsAll | PrecostDesc$ Overload | CostDesc$ {4}{W}{W} | NonBasicSpell$ True | AILogic$ Main1 | SpellDescription$ (You may cast this spell for its overload cost. If you do, change its text by replacing all instances of "target" with "each.")
|
||||||
SVar:DBGetLandsAll:DB$ RepeatEach | RepeatPlayers$ Player | RepeatSubAbility$ DBGetLandsOne | SubAbility$ DBCleanup
|
SVar:DBGetLandsAll:DB$ RepeatEach | RepeatPlayers$ Player | RepeatSubAbility$ DBGetLandsOne | SubAbility$ DBCleanup
|
||||||
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
|
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
|
||||||
SVar:DBGetLandsOne:DB$ ChangeZone | Optional$ True | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeNum$ X | DefinedPlayer$ Player.IsRemembered | ShuffleNonMandatory$ False | ConditionCheckSVar$ X | ConditionSVarCompare$ GE1
|
SVar:DBGetLandsOne:DB$ ChangeZone | Optional$ True | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeNum$ X | DefinedPlayer$ Player.IsRemembered | ShuffleNonMandatory$ False | ConditionCheckSVar$ X | ConditionSVarCompare$ GE1
|
||||||
|
|||||||
Reference in New Issue
Block a user