diff --git a/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java b/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java index 87ed8cb9fc2..aca2611dc01 100644 --- a/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java +++ b/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java @@ -787,7 +787,8 @@ public class PlayerControllerAi extends PlayerController { case "BetterTgtThanRemembered": if (source.getRememberedCount() > 0) { Card rem = (Card) source.getFirstRemembered(); - if (!rem.isInZone(ZoneType.Battlefield)) { + // avoid pumping opponent creature + if (!rem.isInZone(ZoneType.Battlefield) || rem.getController().isOpponentOf(source.getController())) { return true; } for (Card c : source.getController().getCreaturesInPlay()) { diff --git a/forge-ai/src/main/java/forge/ai/ability/CharmAi.java b/forge-ai/src/main/java/forge/ai/ability/CharmAi.java index 46da4dca610..777776f5aa7 100644 --- a/forge-ai/src/main/java/forge/ai/ability/CharmAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/CharmAi.java @@ -227,7 +227,7 @@ public class CharmAi extends SpellAbilityAi { if (AiPlayDecision.WillPlay == aic.canPlaySa(sub)) { chosenList.add(sub); if (chosenList.size() == min) { - break; // enough choices + break; // enough choices } } } diff --git a/forge-ai/src/main/java/forge/ai/ability/LifeLoseAi.java b/forge-ai/src/main/java/forge/ai/ability/LifeLoseAi.java index 13b67c55459..df3cecd4588 100644 --- a/forge-ai/src/main/java/forge/ai/ability/LifeLoseAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/LifeLoseAi.java @@ -28,7 +28,6 @@ public class LifeLoseAi extends SpellAbilityAi { */ @Override public boolean chkAIDrawback(SpellAbility sa, Player ai) { - final PlayerCollection tgtPlayers = getPlayers(ai, sa); final Card source = sa.getHostCard(); @@ -127,6 +126,7 @@ public class LifeLoseAi extends SpellAbilityAi { } final PlayerCollection tgtPlayers = getPlayers(ai, sa); + // TODO: check against the amount we could obtain when multiple activations are possible PlayerCollection filteredPlayer = tgtPlayers .filter(Predicates.and(PlayerPredicates.isOpponentOf(ai), PlayerPredicates.lifeLessOrEqualTo(amount))); // killing opponents asap diff --git a/forge-ai/src/main/java/forge/ai/ability/PumpAiBase.java b/forge-ai/src/main/java/forge/ai/ability/PumpAiBase.java index b874220dfa3..5c100147fb8 100644 --- a/forge-ai/src/main/java/forge/ai/ability/PumpAiBase.java +++ b/forge-ai/src/main/java/forge/ai/ability/PumpAiBase.java @@ -532,8 +532,7 @@ public abstract class PumpAiBase extends SpellAbilityAi { protected boolean containsNonCombatKeyword(final List keywords) { for (final String keyword : keywords) { - // since most keywords are combat relevant check for those that are - // not + // since most keywords are combat relevant check for those that are not if (keyword.endsWith("This card doesn't untap during your next untap step.") || keyword.endsWith("Shroud") || keyword.endsWith("Hexproof")) { return true; diff --git a/forge-core/src/main/java/forge/card/CardEdition.java b/forge-core/src/main/java/forge/card/CardEdition.java index c5879a88b3e..3d99ff61a87 100644 --- a/forge-core/src/main/java/forge/card/CardEdition.java +++ b/forge-core/src/main/java/forge/card/CardEdition.java @@ -194,10 +194,15 @@ public final class CardEdition implements Comparable { // immutable } catch (NumberFormatException ex) { String nonNumeric = sortableCollNr.replaceAll("[0-9]", ""); String onlyNumeric = sortableCollNr.replaceAll("[^0-9]", ""); - if (sortableCollNr.startsWith(onlyNumeric)) // e.g. 12a, 37+, 2018f, - sortableCollNr = String.format("%05d", Integer.parseInt(onlyNumeric)) + nonNumeric; + try { + collNr = Integer.parseInt(onlyNumeric); + } catch (NumberFormatException exon) { + collNr = 0; // this is the case of ONLY-letters collector numbers + } + if ((collNr > 0) && (sortableCollNr.startsWith(onlyNumeric))) // e.g. 12a, 37+, 2018f, + sortableCollNr = String.format("%05d", collNr) + nonNumeric; else // e.g. WS6, S1 - sortableCollNr = nonNumeric + String.format("%05d", Integer.parseInt(onlyNumeric)); + sortableCollNr = nonNumeric + String.format("%05d", collNr); } return sortableCollNr; } diff --git a/forge-game/src/main/java/forge/game/GameAction.java b/forge-game/src/main/java/forge/game/GameAction.java index 6aaefde06a2..a1f9b6ec347 100644 --- a/forge-game/src/main/java/forge/game/GameAction.java +++ b/forge-game/src/main/java/forge/game/GameAction.java @@ -2131,25 +2131,19 @@ public class GameAction { public void dealDamage(final boolean isCombat, final CardDamageMap damageMap, final CardDamageMap preventMap, final GameEntityCounterTable counterTable, final SpellAbility cause) { - CardDamageMap replaceDamageMap = new CardDamageMap(damageMap); - // Run replacement effect for each entity dealt damage - // TODO: List all possible replacement effects and run them in APNAP order. - // TODO: To handle "Prevented this way" and abilities like "Phantom Nomad", should buffer the replaced SA - // and only run them after all prevention and redirection effects are processed. + // Clear assigned damage if is combat for (Map.Entry> et : damageMap.columnMap().entrySet()) { final GameEntity ge = et.getKey(); if (isCombat && ge instanceof Card) { ((Card) ge).clearAssignedDamage(); } - for (Map.Entry e : et.getValue().entrySet()) { - if (e.getValue() > 0) { - ge.replaceDamage(e.getValue(), e.getKey(), isCombat, replaceDamageMap, preventMap, counterTable, cause); - } - } } + // Run replacement effect for each entity dealt damage + game.getReplacementHandler().runReplaceDamage(isCombat, damageMap, preventMap, counterTable, cause); + // Actually deal damage according to replaced damage map - for (Map.Entry> et : replaceDamageMap.rowMap().entrySet()) { + for (Map.Entry> et : damageMap.rowMap().entrySet()) { final Card sourceLKI = et.getKey(); int sum = 0; for (Map.Entry e : et.getValue().entrySet()) { @@ -2168,9 +2162,8 @@ public class GameAction { preventMap.triggerPreventDamage(isCombat); preventMap.clear(); - replaceDamageMap.triggerDamageDoneOnce(isCombat, game); + damageMap.triggerDamageDoneOnce(isCombat, game); damageMap.clear(); - replaceDamageMap.clear(); counterTable.triggerCountersPutAll(game); counterTable.clear(); diff --git a/forge-game/src/main/java/forge/game/GameEntity.java b/forge-game/src/main/java/forge/game/GameEntity.java index 19e88d6d1f9..ba4a2f26498 100644 --- a/forge-game/src/main/java/forge/game/GameEntity.java +++ b/forge-game/src/main/java/forge/game/GameEntity.java @@ -23,13 +23,11 @@ import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; -import forge.game.ability.AbilityKey; import forge.game.ability.AbilityUtils; import forge.game.ability.ApiType; import forge.game.card.Card; import forge.game.card.CardCollection; import forge.game.card.CardCollectionView; -import forge.game.card.CardDamageMap; import forge.game.card.CardLists; import forge.game.card.CardPredicates; import forge.game.card.CounterEnumType; @@ -67,43 +65,6 @@ public abstract class GameEntity extends GameObject implements IIdentifiable { getView().updateName(this); } - // final Iterable source - public void replaceDamage(final int damage, final Card source, final boolean isCombat, - final CardDamageMap damageMap, final CardDamageMap preventMap, GameEntityCounterTable counterTable, final SpellAbility cause) { - boolean prevention = source.canDamagePrevented(isCombat) && (cause == null || !cause.hasParam("NoPrevention")); - - // Replacement effects - final Map repParams = AbilityKey.mapFromAffected(this); - repParams.put(AbilityKey.DamageSource, source); - repParams.put(AbilityKey.DamageAmount, damage); - repParams.put(AbilityKey.IsCombat, isCombat); - repParams.put(AbilityKey.NoPreventDamage, !prevention); - repParams.put(AbilityKey.DamageMap, damageMap); - repParams.put(AbilityKey.PreventMap, preventMap); - repParams.put(AbilityKey.CounterTable, counterTable); - if (cause != null) { - repParams.put(AbilityKey.Cause, cause); - } - - switch (getGame().getReplacementHandler().run(ReplacementType.DamageDone, repParams)) { - case NotReplaced: - break; - case Updated: - int newDamage = (int) repParams.get(AbilityKey.DamageAmount); - GameEntity newTarget = (GameEntity) repParams.get(AbilityKey.Affected); - // check if this is still the affected card or player - if (this.equals(newTarget)) { - damageMap.put(source, this, newDamage - damage); - } else { - damageMap.remove(source, this); - damageMap.put(source, newTarget, newDamage); - } - break; - default: - damageMap.remove(source, this); - } - } - // This function handles damage after replacement and prevention effects are applied public abstract int addDamageAfterPrevention(final int damage, final Card source, final boolean isCombat, GameEntityCounterTable counterTable); diff --git a/forge-game/src/main/java/forge/game/ability/AbilityKey.java b/forge-game/src/main/java/forge/game/ability/AbilityKey.java index a3d3089fa63..7c002b52839 100644 --- a/forge-game/src/main/java/forge/game/ability/AbilityKey.java +++ b/forge-game/src/main/java/forge/game/ability/AbilityKey.java @@ -96,12 +96,14 @@ public enum AbilityKey { PayingMana("PayingMana"), Phase("Phase"), Player("Player"), + PreventedAmount("PreventedAmount"), PreventMap("PreventMap"), Prevention("Prevention"), Produced("Produced"), Regeneration("Regeneration"), ReplacementEffect("ReplacementEffect"), ReplacementResult("ReplacementResult"), + ReplacementResultMap("ReplacementResultMap"), Result("Result"), Scheme("Scheme"), Source("Source"), diff --git a/forge-game/src/main/java/forge/game/ability/effects/AddTurnEffect.java b/forge-game/src/main/java/forge/game/ability/effects/AddTurnEffect.java index c18f9aed31a..46dd4b005f3 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/AddTurnEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/AddTurnEffect.java @@ -16,13 +16,11 @@ public class AddTurnEffect extends SpellAbilityEffect { @Override protected String getStackDescription(SpellAbility sa) { - final StringBuilder sb = new StringBuilder(); final int numTurns = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumTurns"), sa); List tgtPlayers = getTargetPlayers(sa); - for (final Player player : tgtPlayers) { sb.append(player).append(" "); } diff --git a/forge-game/src/main/java/forge/game/ability/effects/ManaEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ManaEffect.java index a12624171d0..73f1edd0db6 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ManaEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ManaEffect.java @@ -31,13 +31,12 @@ public class ManaEffect extends SpellAbilityEffect { @Override public void resolve(SpellAbility sa) { final Card card = sa.getHostCard(); - AbilityManaPart abMana = sa.getManaPart(); + final List tgtPlayers = getTargetPlayers(sa); // Spells are not undoable - sa.setUndoable(sa.isAbility() && sa.isUndoable()); + sa.setUndoable(sa.isAbility() && sa.isUndoable() && tgtPlayers.size() < 2); - final List tgtPlayers = getTargetPlayers(sa); final boolean optional = sa.hasParam("Optional"); final Game game = sa.getActivatingPlayer().getGame(); diff --git a/forge-game/src/main/java/forge/game/ability/effects/ReplaceDamageEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ReplaceDamageEffect.java index e3353860d3b..4bdd454fb15 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ReplaceDamageEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ReplaceDamageEffect.java @@ -5,17 +5,12 @@ import java.util.Map; import org.apache.commons.lang3.StringUtils; import forge.game.Game; -import forge.game.GameEntity; -import forge.game.GameLogEntryType; import forge.game.ability.AbilityKey; import forge.game.ability.AbilityUtils; import forge.game.ability.SpellAbilityEffect; import forge.game.card.Card; -import forge.game.card.CardDamageMap; import forge.game.replacement.ReplacementResult; -import forge.game.replacement.ReplacementType; import forge.game.spellability.SpellAbility; -import forge.util.TextUtil; /** * This class handles two kinds of prevention effect: @@ -37,8 +32,6 @@ public class ReplaceDamageEffect extends SpellAbilityEffect { @SuppressWarnings("unchecked") Map originalParams = (Map) sa.getReplacingObject(AbilityKey.OriginalParams); - Map params = AbilityKey.newMap(originalParams); - Integer dmg = (Integer) sa.getReplacingObject(AbilityKey.DamageAmount); String varValue = sa.getParamOrDefault("Amount", "1"); @@ -59,21 +52,6 @@ public class ReplaceDamageEffect extends SpellAbilityEffect { } // Set PreventedDamage SVar card.setSVar("PreventedDamage", "Number$" + n); - - Card sourceLKI = (Card) sa.getReplacingObject(AbilityKey.Source); - GameEntity target = (GameEntity) sa.getReplacingObject(AbilityKey.Target); - - // Set prevent map entry - CardDamageMap preventMap = (CardDamageMap) originalParams.get(AbilityKey.PreventMap); - preventMap.put(sourceLKI, target, n); - - // Following codes are commented out since DamagePrevented trigger is currently not used by any Card. - // final Map runParams = AbilityKey.newMap(); - // runParams.put(AbilityKey.DamageTarget, target); - // runParams.put(AbilityKey.DamageAmount, dmg); - // runParams.put(AbilityKey.DamageSource, sourceLKI); - // runParams.put(AbilityKey.IsCombatDamage, originalParams.get(AbilityKey.IsCombat)); - // game.getTriggerHandler().runTrigger(TriggerType.DamagePrevented, runParams, false); } // no damage for original target anymore @@ -81,33 +59,8 @@ public class ReplaceDamageEffect extends SpellAbilityEffect { originalParams.put(AbilityKey.ReplacementResult, ReplacementResult.Replaced); return; } - params.put(AbilityKey.DamageAmount, dmg); - - // need to log Updated events there, or the log is wrong order - String message = sa.getReplacementEffect().toString(); - if ( !StringUtils.isEmpty(message)) { - message = TextUtil.fastReplace(message, "CARDNAME", card.getName()); - game.getGameLog().add(GameLogEntryType.EFFECT_REPLACED, message); - } - - //try to call replacementHandler with new Params - final ReplacementType event = sa.getReplacementEffect().getMode(); - ReplacementResult result = game.getReplacementHandler().run(event, params); - switch (result) { - case NotReplaced: - case Updated: { - for (Map.Entry e : params.entrySet()) { - originalParams.put(e.getKey(), e.getValue()); - } - // effect was updated - originalParams.put(AbilityKey.ReplacementResult, ReplacementResult.Updated); - break; - } - default: - // effect was replaced with something else - originalParams.put(AbilityKey.ReplacementResult, result); - break; - } + originalParams.put(AbilityKey.DamageAmount, dmg); + originalParams.put(AbilityKey.ReplacementResult, ReplacementResult.Updated); } } diff --git a/forge-game/src/main/java/forge/game/ability/effects/ReplaceEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ReplaceEffect.java index e15dd8731f1..08acad9616e 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ReplaceEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ReplaceEffect.java @@ -65,6 +65,14 @@ public class ReplaceEffect extends SpellAbilityEffect { params.put(AbilityKey.EffectOnly, true); } + if (retype == ReplacementType.DamageDone) { + for (Map.Entry e : params.entrySet()) { + originalParams.put(e.getKey(), e.getValue()); + } + originalParams.put(AbilityKey.ReplacementResult, ReplacementResult.Updated); + return; + } + // need to log Updated events there, or the log is wrong order String message = sa.getReplacementEffect().toString(); if ( !StringUtils.isEmpty(message)) { diff --git a/forge-game/src/main/java/forge/game/ability/effects/ReplaceSplitDamageEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ReplaceSplitDamageEffect.java index 0c620531592..8a6bc244cdb 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ReplaceSplitDamageEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ReplaceSplitDamageEffect.java @@ -7,15 +7,12 @@ import org.apache.commons.lang3.StringUtils; import forge.game.Game; import forge.game.GameEntity; -import forge.game.GameEntityCounterTable; import forge.game.GameObject; import forge.game.ability.AbilityKey; import forge.game.ability.AbilityUtils; import forge.game.ability.SpellAbilityEffect; import forge.game.card.Card; -import forge.game.card.CardDamageMap; import forge.game.replacement.ReplacementResult; -import forge.game.replacement.ReplacementType; import forge.game.spellability.SpellAbility; public class ReplaceSplitDamageEffect extends SpellAbilityEffect { @@ -30,19 +27,13 @@ public class ReplaceSplitDamageEffect extends SpellAbilityEffect { return; } - final ReplacementType event = sa.getReplacementEffect().getMode(); - String varValue = sa.getParamOrDefault("VarName", "1"); @SuppressWarnings("unchecked") Map originalParams = (Map) sa.getReplacingObject(AbilityKey.OriginalParams); - Map params = AbilityKey.newMap(originalParams); - Integer dmg = (Integer) sa.getReplacingObject(AbilityKey.DamageAmount); - - int prevent = AbilityUtils.calculateAmount(card, varValue, sa); - + List list = AbilityUtils.getDefinedObjects(card, sa.getParam("DamageTarget"), sa); if (prevent > 0 && list.size() > 0 && list.get(0) instanceof GameEntity) { @@ -56,18 +47,10 @@ public class ReplaceSplitDamageEffect extends SpellAbilityEffect { sa.setSVar(varValue, "Number$" + prevent); card.updateAbilityTextForView(); } - - Card sourceLKI = (Card) sa.getReplacingObject(AbilityKey.Source); - GameEntity target = (GameEntity) sa.getReplacingObject(AbilityKey.Target); GameEntity obj = (GameEntity) list.get(0); - boolean isCombat = (Boolean) originalParams.get(AbilityKey.IsCombat); - CardDamageMap damageMap = (CardDamageMap) originalParams.get(AbilityKey.DamageMap); - CardDamageMap preventMap = (CardDamageMap) originalParams.get(AbilityKey.PreventMap); - GameEntityCounterTable counterTable = (GameEntityCounterTable) originalParams.get(AbilityKey.CounterTable); - SpellAbility cause = (SpellAbility) originalParams.get(AbilityKey.Cause); - damageMap.put(sourceLKI, obj, n); - obj.replaceDamage(n, sourceLKI, isCombat, damageMap, preventMap, counterTable, cause); + originalParams.put(AbilityKey.Affected, obj); + originalParams.put(AbilityKey.DamageAmount, n); } // no damage for original target anymore @@ -75,25 +58,7 @@ public class ReplaceSplitDamageEffect extends SpellAbilityEffect { originalParams.put(AbilityKey.ReplacementResult, ReplacementResult.Replaced); return; } - params.put(AbilityKey.DamageAmount, dmg); - - //try to call replacementHandler with new Params - ReplacementResult result = game.getReplacementHandler().run(event, params); - switch (result) { - case NotReplaced: - case Updated: { - for (Map.Entry e : params.entrySet()) { - originalParams.put(e.getKey(), e.getValue()); - } - // effect was updated - originalParams.put(AbilityKey.ReplacementResult, ReplacementResult.Updated); - break; - } - default: - // effect was replaced with something else - originalParams.put(AbilityKey.ReplacementResult, result); - break; - } + originalParams.put(AbilityKey.ReplacementResult, ReplacementResult.Updated); } } diff --git a/forge-game/src/main/java/forge/game/ability/effects/SkipTurnEffect.java b/forge-game/src/main/java/forge/game/ability/effects/SkipTurnEffect.java index 752cae43772..0cb399042c8 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/SkipTurnEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/SkipTurnEffect.java @@ -67,4 +67,3 @@ public class SkipTurnEffect extends SpellAbilityEffect { } } } - diff --git a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java index 0bf1f24627b..968b0b9d1de 100644 --- a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java +++ b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java @@ -3232,8 +3232,17 @@ public class CardFactoryUtil { if (keyword.startsWith("Affinity")) { final String[] k = keyword.split(":"); final String t = k[1]; + String d = ""; + if (k.length > 2) { + final StringBuilder s = new StringBuilder(); + s.append(k[2]).append("s"); + d = s.toString(); + } String desc = "Artifact".equals(t) ? "artifacts" : CardType.getPluralType(t); + if (!d.isEmpty()) { + desc = d; + } StringBuilder sb = new StringBuilder(); sb.append("Mode$ ReduceCost | ValidCard$ Card.Self | Type$ Spell | Amount$ AffinityX | EffectZone$ All"); sb.append("| Description$ Affinity for ").append(desc); diff --git a/forge-game/src/main/java/forge/game/keyword/Keyword.java b/forge-game/src/main/java/forge/game/keyword/Keyword.java index 3f5bed4a1b9..bc393018cc3 100644 --- a/forge-game/src/main/java/forge/game/keyword/Keyword.java +++ b/forge-game/src/main/java/forge/game/keyword/Keyword.java @@ -18,7 +18,7 @@ public enum Keyword { UNDEFINED("", SimpleKeyword.class, false, ""), ABSORB("Absorb", KeywordWithAmount.class, false, "If a source would deal damage to this creature, prevent %d of that damage."), ADAPT("Adapt", KeywordWithCostAndAmount.class, false, "If this creature has no +1/+1 counters on it, put {%2$d:+1/+1 counter} on it."), - AFFINITY("Affinity", KeywordWithType.class, false, "This spell costs you {1} less to cast for each %s you control."), + AFFINITY("Affinity", KeywordWithType.class, false, "This spell costs {1} less to cast for each %s you control."), AFFLICT("Afflict", KeywordWithAmount.class, false, "Whenever this creature becomes blocked, defending player loses %d life."), AFTERLIFE("Afterlife", KeywordWithAmount.class, false, "When this creature dies, create {%1$d:1/1 white and black Spirit creature token} with flying."), AFTERMATH("Aftermath", SimpleKeyword.class, false, "Cast this spell only from your graveyard. Then exile it."), diff --git a/forge-game/src/main/java/forge/game/keyword/KeywordWithType.java b/forge-game/src/main/java/forge/game/keyword/KeywordWithType.java index 3d30c625d9a..8960a920e6a 100644 --- a/forge-game/src/main/java/forge/game/keyword/KeywordWithType.java +++ b/forge-game/src/main/java/forge/game/keyword/KeywordWithType.java @@ -11,6 +11,9 @@ public class KeywordWithType extends KeywordInstance { type = details.toLowerCase(); } else if (details.contains(":")) { type = details.split(":")[0]; + if (this.toString().startsWith("Affinity")) { + type = details.split(":")[1]; + } } else { type = details; } diff --git a/forge-game/src/main/java/forge/game/replacement/ReplaceProduceMana.java b/forge-game/src/main/java/forge/game/replacement/ReplaceProduceMana.java index 1ff2b0e961e..b4a658c579c 100644 --- a/forge-game/src/main/java/forge/game/replacement/ReplaceProduceMana.java +++ b/forge-game/src/main/java/forge/game/replacement/ReplaceProduceMana.java @@ -38,6 +38,9 @@ public class ReplaceProduceMana extends ReplacementEffect { if (!matchesValidParam("ValidPlayer", runParams.get(AbilityKey.Player))) { return false; } + if (!matchesValidParam("ValidActivator", runParams.get(AbilityKey.Activator))) { + return false; + } if (!matchesValidParam("ValidAbility", runParams.get(AbilityKey.AbilityMana))) { return false; } @@ -58,7 +61,6 @@ public class ReplaceProduceMana extends ReplacementEffect { return true; } - public void setReplacingObjects(Map runParams, SpellAbility sa) { sa.setReplacingObject(AbilityKey.Mana, runParams.get(AbilityKey.Mana)); } diff --git a/forge-game/src/main/java/forge/game/replacement/ReplacementHandler.java b/forge-game/src/main/java/forge/game/replacement/ReplacementHandler.java index d4363e496b5..589839448d6 100644 --- a/forge-game/src/main/java/forge/game/replacement/ReplacementHandler.java +++ b/forge-game/src/main/java/forge/game/replacement/ReplacementHandler.java @@ -17,7 +17,10 @@ */ package forge.game.replacement; +import java.util.ArrayList; import java.util.EnumSet; +import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; @@ -30,6 +33,7 @@ import com.google.common.collect.Sets; import forge.game.CardTraitBase; import forge.game.Game; import forge.game.GameEntity; +import forge.game.GameEntityCounterTable; import forge.game.GameLogEntryType; import forge.game.IHasSVars; import forge.game.ability.AbilityFactory; @@ -45,6 +49,8 @@ import forge.game.card.CardUtil; import forge.game.keyword.KeywordInterface; import forge.game.keyword.KeywordsChange; import forge.game.player.Player; +import forge.game.player.PlayerCollection; +import forge.game.spellability.AbilitySub; import forge.game.spellability.SpellAbility; import forge.game.zone.Zone; import forge.game.zone.ZoneType; @@ -58,6 +64,10 @@ public class ReplacementHandler { private final Game game; private Set hasRun = Sets.newHashSet(); + + // List of all replacement effect candidates for DamageDone event, in APNAP order + private final List>>> replaceDamageList = new ArrayList<>(); + /** * ReplacementHandler. * @param gameState @@ -281,24 +291,6 @@ public class ReplacementHandler { return res; } - private void putPreventMapEntry(final Map runParams) { - Card sourceLKI = (Card) runParams.get(AbilityKey.DamageSource); - GameEntity target = (GameEntity) runParams.get(AbilityKey.Affected); - Integer damage = (Integer) runParams.get(AbilityKey.DamageAmount); - - // Set prevent map entry - CardDamageMap preventMap = (CardDamageMap) runParams.get(AbilityKey.PreventMap); - preventMap.put(sourceLKI, target, damage); - - // Following codes are commented out since DamagePrevented trigger is currently not used by any Card. - // final Map trigParams = AbilityKey.newMap(); - // trigParams.put(AbilityKey.DamageTarget, target); - // trigParams.put(AbilityKey.DamageAmount, damage); - // trigParams.put(AbilityKey.DamageSource, sourceLKI); - // trigParams.put(AbilityKey.IsCombatDamage, runParams.get(AbilityKey.IsCombat)); - // game.getTriggerHandler().runTrigger(TriggerType.DamagePrevented, trigParams, false); - } - /** * * Runs a single replacement effect. @@ -368,14 +360,21 @@ public class ReplacementHandler { } } - if (mapParams.containsKey("Prevent") && mapParams.get("Prevent").equals("True")) { - if (replacementEffect.getMode() == ReplacementType.DamageDone) { - if (Boolean.TRUE.equals(runParams.get(AbilityKey.NoPreventDamage))) { - return ReplacementResult.NotReplaced; + boolean isPrevent = mapParams.containsKey("Prevent") && mapParams.get("Prevent").equals("True"); + if (isPrevent || mapParams.containsKey("PreventionEffect")) { + if (Boolean.TRUE.equals(runParams.get(AbilityKey.NoPreventDamage))) { + // If can't prevent damage, result is not replaced + // But still put "prevented" amount for buffered SA + if (mapParams.containsKey("AlwaysReplace")) { + runParams.put(AbilityKey.PreventedAmount, runParams.get(AbilityKey.DamageAmount)); + } else { + runParams.put(AbilityKey.PreventedAmount, 0); } - putPreventMapEntry(runParams); + return ReplacementResult.NotReplaced; + } + if (isPrevent) { + return ReplacementResult.Prevented; // Nothing should replace the event. } - return ReplacementResult.Prevented; // Nothing should replace the event. } if (mapParams.containsKey("Skip")) { @@ -384,25 +383,19 @@ public class ReplacementHandler { } } - boolean cantPreventDamage = (replacementEffect.getMode() == ReplacementType.DamageDone - && mapParams.containsKey("PreventionEffect") - && Boolean.TRUE.equals(runParams.get(AbilityKey.NoPreventDamage))); - Player player = host.getController(); - if (!cantPreventDamage || mapParams.containsKey("AlwaysReplace")) { - player.getController().playSpellAbilityNoStack(effectSA, true); - if (replacementEffect.getMode() == ReplacementType.DamageDone - && effectSA.getApi() != ApiType.ReplaceDamage && !cantPreventDamage) { - putPreventMapEntry(runParams); + if (effectSA != null) { + ApiType apiType = effectSA.getApi(); + if (replacementEffect.getMode() != ReplacementType.DamageDone || + (apiType == ApiType.ReplaceDamage || apiType == ApiType.ReplaceSplitDamage || apiType == ApiType.ReplaceEffect)) { + player.getController().playSpellAbilityNoStack(effectSA, true); + } else { + // The SA if buffered, but replacement result should be set to Replaced + runParams.put(AbilityKey.ReplacementResult, ReplacementResult.Replaced); } } - // If can't prevent damage, result is not replaced - if (cantPreventDamage) { - return ReplacementResult.NotReplaced; - } - // if the spellability is a replace effect then its some new logic // if ReplacementResult is set in run params use that instead if (runParams.containsKey(AbilityKey.ReplacementResult)) { @@ -412,6 +405,322 @@ public class ReplacementHandler { return ReplacementResult.Replaced; } + private void getPossibleReplaceDamageList(PlayerCollection players, final boolean isCombat, final CardDamageMap damageMap, final SpellAbility cause) { + for (Map.Entry> et : damageMap.columnMap().entrySet()) { + final GameEntity target = et.getKey(); + int playerIndex = (target instanceof Player ? players.indexOf(((Player) target)) : + players.indexOf(((Card) target).getController())); + Map>> replaceCandidateMap = replaceDamageList.get(playerIndex); + for (Map.Entry e : et.getValue().entrySet()) { + Card source = e.getKey(); + Integer damage = e.getValue(); + if (damage > 0) { + boolean prevention = source.canDamagePrevented(isCombat) && + (cause == null || !cause.hasParam("NoPrevention")); + final Map repParams = AbilityKey.mapFromAffected(target); + repParams.put(AbilityKey.DamageSource, source); + repParams.put(AbilityKey.DamageAmount, damage); + repParams.put(AbilityKey.IsCombat, isCombat); + repParams.put(AbilityKey.NoPreventDamage, !prevention); + if (cause != null) { + repParams.put(AbilityKey.Cause, cause); + } + + List reList = getReplacementList(ReplacementType.DamageDone, repParams, ReplacementLayer.Other); + for (ReplacementEffect re : reList) { + if (!replaceCandidateMap.containsKey(re)) { + replaceCandidateMap.put(re, new ArrayList<>()); + } + List> runParamList = replaceCandidateMap.get(re); + runParamList.add(repParams); + } + } + } + } + } + + private void runSingleReplaceDamageEffect(ReplacementEffect re, Map runParams, Map>> replaceCandidateMap, + Map>> executedDamageMap, Player decider, final CardDamageMap damageMap, final CardDamageMap preventMap) { + List> executedParamList = executedDamageMap.get(re); + ApiType apiType = re.getOverridingAbility() != null ? re.getOverridingAbility().getApi() : null; + Card source = (Card) runParams.get(AbilityKey.DamageSource); + GameEntity target = (GameEntity) runParams.get(AbilityKey.Affected); + int damage = (int) runParams.get(AbilityKey.DamageAmount); + Map mapParams = re.getMapParams(); + + ReplacementResult res = executeReplacement(runParams, re, decider, game); + GameEntity newTarget = (GameEntity) runParams.get(AbilityKey.Affected); + int newDamage = (int) runParams.get(AbilityKey.DamageAmount); + + // ReplaceSplitDamage will split the damage event into two event, so need to create run params for old event + // (original run params is changed for new event) + Map oldParams = null; + + if (res != ReplacementResult.NotReplaced) { + // Remove this event from other possible replacers + Iterator>>> itr = replaceCandidateMap.entrySet().iterator(); + while (itr.hasNext()) { + Map.Entry>> entry = itr.next(); + if (entry.getKey() == re) continue; + if (entry.getValue().contains(runParams)) { + entry.getValue().remove(runParams); + if (entry.getValue().isEmpty()) { + itr.remove(); + } + } + } + // Add updated event to possible replacers + if (res == ReplacementResult.Updated || apiType == ApiType.ReplaceSplitDamage) { + Map>> newReplaceCandidateMap = replaceCandidateMap; + if (!target.equals(newTarget)) { + PlayerCollection players = game.getPlayersInTurnOrder(); + int playerIndex = (newTarget instanceof Player ? players.indexOf(((Player) newTarget)) : + players.indexOf(((Card) newTarget).getController())); + newReplaceCandidateMap = replaceDamageList.get(playerIndex); + } + + List reList = getReplacementList(ReplacementType.DamageDone, runParams, ReplacementLayer.Other); + for (ReplacementEffect newRE : reList) { + // Skip if this has already been executed by given replacement effect + if (executedDamageMap.containsKey(newRE) && executedDamageMap.get(newRE).contains(runParams)) { + continue; + } + if (!newReplaceCandidateMap.containsKey(newRE)) { + newReplaceCandidateMap.put(newRE, new ArrayList<>()); + } + List> runParamList = newReplaceCandidateMap.get(newRE); + runParamList.add(runParams); + } + } + // Add old updated event too for ReplaceSplitDamage + if (apiType == ApiType.ReplaceSplitDamage && res == ReplacementResult.Updated) { + oldParams = AbilityKey.newMap(runParams); + oldParams.put(AbilityKey.Affected, target); + oldParams.put(AbilityKey.DamageAmount, damage - newDamage); + List reList = getReplacementList(ReplacementType.DamageDone, oldParams, ReplacementLayer.Other); + for (ReplacementEffect newRE : reList) { + if (!replaceCandidateMap.containsKey(newRE)) { + replaceCandidateMap.put(newRE, new ArrayList<>()); + } + List> runParamList = replaceCandidateMap.get(newRE); + runParamList.add(oldParams); + } + } + } + + @SuppressWarnings("unchecked") + Map resultMap = (Map) runParams.get(AbilityKey.ReplacementResultMap); + resultMap.put(re, res); + + // Update damage map and prevent map + switch (res) { + case NotReplaced: + break; + case Updated: + // check if this is still the affected card or player + if (target.equals(newTarget)) { + damageMap.put(source, target, newDamage - damage); + } else if (apiType == ApiType.ReplaceSplitDamage) { + damageMap.put(source, target, -newDamage); + } + if (!target.equals(newTarget)) { + if (apiType != ApiType.ReplaceSplitDamage) { + damageMap.remove(source, target); + } + damageMap.put(source, newTarget, newDamage); + } + if (apiType == ApiType.ReplaceDamage) { + preventMap.put(source, target, damage - newDamage); + // Record prevented amount + runParams.put(AbilityKey.PreventedAmount, damage - newDamage); + } + break; + default: + damageMap.remove(source, target); + if (apiType == ApiType.ReplaceDamage || + (mapParams.containsKey("Prevent") && mapParams.get("Prevent").equals("True")) || + mapParams.containsKey("PreventionEffect")) { + preventMap.put(source, target, damage); + // Record prevented amount + runParams.put(AbilityKey.PreventedAmount, damage); + } + if (apiType == ApiType.ReplaceSplitDamage) { + damageMap.put(source, newTarget, newDamage); + } + } + + // Put run params into executed param list so this replacement effect won't handle them again + // (For example, if the damage is redirected back) + executedParamList.add(runParams); + if (apiType == ApiType.ReplaceSplitDamage) { + executedParamList.add(oldParams); + } + + // Log the replacement effect + if (res != ReplacementResult.NotReplaced) { + String message = re.getDescription(); + if ( !StringUtils.isEmpty(message)) { + if (re.getHostCard() != null) { + message = TextUtil.fastReplace(message, "CARDNAME", re.getHostCard().getName()); + } + game.getGameLog().add(GameLogEntryType.EFFECT_REPLACED, message); + } + } + } + + private void executeReplaceDamageBufferedSA(Map>> executedDamageMap) { + for (Map.Entry>> entry : executedDamageMap.entrySet()) { + ReplacementEffect re = entry.getKey(); + if (re.getOverridingAbility() == null) { + continue; + } + SpellAbility bufferedSA = re.getOverridingAbility(); + ApiType apiType = bufferedSA.getApi(); + if (apiType == ApiType.ReplaceDamage || apiType == ApiType.ReplaceSplitDamage || apiType == ApiType.ReplaceEffect) { + bufferedSA = bufferedSA.getSubAbility(); + if (bufferedSA == null) { + continue; + } + } + + List> executedParamList = entry.getValue(); + if (executedParamList.isEmpty()) { + continue; + } + + Map mapParams = re.getMapParams(); + boolean isPrevention = (mapParams.containsKey("Prevent") && mapParams.get("Prevent").equals("True")) || mapParams.containsKey("PreventionEffect"); + boolean executePerSource = (mapParams.containsKey("ExecuteMode") && mapParams.get("ExecuteMode").equals("PerSource")); + boolean executePerTarget = (mapParams.containsKey("ExecuteMode") && mapParams.get("ExecuteMode").equals("PerTarget")); + + while (!executedParamList.isEmpty()) { + Map runParams = AbilityKey.newMap(); + List damageSourceList = new ArrayList<>(); + List affectedList = new ArrayList<>(); + int damageSum = 0; + + Iterator> itr = executedParamList.iterator(); + while (itr.hasNext()) { + Map executedParams = itr.next(); + + @SuppressWarnings("unchecked") + Map resultMap = (Map) executedParams.get(AbilityKey.ReplacementResultMap); + ReplacementResult res = resultMap.get(re); + if (res == ReplacementResult.NotReplaced && (!isPrevention || Boolean.FALSE.equals(executedParams.get(AbilityKey.NoPreventDamage)))) { + itr.remove(); + continue; + } + + Card source = (Card) executedParams.get(AbilityKey.DamageSource); + if (executePerSource && !damageSourceList.isEmpty() && !damageSourceList.contains(source)) { + continue; + } + + GameEntity target = (GameEntity) executedParams.get(AbilityKey.Affected); + if (executePerTarget && !affectedList.isEmpty() && !affectedList.contains(target)) { + continue; + } + + itr.remove(); + int damage = (int) executedParams.get(isPrevention ? AbilityKey.PreventedAmount : AbilityKey.DamageAmount); + if (!damageSourceList.contains(source)) { + damageSourceList.add(source); + } + if (!affectedList.contains(target)) { + affectedList.add(target); + } + damageSum += damage; + } + + if (damageSum > 0) { + runParams.put(AbilityKey.DamageSource, (damageSourceList.size() > 1 ? damageSourceList : damageSourceList.get(0))); + runParams.put(AbilityKey.Affected, (affectedList.size() > 1 ? affectedList : affectedList.get(0))); + runParams.put(AbilityKey.DamageAmount, damageSum); + + re.setReplacingObjects(runParams, re.getOverridingAbility()); + bufferedSA.setActivatingPlayer(re.getHostCard().getController()); + AbilityUtils.resolve(bufferedSA); + } + } + } + } + + public void runReplaceDamage(final boolean isCombat, final CardDamageMap damageMap, final CardDamageMap preventMap, + final GameEntityCounterTable counterTable, final SpellAbility cause) { + PlayerCollection players = game.getPlayersInTurnOrder(); + for (int i = 0; i < players.size(); i++) { + replaceDamageList.add(new HashMap<>()); + } + + // Map of all executed replacement effect for DamageDone event, including run params + Map>> executedDamageMap = new HashMap<>(); + + // First, gather all possible replacement effects + getPossibleReplaceDamageList(players, isCombat, damageMap, cause); + + // Next, handle replacement effects in APNAP order + // Handle "Prevented this way" and abilities like "Phantom Nomad", by buffer the replaced SA + // and only run them after all prevention and redirection effects are processed. + while (true) { + Player decider = null; + Map>> replaceCandidateMap = null; + for (int i = 0; i < players.size(); i++) { + if (replaceDamageList.get(i).isEmpty()) continue; + decider = players.get(i); + replaceCandidateMap = replaceDamageList.get(i); + break; + } + if (replaceCandidateMap == null) { + break; + } + + List possibleReplacers = new ArrayList<>(replaceCandidateMap.keySet()); + ReplacementEffect chosenRE = decider.getController().chooseSingleReplacementEffect(Localizer.getInstance().getMessage("lblChooseFirstApplyReplacementEffect"), possibleReplacers); + List> runParamList = replaceCandidateMap.get(chosenRE); + + if (!executedDamageMap.containsKey(chosenRE)) { + executedDamageMap.put(chosenRE, new ArrayList<>()); + } + + // Run all possible events for chosen replacement effect + chosenRE.setHasRun(true); + SpellAbility effectSA = chosenRE.getOverridingAbility(); + SpellAbility bufferedSA = effectSA; + boolean needRestoreSubSA = false; + if (effectSA != null) { + ApiType apiType = effectSA.getApi(); + // Temporary remove sub ability from ReplaceDamage, ReplaceSplitDamage and ReplaceEffect API so they could be run later + if (apiType == ApiType.ReplaceDamage || apiType == ApiType.ReplaceSplitDamage || apiType == ApiType.ReplaceEffect) { + bufferedSA = effectSA.getSubAbility(); + if (bufferedSA != null) { + needRestoreSubSA = true; + effectSA.setSubAbility(null); + } + } + } + + for (Map runParams : runParamList) { + if (!runParams.containsKey(AbilityKey.ReplacementResultMap)) { + Map resultMap = new HashMap<>(); + runParams.put(AbilityKey.ReplacementResultMap, resultMap); + } + runSingleReplaceDamageEffect(chosenRE, runParams, replaceCandidateMap, executedDamageMap, decider, damageMap, preventMap); + } + + // Restore temporary removed SA + if (needRestoreSubSA) { + effectSA.setSubAbility((AbilitySub)bufferedSA); + } + chosenRE.setHasRun(false); + replaceCandidateMap.remove(chosenRE); + } + + replaceDamageList.clear(); + + // Finally, run all buffered SA to finish the replacement processing + executeReplaceDamageBufferedSA(executedDamageMap); + } + /** * * Creates an instance of the proper replacement effect object based on raw diff --git a/forge-gui-desktop/src/main/java/forge/screens/workshop/views/VCardScript.java b/forge-gui-desktop/src/main/java/forge/screens/workshop/views/VCardScript.java index 8206ebc0add..90c8868dcba 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/workshop/views/VCardScript.java +++ b/forge-gui-desktop/src/main/java/forge/screens/workshop/views/VCardScript.java @@ -14,6 +14,7 @@ import forge.gui.framework.DragTab; import forge.gui.framework.EDocID; import forge.gui.framework.IVDoc; import forge.screens.workshop.controllers.CCardScript; +import forge.toolbox.FScrollPane; import forge.toolbox.FTextPane; import forge.util.Localizer; import net.miginfocom.swing.MigLayout; @@ -32,6 +33,7 @@ public enum VCardScript implements IVDoc { private final DragTab tab = new DragTab(Localizer.getInstance().getMessage("lblCardScript")); private final FTextPane txtScript = new FTextPane(); + private final FScrollPane scrollScript; private final StyledDocument doc; private final Style error; @@ -41,6 +43,7 @@ public enum VCardScript implements IVDoc { txtScript.setFocusable(true); doc = new DefaultStyledDocument(); txtScript.setDocument(doc); + scrollScript = new FScrollPane(txtScript, true); error = doc.addStyle("error", null); error.addAttribute(StyleConstants.Background, Color.red); error.addAttribute(StyleConstants.Bold, Boolean.valueOf(true)); @@ -107,6 +110,6 @@ public enum VCardScript implements IVDoc { public void populate() { JPanel body = parentCell.getBody(); body.setLayout(new MigLayout("insets 1, gap 0, wrap")); - body.add(txtScript, "w 100%, h 100%"); + body.add(scrollScript, "w 100%, h 100%"); } } diff --git a/forge-gui-desktop/src/test/java/forge/ai/simulation/GameSimulatorTest.java b/forge-gui-desktop/src/test/java/forge/ai/simulation/GameSimulatorTest.java index 51974682e0c..7ec016eb0fe 100644 --- a/forge-gui-desktop/src/test/java/forge/ai/simulation/GameSimulatorTest.java +++ b/forge-gui-desktop/src/test/java/forge/ai/simulation/GameSimulatorTest.java @@ -2166,4 +2166,149 @@ public class GameSimulatorTest extends SimulationTestCase { assertEquals(true, simGoblin.isRed() && simGoblin.isBlack()); assertEquals(true, simGoblin.getType().hasSubtype("Zombie")); } + + public void testCantBePrevented() { + String polukranosCardName = "Polukranos, Unchained"; + String hydraCardName = "Phyrexian Hydra"; + String leylineCardName = "Leyline of Punishment"; + + Game game = initAndCreateGame(); + Player p = game.getPlayers().get(0); + + Card polukranos = addCard(polukranosCardName, p); + polukranos.addCounter(CounterEnumType.P1P1, 6, p, false, null); + addCard(hydraCardName, p); + addCard(leylineCardName, p); + for (int i = 0; i < 2; ++i) { + addCard("Mountain", p); + } + Card pyroCard = addCardToZone("Pyroclasm", p, ZoneType.Hand); + SpellAbility pyroSA = pyroCard.getFirstSpellAbility(); + + game.getPhaseHandler().devModeSet(PhaseType.MAIN1, p); + game.getAction().checkStateEffects(true); + + GameSimulator sim = createSimulator(game, p); + sim.simulateSpellAbility(pyroSA); + Game simGame = sim.getSimulatedGameState(); + Card simPolukranos = findCardWithName(simGame, polukranosCardName); + Card simHydra = findCardWithName(simGame, hydraCardName); + + assertTrue(simPolukranos.hasCounters()); + assertEquals(4, simPolukranos.getCounters(CounterEnumType.P1P1)); + assertEquals(2, simPolukranos.getDamage()); + + assertFalse(simHydra.hasCounters()); + assertEquals(2, simHydra.getDamage()); + } + + public void testAlphaBrawl() { + Game game = initAndCreateGame(); + Player p1 = game.getPlayers().get(0); + Player p2 = game.getPlayers().get(1); + + String nishobaName = "Phantom Nishoba"; + String capridorName = "Stormwild Capridor"; + String pridemateName = "Ajani's Pridemate"; + String indestructibilityName = "Indestructibility"; + String bearName = "Runeclaw Bear"; + String alphaBrawlName = "Alpha Brawl"; + + // enough to cast Alpha Brawl + for (int i = 0; i < 8; ++i) { + addCard("Mountain", p2); + } + + Card nishoba = addCard(nishobaName, p1); + nishoba.addCounter(CounterEnumType.P1P1, 7, p1, false, null); + addCard(capridorName, p1); + Card pridemate = addCard(pridemateName, p1); + Card indestructibility = addCard(indestructibilityName, p1); + indestructibility.attachToEntity(pridemate); + addCard(bearName, p1); + + Card alphaBrawl = addCardToZone(alphaBrawlName, p2, ZoneType.Hand); + SpellAbility alphaBrawlSA = alphaBrawl.getFirstSpellAbility(); + + game.getPhaseHandler().devModeSet(PhaseType.MAIN2, p2); + game.getAction().checkStateEffects(true); + + GameSimulator sim = createSimulator(game, p2); + alphaBrawlSA.setTargetCard(nishoba); + sim.simulateSpellAbility(alphaBrawlSA); + Game simGame = sim.getSimulatedGameState(); + Card simNishoba = findCardWithName(simGame, nishobaName); + Card simCapridor = findCardWithName(simGame, capridorName); + Card simPridemate = findCardWithName(simGame, pridemateName); + Card simBear = findCardWithName(simGame, bearName); + + // bear is destroyed + assertNull(simBear); + + assertNotNull(simNishoba); + assertTrue(simNishoba.hasCounters()); + // Damage prevented and only 1 +1/+1 counter is removed + assertEquals(0, simNishoba.getDamage()); + assertTrue(simNishoba.hasCounters()); + assertEquals(6, simNishoba.getCounters(CounterEnumType.P1P1)); + assertEquals(6, simNishoba.getToughnessBonusFromCounters()); + assertEquals(6, simNishoba.getPowerBonusFromCounters()); + + assertNotNull(simCapridor); + // Damage prevented and that many +1/+1 counters are put + assertEquals(0, simCapridor.getDamage()); + assertTrue(simCapridor.hasCounters()); + assertEquals(7, simCapridor.getCounters(CounterEnumType.P1P1)); + assertEquals(7, simCapridor.getToughnessBonusFromCounters()); + assertEquals(7, simCapridor.getPowerBonusFromCounters()); + + assertNotNull(simPridemate); + assertEquals(7, simPridemate.getDamage()); + // Life gain only triggered once + assertTrue(simPridemate.hasCounters()); + assertEquals(1, simPridemate.getCounters(CounterEnumType.P1P1)); + assertEquals(1, simPridemate.getToughnessBonusFromCounters()); + assertEquals(1, simPridemate.getPowerBonusFromCounters()); + + // 2 times 7 damage with life gain = 14 + 20 = 34 (damage to Stormwild Capridor is prevented) + assertEquals(34, simGame.getPlayers().get(0).getLife()); + } + + public void testGlarecaster() { + String glarecasterName = "Glarecaster"; + + Game game = initAndCreateGame(); + Player p = game.getPlayers().get(0); + Player p2 = game.getPlayers().get(1); + + Card glarecaster = addCard(glarecasterName, p); + // enough to activate Glarecaster and cast Inferno + for (int i = 0; i < 7; ++i) { + addCard("Plains", p); + addCard("Mountain", p); + } + Card infernoCard = addCardToZone("Inferno", p, ZoneType.Hand); + SpellAbility infernoSA = infernoCard.getFirstSpellAbility(); + + game.getPhaseHandler().devModeSet(PhaseType.MAIN1, p); + game.getAction().checkStateEffects(true); + + SpellAbility saGlarecaster = findSAWithPrefix(glarecaster, "{5}{W}"); + assertNotNull(saGlarecaster); + saGlarecaster.getTargets().add(p2); + + GameSimulator sim = createSimulator(game, p); + int score = sim.simulateSpellAbility(saGlarecaster).value; + assertTrue(score > 0); + sim.simulateSpellAbility(infernoSA); + Game simGame = sim.getSimulatedGameState(); + Card simGlarecaster = findCardWithName(simGame, glarecasterName); + + assertNotNull(simGlarecaster); + assertEquals(0, simGlarecaster.getDamage()); + + // 6 * 3 = 18 damage are all dealt to p2 + assertEquals(20, simGame.getPlayers().get(0).getLife()); + assertEquals(2, simGame.getPlayers().get(1).getLife()); + } } diff --git a/forge-gui/res/cardsfolder/c/channel_harm.txt b/forge-gui/res/cardsfolder/c/channel_harm.txt index 51a797e798a..5df6bd002e3 100644 --- a/forge-gui/res/cardsfolder/c/channel_harm.txt +++ b/forge-gui/res/cardsfolder/c/channel_harm.txt @@ -3,7 +3,6 @@ ManaCost:5 W Types:Instant A:SP$ Effect | Cost$ 5 W | ValidTgts$ Creature | ReplacementEffects$ ChannelHarmRep | RememberObjects$ Targeted | ExileOnMoved$ Battlefield | AILogic$ Fog | SpellDescription$ Prevent all damage that would be dealt to you and permanents you control this turn by sources you don't control. If damage is prevented this way, you may have CARDNAME deal that much damage to target creature. SVar:ChannelHarmRep:Event$ DamageDone | ActiveZones$ Command | ValidTarget$ You,Permanent.YouCtrl | ValidSource$ Card.YouDontCtrl,Emblem.YouDontCtrl | ReplaceWith$ DamageSourceInstead | PreventionEffect$ True | Description$ Prevent all damage that would be dealt to you and permanents you control this turn by sources you don't control. If damage is prevented this way, you may have EFFECTHOST deal that much damage to target creature. -SVar:DamageSourceInstead:DB$ DealDamage | NumDmg$ X | Defined$ Remembered | DamageSouce$ EffectSource | OptionalDecider$ You +SVar:DamageSourceInstead:DB$ DealDamage | NumDmg$ X | Defined$ Remembered | DamageSource$ EffectSource | OptionalDecider$ You SVar:X:ReplaceCount$DamageAmount -SVar:Picture:http://www.wizards.com/global/images/magic/general/channel_harm.jpg Oracle:Prevent all damage that would be dealt to you and permanents you control this turn by sources you don't control. If damage is prevented this way, you may have Channel Harm deal that much damage to target creature. diff --git a/forge-gui/res/cardsfolder/c/comeuppance.txt b/forge-gui/res/cardsfolder/c/comeuppance.txt index 0a121ba70a7..04408ffb0ec 100644 --- a/forge-gui/res/cardsfolder/c/comeuppance.txt +++ b/forge-gui/res/cardsfolder/c/comeuppance.txt @@ -2,9 +2,8 @@ Name:Comeuppance ManaCost:3 W Types:Instant A:SP$ Effect | Cost$ 3 W | ReplacementEffects$ RPrevent -SVar:RPrevent:Event$ DamageDone | ValidSource$ Card.YouDontCtrl,Emblem.YouDontCtrl | ValidTarget$ You,Planeswalker.YouCtrl | ReplaceWith$ DamageCreature | PreventionEffect$ True | Description$ Prevent all damage that would be dealt to you and planeswalkers you control this turn by sources you don't control. If damage from a creature source is prevented this way, CARDNAME deals that much damage to that creature. If damage from a noncreature source is prevented this way, CARDNAME deals that much damage to the source's controller. +SVar:RPrevent:Event$ DamageDone | ValidSource$ Card.YouDontCtrl,Emblem.YouDontCtrl | ValidTarget$ You,Planeswalker.YouCtrl | ReplaceWith$ DamageCreature | PreventionEffect$ True | ExecuteMode$ PerSource | Description$ Prevent all damage that would be dealt to you and planeswalkers you control this turn by sources you don't control. If damage from a creature source is prevented this way, CARDNAME deals that much damage to that creature. If damage from a noncreature source is prevented this way, CARDNAME deals that much damage to the source's controller. SVar:DamageCreature:DB$ DealDamage | Defined$ ReplacedSource | DamageSource$ EffectSource | NumDmg$ X | ConditionDefined$ ReplacedSource | ConditionPresent$ Card.Creature | ConditionCompare$ GE1 | SubAbility$ DamageNonCreature SVar:DamageNonCreature:DB$ DealDamage | Defined$ ReplacedSourceController | DamageSource$ EffectSource | NumDmg$ X | ConditionDefined$ ReplacedSource | ConditionPresent$ Card.nonCreature | ConditionCompare$ GE1 SVar:X:ReplaceCount$DamageAmount -SVar:Picture:http://www.wizards.com/global/images/magic/general/comeuppance.jpg Oracle:Prevent all damage that would be dealt to you and planeswalkers you control this turn by sources you don't control. If damage from a creature source is prevented this way, Comeuppance deals that much damage to that creature. If damage from a noncreature source is prevented this way, Comeuppance deals that much damage to the source's controller. \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/c/crumbling_sanctuary.txt b/forge-gui/res/cardsfolder/c/crumbling_sanctuary.txt index 76ca167bb04..1239600c16f 100644 --- a/forge-gui/res/cardsfolder/c/crumbling_sanctuary.txt +++ b/forge-gui/res/cardsfolder/c/crumbling_sanctuary.txt @@ -1,7 +1,7 @@ Name:Crumbling Sanctuary ManaCost:5 Types:Artifact -R:Event$ DamageDone | ActiveZones$ Battlefield | ValidTarget$ Player | ReplaceWith$ ExileTop | Description$ If damage would be dealt to a player, that player exiles that many cards from the top of their library instead. +R:Event$ DamageDone | ActiveZones$ Battlefield | ValidTarget$ Player | ReplaceWith$ ExileTop | ExecuteMode$ PerTarget | Description$ If damage would be dealt to a player, that player exiles that many cards from the top of their library instead. SVar:ExileTop:DB$ Dig | Defined$ ReplacedTarget | DigNum$ X | ChangeNum$ All | DestinationZone$ Exile SVar:X:ReplaceCount$DamageAmount SVar:NonStackingEffect:True diff --git a/forge-gui/res/cardsfolder/d/deep_water.txt b/forge-gui/res/cardsfolder/d/deep_water.txt index 8c300df12bd..1048cd1c09e 100644 --- a/forge-gui/res/cardsfolder/d/deep_water.txt +++ b/forge-gui/res/cardsfolder/d/deep_water.txt @@ -2,7 +2,7 @@ Name:Deep Water ManaCost:U U Types:Enchantment A:AB$ Effect | Cost$ U | ReplacementEffects$ ReplaceU | SpellDescription$ Until end of turn, if you tap a land you control for mana, it produces {U} instead of any other type. -SVar:ReplaceU:Event$ ProduceMana | ActiveZones$ Command | ValidPlayer$ You | ValidCard$ Land.YouCtrl | ValidAbility$ Activated.hasTapCost | ReplaceWith$ ProduceU | Description$ If you tap a land you control for mana, it produces U instead of any other type. +SVar:ReplaceU:Event$ ProduceMana | ActiveZones$ Command | ValidActivator$ You | ValidCard$ Land.YouCtrl | ValidAbility$ Activated.hasTapCost | ReplaceWith$ ProduceU | Description$ If you tap a land you control for mana, it produces U instead of any other type. SVar:ProduceU:DB$ ReplaceMana | ReplaceType$ U AI:RemoveDeck:All AI:RemoveDeck:Random diff --git a/forge-gui/res/cardsfolder/f/fathom_fleet_cutthroat.txt b/forge-gui/res/cardsfolder/f/fathom_fleet_cutthroat.txt index 8578bb3da10..bda45911694 100644 --- a/forge-gui/res/cardsfolder/f/fathom_fleet_cutthroat.txt +++ b/forge-gui/res/cardsfolder/f/fathom_fleet_cutthroat.txt @@ -3,6 +3,6 @@ ManaCost:3 B Types:Creature Human Pirate PT:3/3 T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigDestroy | TriggerController$ TriggeredCardController | TriggerDescription$ When CARDNAME enters the battlefield, destroy target creature an opponent controls that was dealt damage this turn. -SVar:TrigDestroy:DB$ Destroy | ValidTgts$ Creature.OppCtrl+wasDealtDamageThisTurn | TgtPrompt$ Select target creature tan opponent controls that was dealt damage this turn. +SVar:TrigDestroy:DB$ Destroy | ValidTgts$ Creature.OppCtrl+wasDealtDamageThisTurn | TgtPrompt$ Select target creature an opponent controls that was dealt damage this turn. SVar:Picture:http://www.wizards.com/global/images/magic/general/fathom_fleet_cutthroat.jpg Oracle:When Fathom Fleet Cutthroat enters the battlefield, destroy target creature an opponent controls that was dealt damage this turn. \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/h/harvest_mage.txt b/forge-gui/res/cardsfolder/h/harvest_mage.txt index 9a808e30a70..1f7b9053a52 100644 --- a/forge-gui/res/cardsfolder/h/harvest_mage.txt +++ b/forge-gui/res/cardsfolder/h/harvest_mage.txt @@ -3,7 +3,7 @@ ManaCost:G Types:Creature Human Spellshaper PT:1/1 A:AB$ Effect | Cost$ G T Discard<1/Card> | ReplacementEffects$ HarvestReplacement | AILogic$ Never | Stackable$ False | SpellDescription$ Until end of turn, if you tap a land for mana, it produces one mana of a color of your choice instead of any other type and amount. -SVar:HarvestReplacement:Event$ ProduceMana | ActiveZones$ Command | ValidPlayer$ You | ValidCard$ Land | ValidAbility$ Activated.hasTapCost | ReplaceWith$ HarvestProduce | Description$ If you tap a land for mana, it produces one mana of a color of your choice instead of any other type and amount. +SVar:HarvestReplacement:Event$ ProduceMana | ActiveZones$ Command | ValidActivator$ You | ValidCard$ Land | ValidAbility$ Activated.hasTapCost | ReplaceWith$ HarvestProduce | Description$ If you tap a land for mana, it produces one mana of a color of your choice instead of any other type and amount. SVar:HarvestProduce:DB$ ReplaceMana | ReplaceMana$ Any AI:RemoveDeck:All SVar:NonStackingEffect:True diff --git a/forge-gui/res/cardsfolder/i/ironscale_hydra.txt b/forge-gui/res/cardsfolder/i/ironscale_hydra.txt index fa3e447abc4..e550ae82491 100644 --- a/forge-gui/res/cardsfolder/i/ironscale_hydra.txt +++ b/forge-gui/res/cardsfolder/i/ironscale_hydra.txt @@ -2,7 +2,7 @@ Name:Ironscale Hydra ManaCost:3 G G Types:Creature Hydra PT:5/5 -R:Event$ DamageDone | ActiveZones$ Battlefield | ValidTarget$ Card.Self | ValidSource$ Creature | IsCombat$ True | ReplaceWith$ Counters | PreventionEffect$ True | AlwaysReplace$ True | Description$ If a creature would deal combat damage to CARDNAME, prevent that damage and put a +1/+1 counter on CARDNAME. +R:Event$ DamageDone | ActiveZones$ Battlefield | ValidTarget$ Card.Self | ValidSource$ Creature | IsCombat$ True | ReplaceWith$ Counters | PreventionEffect$ True | AlwaysReplace$ True | ExecuteMode$ PerSource | Description$ If a creature would deal combat damage to CARDNAME, prevent that damage and put a +1/+1 counter on CARDNAME. SVar:Counters:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ 1 DeckHas:Ability$Counters Oracle:If a creature would deal combat damage to Ironscale Hydra, prevent that damage and put a +1/+1 counter on Ironscale Hydra. diff --git a/forge-gui/res/cardsfolder/m/mana_reflection.txt b/forge-gui/res/cardsfolder/m/mana_reflection.txt index 04ebe0d17df..1bc8429723d 100644 --- a/forge-gui/res/cardsfolder/m/mana_reflection.txt +++ b/forge-gui/res/cardsfolder/m/mana_reflection.txt @@ -1,6 +1,6 @@ Name:Mana Reflection ManaCost:4 G G Types:Enchantment -R:Event$ ProduceMana | ActiveZones$ Battlefield | ValidPlayer$ You | ValidCard$ Permanent | ValidAbility$ Activated.hasTapCost | ReplaceWith$ ProduceTwice | Description$ If you tap a permanent for mana, it produces twice as much of that mana instead. +R:Event$ ProduceMana | ActiveZones$ Battlefield | ValidActivator$ You | ValidCard$ Permanent | ValidAbility$ Activated.hasTapCost | ReplaceWith$ ProduceTwice | Description$ If you tap a permanent for mana, it produces twice as much of that mana instead. SVar:ProduceTwice:DB$ ReplaceMana | ReplaceAmount$ 2 Oracle:If you tap a permanent for mana, it produces twice as much of that mana instead. diff --git a/forge-gui/res/cardsfolder/n/nine_lives.txt b/forge-gui/res/cardsfolder/n/nine_lives.txt index 303384b5739..0d9710345e0 100644 --- a/forge-gui/res/cardsfolder/n/nine_lives.txt +++ b/forge-gui/res/cardsfolder/n/nine_lives.txt @@ -2,7 +2,7 @@ Name:Nine Lives ManaCost:1 W W Types:Enchantment K:Hexproof -R:Event$ DamageDone | ActiveZones$ Battlefield | ValidSource$ Card,Emblem | ValidTarget$ You | PreventionEffect$ True | ReplaceWith$ AddCounters | AlwaysReplace$ True | Description$ If a source would deal damage to you, prevent that damage and put an incarnation counter on CARDNAME. +R:Event$ DamageDone | ActiveZones$ Battlefield | ValidSource$ Card,Emblem | ValidTarget$ You | PreventionEffect$ True | ReplaceWith$ AddCounters | AlwaysReplace$ True | ExecuteMode$ PerSource | Description$ If a source would deal damage to you, prevent that damage and put an incarnation counter on CARDNAME. SVar:AddCounters:DB$ PutCounter | Defined$ Self | CounterType$ INCARNATION | CounterNum$ 1 T:Mode$ Always | TriggerZones$ Battlefield | IsPresent$ Card.Self+counters_GE9_INCARNATION | Execute$ TrigExile | TriggerDescription$ When there are nine or more incarnation counters on CARDNAME, exile it. SVar:TrigExile:DB$ ChangeZone | Origin$ Battlefield | Destination$ Exile | Defined$ Self diff --git a/forge-gui/res/cardsfolder/n/nyxbloom_ancient.txt b/forge-gui/res/cardsfolder/n/nyxbloom_ancient.txt index 6186b559ecf..1537e062d68 100755 --- a/forge-gui/res/cardsfolder/n/nyxbloom_ancient.txt +++ b/forge-gui/res/cardsfolder/n/nyxbloom_ancient.txt @@ -3,6 +3,6 @@ ManaCost:4 G G G Types:Enchantment Creature Elemental PT:5/5 K:Trample -R:Event$ ProduceMana | ActiveZones$ Battlefield | ValidCard$ Permanent.YouCtrl | ValidAbility$ Activated.hasTapCost | ReplaceWith$ ProduceThrice | Description$ If you tap a permanent for mana, it produces three times as much of that mana instead. +R:Event$ ProduceMana | ActiveZones$ Battlefield | ValidActivator$ You | ValidCard$ Permanent | ValidAbility$ Activated.hasTapCost | ReplaceWith$ ProduceThrice | Description$ If you tap a permanent for mana, it produces three times as much of that mana instead. SVar:ProduceThrice:DB$ ReplaceMana | ReplaceAmount$ 3 Oracle:Trample\nIf you tap a permanent for mana, it produces three times as much of that mana instead. diff --git a/forge-gui/res/cardsfolder/o/opal_eye_kondas_yojimbo.txt b/forge-gui/res/cardsfolder/o/opal_eye_kondas_yojimbo.txt index 27b6aae4b7e..6e486a6f989 100644 --- a/forge-gui/res/cardsfolder/o/opal_eye_kondas_yojimbo.txt +++ b/forge-gui/res/cardsfolder/o/opal_eye_kondas_yojimbo.txt @@ -6,12 +6,11 @@ K:Defender K:Bushido:1 A:AB$ ChooseSource | Cost$ T | Choices$ Card,Emblem | AILogic$ NeedsPrevention | SubAbility$ DBEffect | SpellDescription$ The next time a source of your choice would deal damage this turn, that damage is dealt to CARDNAME instead. SVar:DBEffect:DB$ Effect | ReplacementEffects$ SelflessDamage | Triggers$ OutOfSight | Duration$ UntilHostLeavesPlayOrEOT | SubAbility$ DBCleanup | ConditionDefined$ ChosenCard | ConditionPresent$ Card,Emblem | ConditionCompare$ GE1 -SVar:SelflessDamage:Event$ DamageDone | ValidTarget$ You | ValidSource$ Card.ChosenCard,Emblem.ChosenCard | ReplaceWith$ SelflessDmg | DamageTarget$ EffectSource | Description$ The next time a source of your choice would deal damage to you this turn, that damage is dealt to EFFECTSOURCE instead. +SVar:SelflessDamage:Event$ DamageDone | ValidSource$ Card.ChosenCard,Emblem.ChosenCard | ReplaceWith$ SelflessDmg | DamageTarget$ EffectSource | Description$ The next time a source of your choice would deal damage to you this turn, that damage is dealt to EFFECTSOURCE instead. SVar:SelflessDmg:DB$ ReplaceEffect | VarName$ Affected | VarValue$ EffectSource | VarType$ Card | SubAbility$ ExileEffect SVar:OutOfSight:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | Defined$ ChosenCard | Execute$ ExileEffect | Static$ True SVar:ExileEffect:DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile | Static$ True SVar:DBCleanup:DB$ Cleanup | ClearChosenCard$ True A:AB$ PreventDamage | Cost$ 1 W | Defined$ Self | Amount$ 1 | SpellDescription$ Prevent the next 1 damage that would be dealt to CARDNAME this turn. AI:RemoveDeck:All -SVar:Picture:http://www.wizards.com/global/images/magic/general/opal_eye_kondas_yojimbo.jpg Oracle:Defender (This creature can't attack.)\nBushido 1 (Whenever this creature blocks or becomes blocked, it gets +1/+1 until end of turn.)\n{T}: The next time a source of your choice would deal damage this turn, that damage is dealt to Opal-Eye, Konda's Yojimbo instead.\n{1}{W}: Prevent the next 1 damage that would be dealt to Opal-Eye this turn. diff --git a/forge-gui/res/cardsfolder/p/phantom_nomad.txt b/forge-gui/res/cardsfolder/p/phantom_nomad.txt index a273f388e06..7c709ca6bd6 100644 --- a/forge-gui/res/cardsfolder/p/phantom_nomad.txt +++ b/forge-gui/res/cardsfolder/p/phantom_nomad.txt @@ -3,13 +3,6 @@ ManaCost:1 W Types:Creature Spirit Nomad PT:0/0 K:etbCounter:P1P1:2 -T:Mode$ Phase | Static$ True | Phase$ First Strike Damage | Execute$ DBCleanup -T:Mode$ Phase | Static$ True | Phase$ EndCombat | Execute$ DBCleanup -R:Event$ DamageDone | IsCombat$ True | ActiveZones$ Battlefield | ValidTarget$ Card.Self | ReplaceWith$ DBRemoveCountersInCombat | PreventionEffect$ True | AlwaysReplace$ True | Description$ If damage would be dealt to CARDNAME, prevent that damage. Remove a +1/+1 counter from CARDNAME. -R:Event$ DamageDone | IsCombat$ False | ActiveZones$ Battlefield | ValidTarget$ Card.Self | ReplaceWith$ DBRemoveCounters | PreventionEffect$ True | AlwaysReplace$ True | Secondary$ True | Description$ If damage would be dealt to CARDNAME, prevent that damage. Remove a +1/+1 counter from CARDNAME. -SVar:DBRemoveCountersInCombat:DB$ RemoveCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ 1 | ConditionCheckSVar$ TimesFlagged | ConditionSVarCompare$ EQ0 | SubAbility$ DBFlagRemoveCounters -SVar:DBFlagRemoveCounters:DB$ StoreSVar | SVar$ TimesFlagged | Type$ CountSVar | Expression$ TimesFlagged/Plus.1 +R:Event$ DamageDone | ActiveZones$ Battlefield | ValidTarget$ Card.Self | ReplaceWith$ DBRemoveCounters | PreventionEffect$ True | AlwaysReplace$ True | Description$ If damage would be dealt to CARDNAME, prevent that damage. Remove a +1/+1 counter from CARDNAME. SVar:DBRemoveCounters:DB$ RemoveCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ 1 -SVar:TimesFlagged:Number$0 -SVar:DBCleanup:DB$ StoreSVar | SVar$ TimesFlagged | Type$ Number | Expression$ 0 Oracle:Phantom Nomad enters the battlefield with two +1/+1 counters on it.\nIf damage would be dealt to Phantom Nomad, prevent that damage. Remove a +1/+1 counter from Phantom Nomad. diff --git a/forge-gui/res/cardsfolder/s/soul_scar_mage.txt b/forge-gui/res/cardsfolder/s/soul_scar_mage.txt index 4990a00606f..70e49cc058e 100644 --- a/forge-gui/res/cardsfolder/s/soul_scar_mage.txt +++ b/forge-gui/res/cardsfolder/s/soul_scar_mage.txt @@ -3,9 +3,8 @@ ManaCost:R Types:Creature Human Wizard PT:1/2 K:Prowess -R:Event$ DamageDone | ActiveZones$ Battlefield | ValidSource$ Card.YouCtrl,Emblem.YouCtrl | ValidTarget$ Creature.OppCtrl | ReplaceWith$ Counters | IsCombat$ False | Description$ If a source you control would deal noncombat damage to a creature an opponent controls, put that many -1/-1 counters on that creature instead. +R:Event$ DamageDone | ActiveZones$ Battlefield | ValidSource$ Card.YouCtrl,Emblem.YouCtrl | ValidTarget$ Creature.OppCtrl | ReplaceWith$ Counters | IsCombat$ False | ExecuteMode$ PerTarget | Description$ If a source you control would deal noncombat damage to a creature an opponent controls, put that many -1/-1 counters on that creature instead. SVar:Counters:DB$PutCounter | Defined$ ReplacedTarget | CounterType$ M1M1 | CounterNum$ X SVar:X:ReplaceCount$DamageAmount DeckHas:Ability$Counters -SVar:Picture:http://www.wizards.com/global/images/magic/general/soul_scar_mage.jpg Oracle:Prowess\nIf a source you control would deal noncombat damage to a creature an opponent controls, put that many -1/-1 counters on that creature instead. \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/s/swans_of_bryn_argoll.txt b/forge-gui/res/cardsfolder/s/swans_of_bryn_argoll.txt index fc49dbbe8bf..90f02d8f182 100644 --- a/forge-gui/res/cardsfolder/s/swans_of_bryn_argoll.txt +++ b/forge-gui/res/cardsfolder/s/swans_of_bryn_argoll.txt @@ -3,8 +3,7 @@ ManaCost:2 WU WU Types:Creature Bird Spirit PT:4/3 K:Flying -R:Event$ DamageDone | ActiveZones$ Battlefield | ValidTarget$ Card.Self | ReplaceWith$ DBDraw | PreventionEffect$ True | Description$ If a source would deal damage to CARDNAME, prevent that damage. The source's controller draws cards equal to the damage prevented this way. +R:Event$ DamageDone | ActiveZones$ Battlefield | ValidTarget$ Card.Self | ReplaceWith$ DBDraw | PreventionEffect$ True | ExecuteMode$ PerSource | Description$ If a source would deal damage to CARDNAME, prevent that damage. The source's controller draws cards equal to the damage prevented this way. SVar:DBDraw:DB$ Draw | NumCards$ X | Defined$ ReplacedSourceController SVar:X:ReplaceCount$DamageAmount -SVar:Picture:http://www.wizards.com/global/images/magic/general/swans_of_bryn_argoll.jpg Oracle:Flying\nIf a source would deal damage to Swans of Bryn Argoll, prevent that damage. The source's controller draws cards equal to the damage prevented this way. diff --git a/forge-gui/res/cardsfolder/s/szadek_lord_of_secrets.txt b/forge-gui/res/cardsfolder/s/szadek_lord_of_secrets.txt index 7948de06152..27ba3d7c547 100644 --- a/forge-gui/res/cardsfolder/s/szadek_lord_of_secrets.txt +++ b/forge-gui/res/cardsfolder/s/szadek_lord_of_secrets.txt @@ -3,7 +3,7 @@ ManaCost:3 U U B B Types:Legendary Creature Vampire PT:5/5 K:Flying -R:Event$ DamageDone | ActiveZones$ Battlefield | IsCombat$ True | ValidSource$ Card.Self | ValidTarget$ Opponent | ReplaceWith$ CountersAndMill | Description$ If CARDNAME would deal combat damage to a player, instead put that many +1/+1 counters on Szadek and that player mills that many cards. +R:Event$ DamageDone | ActiveZones$ Battlefield | IsCombat$ True | ValidSource$ Card.Self | ValidTarget$ Player | ReplaceWith$ CountersAndMill | ExecuteMode$ PerTarget | Description$ If CARDNAME would deal combat damage to a player, instead put that many +1/+1 counters on Szadek and that player mills that many cards. SVar:X:ReplaceCount$DamageAmount SVar:CountersAndMill:DB$ PutCounter | Defined$ Self | CounterNum$ X | CounterType$ P1P1 | SubAbility$ Mill SVar:Mill:DB$ Mill | Defined$ ReplacedTarget | NumCards$ X diff --git a/forge-gui/res/cardsfolder/u/undead_alchemist.txt b/forge-gui/res/cardsfolder/u/undead_alchemist.txt index 4460b477ee0..423bdaaa638 100644 --- a/forge-gui/res/cardsfolder/u/undead_alchemist.txt +++ b/forge-gui/res/cardsfolder/u/undead_alchemist.txt @@ -2,7 +2,7 @@ Name:Undead Alchemist ManaCost:3 U Types:Creature Zombie PT:4/2 -R:Event$ DamageDone | ActiveZones$ Battlefield | ValidSource$ Creature.Zombie+YouCtrl | ValidTarget$ Opponent | ReplaceWith$ Mill | IsCombat$ True | Description$ If a Zombie you control would deal combat damage to a player, instead that player mills that many cards. +R:Event$ DamageDone | ActiveZones$ Battlefield | ValidSource$ Creature.Zombie+YouCtrl | ValidTarget$ Player | ReplaceWith$ Mill | IsCombat$ True | ExecuteMode$ PerTarget | Description$ If a Zombie you control would deal combat damage to a player, instead that player mills that many cards. SVar:Mill:DB$ Mill | Defined$ ReplacedTarget | NumCards$ X SVar:X:ReplaceCount$DamageAmount T:Mode$ ChangesZone | ValidCard$ Creature.nonToken+OppOwn | Origin$ Library | Destination$ Graveyard | Execute$ ExileAndToken | TriggerZones$ Battlefield | TriggerDescription$ Whenever a creature card is put into an opponent's graveyard from their library, exile that card and create a 2/2 black Zombie creature token. diff --git a/forge-gui/res/cardsfolder/upcoming/abiding_grace.txt b/forge-gui/res/cardsfolder/upcoming/abiding_grace.txt new file mode 100644 index 00000000000..0ce257e9859 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/abiding_grace.txt @@ -0,0 +1,9 @@ +Name:Abiding Grace +ManaCost:2 W +Types:Enchantment +T:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigCharm | TriggerDescription$ At the beginning of your end step, ABILITY +SVar:TrigCharm:DB$ Charm | Choices$ DBGainLife,DBChangeZone | CharmNum$ 1 +SVar:DBGainLife:DB$ GainLife | Defined$ You | LifeAmount$ 1 | SpellDescription$ You gain 1 life. +SVar:DBChangeZone:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | TgtPrompt$ Choose target creature card with mana value 1 in your graveyard | ValidTgts$ Creature.cmcEQ1+YouCtrl | SpellDescription$ Return target creature card with mana value 1 from your graveyard to the battlefield. +DeckHas:Ability$LifeGain +Oracle:At the beginning of your end step, choose one —\n• You gain 1 life.\n• Return target creature card with mana value 1 from your graveyard to the battlefield. diff --git a/forge-gui/res/cardsfolder/upcoming/aeve_progenitor_ooze.txt b/forge-gui/res/cardsfolder/upcoming/aeve_progenitor_ooze.txt new file mode 100644 index 00000000000..4167b274352 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/aeve_progenitor_ooze.txt @@ -0,0 +1,10 @@ +Name:Aeve, Progenitor Ooze +ManaCost:2 G G G +Types:Legendary Creature Ooze +PT:2/2 +K:Storm +S:Mode$ Continuous | Affected$ Card.token+Self | RemoveType$ Legendary | Description$ CARDNAME is not legendary if it's a token. +K:etbCounter:P1P1:X:no condition:CARDNAME enters the battlefield with a +1/+1 counter on it for each other Ooze you control. +SVar:X:Count$LastStateBattlefield Ooze.YouCtrl+Other +DeckHas:Ability$Counters +Oracle:Storm (When you cast this spell, copy it for each spell cast before it this turn. The copies become tokens.)\nAeve, Progenitor Ooze is not legendary if it's a token.\nAeve enters the battlefield with a +1/+1 counter on it for each other Ooze you control. diff --git a/forge-gui/res/cardsfolder/upcoming/altar_of_the_goyf.txt b/forge-gui/res/cardsfolder/upcoming/altar_of_the_goyf.txt new file mode 100644 index 00000000000..e5f2ba527b6 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/altar_of_the_goyf.txt @@ -0,0 +1,9 @@ +Name:Altar of the Goyf +ManaCost:5 +Types:Tribal Artifact Lhurgoyf +T:Mode$ Attacks | ValidCard$ Creature.YouCtrl | Alone$ True | TriggerZones$ Battlefield | Execute$ TrigPump | TriggerDescription$ Whenever a creature you control attacks alone, it gets +X/+X until end of turn, where X is the number of card types among cards in all graveyards. +SVar:TrigPump:DB$ Pump | Defined$ TriggeredAttacker | NumAtt$ +X | NumDef$ +X +S:Mode$ Continuous | Affected$ Creature.Lhurgoyf+YouCtrl | AddKeyword$ Trample | Description$ Lhurgoyf creatures you control have trample. +SVar:X:Count$CardTypes.Graveyard +SVar:PlayMain1:TRUE +Oracle:Whenever a creature you control attacks alone, it gets +X/+X until end of turn, where X is the number of card types among cards in all graveyards.\nLhurgoyf creatures you control have trample. diff --git a/forge-gui/res/cardsfolder/upcoming/arcbound_shikari.txt b/forge-gui/res/cardsfolder/upcoming/arcbound_shikari.txt new file mode 100644 index 00000000000..2b0721cdc93 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/arcbound_shikari.txt @@ -0,0 +1,11 @@ +Name:Arcbound Shikari +ManaCost:1 R W +Types:Artifact Creature Cat Soldier +PT:0/0 +K:First Strike +T:Mode$ ChangesZone | ValidCard$ Card.Self | Destination$ Battlefield | Execute$ TrigPutCounter | TriggerDescription$ When CARDNAME enters the battlefield, put a +1/+1 counter on each other artifact creature you control. +SVar:TrigPutCounter:DB$ PutCounterAll | ValidCards$ Creature.Artifact+Other+YouCtrl | CounterType$ P1P1 | CounterNum$ 1 +K:Modular:2 +DeckHas:Ability$Counters +SVar:PlayMain1:TRUE +Oracle:First strike\nWhen Arcbound Shikari enters the battlefield, put a +1/+1 counter on each other artifact creature you control.\nModular 2 (This creature enters the battlefield with two +1/+1 counters on it. When it does, you may put its counters on target artifact creature.) diff --git a/forge-gui/res/cardsfolder/upcoming/archon_of_cruelty.txt b/forge-gui/res/cardsfolder/upcoming/archon_of_cruelty.txt new file mode 100644 index 00000000000..5d0eaf48cc3 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/archon_of_cruelty.txt @@ -0,0 +1,14 @@ +Name:Archon of Cruelty +ManaCost:6 B B +Types:Creature Archon +PT:6/6 +K:Flying +T:Mode$ ChangesZone | ValidCard$ Card.Self | Destination$ Battlefield | Execute$ TrigSac | TriggerDescription$ Whenever CARDNAME enters the battlefield or attacks, target opponent sacrifices a creature or planeswalker, discards a card, and loses 3 life. You draw a card and gain 3 life. +T:Mode$ Attacks | ValidCard$ Card.Self | Secondary$ True | Execute$ TrigSac | TriggerDescription$ Whenever CARDNAME enters the battlefield or attacks, target opponent sacrifices a creature or planeswalker, discards a card, and loses 3 life. You draw a card and gain 3 life. +SVar:TrigSac:DB$ Sacrifice | ValidTgts$ Opponent | TgtPrompt$ Select target opponent | SacValid$ Creature,Planeswalker | SacMessage$ creature or planeswalker | SubAbility$ DBDiscard +SVar:DBDiscard:DB$ Discard | Defined$ Targeted | NumCards$ 1 | Mode$ TgtChoose | SubAbility$ DBLoseLife +SVar:DBLoseLife:DB$ LoseLife | Defined$ Targeted | LifeAmount$ 3 | SubAbility$ DBDraw +SVar:DBDraw:DB$ Draw | SubAbility$ DBGainLife +SVar:DBGainLife:DB$ GainLife | LifeAmount$ 3 +DeckHas:Ability$LifeGain +Oracle:Flying\nWhenever Archon of Cruelty enters the battlefield or attacks, target opponent sacrifices a creature or planeswalker, discards a card, and loses 3 life. You draw a card and gain 3 life. diff --git a/forge-gui/res/cardsfolder/upcoming/batterbone.txt b/forge-gui/res/cardsfolder/upcoming/batterbone.txt new file mode 100644 index 00000000000..6cf334f3bac --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/batterbone.txt @@ -0,0 +1,8 @@ +Name:Batterbone +ManaCost:2 +Types:Artifact Equipment +K:Living Weapon +K:Equip:5 +S:Mode$ Continuous | Affected$ Creature.EquippedBy | AddPower$ 1 | AddToughness$ 1 | AddKeyword$ Vigilance & Lifelink | Description$ Equipped creature gets +1/+1 and has vigilance and lifelink. +DeckHas:Ability$LifeGain & Ability$Token +Oracle:Living weapon (When this Equipment enters the battlefield, create a 0/0 black Phyrexian Germ creature token, then attach this to it.)\nEquipped creature gets +1/+1 and has vigilance and lifelink.\nEquip {5} diff --git a/forge-gui/res/cardsfolder/upcoming/bottle_golems.txt b/forge-gui/res/cardsfolder/upcoming/bottle_golems.txt new file mode 100644 index 00000000000..23e9a23d768 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/bottle_golems.txt @@ -0,0 +1,10 @@ +Name:Bottle Golems +ManaCost:4 +Types:Artifact Creature Golem +PT:3/3 +K:Trample +T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigSac | TriggerController$ TriggeredCardController | TriggerDescription$ When CARDNAME dies, you gain life equal to its power. +SVar:TrigSac:DB$GainLife | Defined$ TriggeredCardController | LifeAmount$ XPower +SVar:XPower:TriggeredCard$CardPower +DeckHas:Ability$LifeGain +Oracle:Trample\nWhen Bottle Golems dies, you gain life equal to its power. diff --git a/forge-gui/res/cardsfolder/upcoming/chatterstorm.txt b/forge-gui/res/cardsfolder/upcoming/chatterstorm.txt new file mode 100644 index 00000000000..24f309643df --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/chatterstorm.txt @@ -0,0 +1,7 @@ +Name:Chatterstorm +ManaCost:1 G +Types:Sorcery +K:Storm +A:SP$ Token | Cost$ 1 G | TokenAmount$ 1 | TokenScript$ g_1_1_squirrel | TokenOwner$ You | SpellDescription$ Create a 1/1 green Squirrel creature token. +DeckHas:Ability$Token +Oracle:Create a 1/1 green Squirrel creature token.\nStorm (When you cast this spell, copy it for each spell cast before it this turn.) diff --git a/forge-gui/res/cardsfolder/upcoming/drey_keeper.txt b/forge-gui/res/cardsfolder/upcoming/drey_keeper.txt new file mode 100644 index 00000000000..ada59c2c187 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/drey_keeper.txt @@ -0,0 +1,11 @@ +Name:Drey Keeper +ManaCost:3 B G +Types:Creature Elf Druid +PT:2/2 +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigToken | TriggerDescription$ When CARDNAME enters the battlefield, create two 1/1 green Squirrel creature tokens. +SVar:TrigToken:DB$ Token | TokenAmount$ 2 | TokenScript$ g_1_1_squirrel | TokenOwner$ You +A:AB$ PumpAll | Cost$ 3 B | ValidCards$ Squirrel.YouCtrl | NumAtt$ +1 | KW$ Menace | SpellDescription$ Squirrels you control get +1/+0 and gain menace until end of turn. +SVar:PlayMain1:TRUE +DeckNeeds:Type$Squirrel +DeckHas:Ability$Token +Oracle:When Drey Keeper enters the battlefield, create two 1/1 green Squirrel creature tokens.\n{3}{B}: Squirrels you control get +1/+0 and gain menace until end of turn. diff --git a/forge-gui/res/cardsfolder/upcoming/endurance.txt b/forge-gui/res/cardsfolder/upcoming/endurance.txt new file mode 100644 index 00000000000..3aaed429e41 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/endurance.txt @@ -0,0 +1,10 @@ +Name:Endurance +ManaCost:1 G G +Types:Creature Elemental Incarnation +PT:3/4 +K:Flash +K:Reach +K:Evoke:ExileFromHand<1/Card.Green+Other> +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigBottom | TriggerDescription$ When CARDNAME enters the battlefield, up to one target player puts all the cards from their graveyard on the bottom of their library in a random order. +SVar:TrigBottom:DB$ ChangeZoneAll | ValidTgts$ Player | TargetMin$ 0 | TargetMax$ 1 | TgtPrompt$ Select up to one target player | ChangeType$ Card | Origin$ Graveyard | Destination$ Library | Reveal$ False | RandomOrder$ True +Oracle:Flash\nReach\nWhen Endurance enters the battlefield, up to one target player puts all the cards from their graveyard on the bottom of their library in a random order.\nEvoke — Exile a green card from your hand. diff --git a/forge-gui/res/cardsfolder/upcoming/fae_offering.txt b/forge-gui/res/cardsfolder/upcoming/fae_offering.txt new file mode 100644 index 00000000000..feade557e79 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/fae_offering.txt @@ -0,0 +1,12 @@ +Name:Fae Offering +ManaCost:2 G +Types:Enchantment +T:Mode$ Phase | Phase$ End of Turn | TriggerZones$ Battlefield | CheckSVar$ Z | SVarCompare$ GE2 | Execute$ TrigTokenClue | TriggerDescription$ At the beginning of each end step, if you cast a creature and non-creature spell this turn, create a Clue token, a Food token, and a Treasure token. +SVar:TrigTokenClue:DB$ Token | TokenAmount$ 1 | TokenScript$ c_a_clue_draw | TokenOwner$ You | SubAbility$ TrigTokenFood +SVar:TrigTokenFood:DB$ Token | TokenAmount$ 1 | TokenScript$ c_a_food_sac | TokenOwner$ You | SubAbility$ TrigTokenTreasure +SVar:TrigTokenTreasure:DB$ Token | TokenAmount$ 1 | TokenScript$ c_a_treasure_sac | TokenOwner$ You +SVar:X:Count$ThisTurnCast_Creature.YouCtrl/LimitMax.1 +SVar:Y:Count$ThisTurnCast_Card.nonCreature+YouCtrl/LimitMax.1 +SVar:Z:SVar$X/Plus.Y +DeckHas:Ability$Token +Oracle:At the beginning of each end step, if you cast a creature and non-creature spell this turn, create a Clue token, a Food token, and a Treasure token. diff --git a/forge-gui/res/cardsfolder/upcoming/fast_furious.txt b/forge-gui/res/cardsfolder/upcoming/fast_furious.txt new file mode 100644 index 00000000000..1f9a9947e0a --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/fast_furious.txt @@ -0,0 +1,16 @@ +Name:Fast +ManaCost:2 R +Types:Instant +A:SP$ Discard | NumCards$ 1 | Mode$ TgtChoose | SubAbility$ DBDraw | SpellDescription$ Discard a card, then draw two cards. +SVar:DBDraw:DB$ Draw | NumCards$ 2 +DeckHas:Ability$Discard +AlternateMode:Split +Oracle:Discard a card, then draw two cards. + +ALTERNATE + +Name:Furious +ManaCost:3 R R +Types:Sorcery +A:SP$ DamageAll | ValidCards$ Creature.withoutFlying | NumDmg$ 3 | ValidDescription$ each creature without flying | SpellDescription$ CARDNAME deals 3 damage to each creature without flying. +Oracle:Furious deals 3 damage to each creature without flying. diff --git a/forge-gui/res/cardsfolder/upcoming/gaeas_will.txt b/forge-gui/res/cardsfolder/upcoming/gaeas_will.txt new file mode 100644 index 00000000000..89af506ab71 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/gaeas_will.txt @@ -0,0 +1,11 @@ +Name:Gaea's Will +ManaCost:no cost +Types:Sorcery +Colors:green +K:Suspend:4:G +A:SP$ Effect | Cost$ 0 | Name$ Yawgmoth's Will Effect | ReplacementEffects$ GraveToExile | StaticAbilities$ STPlay | AILogic$ YawgmothsWill | AINoRecursiveCheck$ True | SpellDescription$ Until end of turn, you may play lands and cast spells from your graveyard. If a card would be put into your graveyard from anywhere this turn, exile that card instead. +SVar:STPlay:Mode$ Continuous | EffectZone$ Command | Affected$ Card.YouCtrl | AffectedZone$ Graveyard | MayPlay$ True | Description$ You may play cards from your graveyard. +SVar:GraveToExile:Event$ Moved | ActiveZones$ Command | Destination$ Graveyard | ValidCard$ Card.nonToken+YouOwn | ReplaceWith$ Exile | Description$ If a card would be put into your graveyard from anywhere, exile it instead. +SVar:Exile:DB$ ChangeZone | Hidden$ True | Origin$ All | Destination$ Exile | Defined$ ReplacedCard +SVar:PlayMain1:ALWAYS +Oracle:Suspend 4 — {G}\nUntil end of turn, you may play lands and cast spells from your graveyard.\nIf a card would be put into your graveyard from anywhere this turn, exile that card instead. diff --git a/forge-gui/res/cardsfolder/upcoming/general_ferrous_rokiric.txt b/forge-gui/res/cardsfolder/upcoming/general_ferrous_rokiric.txt new file mode 100644 index 00000000000..531547de192 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/general_ferrous_rokiric.txt @@ -0,0 +1,11 @@ +Name:General Ferrous Rokiric +ManaCost:1 R W +Types:Legendary Creature Human Soldier +PT:3/1 +K:Hexproof:Card.MonoColor:monocolored +T:Mode$ SpellCast | ValidCard$ Card.MultiColor | ValidActivatingPlayer$ You | Execute$ TrigToken | TriggerZones$ Battlefield | TriggerDescription$ Whenever you cast a multicolored spell, create a 4/4 red and white Golem creature token. +SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenOwner$ You | TokenScript$ rw_4_4_golem +AI:RemoveDeck:Random +SVar:BuffedBy:Card.MultiColor +DeckHas:Ability$Token +Oracle:Hexproof from monocolored (This creature can't be the target of monocolored spells or abilities your opponents control.)\nWhenever you cast a multicolored spell, create a 4/4 red and white Golem creature token. diff --git a/forge-gui/res/cardsfolder/upcoming/geyadrone_dihada.txt b/forge-gui/res/cardsfolder/upcoming/geyadrone_dihada.txt new file mode 100644 index 00000000000..88354ea8189 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/geyadrone_dihada.txt @@ -0,0 +1,14 @@ +Name:Geyadrone Dihada +ManaCost:1 U B R +Types:Legendary Planeswalker Dihada +Loyalty:4 +S:Mode$ Continuous | Affected$ Card.Self | AddKeyword$ Protection:Permanent.counters_GE1_CORRUPTION:Protection from permanents with corruption counters on them | Description$ Protection from permanents with corruption counters on them +A:AB$ Pump | Cost$ AddCounter<1/LOYALTY> | Planeswalker$ True | TargetMin$ 0 | TargetMax$ 1 | TgtPrompt$ Choose up to one other target creature or planeswalker | ValidTgts$ Creature.Other,Planeswalker.Other | SubAbility$ DBLoseLife | SpellDescription$ Each opponent loses 2 life and you gain 2 life. Put a corruption counter on up to one other target creature or planeswalker. +SVar:DBLoseLife:DB$ LoseLife | Defined$ Player.Opponent | LifeAmount$ 2 | SubAbility$ DBGainLife +SVar:DBGainLife:DB$ GainLife | Defined$ You | LifeAmount$ 2 | SubAbility$ DBPutCounter +SVar:DBPutCounter:DB$ PutCounter | Defined$ Targeted | CounterType$ CORRUPTION | CounterNum$ 1 +A:AB$ GainControl | Cost$ SubCounter<3/LOYALTY> | Planeswalker$ True | ValidTgts$ Creature,Planeswalker | TgtPrompt$ Select target creature or planeswalker | LoseControl$ EOT | Untap$ True | AddKWs$ Haste | SubAbility$ DBPutCounter | SpellDescription$ Gain control of target creature or planeswalker until end of turn. Untap it and put a corruption counter on it. It gains haste until end of turn. +SVar:DBPutCounter:DB$PutCounter | Defined$ Targeted | CounterType$ CORRUPTION | CounterNum$ 1 +A:AB$ GainControl | Cost$ SubCounter<7/LOYALTY> | Planeswalker$ True | Ultimate$ True | AllValid$ Permanent.counters_GE1_CORRUPTION | SpellDescription$ Gain control of each permanent with a corruption counter on it. +DeckHas:Ability$LifeGain +Oracle:Protection from permanents with corruption counters on them\n[+1]: Each opponent loses 2 life and you gain 2 life. Put a corruption counter on up to one other target creature or planeswalker.\n[−3]: Gain control of target creature or planeswalker until end of turn. Untap it and put a corruption counter on it. It gains haste until end of turn.\n[−7]: Gain control of each permanent with a corruption counter on it. diff --git a/forge-gui/res/cardsfolder/upcoming/glinting_creeper.txt b/forge-gui/res/cardsfolder/upcoming/glinting_creeper.txt new file mode 100644 index 00000000000..4dbaee319dc --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/glinting_creeper.txt @@ -0,0 +1,10 @@ +Name:Glinting Creeper +ManaCost:4 G +Types:Creature Plant +PT:0/0 +K:etbCounter:P1P1:Y:no Condition:Converge — CARDNAME enters the battlefield with a +1/+1 counter on it for each color of mana spent to cast it. +K:CantBeBlockedBy Creature.powerLE2 +SVar:X:Count$Converge +SVar:Y:SVar$X/Twice +DeckHints:Ability$Counters +Oracle:Converge — Glinting Creeper enters the battlefield with a +1/+1 counter on it for each color of mana spent to cast it.\nGlinting Creeper can't be blocked by creautures with power 2 or less. diff --git a/forge-gui/res/cardsfolder/upcoming/herd_baloth.txt b/forge-gui/res/cardsfolder/upcoming/herd_baloth.txt new file mode 100644 index 00000000000..507a3b36ce7 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/herd_baloth.txt @@ -0,0 +1,9 @@ +Name:Herd Baloth +ManaCost:3 G G +Types:Creature Beast +PT:4/4 +T:Mode$ CounterAddedOnce | ValidCard$ Card.Self | TriggerZones$ Battlefield | CounterType$ P1P1 | Execute$ TrigToken | OptionalDecider$ You | TriggerDescription$ Whenever one or more +1/+1 counters are put on CARDNAME, you may create a 4/4 green Beast creature token. +SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ g_4_4_beast | TokenOwner$ You +DeckHints:Ability$Counters +DeckHas:Ability$Token +Oracle:Whenever one or more +1/+1 counters are put on Herd Baloth, you may create a 4/4 green Beast creature token. diff --git a/forge-gui/res/cardsfolder/upcoming/ignoble_hierarch.txt b/forge-gui/res/cardsfolder/upcoming/ignoble_hierarch.txt new file mode 100644 index 00000000000..cf1d84fdc14 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/ignoble_hierarch.txt @@ -0,0 +1,9 @@ +Name:Ignoble Hierarch +ManaCost:G +Types:Creature Goblin Shaman +PT:0/1 +A:AB$ Mana | Cost$ T | Produced$ B | SpellDescription$ Add {B}. +A:AB$ Mana | Cost$ T | Produced$ R | SpellDescription$ Add {R}. +A:AB$ Mana | Cost$ T | Produced$ G | SpellDescription$ Add {G}. +K:Exalted +Oracle:Exalted (Whenever a creature you control attacks alone, that creature gets +1/+1 until end of turn.)\n{T}: Add {B}, {R}, or {G}. diff --git a/forge-gui/res/cardsfolder/upcoming/jade_avenger.txt b/forge-gui/res/cardsfolder/upcoming/jade_avenger.txt new file mode 100644 index 00000000000..e2200ddb132 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/jade_avenger.txt @@ -0,0 +1,6 @@ +Name:Jade Avenger +ManaCost:1 G +Types:Creature Frog Samurai +PT:2/2 +K:Bushido:2 +Oracle:Bushido 2 (Whenever this creature blocks or becomes blocked, it gets +2/+2 until end of turn.) diff --git a/forge-gui/res/cardsfolder/upcoming/junk_winder.txt b/forge-gui/res/cardsfolder/upcoming/junk_winder.txt index df1adbab75e..bb2250d7694 100644 --- a/forge-gui/res/cardsfolder/upcoming/junk_winder.txt +++ b/forge-gui/res/cardsfolder/upcoming/junk_winder.txt @@ -2,9 +2,9 @@ Name:Junk Winder ManaCost:5 U U Types:Creature Serpent PT:5/6 -K:Affinity:Permanent.token +K:Affinity:Permanent.token:token T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Permanent.token+YouCtrl | TriggerZones$ Battlefield | Execute$ TrigTap | TriggerDescription$ Whenever a token enters the battlefield under your control, tap target nonland permanent an opponent controls. It doesn't untap during its controller's next untap step. SVar:TrigTap:DB$ Tap | ValidTgts$ Permanent.nonLand+OppCtrl | TgtPrompt$ Choose target nonland permanent an opponent controls | SubAbility$ DBPump -SVar:DBPump:DB$ Pump | Defined$ Targeted | KW$ HIDDEN This card doesn't untap during your next untap step. | Permanent$ True +SVar:DBPump:DB$ Pump | Defined$ Targeted | KW$ HIDDEN This card doesn't untap during your next untap step. | Duration$ Permanent DeckNeeds:Ability$Token Oracle:Affinity for tokens (This spell costs {1} less to cast for each token you control.)\nWhenever a token enters the battlefield under your control, tap target nonland permanent an opponent controls. It doesn't untap during its controller's next untap step. diff --git a/forge-gui/res/cardsfolder/upcoming/lonis_cryptozoologist.txt b/forge-gui/res/cardsfolder/upcoming/lonis_cryptozoologist.txt new file mode 100644 index 00000000000..6344d89d46d --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/lonis_cryptozoologist.txt @@ -0,0 +1,15 @@ +Name:Lonis, Cryptozoologist +ManaCost:G U +Types:Legendary Creature Snake Elf Scout +PT:1/2 +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Creature.YouCtrl+nonToken+Other | TriggerZones$ Battlefield | Execute$ TrigInvestigate | TriggerDescription$ Whenever another nontoken creature enters the battlefield under your control, investigate. (Create a colorless Clue artifact token with "{2}, Sacrifice this artifact: Draw a card.") +SVar:TrigInvestigate:DB$ Investigate +A:AB$ Dig | Cost$ T Sac | ValidTgts$ Player.Opponent | TgtPrompt$ Select target opponent | Reveal$ True | NoMove$ True | DigNum$ X | RememberRevealed$ True | DestinationZone$ Library | SubAbility$ PickOne | SpellDescription$ Target opponent reveals the top X cards of their library. You may put a nonland permanent card with mana value X or less from among them onto the battlefield under your control. That player puts the rest on the bottom of their library in a random order. +SVar:PickOne:DB$ ChooseCard | Defined$ You | Amount$ 1 | Mandatory$ True | ChoiceTitle$ Choose a nonland permanent to put on the battlefield under your control | Choices$ Permanent.nonLand+cmcLEX+IsRemembered | ChoiceZone$ Library | SubAbility$ MoveChosen +SVar:MoveChosen:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | GainControl$ True | Defined$ ChosenCard | SubAbility$ DBBottom +SVar:DBBottom:DB$ ChangeZoneAll | ChangeType$ Card.IsRemembered | Origin$ Library | Destination$ Library | LibraryPosition$ -1 | RandomOrder$ True | SubAbility$ DBCleanup +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +SVar:X:Count$xPaid +DeckHints:Ability$Investigate +DeckHas:Ability$Investigate & Ability$Token +Oracle:Whenever another nontoken creature enters the battlefield under your control, investigate. (Create a colorless Clue artifact token with "{2}, Sacrifice this artifact: Draw a card.")\n{T}, Sacrifice X Clues: Target opponent reveals the top X cards of their library. You may put a nonland permanent card with mana value X or less from among them onto the battlefield under your control. That player puts the rest on the bottom of their library in a random order. diff --git a/forge-gui/res/cardsfolder/upcoming/lose_focus.txt b/forge-gui/res/cardsfolder/upcoming/lose_focus.txt new file mode 100644 index 00000000000..f7ca4210d41 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/lose_focus.txt @@ -0,0 +1,6 @@ +Name:Lose Focus +ManaCost:1 U +Types:Instant +K:Replicate:U +A:SP$ Counter | Cost$ 1 U | TargetType$ Spell | TgtPrompt$ Select target spell | ValidTgts$ Card | UnlessCost$ 2 | SpellDescription$ Counter target spell unless its controller pays {2}. +Oracle:Replicate {U} (When you cast this spell, copy it for each time you paid its replicate cost. You may choose new targets for the copies.)\nCounter target spell unless its controller pays {2}. diff --git a/forge-gui/res/cardsfolder/upcoming/moderation.txt b/forge-gui/res/cardsfolder/upcoming/moderation.txt new file mode 100644 index 00000000000..7c2f07bc1cc --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/moderation.txt @@ -0,0 +1,7 @@ +Name:Moderation +ManaCost:1 W U +Types:Enchantment +S:Mode$ CantBeCast | ValidCard$ Card | Caster$ You | NumLimitEachTurn$ 1 | Description$ You can't cast more than one spell each turn. +T:Mode$ SpellCast | ValidActivatingPlayer$ You | Execute$ TrigDraw | TriggerDescription$ Whenever you cast a spell, draw a card. +SVar:TrigDraw:DB$ Draw | NumCards$ 1 +Oracle:You can't cast more than one spell each turn.\nWhenever you cast a spell, draw a card. diff --git a/forge-gui/res/cardsfolder/upcoming/monoskelion.txt b/forge-gui/res/cardsfolder/upcoming/monoskelion.txt new file mode 100644 index 00000000000..be1e58da78b --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/monoskelion.txt @@ -0,0 +1,8 @@ +Name:Monoskelion +ManaCost:2 +Types:Artifact Creature Construct +PT:1/1 +K:etbCounter:P1P1:1 +A:AB$ DealDamage | AILogic$ Triskelion | Cost$ SubCounter<1/P1P1> | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ 1 | SpellDescription$ CARDNAME deals 1 damage to any target. +DeckHas:Ability$Counters +Oracle:Monoskelion enters the battlefield with a +1/+1 counter on it.\nRemove a +1/+1 counter from Monoskelion: It deals 1 damage to any target. diff --git a/forge-gui/res/cardsfolder/upcoming/nettlecyst.txt b/forge-gui/res/cardsfolder/upcoming/nettlecyst.txt new file mode 100644 index 00000000000..fd5b56ebd6b --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/nettlecyst.txt @@ -0,0 +1,12 @@ +Name:Nettlecyst +ManaCost:3 +Types:Artifact Equipment +K:Living Weapon +K:Equip:2 +S:Mode$ Continuous | Affected$ Card.EquippedBy | AddPower$ X | AddToughness$ X | Description$ Equipped creature gets +1/+1 for each artifact and/or enchantment you control. +SVar:X:Count$Valid Artifact.YouCtrl,Enchantment.YouCtrl +SVar:NeedsToPlayVar:X GE1 +SVar:BuffedBy:Artifact,Enchantment +DeckHas:Ability$Token +DeckHints:Type$Artifact|Enchantment +Oracle:Living weapon (When this Equipment enters the battlefield, create a 0/0 black Germ creature token, then attach this to it.)\nEquipped creature gets +1/+1 for each artifact and/or enchantment you control.\nEquip {2} diff --git a/forge-gui/res/cardsfolder/upcoming/orchard_strider.txt b/forge-gui/res/cardsfolder/upcoming/orchard_strider.txt new file mode 100644 index 00000000000..3cf7f170d13 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/orchard_strider.txt @@ -0,0 +1,9 @@ +Name:Orchard Strider +ManaCost:4 G G +Types:Creature Treefolk +PT:6/4 +T:Mode$ ChangesZone | ValidCard$ Card.Self | Destination$ Battlefield | Execute$ TrigToken | TriggerDescription$ When CARDNAME enters the battlefield, create two Food tokens. (They're artifacts with "{2}, {T}, Sacrifice this artifact: You gain 3 life.") +SVar:TrigToken:DB$ Token | TokenScript$ c_a_food_sac | TokenAmount$ 2 +K:TypeCycling:Basic:1 G +DeckHas:Ability$Token & Ability$LifeGain +Oracle:When Orchard Strider enters the battlefield, create two Food tokens. (They're artifacts with "{2}, {T}, Sacrifice this artifact: You gain 3 life.")\nBasic landcycling {1}{G} ({1}{G}, Discard this card: Search your library for a basic land card, reveal it, put it into your hand, then shuffle.) diff --git a/forge-gui/res/cardsfolder/upcoming/piru_the_volatile.txt b/forge-gui/res/cardsfolder/upcoming/piru_the_volatile.txt new file mode 100644 index 00000000000..6894a141ada --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/piru_the_volatile.txt @@ -0,0 +1,10 @@ +Name:Piru, the Volatile +ManaCost:2 R R W W B B +Types:Legendary Creature Elder Dragon +PT:7/7 +K:Flying +K:Lifelink +K:UpkeepCost:R W B +T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigDamage | TriggerController$ TriggeredCardController | TriggerDescription$ When CARDNAME dies, it deals 7 damage to each nonlegendary creature. +SVar:TrigDamage:DB$ DamageAll | ValidCards$ Creature.nonLegendary | NumDmg$ 7 | ValidDescription$ each nonlegendary creature. +Oracle:Flying, lifelink\nAt the beginning of your upkeep, sacrifice Piru, the Volatile unless you pay {R}{W}{B}.\nWhen Piru dies, it deals 7 damage to each nonlegendary creature. diff --git a/forge-gui/res/cardsfolder/upcoming/priest_of_fell_rites.txt b/forge-gui/res/cardsfolder/upcoming/priest_of_fell_rites.txt new file mode 100644 index 00000000000..39baadc1992 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/priest_of_fell_rites.txt @@ -0,0 +1,7 @@ +Name:Priest of Fell Rites +ManaCost:W B +Types:Creature Human Warlock +PT:2/2 +A:AB$ ChangeZone | Cost$ T PayLife<3> Sac<1/CARDNAME> | Origin$ Graveyard | Destination$ Battlefield | TgtPrompt$ Choose target creature card in your graveyard | ValidTgts$ Creature.YouCtrl | SorcerySpeed$ True | SpellDescription$ Return target creature card from your graveyard to the battlefield. +K:Unearth:3 W B +Oracle:{T}, Pay 3 life, Sacrifice Priest of Fell Rites: Return target creature card from your graveyard to the battlefield. Activate only as a sorcery.\nUnearth {3}{W}{B} ({3}{W}{B}: Return this card from your graveyard to the battlefield. It gains haste. Exile it at the beginning of the next end step or if it would leave the battlefield. Unearth only as a sorcery.) diff --git a/forge-gui/res/cardsfolder/upcoming/prophetic_titan.txt b/forge-gui/res/cardsfolder/upcoming/prophetic_titan.txt new file mode 100644 index 00000000000..5eebcdb16d6 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/prophetic_titan.txt @@ -0,0 +1,12 @@ +Name:Prophetic Titan +ManaCost:4 U R +Types:Creature Giant Wizard +PT:4/4 +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigCharm | TriggerDescription$ Delirium — When CARDNAME enters the battlefield, choose one. If there are four or more card types in your graveyard, choose both. +SVar:TrigCharm:DB$ Charm | CharmNum$ X | Choices$ DBDealDamage,DBDig | AdditionalDescription$ If there are four or more card types in your graveyard, choose both. +SVar:DBDealDamage:DB$ DealDamage | ValidTgts$ Creature,Planeswalker,Player | TgtPrompt$ Select any target | NumDmg$ 4 | SpellDescription$ CARDNAME deals 4 damage to any target. +SVar:DBDig:DB$ Dig | DigNum$ 4 | RestRandomOrder$ True | SpellDescription$ Look at the top four cards of your library. Put one in your hand and the rest on the bottom of your library in a random order. +SVar:X:Count$Compare Y GE4.2.1 +SVar:Y:Count$CardControllerTypes.Graveyard +SVar:PlayMain1:TRUE +Oracle:Delirium — When Prophetic Titan enters the battlefield, choose one. If there are four or more card types in your graveyard, choose both.\n• Prophetic Titan deals 4 damage to any target.\n• Look at the top four cards of your library. Put one in your hand and the rest on the bottom of your library in a random order. diff --git a/forge-gui/res/cardsfolder/upcoming/rift_sower.txt b/forge-gui/res/cardsfolder/upcoming/rift_sower.txt new file mode 100644 index 00000000000..95609684341 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/rift_sower.txt @@ -0,0 +1,7 @@ +Name:Rift Sower +ManaCost:2 G +Types:Creature Elf Druid +PT:1/3 +K:Suspend:2:G +A:AB$ Mana | Cost$ T | Produced$ Any | SpellDescription$ Add one mana of any color. +Oracle:{T}: Add one mana of any color.\nSuspend 2—{G} (Rather than cast this card from your hand, you may pay {G} and exile it with two time counters on it. At the beginning of your upkeep, remove a time counter. When the last is removed, cast it without paying its mana cost.) diff --git a/forge-gui/res/cardsfolder/upcoming/road_ruin.txt b/forge-gui/res/cardsfolder/upcoming/road_ruin.txt new file mode 100644 index 00000000000..146d84a0849 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/road_ruin.txt @@ -0,0 +1,16 @@ +Name:Road +ManaCost:2 G +AlternateMode: Split +Types:Instant +A:SP$ ChangeZone | Cost$ 2 G | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeNum$ 1 | Tapped$ True | SpellDescription$ Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. +Oracle:Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. + +ALTERNATE + +Name:Ruin +ManaCost:1 R R +Types:Sorcery +K:Aftermath +A:SP$ DealDamage | Cost$ 1 R R | ValidTgts$ Creature | TgtPrompt$ Select target creature | NumDmg$ X | SpellDescription$ CARDNAME deals damage to target creature equal to the number of lands you control. +SVar:X:Count$TypeYouCtrl.Land +Oracle:Ruin deals damage to target creature equal to the number of lands you control. diff --git a/forge-gui/res/cardsfolder/upcoming/sanctifier_en_vec.txt b/forge-gui/res/cardsfolder/upcoming/sanctifier_en_vec.txt new file mode 100644 index 00000000000..3c5f3cc57d5 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/sanctifier_en_vec.txt @@ -0,0 +1,12 @@ +Name:Sanctifier en-Vec +ManaCost:W W +Types:Creature Human Cleric +PT:2/2 +K:Protection from black +K:Protection from red +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChangeZoneAll | TriggerDescription$ When CARDNAME enters the battlefield, exile all cards that are black or red from all graveyards. +SVar:TrigChangeZoneAll:DB$ ChangeZoneAll | ChangeType$ Card.Black,Card.Red | Origin$ Graveyard | Destination$ Exile +R:Event$ Moved | ActiveZones$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Black,Card.Red | ReplaceWith$ Exile | Description$ If a black or red permanent, spell, or card not on the battlefield would be put into a graveyard, exile it instead. +SVar:Exile:DB$ ChangeZone | Hidden$ True | Origin$ All | Destination$ Exile | Defined$ ReplacedCard +SVar:NonStackingEffect:True +Oracle:Protection from black and from red\nWhen Sanctifier en-Vec enters the battlefield, exile all cards that are black or red from all graveyards.\nIf a black or red permanent, spell, or card not on the battlefield would be put into a graveyard, exile it instead. diff --git a/forge-gui/res/cardsfolder/upcoming/sanctuary_raptor.txt b/forge-gui/res/cardsfolder/upcoming/sanctuary_raptor.txt new file mode 100644 index 00000000000..906008e3bf6 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/sanctuary_raptor.txt @@ -0,0 +1,9 @@ +Name:Sanctuary Raptor +ManaCost:3 +Types:Artifact Creature Bird +PT:2/1 +K:Flying +T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigPump | IsPresent$ Permanent.token+YouCtrl | PresentCompare$ GE3 | TriggerDescription$ Whenever CARDNAME attacks, if you control three or more tokens, CARDNAME gets +2/+0 and gains first strike until end of turn. +SVar:TrigPump:DB$ Pump | Defined$ Self | NumAtt$ 2 | KW$ First Strike +DeckHints:Ability$Token +Oracle:Whenever Sanctuary Raptor attacks, if you control three or more tokens, Sanctuary Raptor gets +2/+0 and gains first strike until end of turn. diff --git a/forge-gui/res/cardsfolder/upcoming/sanctum_weaver.txt b/forge-gui/res/cardsfolder/upcoming/sanctum_weaver.txt new file mode 100644 index 00000000000..8e395cc5114 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/sanctum_weaver.txt @@ -0,0 +1,7 @@ +Name:Sanctum Weaver +ManaCost:1 G +Types:Enchantment Creature Dryad +PT:0/2 +A:AB$ Mana | Cost$ T | Produced$ Any | Amount$ X | SpellDescription$ Add X mana of any one color, where X is the number of enchantments you control. +SVar:X:Count$Valid Enchantment.YouCtrl +Oracle:{T}: Add X mana of any one color, where X is the number of enchantments you control. diff --git a/forge-gui/res/cardsfolder/upcoming/scurry_oak.txt b/forge-gui/res/cardsfolder/upcoming/scurry_oak.txt new file mode 100644 index 00000000000..c0f553b7ed0 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/scurry_oak.txt @@ -0,0 +1,10 @@ +Name:Scurry Oak +ManaCost:2 G +Types:Creature Treefolk +PT:1/2 +K:Evolve +T:Mode$ CounterAddedOnce | ValidCard$ Card.Self | TriggerZones$ Battlefield | CounterType$ P1P1 | OptionalDecider$ You | Execute$ TrigSquirrel | TriggerDescription$ Whenever one or more +1/+1 counters are put on CARDNAME, you may create a 1/1 green Squirrel creature token. +SVar:TrigSquirrel:DB$ Token | TokenAmount$ 1 | TokenScript$ g_1_1_squirrel | TokenOwner$ You +DeckHints:Ability$Counters +DeckHas:Ability$Token & Ability$Counters +Oracle:Evolve (Whenever a creature enters the battlefield under your control, if that creature has greater power or toughness than this creature, put a +1/+1 counter on this creature.)\nWhenever one or more +1/+1 counters are put on Scurry Oak, you may create a 1/1 green Squirrel creature token. diff --git a/forge-gui/res/cardsfolder/upcoming/shattered_ego.txt b/forge-gui/res/cardsfolder/upcoming/shattered_ego.txt new file mode 100644 index 00000000000..33fe02bbc95 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/shattered_ego.txt @@ -0,0 +1,8 @@ +Name:Shattered Ego +ManaCost:U +Types:Enchantment Aura +K:Enchant creature +A:SP$ Attach | Cost$ U | ValidTgts$ Creature | AILogic$ Curse +S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddPower$ -3 | Description$ Enchanted creature gets -3/-0. +A:AB$ ChangeZone | Cost$ 3 U U | Defined$ Enchanted | Origin$ Battlefield | Destination$ Library | LibraryPosition$ 2 | SpellDescription$ Put enchanted creature into its owner's library third from the top. +Oracle:Enchant creature\nEnchanted creature gets -3/-0.\n{3}{U}{U}: Put enchanted creature into its owner's library third from the top. diff --git a/forge-gui/res/cardsfolder/upcoming/steel_dromedary.txt b/forge-gui/res/cardsfolder/upcoming/steel_dromedary.txt new file mode 100644 index 00000000000..9725e48029a --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/steel_dromedary.txt @@ -0,0 +1,12 @@ +Name:Steel Dromedary +ManaCost:3 +Types:Artifact Creature Camel +PT:2/2 +K:ETBReplacement:Other:CamelTapped +SVar:CamelTapped:DB$ Tap | Defined$ Self | ETB$ True | SubAbility$ DBAddCounter | SpellDescription$ CARDNAME enters the battlefield tapped with two +1/+1 counters on it. +SVar:DBAddCounter:DB$ PutCounter | ETB$ True | Defined$ Self | CounterType$ P1P1 | CounterNum$ 2 +S:Mode$ Continuous | Affected$ Card.Self+counters_GE1_P1P1 | AddHiddenKeyword$ CARDNAME doesn't untap during your untap step. | Description$ As long as there are +1/+1 counters on CARDNAME, it doesn't untap during your untap step. +T:Mode$ Phase | Phase$ BeginCombat | ValidPlayer$ You | TriggerZones$ Battlefield | OptionalDecider$ You | Execute$ TrigMoveCounter | TriggerDescription$ At the beginning of combat on your turn, you may move a +1/+1 counter from CARDNAME onto another target creature. +SVar:TrigMoveCounter:DB$ MoveCounter | ValidTgts$ Creature.Other | TgtPrompt$ Select another target creature | Source$ Self | CounterType$ P1P1 | CounterNum$ 1 +DeckHas:Ability$Counters +Oracle:Steel Dromedary enters the battlefield tapped with two +1/+1 counters on it.\nAs long as there are +1/+1 counters on Steel Dromedary, it doesn't untap during your untap step.\nAt the beginning of combat on your turn, you may move a +1/+1 counter from Steel Dromedary onto another target creature. diff --git a/forge-gui/res/cardsfolder/upcoming/strike_it_rich.txt b/forge-gui/res/cardsfolder/upcoming/strike_it_rich.txt new file mode 100644 index 00000000000..082f74b55e5 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/strike_it_rich.txt @@ -0,0 +1,7 @@ +Name:Strike It Rich +ManaCost:R +Types:Sorcery +K:Flashback:2 R +A:SP$ Token | TokenScript$ c_a_treasure_sac | SpellDescription$ Create a Treasure token. (It's an artifact with "{T}, Sacrifice this artifact: Add one mana of any color.") +DeckHas:Ability$Token & Ability$Sacrifice +Oracle:Create a Treasure token. (It's an artifact with "{T}, Sacrifice this artifact: Add one mana of any color.")\nFlashback {2}{R} (You may cast this card from your graveyard for its flashback cost. Then exile it.) diff --git a/forge-gui/res/cardsfolder/upcoming/sudden_edict.txt b/forge-gui/res/cardsfolder/upcoming/sudden_edict.txt new file mode 100644 index 00000000000..09553adb198 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/sudden_edict.txt @@ -0,0 +1,6 @@ +Name:Sudden Edict +ManaCost:1 B +Types:Instant +K:Split second +A:SP$ Sacrifice | Cost$ 1 B | ValidTgts$ Player | SacValid$ Creature | SacMessage$ Creature | SpellDescription$ Target player sacrifices a creature. +Oracle:Split second (As long as this spell is on the stack, players can't cast spells or activate abilities that aren't mana abilities.)\nTarget player sacrifices a creature. diff --git a/forge-gui/res/cardsfolder/upcoming/sword_of_hearth_and_home.txt b/forge-gui/res/cardsfolder/upcoming/sword_of_hearth_and_home.txt new file mode 100644 index 00000000000..0afa6aef4ab --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/sword_of_hearth_and_home.txt @@ -0,0 +1,12 @@ +Name:Sword of Hearth and Home +ManaCost:3 +Types:Artifact Equipment +K:Equip:2 +S:Mode$ Continuous | Affected$ Creature.EquippedBy | AddPower$ 2 | AddToughness$ 2 | AddSVar$ SwordOfHearthAndHomeCE | AddKeyword$ Protection from green & Protection from white | Description$ Equipped creature gets +2/+2 and has protection from green and from white. +T:Mode$ DamageDone | ValidSource$ Creature.EquippedBy | ValidTarget$ Player | CombatDamage$ True | Execute$ TrigBlink | TriggerZones$ Battlefield | TriggerDescription$ Whenever equipped creature deals combat damage to a player, exile up to one target creature you own, then search your library for a basic land card. Put both cards onto the battlefield under your control, then shuffle. +SVar:TrigBlink:DB$ ChangeZone | Origin$ Battlefield | Destination$ Exile | RememberChanged$ True | ValidTgts$ Creature.YouOwn | TargetMin$ 0 | TargetMax$ 1 | TgtPrompt$ Select target creature you own | SubAbility$ DBLand +SVar:DBLand:DB$ ChangeZone | Origin$ Library | Destination$ Library | ChangeType$ Land.Basic | ChangeNum$ 1 | RememberChanged$ True | SubAbility$ DBReturn +SVar:DBReturn:DB$ ChangeZone | Defined$ Remembered | Origin$ All | Destination$ Battlefield | GainControl$ True | SubAbility$ DBCleanup +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +SVar:SwordOfHearthAndHomeCE:SVar:MustBeBlocked:AttackingPlayerConservative +Oracle:Equipped creature gets +2/+2 and has protection from green and from white.\nWhenever equipped creature deals combat damage to a player, exile up to one target creature you own, then search your library for a basic land card. Put both cards onto the battlefield under your control, then shuffle.\nEquip {2} diff --git a/forge-gui/res/cardsfolder/upcoming/sylvan_anthem.txt b/forge-gui/res/cardsfolder/upcoming/sylvan_anthem.txt new file mode 100644 index 00000000000..40ba97bc516 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/sylvan_anthem.txt @@ -0,0 +1,7 @@ +Name:Sylvan Anthem +ManaCost:G G +Types:Enchantment +S:Mode$ Continuous | Affected$ Creature.Green+YouCtrl | AddPower$ 1 | AddToughness$ 1 | Description$ Green creatures you control get +1/+1. +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Creature.Green+YouCtrl | TriggerZones$ Battlefield | Execute$ TrigScry | TriggerDescription$ Whenever a green creature enters the battlefield under your control, scry 1. (Look at the top card of your library. You may put that card on the bottom of your library.) +SVar:TrigScry:DB$ Scry | ScryNum$ 1 +Oracle:Green creatures you control get +1/+1.\nWhenever a green creature enters the battlefield under your control, scry 1. diff --git a/forge-gui/res/cardsfolder/upcoming/territorial_kavu.txt b/forge-gui/res/cardsfolder/upcoming/territorial_kavu.txt new file mode 100644 index 00000000000..394e3cbdc8d --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/territorial_kavu.txt @@ -0,0 +1,14 @@ +Name:Territorial Kavu +ManaCost:R G +Types:Creature Kavu +PT:*/* +S:Mode$ Continuous | EffectZone$ All | CharacteristicDefining$ True | SetPower$ X | SetToughness$ X | Description$ Domain — CARDNAME's power and toughness are each equal to the number of basic land types among lands you control. +SVar:X:Count$Domain +T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigCharm | TriggerDescription$ Whenever CARDNAME attacks, ABILITY +SVar:TrigCharm:DB$ Charm | Choices$ DBDiscard,DBExile +SVar:DBDiscard:DB$ Discard | NumCards$ 1 | Mode$ TgtChoose | RememberDiscarded$ True | SubAbility$ DBDraw | SpellDescription$ Discard a card. If you do, draw a card. +SVar:DBDraw:DB$ Draw | ConditionDefined$ Remembered | ConditionPresent$ Card | ConditionCompare$ GE1 | SubAbility$ DBCleanup +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +SVar:DBExile:DB$ ChangeZone | ValidTgts$ Card | TgtPrompt$ Select up to one target card in a graveyard | TargetMin$ 0 | TargetMax$ 1 | Origin$ Graveyard | Destination$ Exile | SpellDescription$ Exile up to one target card from a graveyard. +DeckHas:Ability$Discard +Oracle:Domain — Territorial Kavu's power and toughness are each equal to the number of basic land types among lands you control.\nWhenever Territorial Kavu attacks, choose one —\n• Discard a card. If you do, draw a card.\n• Exile up to one target card from a graveyard. diff --git a/forge-gui/res/cardsfolder/upcoming/thraben_watcher.txt b/forge-gui/res/cardsfolder/upcoming/thraben_watcher.txt new file mode 100644 index 00000000000..d5c6567f328 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/thraben_watcher.txt @@ -0,0 +1,9 @@ +Name:Thraben Watcher +ManaCost:2 W W +Types:Creature Angel +PT:2/2 +K:Flying +K:Vigilance +S:Mode$ Continuous | Affected$ Creature.nonToken+Other+YouCtrl | AddPower$ 1 | AddToughness$ 1 | AddKeyword$ Vigilance | Description$ Other nontoken creatures you control get +1/+1 and have vigilance. +SVar:PlayMain1:TRUE +Oracle:Flying, vigilance\nOther nontoken creatures you control get +1/+1 and have vigilance. diff --git a/forge-gui/res/cardsfolder/upcoming/timeless_witness.txt b/forge-gui/res/cardsfolder/upcoming/timeless_witness.txt new file mode 100644 index 00000000000..1eba90fe7f3 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/timeless_witness.txt @@ -0,0 +1,9 @@ +Name:Timeless Witness +ManaCost:2 G G +Types:Creature Human Shaman +PT:2/1 +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChangeZone | OptionalDecider$ You | TriggerDescription$ When CARDNAME enters the battlefield, you may return target card from your graveyard to your hand. +SVar:TrigChangeZone:DB$ChangeZone | Origin$ Graveyard | Destination$ Hand | ValidTgts$ Card.YouCtrl +K:Eternalize:5 G G +DeckHas:Ability$Token +Oracle:When Eternal Witness enters the battlefield, you may return target card from your graveyard to your hand.\nEternalize {5}{G}{G} ({5}{G}{G}, Exile this card from your graveyard: Create a token that's a copy of it, except it's a 4/4 black Zombie Human Shaman with no mana cost. Eternalize only as a sorcery.) diff --git a/forge-gui/res/cardsfolder/upcoming/tormods_cryptkeeper.txt b/forge-gui/res/cardsfolder/upcoming/tormods_cryptkeeper.txt new file mode 100644 index 00000000000..ee05392ddc1 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/tormods_cryptkeeper.txt @@ -0,0 +1,8 @@ +Name:Tormod's Cryptkeeper +ManaCost:3 +Types:Artifact Creature Golem +PT:3/2 +K:Vigilance +A:AB$ ChangeZoneAll | Cost$ T Sac<1/CARDNAME> | Origin$ Graveyard | Destination$ Exile | ValidTgts$ Player | TgtPrompt$ Select target player | ChangeType$ Card | SpellDescription$ Exile all cards from target player's graveyard. +AI:RemoveDeck:Random +Oracle:Vigilance\n{T}, Sacrifice Tormod's Cryptkeeper: Exile all cards from target player's graveyard. diff --git a/forge-gui/res/cardsfolder/upcoming/urban_daggertooth.txt b/forge-gui/res/cardsfolder/upcoming/urban_daggertooth.txt new file mode 100644 index 00000000000..07225de8d14 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/urban_daggertooth.txt @@ -0,0 +1,9 @@ +Name:Urban Daggertooth +ManaCost:2 G G +Types:Creature Dinosaur +PT:4/3 +K:Vigilance +T:Mode$ DamageDoneOnce | Execute$ TrigProliferate | ValidTarget$ Card.Self | TriggerZones$ Battlefield | TriggerDescription$ Enrage — Whenever CARDNAME is dealt damage, proliferate. +SVar:TrigProliferate:DB$Proliferate +SVar:HasCombatEffect:TRUE +Oracle:Enrage — Whenever Urban Daggertooth is dealt damage, proliferate. (Choose any number of permanents and/or players, then give each another counter of each kind already there.) diff --git a/forge-gui/res/cardsfolder/upcoming/yavimaya_cradle_of_growth.txt b/forge-gui/res/cardsfolder/upcoming/yavimaya_cradle_of_growth.txt new file mode 100644 index 00000000000..9bcb0db425b --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/yavimaya_cradle_of_growth.txt @@ -0,0 +1,6 @@ +Name:Yavimaya, Cradle of Growth +ManaCost:no cost +Types:Legendary Land +S:Mode$ Continuous | Affected$ Land | AddType$ Forest | Description$ Each land is a Forest in addition to its other land types. +AI:RemoveDeck:Random +Oracle:Each land is a Forest in addition to its other land types. diff --git a/forge-gui/res/cardsfolder/v/vigor.txt b/forge-gui/res/cardsfolder/v/vigor.txt index 3405c022579..cbb45842ece 100644 --- a/forge-gui/res/cardsfolder/v/vigor.txt +++ b/forge-gui/res/cardsfolder/v/vigor.txt @@ -3,10 +3,9 @@ ManaCost:3 G G G Types:Creature Elemental Incarnation PT:6/6 K:Trample -R:Event$ DamageDone | ActiveZones$ Battlefield | ValidTarget$ Creature.YouCtrl+Other | ReplaceWith$ Counters | PreventionEffect$ True | Description$ If damage would be dealt to another creature you control, prevent that damage. Put a +1/+1 counter on that creature for each 1 damage prevented this way. +R:Event$ DamageDone | ActiveZones$ Battlefield | ValidTarget$ Creature.YouCtrl+Other | ReplaceWith$ Counters | PreventionEffect$ True | ExecuteMode$ PerTarget | Description$ If damage would be dealt to another creature you control, prevent that damage. Put a +1/+1 counter on that creature for each 1 damage prevented this way. SVar:Counters:DB$PutCounter | Defined$ ReplacedTarget | CounterType$ P1P1 | CounterNum$ X SVar:X:ReplaceCount$DamageAmount T:Mode$ ChangesZone | Origin$ Any | Destination$ Graveyard | ValidCard$ Creature.Self | Execute$ TrigShuffle | TriggerDescription$ When CARDNAME is put into a graveyard from anywhere, shuffle it into its owner's library. SVar:TrigShuffle:DB$ ChangeZone | Origin$ Graveyard | Destination$ Library | Shuffle$ True | Defined$ TriggeredCardLKICopy -SVar:Picture:http://www.wizards.com/global/images/magic/general/vigor.jpg Oracle:Trample\nIf damage would be dealt to another creature you control, prevent that damage. Put a +1/+1 counter on that creature for each 1 damage prevented this way.\nWhen Vigor is put into a graveyard from anywhere, shuffle it into its owner's library. diff --git a/forge-gui/res/lists/TypeLists.txt b/forge-gui/res/lists/TypeLists.txt index f05e74dfa84..4956e4953e5 100644 --- a/forge-gui/res/lists/TypeLists.txt +++ b/forge-gui/res/lists/TypeLists.txt @@ -320,6 +320,7 @@ Dack Dakkon Daretti Davriel +Dihada Domri Dovin Duck