Merge branch 'Card-Forge:master' into master

This commit is contained in:
TabletopGeneral
2023-07-11 07:13:52 -04:00
committed by GitHub
64 changed files with 258 additions and 219 deletions

View File

@@ -277,7 +277,7 @@ public class AiBlockController {
if (mode == TriggerType.DamageDone) {
if (trigger.matchesValidParam("ValidSource", attacker)
&& attacker.getNetCombatDamage() > 0
&& !"False".equals(trigger.getParam("CombatDamage")) && attacker.getNetCombatDamage() > 0
&& trigger.matchesValidParam("ValidTarget", combat.getDefenderByAttacker(attacker))) {
value += 50;
}

View File

@@ -812,16 +812,11 @@ public class AiController {
for (Card tgt : tc.getTargetCards()) {
// TODO some older cards don't use the keyword, so check for trigger instead
if (tgt.hasKeyword(Keyword.WARD) && tgt.isInPlay() && tgt.getController().isOpponentOf(host.getController())) {
int amount = 0;
Cost wardCost = ComputerUtilCard.getTotalWardCost(tgt);
if (wardCost.hasManaCost()) {
amount = wardCost.getTotalMana().getCMC();
if (amount > 0 && !ComputerUtilCost.canPayCost(sa, player, true)) {
return AiPlayDecision.CantAfford;
}
xCost = wardCost.getTotalMana().getCMC() > 0;
}
SpellAbilityAi topAI = new SpellAbilityAi() {
};
SpellAbilityAi topAI = new SpellAbilityAi() {};
if (!topAI.willPayCosts(player, sa, wardCost, host)) {
return AiPlayDecision.CostNotAcceptable;
}
@@ -831,7 +826,7 @@ public class AiController {
}
// check if some target raised cost
if (oldCMC > -1) {
if (!xCost && oldCMC > -1) {
int finalCMC = CostAdjustment.adjust(sa.getPayCosts(), sa).getTotalMana().getCMC();
if (finalCMC > oldCMC) {
xCost = true;

View File

@@ -673,7 +673,6 @@ public class ComputerUtilCombat {
&& !blocker.hasKeyword(Keyword.INDESTRUCTIBLE)) {
return 0;
}
} // flanking
final int defBushidoMagnitude = blocker.getKeywordMagnitude(Keyword.BUSHIDO);
@@ -859,7 +858,7 @@ public class ComputerUtilCombat {
}
} else if (mode == TriggerType.DamageDone) {
willTrigger = true;
if (trigger.hasParam("ValidSource")) {
if (trigger.hasParam("ValidSource") && !"False".equals(trigger.getParam("CombatDamage"))) {
if (!(trigger.matchesValidParam("ValidSource", defender)
&& defender.getNetCombatDamage() > 0
&& trigger.matchesValidParam("ValidTarget", attacker))) {
@@ -2461,7 +2460,7 @@ public class ComputerUtilCombat {
CardCollectionView trigCards = attacker.getController().getCardsIn(ZoneType.Battlefield);
for (Card c : trigCards) {
for (Trigger t : c.getTriggers()) {
if (t.getMode() == TriggerType.DamageDone && "True".equals(t.getParam("CombatDamage")) && t.matchesValidParam("ValidSource", attacker)) {
if (t.getMode() == TriggerType.DamageDone && !"False".equals(t.getParam("CombatDamage")) && t.matchesValidParam("ValidSource", attacker)) {
SpellAbility ab = t.getOverridingAbility();
if (ab.getApi() == ApiType.Poison && "TriggeredTarget".equals(ab.getParam("Defined"))) {
pd += AbilityUtils.calculateAmount(attacker, ab.getParam("Num"), ab);

View File

@@ -22,6 +22,7 @@ import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.Spell;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetChoices;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
import forge.util.TextUtil;
@@ -556,12 +557,14 @@ public class ComputerUtilCost {
}
// Ward - will be accounted for when rechecking a targeted ability
if (!sa.isTrigger() && sa.usesTargeting() && (!sa.isSpell() || !cannotBeCountered)) {
for (Card tgt : sa.getTargets().getTargetCards()) {
if (tgt.hasKeyword(Keyword.WARD) && tgt.isInPlay() && tgt.getController().isOpponentOf(sa.getHostCard().getController())) {
Cost wardCost = ComputerUtilCard.getTotalWardCost(tgt);
if (wardCost.hasManaCost()) {
extraManaNeeded += wardCost.getTotalMana().getCMC();
if (!sa.isTrigger() && (!sa.isSpell() || !cannotBeCountered)) {
for (TargetChoices tc : sa.getAllTargetChoices()) {
for (Card tgt : tc.getTargetCards()) {
if (tgt.hasKeyword(Keyword.WARD) && tgt.isInPlay() && tgt.getController().isOpponentOf(sa.getHostCard().getController())) {
Cost wardCost = ComputerUtilCard.getTotalWardCost(tgt);
if (wardCost.hasManaCost()) {
extraManaNeeded += wardCost.getTotalMana().getCMC();
}
}
}
}

View File

@@ -86,7 +86,7 @@ public class AnimateAi extends SpellAbilityAi {
}
}
// Don't use instant speed animate abilities before AI's COMBAT_BEGIN
if (!ph.is(PhaseType.COMBAT_BEGIN) && ph.isPlayerTurn(ai) && !SpellAbilityAi.isSorcerySpeed(sa, ai)
if (!ph.is(PhaseType.COMBAT_BEGIN) && ph.isPlayerTurn(ai) && !isSorcerySpeed(sa, ai)
&& !sa.hasParam("ActivationPhases") && !"Permanent".equals(sa.getParam("Duration"))) {
return false;
}
@@ -174,7 +174,7 @@ public class AnimateAi extends SpellAbilityAi {
}
}
if (!SpellAbilityAi.isSorcerySpeed(sa, aiPlayer) && !"Permanent".equals(sa.getParam("Duration"))) {
if (!isSorcerySpeed(sa, aiPlayer) && !"Permanent".equals(sa.getParam("Duration"))) {
if (sa.hasParam("Crew") && c.isCreature()) {
// Do not try to crew a vehicle which is already a creature
return false;

View File

@@ -1069,7 +1069,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
}
if (!immediately && (!game.getPhaseHandler().getNextTurn().equals(ai)
|| game.getPhaseHandler().getPhase().isBefore(PhaseType.END_OF_TURN))
&& !sa.hasParam("PlayerTurn") && !SpellAbilityAi.isSorcerySpeed(sa, ai)
&& !sa.hasParam("PlayerTurn") && !isSorcerySpeed(sa, ai)
&& !ComputerUtil.activateForCost(sa, ai)) {
return false;
}

View File

@@ -242,7 +242,7 @@ public class CloneAi extends SpellAbilityAi {
// don't use instant speed clone abilities outside computers
// Combat_Begin step
if (!ph.is(PhaseType.COMBAT_BEGIN)
&& ph.isPlayerTurn(ai) && !SpellAbilityAi.isSorcerySpeed(sa, ai)
&& ph.isPlayerTurn(ai) && !isSorcerySpeed(sa, ai)
&& !sa.hasParam("ActivationPhases") && sa.hasParam("Duration")) {
return false;
}

View File

@@ -123,7 +123,7 @@ public class CounterAi extends SpellAbilityAi {
if (toPay <= usableManaSources) {
// If this is a reusable Resource, feel free to play it most of the time
if (!SpellAbilityAi.playReusable(ai, sa)) {
if (!playReusable(ai, sa)) {
return false;
}
}
@@ -287,7 +287,7 @@ public class CounterAi extends SpellAbilityAi {
if (toPay <= usableManaSources) {
// If this is a reusable Resource, feel free to play it most
// of the time
if (!SpellAbilityAi.playReusable(ai,sa) || (MyRandom.getRandom().nextFloat() < .4)) {
if (!playReusable(ai,sa) || (MyRandom.getRandom().nextFloat() < .4)) {
return false;
}
}

View File

@@ -36,7 +36,7 @@ public class CountersMoveAi extends SpellAbilityAi {
}
}
if (!SpellAbilityAi.playReusable(ai, sa)) {
if (!playReusable(ai, sa)) {
return false;
}

View File

@@ -437,7 +437,7 @@ public class CountersPutAi extends CountersAi {
}
if (sa.usesTargeting()) {
if (!ai.getGame().getStack().isEmpty() && !SpellAbilityAi.isSorcerySpeed(sa, ai)) {
if (!ai.getGame().getStack().isEmpty() && !isSorcerySpeed(sa, ai)) {
// only evaluates case where all tokens are placed on a single target
if (sa.getMinTargets() < 2) {
if (ComputerUtilCard.canPumpAgainstRemoval(ai, sa)) {
@@ -550,7 +550,7 @@ public class CountersPutAi extends CountersAi {
if (sa.isCurse()) {
choice = chooseCursedTarget(list, type, amount, ai);
} else {
if (type.equals("P1P1") && !SpellAbilityAi.isSorcerySpeed(sa, ai)) {
if (type.equals("P1P1") && !isSorcerySpeed(sa, ai)) {
for (Card c : list) {
if (ComputerUtilCard.shouldPumpCard(ai, sa, c, amount, amount, Lists.newArrayList())) {
choice = c;
@@ -623,7 +623,7 @@ public class CountersPutAi extends CountersAi {
return false;
}
// Instant +1/+1
if (type.equals("P1P1") && !SpellAbilityAi.isSorcerySpeed(sa, ai)) {
if (type.equals("P1P1") && !isSorcerySpeed(sa, ai)) {
if (!(ph.getNextTurn() == ai && ph.is(PhaseType.END_OF_TURN) && abCost.isReusuableResource())) {
return false; // only if next turn and cost is reusable
}

View File

@@ -137,7 +137,7 @@ public class CountersPutAllAi extends SpellAbilityAi {
}
}
if (SpellAbilityAi.playReusable(ai, sa)) {
if (playReusable(ai, sa)) {
return chance;
}

View File

@@ -1,14 +1,11 @@
package forge.ai.ability;
import com.google.common.collect.Iterables;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCombat;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.card.Card;
import forge.game.card.CardCollectionView;
import forge.game.card.CardPredicates;
import forge.game.keyword.Keyword;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
@@ -46,11 +43,6 @@ public abstract class DamageAiBase extends SpellAbilityAi {
}
protected boolean shouldTgtP(final Player comp, final SpellAbility sa, final int d, final boolean noPrevention) {
// TODO: once the "planeswalker redirection" rule is removed completely, just remove this code and
// remove the "burn Planeswalkers" code in the called method.
return shouldTgtP(comp, sa, d, noPrevention, false);
}
protected boolean shouldTgtP(final Player comp, final SpellAbility sa, final int d, final boolean noPrevention, final boolean noPlaneswalkerRedirection) {
int restDamage = d;
final Game game = comp.getGame();
Player enemy = comp.getWeakestOpponent();
@@ -89,13 +81,6 @@ public abstract class DamageAiBase extends SpellAbilityAi {
}
}
// burn Planeswalkers
// TODO: Must be removed completely when the "planeswalker redirection" rule is removed.
if (!noPlaneswalkerRedirection
&& Iterables.any(enemy.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.PLANESWALKERS)) {
return true;
}
if (avoidTargetP(comp, sa)) {
return false;
}
@@ -131,7 +116,7 @@ public abstract class DamageAiBase extends SpellAbilityAi {
// chance to burn player based on current hand size
if (hand.size() > 2) {
float value = 0;
if (SpellAbilityAi.isSorcerySpeed(sa, comp)) {
if (isSorcerySpeed(sa, comp)) {
//lower chance for sorcery as other spells may be cast in main2
if (phase.isPlayerTurn(comp) && phase.is(PhaseType.MAIN2)) {
value = 1.0f * restDamage / enemy.getLife();

View File

@@ -1,28 +1,10 @@
package forge.ai.ability;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import forge.ai.AiController;
import forge.ai.AiProps;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilAbility;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCombat;
import forge.ai.ComputerUtilCost;
import forge.ai.ComputerUtilMana;
import forge.ai.PlayerControllerAi;
import forge.ai.SpecialCardAi;
import forge.ai.SpellAbilityAi;
import forge.ai.*;
import forge.card.MagicColor;
import forge.card.mana.ManaCost;
import forge.game.Game;
@@ -30,11 +12,7 @@ import forge.game.GameEntity;
import forge.game.GameObject;
import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.card.CounterEnumType;
import forge.game.card.*;
import forge.game.cost.Cost;
import forge.game.cost.CostPartMana;
import forge.game.keyword.Keyword;
@@ -51,6 +29,12 @@ import forge.game.staticability.StaticAbilityMustTarget;
import forge.game.zone.ZoneType;
import forge.util.Aggregates;
import forge.util.MyRandom;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
public class DamageDealAi extends DamageAiBase {
@Override
@@ -630,7 +614,7 @@ public class DamageDealAi extends DamageAiBase {
if (tgt.canTgtPlaneswalker()) {
// We can damage planeswalkers with this, consider targeting.
Card c = dealDamageChooseTgtPW(ai, sa, dmg, noPrevention, enemy, false);
if (c != null && !shouldTgtP(ai, sa, dmg, noPrevention, true)) {
if (c != null && !shouldTgtP(ai, sa, dmg, noPrevention)) {
tcs.add(c);
if (divided) {
int assignedDamage = ComputerUtilCombat.getEnoughDamageToKill(c, dmg, source, false, noPrevention);
@@ -746,17 +730,17 @@ public class DamageDealAi extends DamageAiBase {
}
return false;
}
// TODO: Improve Damage, we shouldn't just target the player just because we can
if (sa.canTarget(enemy) && sa.canAddMoreTarget()) {
if (((phase.is(PhaseType.END_OF_TURN) && phase.getNextTurn().equals(ai))
|| (SpellAbilityAi.isSorcerySpeed(sa, ai) && phase.is(PhaseType.MAIN2))
|| ("PingAfterAttack".equals(logic) && phase.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS) && phase.isPlayerTurn(ai))
|| immediately || shouldTgtP(ai, sa, dmg, noPrevention)) &&
(!avoidTargetP(ai, sa))) {
tcs.add(enemy);
if (divided) {
sa.addDividedAllocation(enemy, dmg);
break;
if ((phase.is(PhaseType.END_OF_TURN) && phase.getNextTurn().equals(ai))
|| (isSorcerySpeed(sa, ai) && phase.is(PhaseType.MAIN2))
|| immediately) {
boolean pingAfterAttack = "PingAfterAttack".equals(logic) && phase.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS) && phase.isPlayerTurn(ai);
if ((pingAfterAttack && !avoidTargetP(ai, sa)) || shouldTgtP(ai, sa, dmg, noPrevention)) {
tcs.add(enemy);
if (divided) {
sa.addDividedAllocation(enemy, dmg);
break;
}
}
}
}
@@ -836,7 +820,7 @@ public class DamageDealAi extends DamageAiBase {
if (!positive && !(saMe instanceof AbilitySub)) {
return false;
}
if (!urgent && !SpellAbilityAi.playReusable(ai, saMe)) {
if (!urgent && !playReusable(ai, saMe)) {
return false;
}
return true;

View File

@@ -16,7 +16,7 @@ public class DayTimeAi extends SpellAbilityAi {
if ((sa.getHostCard().isCreature() && sa.getPayCosts().hasTapCost()) || sa.getPayCosts().hasManaCost()) {
// If it involves a cost that may put us at a disadvantage, better activate before own turn if possible
if (!SpellAbilityAi.isSorcerySpeed(sa, aiPlayer)) {
if (!isSorcerySpeed(sa, aiPlayer)) {
return ph.is(PhaseType.END_OF_TURN) && ph.getNextTurn() == aiPlayer;
} else {
return ph.is(PhaseType.MAIN2, aiPlayer); // Give other things a chance to be cast (e.g. Celestus)

View File

@@ -60,7 +60,7 @@ public class DebuffAi extends SpellAbilityAi {
|| !game.getStack().isEmpty()) {
// Instant-speed pumps should not be cast outside of combat when the
// stack is empty, unless there are specific activation phase requirements
if (!SpellAbilityAi.isSorcerySpeed(sa, ai) && !sa.hasParam("ActivationPhases")) {
if (!isSorcerySpeed(sa, ai) && !sa.hasParam("ActivationPhases")) {
return false;
}
}

View File

@@ -174,7 +174,7 @@ public class DestroyAi extends SpellAbilityAi {
if (CardLists.getNotType(list, "Creature").isEmpty()) {
list = ComputerUtilCard.prioritizeCreaturesWorthRemovingNow(ai, list, false);
}
if (!SpellAbilityAi.playReusable(ai, sa)) {
if (!playReusable(ai, sa)) {
list = CardLists.filter(list, Predicates.not(CardPredicates.hasCounter(CounterEnumType.SHIELD, 1)));
list = CardLists.filter(list, new Predicate<Card>() {

View File

@@ -101,13 +101,13 @@ public class DigAi extends SpellAbilityAi {
}
}
if (SpellAbilityAi.playReusable(ai, sa)) {
if (playReusable(ai, sa)) {
return true;
}
if ((!game.getPhaseHandler().getNextTurn().equals(ai)
|| game.getPhaseHandler().getPhase().isBefore(PhaseType.END_OF_TURN))
&& !sa.hasParam("PlayerTurn") && !SpellAbilityAi.isSorcerySpeed(sa, ai)
&& !sa.hasParam("PlayerTurn") && !isSorcerySpeed(sa, ai)
&& (ai.getCardsIn(ZoneType.Hand).size() > 1 || game.getPhaseHandler().getPhase().isBefore(PhaseType.DRAW))
&& !ComputerUtil.activateForCost(sa, ai)) {
return false;

View File

@@ -62,13 +62,13 @@ public class DigMultipleAi extends SpellAbilityAi {
return false;
}
if (SpellAbilityAi.playReusable(ai, sa)) {
if (playReusable(ai, sa)) {
return true;
}
if ((!game.getPhaseHandler().getNextTurn().equals(ai)
|| game.getPhaseHandler().getPhase().isBefore(PhaseType.END_OF_TURN))
&& !sa.hasParam("PlayerTurn") && !SpellAbilityAi.isSorcerySpeed(sa, ai)
&& !sa.hasParam("PlayerTurn") && !isSorcerySpeed(sa, ai)
&& (ai.getCardsIn(ZoneType.Hand).size() > 1 || game.getPhaseHandler().getPhase().isBefore(PhaseType.DRAW))
&& !ComputerUtil.activateForCost(sa, ai)) {
return false;

View File

@@ -23,7 +23,7 @@ public class DigUntilAi extends SpellAbilityAi {
Card source = sa.getHostCard();
final String logic = sa.getParamOrDefault("AILogic", "");
double chance = .4; // 40 percent chance with instant speed stuff
if (SpellAbilityAi.isSorcerySpeed(sa, ai)) {
if (isSorcerySpeed(sa, ai)) {
chance = .667; // 66.7% chance for sorcery speed (since it will
// never activate EOT)
}

View File

@@ -168,7 +168,7 @@ public class DrawAi extends SpellAbilityAi {
@Override
protected boolean checkPhaseRestrictions(Player ai, SpellAbility sa, PhaseHandler ph, String logic) {
if ((!ph.getNextTurn().equals(ai) || ph.getPhase().isBefore(PhaseType.END_OF_TURN))
&& !sa.hasParam("PlayerTurn") && !SpellAbilityAi.isSorcerySpeed(sa, ai)
&& !sa.hasParam("PlayerTurn") && !isSorcerySpeed(sa, ai)
&& ai.getCardsIn(ZoneType.Hand).size() > 1 && !ComputerUtil.activateForCost(sa, ai)
&& !"YawgmothsBargain".equals(logic)) {
return false;

View File

@@ -20,7 +20,7 @@ public class FlipOntoBattlefieldAi extends SpellAbilityAi {
PhaseHandler ph = sa.getHostCard().getGame().getPhaseHandler();
String logic = sa.getParamOrDefault("AILogic", "");
if (!SpellAbilityAi.isSorcerySpeed(sa, aiPlayer) && sa.getPayCosts().hasManaCost()) {
if (!isSorcerySpeed(sa, aiPlayer) && sa.getPayCosts().hasManaCost()) {
return ph.is(PhaseType.END_OF_TURN);
}

View File

@@ -111,7 +111,7 @@ public class LifeGainAi extends SpellAbilityAi {
return lifeCritical || activateForCost
|| (ph.getNextTurn().equals(ai) && !ph.getPhase().isBefore(PhaseType.END_OF_TURN))
|| sa.hasParam("PlayerTurn") || SpellAbilityAi.isSorcerySpeed(sa, ai);
|| sa.hasParam("PlayerTurn") || isSorcerySpeed(sa, ai);
}
/*
@@ -180,8 +180,8 @@ public class LifeGainAi extends SpellAbilityAi {
return true;
}
if (SpellAbilityAi.isSorcerySpeed(sa, ai)
|| sa.getSubAbility() != null || SpellAbilityAi.playReusable(ai, sa)) {
if (isSorcerySpeed(sa, ai)
|| sa.getSubAbility() != null || playReusable(ai, sa)) {
return true;
}

View File

@@ -144,7 +144,7 @@ public class LifeLoseAi extends SpellAbilityAi {
return false;
}
if (SpellAbilityAi.isSorcerySpeed(sa, ai) || sa.hasParam("ActivationPhases") || SpellAbilityAi.playReusable(ai, sa)
if (isSorcerySpeed(sa, ai) || sa.hasParam("ActivationPhases") || playReusable(ai, sa)
|| ComputerUtil.activateForCost(sa, ai)) {
return true;
}

View File

@@ -61,7 +61,7 @@ public class ManifestAi extends SpellAbilityAi {
if (!buff) {
return false;
}
} else if (!SpellAbilityAi.isSorcerySpeed(sa, ai)) {
} else if (!isSorcerySpeed(sa, ai)) {
return false;
}
} else {
@@ -152,7 +152,7 @@ public class ManifestAi extends SpellAbilityAi {
}
}
// Probably should be a little more discerning on playing during OPPs turn
if (SpellAbilityAi.playReusable(ai, sa)) {
if (playReusable(ai, sa)) {
return true;
}
if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_ATTACKERS)) {

View File

@@ -51,7 +51,7 @@ public class MillAi extends SpellAbilityAi {
return (ph.is(PhaseType.MAIN1) || ph.is(PhaseType.MAIN2)) && ph.isPlayerTurn(ai); // Chandra, Torch of Defiance and similar
}
if (!sa.isPwAbility()) { // Planeswalker abilities are only activated at sorcery speed
if ("You".equals(sa.getParam("Defined")) && !(!SpellAbilityAi.isSorcerySpeed(sa, ai) && ph.is(PhaseType.END_OF_TURN)
if ("You".equals(sa.getParam("Defined")) && !(!isSorcerySpeed(sa, ai) && ph.is(PhaseType.END_OF_TURN)
&& ph.getNextTurn().equals(ai))) {
return false; // only self-mill at opponent EOT
}

View File

@@ -1,21 +1,15 @@
package forge.ai.ability;
import org.apache.commons.lang3.StringUtils;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCost;
import forge.ai.ComputerUtilMana;
import forge.ai.SpellAbilityAi;
import forge.card.CardStateName;
import forge.card.CardType.Supertype;
import forge.card.mana.ManaCost;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.card.*;
import forge.game.cost.Cost;
import forge.game.keyword.KeywordInterface;
import forge.game.mana.ManaCostBeingPaid;
@@ -24,6 +18,7 @@ import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import org.apache.commons.lang3.StringUtils;
public class PermanentAi extends SpellAbilityAi {
@@ -49,19 +44,19 @@ public class PermanentAi extends SpellAbilityAi {
*/
@Override
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
final Card card = sa.getHostCard();
final Card source = sa.getHostCard();
// check on legendary
if (!card.ignoreLegendRule() && ai.isCardInPlay(card.getName())) {
if (!source.ignoreLegendRule() && ai.isCardInPlay(source.getName())) {
// TODO check the risk we'd lose the effect with bad timing
if (!card.hasSVar("AILegendaryException")) {
if (!source.hasSVar("AILegendaryException")) {
// AiPlayDecision.WouldDestroyLegend
return false;
} else {
String specialRule = card.getSVar("AILegendaryException");
String specialRule = source.getSVar("AILegendaryException");
if ("TwoCopiesAllowed".equals(specialRule)) {
// One extra copy allowed on the battlefield, e.g. Brothers Yamazaki
if (CardLists.count(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.nameEquals(card.getName())) > 1) {
if (CardLists.count(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.nameEquals(source.getName())) > 1) {
return false;
}
} else if ("AlwaysAllowed".equals(specialRule)) {
@@ -73,23 +68,7 @@ public class PermanentAi extends SpellAbilityAi {
}
}
/* -- not used anymore after Ixalan (Planeswalkers are now legendary, not unique by subtype) --
if (card.isPlaneswalker()) {
CardCollection list = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield),
CardPredicates.Presets.PLANESWALKERS);
for (String type : card.getType().getSubtypes()) { // determine
// planewalker
// subtype
final CardCollection cl = CardLists.getType(list, type);
if (!cl.isEmpty()) {
// AiPlayDecision.WouldDestroyOtherPlaneswalker
return false;
}
break;
}
}*/
if (card.getType().hasSupertype(Supertype.World)) {
if (source.getType().hasSupertype(Supertype.World)) {
CardCollection list = CardLists.getType(ai.getCardsIn(ZoneType.Battlefield), "World");
if (!list.isEmpty()) {
// AiPlayDecision.WouldDestroyWorldEnchantment
@@ -101,7 +80,6 @@ public class PermanentAi extends SpellAbilityAi {
if (mana.countX() > 0) {
// Set PayX here to maximum value.
final int xPay = ComputerUtilCost.getMaxXValue(sa, ai, false);
final Card source = sa.getHostCard();
if (source.hasConverge()) {
int nColors = ComputerUtilMana.getConvergeCount(sa, ai);
for (int i = 1; i <= xPay; i++) {
@@ -123,7 +101,7 @@ public class PermanentAi extends SpellAbilityAi {
}
} else if (mana.isZero()) {
// if mana is zero, but card mana cost does have X, then something is wrong
ManaCost cardCost = card.getManaCost();
ManaCost cardCost = source.getManaCost();
if (cardCost != null && cardCost.countX() > 0) {
// AiPlayDecision.CantPlayAi
return false;
@@ -143,6 +121,38 @@ public class PermanentAi extends SpellAbilityAi {
sa.setXManaCostPaid(xPay);
}
if ("ChaliceOfTheVoid".equals(source.getSVar("AICurseEffect"))) {
int maxX = sa.getXManaCostPaid(); // as set above
CardCollection otherChalices = CardLists.filter(ai.getGame().getCardsIn(ZoneType.Battlefield), CardPredicates.nameEquals("Chalice of the Void"));
outer: for (int i = 0; i <= maxX; i++) {
for (Card chalice : otherChalices) {
if (chalice.getCounters(CounterEnumType.CHARGE) == i) {
continue outer; // already disabled, no point in adding another one
}
}
// assume the AI knows the deck lists of its opponents and if we see a card in a certain zone except for the library or hand,
// it likely won't be cast unless it's bounced back (ideally, this should also somehow account for hidden information such as face down cards in exile)
final int manaValue = i;
CardCollection aiCards = CardLists.filter(ai.getAllCards(), card -> (card.isInZone(ZoneType.Library) || !card.isInZone(ZoneType.Hand))
&& card.getState(CardStateName.Original).getManaCost() != null
&& card.getState(CardStateName.Original).getManaCost().getCMC() == manaValue);
CardCollection oppCards = CardLists.filter(ai.getStrongestOpponent().getAllCards(), card -> (card.isInZone(ZoneType.Library) || !card.isInZone(ZoneType.Hand))
&& card.getState(CardStateName.Original).getManaCost() != null
&& card.getState(CardStateName.Original).getManaCost().getCMC() == manaValue);
if (manaValue == 0) {
aiCards = CardLists.filter(aiCards, Predicates.not(CardPredicates.isType("Land")));
oppCards = CardLists.filter(oppCards, Predicates.not(CardPredicates.isType("Land")));
// also filter out other Chalices in our own deck
aiCards = CardLists.filter(aiCards, Predicates.not(CardPredicates.nameEquals("Chalice of the Void")));
}
if (oppCards.size() > 3 && oppCards.size() >= aiCards.size() * 2) {
sa.setXManaCostPaid(manaValue);
return true;
}
}
return false;
}
if (sa.hasParam("Announce") && sa.getParam("Announce").startsWith("Multikicker")) {
// String announce = sa.getParam("Announce");
ManaCost mkCost = sa.getMultiKickerManaCost();
@@ -152,13 +162,13 @@ public class PermanentAi extends SpellAbilityAi {
mCost = ManaCost.combine(mCost, mkCost);
ManaCostBeingPaid mcbp = new ManaCostBeingPaid(mCost);
if (!ComputerUtilMana.canPayManaCost(mcbp, sa, ai, false)) {
card.setKickerMagnitude(i);
source.setKickerMagnitude(i);
sa.setSVar("Multikicker", String.valueOf(i));
break;
}
card.setKickerMagnitude(i + 1);
source.setKickerMagnitude(i + 1);
}
if (isZeroCost && card.getKickerMagnitude() == 0) {
if (isZeroCost && source.getKickerMagnitude() == 0) {
// Bail if the card cost was {0} and no multikicker was paid (e.g. Everflowing Chalice).
// TODO: update this if there's ever a card where it makes sense to play it for {0} with no multikicker
return false;
@@ -166,13 +176,13 @@ public class PermanentAi extends SpellAbilityAi {
}
// don't play cards without being able to pay the upkeep for
for (KeywordInterface inst : card.getKeywords()) {
for (KeywordInterface inst : source.getKeywords()) {
String ability = inst.getOriginal();
if (ability.startsWith("UpkeepCost")) {
final String[] k = ability.split(":");
final String costs = k[1];
final SpellAbility emptyAbility = new SpellAbility.EmptySa(card, ai);
final SpellAbility emptyAbility = new SpellAbility.EmptySa(source, ai);
emptyAbility.setPayCosts(new Cost(costs, true));
emptyAbility.setTargetRestrictions(sa.getTargetRestrictions());
emptyAbility.setCardState(sa.getCardState());
@@ -186,8 +196,8 @@ public class PermanentAi extends SpellAbilityAi {
}
// check for specific AI preferences
if (card.hasSVar("AICastPreference")) {
String pref = card.getSVar("AICastPreference");
if (source.hasSVar("AICastPreference")) {
String pref = source.getSVar("AICastPreference");
String[] groups = StringUtils.split(pref, "|");
boolean dontCast = false;
for (String group : groups) {
@@ -205,7 +215,7 @@ public class PermanentAi extends SpellAbilityAi {
// Only cast unless there are X or more cards like this on the battlefield under AI control already,
CardCollectionView valid = param.contains("Globally") ? ai.getGame().getCardsIn(ZoneType.Battlefield)
: ai.getCardsIn(ZoneType.Battlefield);
CardCollection ctrld = CardLists.filter(valid, CardPredicates.nameEquals(card.getName()));
CardCollection ctrld = CardLists.filter(valid, CardPredicates.nameEquals(source.getName()));
int numControlled = 0;
if (param.endsWith("WithoutOppAuras")) {
@@ -242,7 +252,7 @@ public class PermanentAi extends SpellAbilityAi {
// if there are X-1 mana sources in play but the AI has an extra land in hand
CardCollection m = ComputerUtilMana.getAvailableManaSources(ai, true);
int extraMana = CardLists.count(ai.getCardsIn(ZoneType.Hand), CardPredicates.Presets.LANDS) > 0 ? 1 : 0;
if (card.getName().equals("Illusions of Grandeur")) {
if (source.getName().equals("Illusions of Grandeur")) {
// TODO: this is currently hardcoded for specific Illusions-Donate cost reduction spells, need to make this generic.
extraMana += Math.min(3, CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), Predicates.or(CardPredicates.nameEquals("Sapphire Medallion"), CardPredicates.nameEquals("Helm of Awakening"))).size()) * 2; // each cost-reduction spell accounts for {1} in both Illusions and Donate
}
@@ -260,7 +270,7 @@ public class PermanentAi extends SpellAbilityAi {
break; // disregard other preferences, always cast as a last resort
}
} else if (param.equals("OnlyFromZone")) {
if (!card.getZone().getZoneType().toString().equals(value)) {
if (!source.getZone().getZoneType().toString().equals(value)) {
dontCast = true;
break; // limit casting to a specific zone only
}

View File

@@ -235,11 +235,19 @@ public class PermanentCreatureAi extends PermanentAi {
* worth it. Not sure what 4. is for. 5. needs to be updated to ensure
* that the net toughness is still positive after static effects.
*/
// AiPlayDecision.WouldBecomeZeroToughnessCreature
if (card.hasStartOfKeyword("etbCounter") || mana.countX() != 0
|| card.hasETBTrigger(false) || card.hasETBReplacement() || card.hasSVar("NoZeroToughnessAI")) {
return true;
}
final Card copy = CardUtil.getLKICopy(card);
ComputerUtilCard.applyStaticContPT(game, copy, null);
// AiPlayDecision.WouldBecomeZeroToughnessCreature
return copy.getNetToughness() > 0 || copy.hasStartOfKeyword("etbCounter") || mana.countX() != 0
|| copy.hasETBTrigger(false) || copy.hasETBReplacement() || copy.hasSVar("NoZeroToughnessAI");
if (copy.getNetToughness() > 0) {
return true;
}
return false;
}
}

View File

@@ -163,7 +163,7 @@ public class ProtectAi extends SpellAbilityAi {
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
final boolean notAiMain1 = !(ph.getPlayerTurn() == ai && ph.getPhase() == PhaseType.MAIN1);
// sorceries can only give protection in order to create an unblockable attacker
return !SpellAbilityAi.isSorcerySpeed(sa, ai) || !notAiMain1;
return !isSorcerySpeed(sa, ai) || !notAiMain1;
}
@Override

View File

@@ -87,7 +87,7 @@ public class PumpAi extends PumpAiBase {
return true;
}
return SpellAbilityAi.isSorcerySpeed(sa, ai) || (ph.getNextTurn().equals(ai) && !ph.getPhase().isBefore(PhaseType.END_OF_TURN));
return isSorcerySpeed(sa, ai) || (ph.getNextTurn().equals(ai) && !ph.getPhase().isBefore(PhaseType.END_OF_TURN));
} else if (logic.equals("Aristocrat")) {
final boolean isThreatened = ComputerUtil.predictThreatenedObjects(ai, null, true).contains(sa.getHostCard());
if (!ph.is(PhaseType.COMBAT_DECLARE_BLOCKERS) && !isThreatened) {
@@ -118,7 +118,7 @@ public class PumpAi extends PumpAiBase {
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS))) {
// Instant-speed pumps should not be cast outside of combat when the
// stack is empty
return sa.isCurse() || SpellAbilityAi.isSorcerySpeed(sa, ai) || main1Preferred;
return sa.isCurse() || isSorcerySpeed(sa, ai) || main1Preferred;
}
return true;
}
@@ -367,7 +367,7 @@ public class PumpAi extends PumpAiBase {
if (ComputerUtilCard.shouldPumpCard(ai, sa, card, defense, attack, keywords, false)) {
return true;
} else if (containsUsefulKeyword(ai, keywords, card, sa, attack)) {
if (game.getPhaseHandler().isPreCombatMain() && SpellAbilityAi.isSorcerySpeed(sa, ai) ||
if (game.getPhaseHandler().isPreCombatMain() && isSorcerySpeed(sa, ai) ||
game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_ATTACKERS, ai) ||
game.getPhaseHandler().is(PhaseType.COMBAT_BEGIN, ai)) {
Card pumped = ComputerUtilCard.getPumpedCreature(ai, sa, card, 0, 0, keywords);

View File

@@ -4,7 +4,6 @@ import com.google.common.collect.Iterables;
import forge.ai.AiPlayDecision;
import forge.ai.PlayerControllerAi;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.cost.Cost;
@@ -26,7 +25,7 @@ public class RevealAi extends RevealAiBase {
boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(.667, sa.getActivationsThisTurn() + 1);
if (SpellAbilityAi.playReusable(ai, sa)) {
if (playReusable(ai, sa)) {
randomReturn = true;
}
return randomReturn;

View File

@@ -1,6 +1,5 @@
package forge.ai.ability;
import forge.ai.SpellAbilityAi;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.util.MyRandom;
@@ -20,7 +19,7 @@ public class RevealHandAi extends RevealAiBase {
boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(.667, sa.getActivationsThisTurn() + 1);
if (SpellAbilityAi.playReusable(ai, sa)) {
if (playReusable(ai, sa)) {
randomReturn = true;
}

View File

@@ -57,7 +57,7 @@ public class ScryAi extends SpellAbilityAi {
// even if there's no mana cost.
if (sa.getPayCosts().hasTapCost()
&& (sa.getPayCosts().hasManaCost() || (sa.getHostCard() != null && sa.getHostCard().isCreature()))
&& !SpellAbilityAi.isSorcerySpeed(sa, ai)) {
&& !isSorcerySpeed(sa, ai)) {
return ph.getNextTurn() == ai && ph.is(PhaseType.END_OF_TURN);
}
@@ -69,7 +69,7 @@ public class ScryAi extends SpellAbilityAi {
// in the playerturn Scry should only be done in Main1 or in upkeep if able
if (ph.isPlayerTurn(ai)) {
if (SpellAbilityAi.isSorcerySpeed(sa, ai)) {
if (isSorcerySpeed(sa, ai)) {
return ph.is(PhaseType.MAIN1) || sa.isPwAbility();
} else {
return ph.is(PhaseType.UPKEEP);
@@ -121,12 +121,12 @@ public class ScryAi extends SpellAbilityAi {
}
double chance = .4; // 40 percent chance of milling with instant speed stuff
if (SpellAbilityAi.isSorcerySpeed(sa, ai)) {
if (isSorcerySpeed(sa, ai)) {
chance = .667; // 66.7% chance for sorcery speed (since it will never activate EOT)
}
boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(chance, sa.getActivationsThisTurn() + 1);
if (SpellAbilityAi.playReusable(ai, sa)) {
if (playReusable(ai, sa)) {
randomReturn = true;
}

View File

@@ -54,13 +54,13 @@ public class SurveilAi extends SpellAbilityAi {
// even if there's no mana cost.
if (sa.getPayCosts().hasTapCost()
&& (sa.getPayCosts().hasManaCost() || (sa.getHostCard() != null && sa.getHostCard().isCreature()))
&& !SpellAbilityAi.isSorcerySpeed(sa, ai)) {
&& !isSorcerySpeed(sa, ai)) {
return ph.getNextTurn() == ai && ph.is(PhaseType.END_OF_TURN);
}
// in the player's turn Surveil should only be done in Main1 or in Upkeep if able
if (ph.isPlayerTurn(ai)) {
if (SpellAbilityAi.isSorcerySpeed(sa, ai)) {
if (isSorcerySpeed(sa, ai)) {
return ph.is(PhaseType.MAIN1) || sa.isPwAbility();
} else {
return ph.is(PhaseType.UPKEEP);
@@ -104,12 +104,12 @@ public class SurveilAi extends SpellAbilityAi {
}
double chance = .4; // 40 percent chance for instant speed
if (SpellAbilityAi.isSorcerySpeed(sa, ai)) {
if (isSorcerySpeed(sa, ai)) {
chance = .667; // 66.7% chance for sorcery speed (since it will never activate EOT)
}
boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(chance, sa.getActivationsThisTurn() + 1);
if (SpellAbilityAi.playReusable(ai, sa)) {
if (playReusable(ai, sa)) {
randomReturn = true;
}

View File

@@ -21,7 +21,7 @@ public class TapAi extends TapAiBase {
if (turn.isOpponentOf(ai) && phase.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
// Tap things down if it's Human's turn
} else if (turn.equals(ai)) {
if (SpellAbilityAi.isSorcerySpeed(sa, ai) && phase.getPhase().isBefore(PhaseType.COMBAT_BEGIN)) {
if (isSorcerySpeed(sa, ai) && phase.getPhase().isBefore(PhaseType.COMBAT_BEGIN)) {
// Cast it if it's a sorcery.
} else if (phase.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
// Aggro Brains are willing to use TapEffects aggressively instead of defensively
@@ -33,7 +33,7 @@ public class TapAi extends TapAiBase {
// Don't tap down after blockers
return false;
}
} else if (!SpellAbilityAi.playReusable(ai, sa)) {
} else if (!playReusable(ai, sa)) {
// Generally don't want to tap things with an Instant during Players turn outside of combat
return false;
}

View File

@@ -133,7 +133,7 @@ public class TokenAi extends SpellAbilityAi {
}
}
if ((ph.isPlayerTurn(ai) || ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS))
&& !sa.hasParam("ActivationPhases") && !sa.hasParam("PlayerTurn") && !SpellAbilityAi.isSorcerySpeed(sa, ai)
&& !sa.hasParam("ActivationPhases") && !sa.hasParam("PlayerTurn") && !isSorcerySpeed(sa, ai)
&& !haste && !pwMinus) {
return false;
}

View File

@@ -20,7 +20,7 @@ public class VentureAi extends SpellAbilityAi {
// If this has a mana cost, do it at opponent's EOT if able to prevent spending mana early; if sorcery, do it in Main2
PhaseHandler ph = aiPlayer.getGame().getPhaseHandler();
if (sa.getPayCosts().hasManaCost() || sa.getPayCosts().hasTapCost()) {
if (SpellAbilityAi.isSorcerySpeed(sa, aiPlayer)) {
if (isSorcerySpeed(sa, aiPlayer)) {
return ph.is(PhaseType.MAIN2, aiPlayer);
} else {
return ph.is(PhaseType.END_OF_TURN) && ph.getNextTurn() == aiPlayer;

View File

@@ -1582,10 +1582,6 @@ public class AbilityUtils {
host.addRemembered(sa.getTargets());
}
if (sa.hasParam("ImprintTargets") && sa.usesTargeting()) {
host.addImprintedCards(sa.getTargets().getTargetCards());
}
if (sa.hasParam("RememberCostMana")) {
host.clearRemembered();
ManaCostBeingPaid activationMana = new ManaCostBeingPaid(sa.getPayCosts().getTotalMana());

View File

@@ -62,10 +62,6 @@ public class FightEffect extends DamageBaseEffect {
return;
}
if (sa.hasParam("RememberObjects")) {
host.addRemembered(AbilityUtils.getDefinedObjects(host, sa.getParam("RememberObjects"), sa));
}
Player controller = host.getController();
boolean isOptional = sa.hasParam("Optional");
@@ -147,7 +143,6 @@ public class FightEffect extends DamageBaseEffect {
}
private void dealDamage(final SpellAbility sa, Card fighterA, Card fighterB) {
boolean usedDamageMap = true;
CardDamageMap damageMap = sa.getDamageMap();
CardDamageMap preventMap = sa.getPreventMap();

View File

@@ -70,7 +70,11 @@ public class StaticAbilityDisableTriggers {
if (trigMode.equals(TriggerType.ChangesZone)) {
// Cause of the trigger the card changing zones
if (!stAb.matchesValidParam("ValidCause", runParams.get(AbilityKey.Card))) {
Card moved = (Card) runParams.get(AbilityKey.Card);
if ("Battlefield".equals(regtrig.getParam("Origin"))) {
moved = (Card) runParams.get(AbilityKey.CardLKI);
}
if (!stAb.matchesValidParam("ValidCause", moved)) {
return false;
}
if (!stAb.matchesValidParam("Destination", runParams.get(AbilityKey.Destination))) {

View File

@@ -89,7 +89,11 @@ public class StaticAbilityPanharmonicon {
if (trigMode.equals(TriggerType.ChangesZone)) {
// Cause of the trigger the card changing zones
if (!stAb.matchesValidParam("ValidCause", runParams.get(AbilityKey.Card))) {
Card moved = (Card) runParams.get(AbilityKey.Card);
if ("Battlefield".equals(trigger.getParam("Origin"))) {
moved = (Card) runParams.get(AbilityKey.CardLKI);
}
if (!stAb.matchesValidParam("ValidCause", moved)) {
return false;
}
if (!stAb.matchesValidParam("Origin", runParams.get(AbilityKey.Origin))) {

View File

@@ -120,19 +120,8 @@ public class TriggerChangesZone extends Trigger {
}
}
if (hasParam("ValidCause")) {
if (!runParams.containsKey(AbilityKey.Cause)) {
return false;
}
SpellAbility cause = (SpellAbility) runParams.get(AbilityKey.Cause);
if (cause == null) {
return false;
}
if (!matchesValid(cause, getParam("ValidCause").split(","))) {
if (!matchesValid(cause.getHostCard(), getParam("ValidCause").split(","))) {
return false;
}
}
if (!matchesValidParam("ValidCause", runParams.get(AbilityKey.Cause))) {
return false;
}
if (hasParam("Fizzle")) {

View File

@@ -25,7 +25,9 @@ public class TriggerChangesZoneAll extends Trigger {
if (!matchesValidParam("ValidCause", runParams.get(AbilityKey.Cause))) {
return false;
} else if (hasParam("ValidAmount")) {
}
if (hasParam("ValidAmount")) {
int right = AbilityUtils.calculateAmount(hostCard, getParam("ValidAmount").substring(2), this);
if (!Expressions.compare(this.filterCards(table).size(), getParam("ValidAmount").substring(0, 2), right)) { return false; }
}

View File

@@ -7,5 +7,5 @@ SVar:TrigCounter:DB$ Counter | Defined$ TriggeredSpellAbility
SVar:X:Count$xPaid
SVar:Y:Count$CardCounters.CHARGE
SVar:AICurseEffect:ChaliceOfTheVoid
AI:RemoveDeck:All
AI:RemoveDeck:Random
Oracle:Chalice of the Void enters the battlefield with X charge counters on it.\nWhenever a player casts a spell with mana value equal to the number of charge counters on Chalice of the Void, counter that spell.

View File

@@ -4,7 +4,7 @@ Types:Legendary Creature Human Shaman
PT:2/2
T:Mode$ SpellCast | ValidCard$ Card.Red | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUntap | TriggerDescription$ Whenever you cast a red spell, untap CARDNAME.
SVar:TrigUntap:DB$ Untap | Defined$ Self
A:AB$ DealDamage | Cost$ T | ValidTgts$ Player,Planeswalker | TgtPrompt$ Select target player or planeswalker | NumDmg$ 1 | SubAbility$ DBTransform | AILogic$ PingAfterAttack | SpellDescription$ CARDNAME deals 1 damage to target player or planeswalker. If CARDNAME has dealt 3 or more damage this turn, exile her, then return her to the battlefield transformed under her owner's control.
A:AB$ DealDamage | Cost$ T | ValidTgts$ Player,Planeswalker | TgtPrompt$ Select target player or planeswalker | NumDmg$ 1 | SubAbility$ DBTransform | AILogic$ PingAfterAttack | SpellDescription$ CARDNAME deals 1 damage to target player or planeswalker. If NICKNAME has dealt 3 or more damage this turn, exile her, then return her to the battlefield transformed under her owner's control.
SVar:DBTransform:DB$ ChangeZone | Origin$ Battlefield | Destination$ Exile | RememberChanged$ True | SubAbility$ DBReturn | ConditionCheckSVar$ X | ConditionSVarCompare$ GE3 | StackDescription$ If CARDNAME has dealt 3 or more damage this turn, exile her, then return her to the battlefield transformed under her owner's control.
SVar:DBReturn:DB$ ChangeZone | Defined$ Remembered | Origin$ Exile | Destination$ Battlefield | Transformed$ True | SubAbility$ DBCleanup | StackDescription$
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True

View File

@@ -2,7 +2,7 @@ Name:Cleaver Skaab
ManaCost:3 U
Types:Creature Zombie Horror
PT:2/4
A:AB$ CopyPermanent | Cost$ 3 T Sac<1/Zombie.Other/another zombie> | Defined$ Sacrificed | NumCopies$ 2 | AILogic$ AtOppEOT
A:AB$ CopyPermanent | Cost$ 3 T Sac<1/Zombie.Other/another zombie> | Defined$ Sacrificed | NumCopies$ 2 | AILogic$ AtOppEOT | SpellDescription$ Create two tokens that are copies of the sacrificed creature.
DeckNeeds:Type$Zombie
DeckHas:Ability$Sacrifice|Token
SVar:AIPreference:SacCost$Zombie.Other

View File

@@ -2,10 +2,9 @@ Name:Parallax Tide
ManaCost:2 U U
Types:Enchantment
K:Fading:5
A:AB$ ChangeZone | Cost$ SubCounter<1/FADE> | ValidTgts$ Land | TgtPrompt$ Select target land | ImprintTargets$ True | Origin$ Battlefield | Destination$ Exile | SpellDescription$ Exile target land.
A:AB$ ChangeZone | Cost$ SubCounter<1/FADE> | ValidTgts$ Land | TgtPrompt$ Select target land | Origin$ Battlefield | Destination$ Exile | SpellDescription$ Exile target land.
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.Self | Execute$ TrigReturn | TriggerDescription$ When CARDNAME leaves the battlefield, each player returns to the battlefield all cards they own exiled with CARDNAME.
SVar:TrigReturn:DB$ ChangeZone | Defined$ Imprinted | Origin$ Exile | Destination$ Battlefield | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearImprinted$ True
SVar:TrigReturn:DB$ ChangeZone | Defined$ ExiledWith | Origin$ Exile | Destination$ Battlefield
SVar:PlayMain1:TRUE
AI:RemoveDeck:All
Oracle:Fading 5 (This enchantment enters the battlefield with five fade counters on it. At the beginning of your upkeep, remove a fade counter from it. If you can't, sacrifice it.)\nRemove a fade counter from Parallax Tide: Exile target land.\nWhen Parallax Tide leaves the battlefield, each player returns to the battlefield all cards they own exiled with Parallax Tide.

View File

@@ -2,10 +2,9 @@ Name:Parallax Wave
ManaCost:2 W W
Types:Enchantment
K:Fading:5
A:AB$ ChangeZone | Cost$ SubCounter<1/FADE> | ValidTgts$ Creature | TgtPrompt$ Select target creature | ImprintTargets$ True | Origin$ Battlefield | Destination$ Exile | SpellDescription$ Exile target creature.
A:AB$ ChangeZone | Cost$ SubCounter<1/FADE> | ValidTgts$ Creature | TgtPrompt$ Select target creature | Origin$ Battlefield | Destination$ Exile | SpellDescription$ Exile target creature.
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.Self | Execute$ TrigReturn | TriggerDescription$ When CARDNAME leaves the battlefield, each player returns to the battlefield all cards they own exiled with CARDNAME.
SVar:TrigReturn:DB$ ChangeZone | Defined$ Imprinted | Origin$ Exile | Destination$ Battlefield | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearImprinted$ True
SVar:TrigReturn:DB$ ChangeZone | Defined$ ExiledWith | Origin$ Exile | Destination$ Battlefield
SVar:PlayMain1:TRUE
AI:RemoveDeck:All
Oracle:Fading 5 (This enchantment enters the battlefield with five fade counters on it. At the beginning of your upkeep, remove a fade counter from it. If you can't, sacrifice it.)\nRemove a fade counter from Parallax Wave: Exile target creature.\nWhen Parallax Wave leaves the battlefield, each player returns to the battlefield all cards they own exiled with Parallax Wave.

View File

@@ -2,7 +2,7 @@ Name:Preston, the Vanisher
ManaCost:3 W
Types:Legendary Creature Rabbit Wizard
PT:2/5
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Creature.nonToken+wasNotCast+YouCtrl | TriggerZones$ Battlefield | Execute$ TrigCopyPermanent | TriggerDescription$ Whenever another nontoken creature enters the battlefield under your control, if it wasn't cast, create a token that's a copy of that creature except it's a 0/1 white Illusion.
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Creature.Other+nonToken+wasNotCast+YouCtrl | TriggerZones$ Battlefield | Execute$ TrigCopyPermanent | TriggerDescription$ Whenever another nontoken creature enters the battlefield under your control, if it wasn't cast, create a token that's a copy of that creature except it's a 0/1 white Illusion.
SVar:TrigCopyPermanent:DB$ CopyPermanent | Defined$ TriggeredCardLKICopy | NumCopies$ 1 | SetColor$ White | SetCreatureTypes$ Illusion | SetPower$ 0 | SetToughness$ 1
A:AB$ ChangeZone | Cost$ 1 W Sac<5/Illusion> | ValidTgts$ Permanent.nonLand | Origin$ Battlefield | Destination$ Exile | TgtPrompt$ Select target nonland permanent | SpellDescription$ Exile target nonland permanent.
DeckHas:Ability$Token|Sacrifice & Type$Illusion

View File

@@ -1,8 +1,7 @@
Name:Sacred Ground
ManaCost:1 W
Types:Enchantment
T:Mode$ Sacrificed | ValidCard$ Land.YouOwn | ValidCause$ SpellAbility.OppCtrl | Execute$ TrigReturn | TriggerZones$ Battlefield | TriggerDescription$ Whenever a spell or ability an opponent controls causes a land to be put into your graveyard from the battlefield, return that card to the battlefield.
T:Mode$ Destroyed | ValidCauser$ Player.Opponent | ValidCard$ Land.YouOwn | Execute$ TrigReturn | Secondary$ True | TriggerZones$ Battlefield | TriggerDescription$ Whenever a spell or ability an opponent controls causes a land to be put into your graveyard from the battlefield, return that card to the battlefield.
SVar:TrigReturn:DB$ ChangeZone | Defined$ TriggeredCard | Origin$ Graveyard | Destination$ Battlefield
T:Mode$ ChangesZone | ValidCard$ Land.YouOwn | ValidCause$ SpellAbility.OppCtrl | Execute$ TrigReturn | TriggerZones$ Battlefield | TriggerDescription$ Whenever a spell or ability an opponent controls causes a land to be put into your graveyard from the battlefield, return that card to the battlefield.
SVar:TrigReturn:DB$ ChangeZone | Defined$ TriggeredNewCardLKICopy | Origin$ Graveyard | Destination$ Battlefield
AI:RemoveDeck:Random
Oracle:Whenever a spell or ability an opponent controls causes a land to be put into your graveyard from the battlefield, return that card to the battlefield.

View File

@@ -2,9 +2,9 @@ Name:Skyshroud Ambush
ManaCost:1 G
Types:Instant
A:SP$ Pump | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Choose target creature you control | ImprintCards$ ThisTargetedCard | SubAbility$ DBFight | StackDescription$ Target creature you control [{c:ThisTargetedCard}] | SpellDescription$ Target creature you control fights target creature you don't control. When the creature you control wins the fight, draw a card.
SVar:DBFight:DB$ Fight | Defined$ ParentTarget | ValidTgts$ Creature.YouDontCtrl | TgtPrompt$ Choose target creature you don't control | RememberObjects$ ThisTargetedCard | SubAbility$ DBEffect | StackDescription$ fights target creature you don't control [{c:ThisTargetedCard}]. When the creature you control wins the fight, draw a card.
SVar:DBEffect:DB$ Effect | Triggers$ TrigWin | RememberObjects$ Remembered | ImprintCards$ Imprinted | Duration$ UntilStateBasedActionChecked | SubAbility$ DBCleanup
SVar:DBFight:DB$ Fight | Defined$ ParentTarget | ValidTgts$ Creature.YouDontCtrl | TgtPrompt$ Choose target creature you don't control | SubAbility$ DBEffect | StackDescription$ fights target creature you don't control [{c:ThisTargetedCard}]. When the creature you control wins the fight, draw a card.
SVar:DBEffect:DB$ Effect | Triggers$ TrigWin | RememberObjects$ ParentTarget | ImprintCards$ Imprinted | Duration$ UntilStateBasedActionChecked | SubAbility$ DBCleanup
SVar:TrigWin:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Creature.IsRemembered | IsPresent$ Creature.IsImprinted | NoResolvingCheck$ True | Execute$ TrigDraw | TriggerDescription$ When the creature you control wins the fight, draw a card.
SVar:TrigDraw:DB$ Draw | NumCards$ 1
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True | ClearImprinted$ True
SVar:DBCleanup:DB$ Cleanup | ClearImprinted$ True
Oracle:Target creature you control fights target creature you don't control. When the creature you control wins the fight, draw a card.

View File

@@ -5,6 +5,7 @@ PT:4/5
K:Exalted
T:Mode$ Attacks | ValidCard$ Creature.YouCtrl | Alone$ True | TriggerZones$ Battlefield | OptionalDecider$ You | Execute$ TrigSearch | TriggerDescription$ Whenever a creature you control attacks alone, you may search your library for an Aura card that could enchant that creature, put it onto the battlefield attached to that creature, then shuffle.
SVar:TrigSearch:DB$ Pump | RememberObjects$ TriggeredAttacker | SubAbility$ DBMoveAura
SVar:DBMoveAura:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Aura.CanEnchantRemembered+YouCtrl | AttachedTo$ Remembered | ChangeNum$ 1 | Hidden$ True | ShuffleNonMandatory$ True
SVar:DBMoveAura:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Aura.CanEnchantRemembered+YouCtrl | AttachedTo$ Remembered | ChangeNum$ 1 | Hidden$ True | ShuffleNonMandatory$ True | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
AI:RemoveDeck:Random
Oracle:Exalted (Whenever a creature you control attacks alone, that creature gets +1/+1 until end of turn.)\nWhenever a creature you control attacks alone, you may search your library for an Aura card that could enchant that creature, put it onto the battlefield attached to that creature, then shuffle.

View File

@@ -4,6 +4,6 @@ Types:Artifact Equipment
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigInitiative | TriggerDescription$ When CARDNAME enters the battlefield, you take the initiative.
SVar:TrigInitiative:DB$ TakeInitiative
T:Mode$ AttackerBlocked | ValidCard$ Creature.EquippedBy | Execute$ TrigDamage | TriggerDescription$ Whenever equipped creature becomes blocked, it deals 2 damage to each creature blocking it.
SVar:TrigDamage:DB$ DamageAll | NumDmg$ 2 | ValidCards$ Creature.blockingEquipped
SVar:TrigDamage:DB$ DamageAll | NumDmg$ 2 | ValidCards$ Creature.blockingEquipped | DamageSource$ TriggeredAttackerLKICopy
K:Equip:1
Oracle:When Trailblazer's Torch enters the battlefield, you take the initiative.\nWhenever equipped creature becomes blocked, it deals 2 damage to each creature blocking it.\nEquip {1} ({1}: Attach to target creature you control. Equip only as a sorcery.)

View File

@@ -5,6 +5,6 @@ PT:1/3
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigScry | TriggerDescription$ When CARDNAME enters the battlefield, scry 2.
SVar:TrigScry:DB$ Scry | ScryNum$ 2
A:AB$ BecomeMonarch | Cost$ 3 R T Sac<1/NICKNAME> | ValidTgts$ Player | SubAbility$ DBDamage | SpellDescription$ Target player becomes the monarch. NICKNAME deals 3 damage to any target.
SVar:DBPower:DB$ DealDamage | ValidTgts$ Any | NumDmg$ 3
SVar:DBDamage:DB$ DealDamage | ValidTgts$ Any | NumDmg$ 3
DeckHas:Ability$Sacrifice
Oracle:When Denethor, Stone Seer enters the battlefield, scry 2.\n{3}{R}, {T}, Sacrifice Denethor: Target player becomes the monarch. Denethor deals 3 damage to any target.
Oracle:When Denethor, Stone Seer enters the battlefield, scry 2.\n{3}{R}, {T}, Sacrifice Denethor: Target player becomes the monarch. Denethor deals 3 damage to any target.

View File

@@ -2,7 +2,6 @@ Name:Esquire of the King
ManaCost:W
Types:Creature Human Soldier
PT:1/1
K:Flying
A:AB$ PumpAll | NumAtt$ +1 | NumDef$ +1 | Cost$ 4 W T | ValidCards$ Creature.YouCtrl | ReduceCost$ X | SpellDescription$ Creatures you control get +1/+1 until end of turn. This ability costs {2} less to activate if you control a legendary creature.
SVar:X:Count$Compare Y GE1.2.0
SVar:Y:Count$Valid Creature.Legendary+YouCtrl

View File

@@ -4,8 +4,8 @@ Types:Legendary Creature Human Advisor
PT:1/4
S:Mode$ CantBlockBy | ValidAttacker$ Creature.Self | Description$ CARDNAME can't be blocked.
T:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Player | CombatDamage$ True | Execute$ TrigDigUntil | TriggerZones$ Battlefield | TriggerDescription$ Whenever NICKNAME deals combat damage to a player, that player exiles cards from the top of their library until they exile an instant or sorcery card. You may cast that card without paying its mana cost. Then that player puts the exiled cards that weren't cast this way on the bottom of their library in a random order.
SVar:TrigDigUntil:DB$ DigUntil | Defined$ TriggeredTarget | Valid$ Instant,Sorcery | ValidDescription$ instant or sorcery | FoundDestination$ Exile | RevealedDestination$ Exile | RememberFound$ True | IsCurse$ True | SubAbility$ DBPlay
SVar:DBPlay:DB$ Play | ValidZone$ Exile | Valid$ Instant.IsRemembered,Sorcery.IsRemembered | ValidSA$ Spell | WithoutManaCost$ True | Optional$ True | ForgetPlayed$ True | SubAbility$ DBRestRandomOrder
SVar:DBRestRandomOrder:DB$ ChangeZoneAll | ChangeType$ Card.IsRemembered | Origin$ Exile | Destination$ Library | LibraryPosition$ -1 | RandomOrder$ True | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
SVar:TrigDigUntil:DB$ DigUntil | Defined$ TriggeredTarget | Valid$ Instant,Sorcery | ValidDescription$ instant or sorcery | FoundDestination$ Exile | RevealedDestination$ Exile | RememberFound$ True | ImprintRevealed$ True | IsCurse$ True | SubAbility$ DBPlay
SVar:DBPlay:DB$ Play | ValidZone$ Exile | Valid$ Instant.IsRemembered,Sorcery.IsRemembered | ValidSA$ Spell | WithoutManaCost$ True | Optional$ True | SubAbility$ DBRestRandomOrder
SVar:DBRestRandomOrder:DB$ ChangeZoneAll | ChangeType$ Card.IsImprinted | Origin$ Exile | Destination$ Library | LibraryPosition$ -1 | RandomOrder$ True | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True | ClearImprinted$ True
Oracle:Grima, Saruman's Footman can't be blocked.\nWhenever Grima deals combat damage to a player, that player exiles cards from the top of their library until they exile an instant or sorcery card. You may cast that card without paying its mana cost. Then that player puts the exiled cards that weren't cast this way on the bottom of their library in a random order.

View File

@@ -1,8 +1,9 @@
Name:Vivien's Invocation
ManaCost:5 G G
Types:Sorcery
A:SP$ Dig | Cost$ 5 G G | DigNum$ 7 | ChangeNum$ 1 | ChangeValid$ Creature | Optional$ True | RestRandomOrder$ True | DestinationZone$ Battlefield | ForceRevealToController$ True | SpellDescription$ Look at the top seven cards of your library. You may put a creature card from among them onto the battlefield. Put the rest on the bottom of your library in a random order. When a creature is put onto the battlefield this way, it deals damage equal to its power to target creature an opponent controls.
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Creature | ValidCause$ Card.Self | Execute$ DBDealDamage | Secondary$ True | TriggerDescription$ When a creature is put onto the battlefield this way, it deals damage equals to its power to target creature an opponent controls.
SVar:DBDealDamage:DB$ DealDamage | ValidTgts$ Creature.OppCtrl | TgtPrompt$ Select target creature an opponent controls | NumDmg$ X | DamageSource$ TriggeredCard
SVar:X:TriggeredCard$CardPower
A:SP$ Dig | Cost$ 5 G G | DigNum$ 7 | ChangeNum$ 1 | ChangeValid$ Creature | Optional$ True | RestRandomOrder$ True | DestinationZone$ Battlefield | ForceRevealToController$ True | RememberChanged$ True | SubAbility$ DBTrig | SpellDescription$ Look at the top seven cards of your library. You may put a creature card from among them onto the battlefield. Put the rest on the bottom of your library in a random order. When a creature is put onto the battlefield this way, it deals damage equal to its power to target creature an opponent controls.
SVar:DBTrig:DB$ ImmediateTrigger | RememberObjects$ Remembered | Execute$ DBDealDamage | ConditionDefined$ Remembered | ConditionPresent$ Card | SubAbility$ DBCleanup | TriggerDescription$ When a creature is put onto the battlefield this way, it deals damage equals to its power to target creature an opponent controls.
SVar:DBDealDamage:DB$ DealDamage | ValidTgts$ Creature.OppCtrl | TgtPrompt$ Select target creature an opponent controls | NumDmg$ X | DamageSource$ DelayTriggerRememberedLKI
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
SVar:X:TriggerRemembered$CardPower
Oracle:Look at the top seven cards of your library. You may put a creature card from among them onto the battlefield. Put the rest on the bottom of your library in a random order. When a creature is put onto the battlefield this way, it deals damage equal to its power to target creature an opponent controls.

View File

@@ -0,0 +1,27 @@
[metadata]
Code=EA3
Date=2023-07-18
Name=Explorer Anthology 3
Type=Online
ScryfallCode=EA3
[cards]
1 R Cyclonic Rift @Chris Rahn
2 R Abbot of Keral Keep @Deruchenko Alexander
3 U Shrapnel Blast @Hideaki Takamura
4 R Eidolon of Blossoms @Min Yum
5 R Chord of Calling @Karl Kopinski
6 M Worldspine Wurm @Richard Wright
7 U Golgari Charm @Zoltan Boros
8 U Simic Charm @Zoltan Boros
9 C Izzet Charm @Zoltan Boros
10 U Gruul Charm @Zoltan Boros
11 U Orzhov Charm @Zoltan Boros
12 R Voice of Resurgence @Winona Nelson
13 R Deathrite Shaman @Steve Argyle
14 R Canopy Vista @Adam Paquette
15 R Cinder Glade @Adam Paquette
16 R Smoldering Marsh @Adam Paquette
17 R Sunken Hollow @Adam Paquette
18 R Prairie Stream @Adam Paquette
19 R Thespian's Stage @John Avon

View File

@@ -0,0 +1,27 @@
[metadata]
Code=HA7
Date=2023-07-18
Name=Historic Anthology 7
Type=Online
ScryfallCode=HA7
[cards]
1 M Sun Titan @Todd Lockwood
2 M Frost Titan @Mike Bierek
3 M Vendilion Clique @Willian Murai
4 M Grave Titan @Nils Hamm
5 M Inferno Titan @Kev Walker
6 C Tribal Flames @Zack Stella
7 M Primeval Titan @Aleksi Briclot
8 C Wild Nacatl @Wayne Reynolds
9 U Acidic Slime @Karl Kopinski
10 R Tooth and Nail @Jesper Ejsing
11 U Bloodbraid Elf @Raymond Swanland
12 U Mortarpod @Eric Deschamps
13 U Worn Powerstone @Henry G. Higginbotham
14 R Sword of Fire and Ice @Mark Zug
15 R Fiery Islet @Richard Wright
16 R Sunbaked Canyon @Yeong-Hao Han
17 R Nurturing Peatland @Sam Burley
18 R Silent Clearing @Seb McKinnon
19 R Waterlogged Grove @John Avon

View File

@@ -93,7 +93,7 @@ ScryfallCode=JMP
85 R Angel of the Dire Hour @Jack Wang
86 R Angelic Arbiter @Steve Argyle
87 C Angelic Edict @Trevor Claxton
88 U Angelic Page @Chris Rahn
88 C Angelic Page @Chris Rahn
89 R Archon of Justice @Jason Chan
90 R Archon of Redemption @Steven Belledin
91 C Battlefield Promotion @Scott Murphy

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,7 @@
[format]
Name:Historic (HA7)
Type:Archived
Subtype:Arena
Effective:2023-07-18
Sets:XLN, RIX, DOM, M19, ANA, PANA, GRN, G18, RNA, WAR, M20, ELD, HA1, THB, HA2, IKO, HA3, M21, JMP, AJMP, AKR, ANB, ZNR, KLR, KHM, HA4, STX, STA, HA5, AFR, J21, MID, VOW, YMID, NEO, YNEO, SNC, YSNC, HBG, HA6, EA1, DMU, YDMU, BRO, BRR, YBRO, EA2, ONE, YONE, SIR, SIS, MOM, MUL, MAT, LTR, HA7, EA3
Banned:Agent of Treachery; Brainstorm; Channel; Counterspell; Dark Ritual; Demonic Tutor; Field of the Dead; Lightning Bolt; Memory Lapse; Mishra's Bauble; Natural Order; Nexus of Fate; Oko, Thief of Crowns; Once Upon a Time; Ragavan, Nimble Pilferer; Swords to Plowshares; Thassa's Oracle; Tibalt's Trickery; Time Warp; Uro, Titan of Nature's Wrath; Veil of Summer; Wilderness Reclamation

View File

@@ -4,5 +4,5 @@ Type:Digital
Subtype:Arena
Effective:2019-11-21
Order:142
Sets:XLN, RIX, DOM, M19, ANA, PANA, GRN, G18, RNA, WAR, M20, ELD, HA1, THB, HA2, IKO, HA3, M21, JMP, AJMP, AKR, ANB, ZNR, KLR, KHM, HA4, STX, STA, HA5, AFR, J21, MID, VOW, YMID, NEO, YNEO, SNC, YSNC, HBG, HA6, EA1, DMU, YDMU, BRO, BRR, YBRO, EA2, ONE, YONE, SIR, SIS, MOM, MUL, MAT, LTR
Sets:XLN, RIX, DOM, M19, ANA, PANA, GRN, G18, RNA, WAR, M20, ELD, HA1, THB, HA2, IKO, HA3, M21, JMP, AJMP, AKR, ANB, ZNR, KLR, KHM, HA4, STX, STA, HA5, AFR, J21, MID, VOW, YMID, NEO, YNEO, SNC, YSNC, HBG, HA6, EA1, DMU, YDMU, BRO, BRR, YBRO, EA2, ONE, YONE, SIR, SIS, MOM, MUL, MAT, LTR, HA7, EA3
Banned:Agent of Treachery; Brainstorm; Channel; Counterspell; Dark Ritual; Demonic Tutor; Field of the Dead; Lightning Bolt; Memory Lapse; Mishra's Bauble; Natural Order; Nexus of Fate; Oko, Thief of Crowns; Once Upon a Time; Ragavan, Nimble Pilferer; Swords to Plowshares; Thassa's Oracle; Tibalt's Trickery; Time Warp; Uro, Titan of Nature's Wrath; Veil of Summer; Wilderness Reclamation

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 697 KiB

After

Width:  |  Height:  |  Size: 823 KiB