diff --git a/forge-game/src/main/java/forge/game/ability/AbilityFactory.java b/forge-game/src/main/java/forge/game/ability/AbilityFactory.java
index 6cee9511771..13d36cd4633 100644
--- a/forge-game/src/main/java/forge/game/ability/AbilityFactory.java
+++ b/forge-game/src/main/java/forge/game/ability/AbilityFactory.java
@@ -6,12 +6,12 @@
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
- *
+ *
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
+ *
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
@@ -28,6 +28,7 @@ import forge.card.CardStateName;
import forge.game.CardTraitBase;
import forge.game.IHasSVars;
import forge.game.ability.effects.CharmEffect;
+import forge.game.ability.effects.RollDiceEffect;
import forge.game.card.Card;
import forge.game.card.CardState;
import forge.game.cost.Cost;
@@ -45,7 +46,7 @@ import io.sentry.event.BreadcrumbBuilder;
*
* AbilityFactory class.
*
- *
+ *
* @author Forge
* @version $Id$
*/
@@ -71,7 +72,7 @@ public final class AbilityFactory {
Spell("SP"),
StaticAbility("ST"),
SubAbility("DB");
-
+
private final String prefix;
AbilityRecordType(String prefix) {
this.prefix = prefix;
@@ -79,7 +80,7 @@ public final class AbilityFactory {
public String getPrefix() {
return prefix;
}
-
+
public SpellAbility buildSpellAbility(ApiType api, Card hostCard, Cost abCost, TargetRestrictions abTgt, Map mapParams ) {
switch(this) {
case Ability: return new AbilityApiBased(api, hostCard, abCost, abTgt, mapParams);
@@ -89,11 +90,11 @@ public final class AbilityFactory {
}
return null; // exception here would be fine!
}
-
+
public ApiType getApiTypeOf(Map abParams) {
return ApiType.smartValueOf(abParams.get(this.getPrefix()));
}
-
+
public static AbilityRecordType getRecordType(Map abParams) {
if (abParams.containsKey(AbilityRecordType.Ability.getPrefix())) {
return AbilityRecordType.Ability;
@@ -108,7 +109,7 @@ public final class AbilityFactory {
}
}
}
-
+
public static final SpellAbility getAbility(final String abString, final Card card) {
return getAbility(abString, card, card.getCurrentState());
}
@@ -119,7 +120,7 @@ public final class AbilityFactory {
*
* getAbility.
*
- *
+ *
* @param abString
* a {@link java.lang.String} object.
* @param state
@@ -129,7 +130,7 @@ public final class AbilityFactory {
public static final SpellAbility getAbility(final String abString, final CardState state) {
return getAbility(abString, state, state);
}
-
+
private static final SpellAbility getAbility(final String abString, final CardState state, final IHasSVars sVarHolder) {
Map mapParams;
try {
@@ -155,7 +156,7 @@ public final class AbilityFactory {
throw new RuntimeException(msg + " of card: " + state.getName(), ex);
}
}
-
+
public static final SpellAbility getAbility(final Card hostCard, final String svar) {
return getAbility(hostCard, svar, hostCard.getCurrentState());
}
@@ -163,7 +164,7 @@ public final class AbilityFactory {
public static final SpellAbility getAbility(final Card hostCard, final String svar, final IHasSVars sVarHolder) {
return getAbility(hostCard.getCurrentState(), svar, sVarHolder);
}
-
+
public static final SpellAbility getAbility(final CardState state, final String svar, final IHasSVars sVarHolder) {
if (!sVarHolder.hasSVar(svar)) {
String source = state.getCard().getName();
@@ -285,15 +286,18 @@ public final class AbilityFactory {
@Override
public AbilitySub apply(String input) {
return getSubAbility(state, input, sVarHolder);
- }
+ }
}));
}
}
if (api == ApiType.RollDice) {
- for (String param : mapParams.keySet()) {
- if (param.startsWith("On") || param.equals("Else")) {
- spellAbility.setAdditionalAbility(param, getSubAbility(state, mapParams.get(param), sVarHolder));
+ final String key = "ResultSubAbilities";
+ if (mapParams.containsKey(key)) {
+ String [] diceAbilities = mapParams.get(key).split(",");
+ for (String ab : diceAbilities) {
+ String [] kv = ab.split(":");
+ spellAbility.setAdditionalAbility(kv[0], getSubAbility(state, kv[1], sVarHolder));
}
}
}
@@ -311,7 +315,6 @@ public final class AbilityFactory {
}
sb.append(mapParams.get("SpellDescription"));
-
spellAbility.setDescription(sb.toString());
} else if (api == ApiType.Charm) {
spellAbility.setDescription(CharmEffect.makeFormatedDescription(spellAbility));
@@ -319,6 +322,12 @@ public final class AbilityFactory {
spellAbility.setDescription("");
}
+ if (api == ApiType.RollDice) {
+ spellAbility.setDescription(spellAbility.getDescription() + RollDiceEffect.makeFormatedDescription(spellAbility));
+ } else if (api == ApiType.Repeat) {
+ spellAbility.setDescription(spellAbility.getDescription() + spellAbility.getAdditionalAbility("RepeatSubAbility").getDescription());
+ }
+
initializeParams(spellAbility);
makeRestrictions(spellAbility);
makeConditions(spellAbility);
@@ -399,7 +408,7 @@ public final class AbilityFactory {
*
* initializeParams.
*
- *
+ *
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
*/
@@ -414,7 +423,7 @@ public final class AbilityFactory {
*
* makeRestrictions.
*
- *
+ *
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
*/
@@ -428,7 +437,7 @@ public final class AbilityFactory {
*
* makeConditions.
*
- *
+ *
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
*/
@@ -444,7 +453,7 @@ public final class AbilityFactory {
* getSubAbility.
*
* @param sSub
- *
+ *
* @return a {@link forge.game.spellability.AbilitySub} object.
*/
private static final AbilitySub getSubAbility(CardState state, String sSub, final IHasSVars sVarHolder) {
@@ -466,19 +475,19 @@ public final class AbilityFactory {
List origin = ZoneType.listValueOf(params.get("Origin"));
final TargetRestrictions tgt = sa.getTargetRestrictions();
-
+
// Don't set the zone if it targets a player
if ((tgt != null) && !tgt.canTgtPlayer()) {
sa.getTargetRestrictions().setZone(origin);
}
}
-
+
}
public static final SpellAbility buildFusedAbility(final Card card) {
- if(!card.isSplitCard())
+ if(!card.isSplitCard())
throw new IllegalStateException("Fuse ability may be built only on split cards");
-
+
CardState leftState = card.getState(CardStateName.LeftSplit);
SpellAbility leftAbility = leftState.getFirstAbility();
Map leftMap = Maps.newHashMap(leftAbility.getMapParams());
diff --git a/forge-game/src/main/java/forge/game/ability/AbilityUtils.java b/forge-game/src/main/java/forge/game/ability/AbilityUtils.java
index b0b6aaf65ae..ac3267a8710 100644
--- a/forge-game/src/main/java/forge/game/ability/AbilityUtils.java
+++ b/forge-game/src/main/java/forge/game/ability/AbilityUtils.java
@@ -740,7 +740,18 @@ public class AbilityUtils {
final SpellAbility root = sa.getRootAbility();
final String[] l = calcX[1].split("/");
final String m = CardFactoryUtil.extractOperators(calcX[1]);
- final Integer count = (Integer) root.getTriggeringObject(AbilityKey.fromString(l[0]));
+ Integer count = null;
+ if (calcX[0].endsWith("Max")) {
+ @SuppressWarnings("unchecked")
+ Iterable numbers = (Iterable) root.getTriggeringObject(AbilityKey.fromString(l[0]));
+ for (Integer n : numbers) {
+ if (count == null || n > count) {
+ count = n;
+ }
+ }
+ } else {
+ count = (Integer) root.getTriggeringObject(AbilityKey.fromString(l[0]));
+ }
val = doXMath(ObjectUtils.firstNonNull(count, 0), m, card, ability);
}
diff --git a/forge-game/src/main/java/forge/game/ability/effects/RollDiceEffect.java b/forge-game/src/main/java/forge/game/ability/effects/RollDiceEffect.java
index ab26ada5ced..4fc0511c5b4 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/RollDiceEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/RollDiceEffect.java
@@ -19,6 +19,28 @@ import forge.util.MyRandom;
public class RollDiceEffect extends SpellAbilityEffect {
+ public static String makeFormatedDescription(SpellAbility sa) {
+ StringBuilder sb = new StringBuilder();
+ final String key = "ResultSubAbilities";
+ if (sa.hasParam(key)) {
+ String [] diceAbilities = sa.getParam(key).split(",");
+ for (String ab : diceAbilities) {
+ String [] kv = ab.split(":");
+ String desc = sa.getAdditionalAbility(kv[0]).getDescription();
+ if (!desc.isEmpty()) {
+ sb.append("\n\n").append(desc);
+ }
+ }
+ }
+
+ return sb.toString();
+ }
+
+ private static int getRollAdvange(final Player player) {
+ String str = "If you would roll one or more dice, instead roll that many dice plus one and ignore the lowest roll.";
+ return player.getKeywords().getAmount(str);
+ }
+
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellEffect#getStackDescription(java.util.Map, forge.card.spellability.SpellAbility)
*/
@@ -32,9 +54,9 @@ public class RollDiceEffect extends SpellAbilityEffect {
} else {
stringBuilder.append(player).append(" rolls ");
}
- stringBuilder.append(sa.getParamOrDefault("Amt", "a")).append(" ");
+ stringBuilder.append(sa.getParamOrDefault("Amount", "a")).append(" ");
stringBuilder.append(sa.getParamOrDefault("Sides", "6")).append("-sided ");
- if (sa.getParamOrDefault("Amt", "1").equals("1")) {
+ if (sa.getParamOrDefault("Amount", "1").equals("1")) {
stringBuilder.append("die.");
} else {
stringBuilder.append("dice.");
@@ -42,6 +64,73 @@ public class RollDiceEffect extends SpellAbilityEffect {
return stringBuilder.toString();
}
+ private void rollDice(SpellAbility sa, Player player, int amount, int sides) {
+ final Card host = sa.getHostCard();
+ final int modifier = AbilityUtils.calculateAmount(host, sa.getParamOrDefault("Modifier", "0"), sa);
+ final int advantage = getRollAdvange(player);
+ amount += advantage;
+ int total = 0;
+ List rolls = new ArrayList<>();
+
+ for (int i = 0; i < amount; i++) {
+ int roll = MyRandom.getRandom().nextInt(sides) + 1;
+ rolls.add(roll);
+ total += roll;
+ }
+
+ if (amount > 0) {
+ String message = Localizer.getInstance().getMessage("lblPlayerRolledResult", player, StringUtils.join(rolls, ", "));
+ player.getGame().getAction().notifyOfValue(sa, player, message, null);
+ }
+
+ // Ignore lowest rolls
+ if (advantage > 0) {
+ rolls.sort(null);
+ for (int i = advantage - 1; i >= 0; --i) {
+ total -= rolls.get(i);
+ rolls.remove(i);
+ }
+ }
+
+ // Run triggers
+ for (Integer roll : rolls) {
+ final Map runParams = AbilityKey.newMap();
+ runParams.put(AbilityKey.Player, player);
+ runParams.put(AbilityKey.Result, roll);
+ player.getGame().getTriggerHandler().runTrigger(TriggerType.RolledDie, runParams, false);
+ }
+ final Map runParams = AbilityKey.newMap();
+ runParams.put(AbilityKey.Player, player);
+ runParams.put(AbilityKey.Result, rolls);
+ player.getGame().getTriggerHandler().runTrigger(TriggerType.RolledDieOnce, runParams, false);
+
+ total += modifier;
+ if (sa.hasParam("ResultSVar")) {
+ host.setSVar(sa.getParam("ResultSVar"), Integer.toString(total));
+ }
+
+ Map diceAbilities = sa.getAdditionalAbilities();
+ SpellAbility resultAbility = null;
+ for (Map.Entry e: diceAbilities.entrySet()) {
+ String diceKey = e.getKey();
+ if (diceKey.contains("-")) {
+ String [] ranges = diceKey.split("-");
+ if (Integer.parseInt(ranges[0]) <= total && Integer.parseInt(ranges[1]) >= total) {
+ resultAbility = e.getValue();
+ break;
+ }
+ } else if (StringUtils.isNumeric(diceKey) && Integer.parseInt(diceKey) == total) {
+ resultAbility = e.getValue();
+ break;
+ }
+ }
+ if (resultAbility != null) {
+ AbilityUtils.resolve(resultAbility);
+ } else if (sa.hasAdditionalAbility("Else")) {
+ AbilityUtils.resolve(sa.getAdditionalAbility("Else"));
+ }
+ }
+
/* (non-Javadoc)
* @see forge.card.ability.SpellAbilityEffect#resolve(forge.card.spellability.SpellAbility)
*/
@@ -49,42 +138,13 @@ public class RollDiceEffect extends SpellAbilityEffect {
public void resolve(SpellAbility sa) {
final Card host = sa.getHostCard();
- int amount = AbilityUtils.calculateAmount(host, sa.getParamOrDefault("Amt", "1"), sa);
+ int amount = AbilityUtils.calculateAmount(host, sa.getParamOrDefault("Amount", "1"), sa);
int sides = AbilityUtils.calculateAmount(host, sa.getParamOrDefault("Sides", "6"), sa);
final PlayerCollection playersToRoll = getTargetPlayers(sa);
for(Player player : playersToRoll) {
- int total = 0;
- List rolls = new ArrayList<>();
- for (int i = 0; i < amount; i++) {
- int roll = MyRandom.getRandom().nextInt(sides) + 1;
- rolls.add(roll);
-
- final Map runParams = AbilityKey.newMap();
- runParams.put(AbilityKey.Player, player);
- runParams.put(AbilityKey.Result, roll);
- player.getGame().getTriggerHandler().runTrigger(TriggerType.RolledDie, runParams, false);
-
- total += roll;
- }
-
- if (amount > 0) {
- String message = Localizer.getInstance().getMessage("lblPlayerRolledResult", player, StringUtils.join(rolls, ", "));
- player.getGame().getAction().notifyOfValue(sa, player, message, null);
- }
-
- if (sa.hasParam("ResultSVar")) {
- host.setSVar(sa.getParam("ResultSVar"), ""+total);
- }
- if (sa.hasAdditionalAbility("OnDoubles") && rolls.get(0).equals(rolls.get(1))) {
- AbilityUtils.resolve(sa.getAdditionalAbility("OnDoubles"));
- }
- if (sa.hasAdditionalAbility("On"+total)) {
- AbilityUtils.resolve(sa.getAdditionalAbility("On"+total));
- } else if (sa.hasAdditionalAbility("Else")) {
- AbilityUtils.resolve(sa.getAdditionalAbility("Else"));
- }
+ rollDice(sa, player, amount, sides);
}
}
-}
\ No newline at end of file
+}
diff --git a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java
index 00994aa4a53..cb1ed0c530b 100644
--- a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java
+++ b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java
@@ -807,7 +807,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
return description;
}
public void setDescription(final String s) {
- originalDescription = s;
+ originalDescription = TextUtil.fastReplace(s, "VERT", "|");
description = originalDescription;
}
diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerRolledDie.java b/forge-game/src/main/java/forge/game/trigger/TriggerRolledDie.java
index 2e22f76f275..a478b73e932 100644
--- a/forge-game/src/main/java/forge/game/trigger/TriggerRolledDie.java
+++ b/forge-game/src/main/java/forge/game/trigger/TriggerRolledDie.java
@@ -17,7 +17,8 @@ public class TriggerRolledDie extends Trigger {
}
/** {@inheritDoc}
- * @param runParams*/
+ * @param runParams
+ */
@Override
public final boolean performTest(final Map runParams) {
if (hasParam("ValidPlayer")) {
diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerRolledDieOnce.java b/forge-game/src/main/java/forge/game/trigger/TriggerRolledDieOnce.java
new file mode 100644
index 00000000000..d7eb65bd34f
--- /dev/null
+++ b/forge-game/src/main/java/forge/game/trigger/TriggerRolledDieOnce.java
@@ -0,0 +1,41 @@
+package forge.game.trigger;
+
+import java.util.Map;
+
+import forge.game.ability.AbilityKey;
+import forge.game.card.Card;
+import forge.game.spellability.SpellAbility;
+import forge.util.Localizer;
+
+public class TriggerRolledDieOnce extends Trigger {
+
+ public TriggerRolledDieOnce(final Map params, final Card host, final boolean intrinsic) {
+ super(params, host, intrinsic);
+ }
+
+ /** {@inheritDoc}
+ * @param runParams
+ */
+ @Override
+ public final boolean performTest(final Map runParams) {
+ if (hasParam("ValidPlayer")) {
+ if (!matchesValid(runParams.get(AbilityKey.Player), getParam("ValidPlayer").split(","),
+ this.getHostCard())) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public final void setTriggeringObjects(final SpellAbility sa, Map runParams) {
+ sa.setTriggeringObjectsFrom(runParams, AbilityKey.Result, AbilityKey.Player);
+ }
+
+ @Override
+ public String getImportantStackObjects(SpellAbility sa) {
+ return Localizer.getInstance().getMessage("lblPlayer") + ": " + sa.getTriggeringObject(AbilityKey.Player) + ", " +
+ Localizer.getInstance().getMessage("lblResultIs", sa.getTriggeringObject(AbilityKey.Result));
+ }
+}
diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerType.java b/forge-game/src/main/java/forge/game/trigger/TriggerType.java
index ad50db3d606..b332b264256 100644
--- a/forge-game/src/main/java/forge/game/trigger/TriggerType.java
+++ b/forge-game/src/main/java/forge/game/trigger/TriggerType.java
@@ -89,6 +89,7 @@ public enum TriggerType {
Regenerated(TriggerRegenerated.class),
Revealed(TriggerRevealed.class),
RolledDie(TriggerRolledDie.class),
+ RolledDieOnce(TriggerRolledDieOnce.class),
RoomEntered(TriggerEnteredRoom.class),
Sacrificed(TriggerSacrificed.class),
Scry(TriggerScry.class),
diff --git a/forge-game/src/main/java/forge/util/MessageUtil.java b/forge-game/src/main/java/forge/util/MessageUtil.java
index 10db6860e03..431054e395f 100644
--- a/forge-game/src/main/java/forge/util/MessageUtil.java
+++ b/forge-game/src/main/java/forge/util/MessageUtil.java
@@ -52,6 +52,8 @@ public class MessageUtil {
: Localizer.getInstance().getMessage("lblPlayerActionFlip", flipper, Lang.joinVerb(flipper, value));
case Protection:
return Localizer.getInstance().getMessage("lblPlayerChooseValue", choser, value);
+ case RollDice:
+ return value;
case Vote:
String chooser = StringUtils.capitalize(mayBeYou(player, target));
return Localizer.getInstance().getMessage("lblPlayerVoteValue", chooser, value);
diff --git a/forge-gui/res/cardsfolder/c/chicken_egg.txt b/forge-gui/res/cardsfolder/c/chicken_egg.txt
index af495aa4312..65c6367d95b 100644
--- a/forge-gui/res/cardsfolder/c/chicken_egg.txt
+++ b/forge-gui/res/cardsfolder/c/chicken_egg.txt
@@ -3,7 +3,7 @@ ManaCost:1 R
Types:Creature Egg
PT:0/1
T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigRoll | TriggerDescription$ At the beginning of your upkeep, roll a six-sided die. If you roll a 6, sacrifice CARDNAME and create a 4/4 red Giant Bird creature token.
-SVar:TrigRoll:DB$ RollDice | On6$ DBSac
+SVar:TrigRoll:DB$ RollDice | ResultSubAbilities$ 6:DBSac
SVar:DBSac:DB$ Sacrifice | Defined$ Self | SubAbility$ DBToken
SVar:DBToken:DB$ Token | TokenScript$ r_4_4_giant_chicken
DeckHas:Ability$Token
diff --git a/forge-gui/res/cardsfolder/d/dungeon_master.txt b/forge-gui/res/cardsfolder/d/dungeon_master.txt
index 6c2c694614b..b56d4c5388c 100644
--- a/forge-gui/res/cardsfolder/d/dungeon_master.txt
+++ b/forge-gui/res/cardsfolder/d/dungeon_master.txt
@@ -6,9 +6,9 @@ K:ETBReplacement:Other:RollLoyal
SVar:RollLoyal:DB$ RollDice | Sides$ 4 | ResultSVar$ Result | SubAbility$ DBLoyalty | SpellDescription$ Add 1d4 loyalty counters to CARDNAME
SVar:DBLoyalty:DB$ PutCounter | Defined$ Self | CounterType$ LOYALTY | CounterNum$ Result | ETB$ True
A:AB$ Token | Cost$ AddCounter<1/LOYALTY> | ValidTgts$ Opponent | TokenOwner$ Targeted | TokenScript$ b_1_1_skeleton_opp_life | Planeswalker$ True | SpellDescription$ Target opponent creates a 1/1 black Skeleton creature token with "When this creature dies, each opponent gains 2 life."
-A:AB$ RollDice | Cost$ AddCounter<1/LOYALTY> | Sides$ 20 | On1$ DBSkipTurn | ResultSVar$ Result | Planeswalker$ True | SubAbility$ DBDraw | SpellDescription$ Roll a d20. If you roll a 1, skip your next turn. If you roll a 12 or higher, draw a card.
+A:AB$ RollDice | Cost$ AddCounter<1/LOYALTY> | Sides$ 20 | ResultSubAbilities$ 1:DBSkipTurn,12-20:DBDraw | Planeswalker$ True | SpellDescription$ Roll a d20. If you roll a 1, skip your next turn. If you roll a 12 or higher, draw a card.
SVar:DBSkipTurn:DB$ SkipTurn | Defined$ You | NumTurns$ 1
-SVar:DBDraw:DB$ Draw | ConditionCheckSVar$ Result | ConditionSVarCompare$ GE12
+SVar:DBDraw:DB$ Draw
A:AB$ Token | Cost$ SubCounter<6/LOYALTY> | Planeswalker$ True | Ultimate$ True | TokenScript$ r_3_3_fighter_first_strike | SubAbility$ DBCleric | SpellDescription$ You get an adventuring party. (Your party is a 3/3 red Fighter with first strike, a 1/1 white Cleric with lifelink, a 2/2 black Rogue with hexproof, and a 1/1 blue Wizard with flying.)
SVar:DBCleric:DB$ Token | TokenScript$ w_1_1_cleric_lifelink | SubAbility$ DBRogue
SVar:DBRogue:DB$ Token | TokenScript$ b_2_2_rogue_hexproof | SubAbility$ DBWizard
diff --git a/forge-gui/res/cardsfolder/g/goblin_tutor.txt b/forge-gui/res/cardsfolder/g/goblin_tutor.txt
index a6b35d65380..3bff47a9588 100644
--- a/forge-gui/res/cardsfolder/g/goblin_tutor.txt
+++ b/forge-gui/res/cardsfolder/g/goblin_tutor.txt
@@ -1,10 +1,10 @@
Name:Goblin Tutor
ManaCost:R
Types:Instant
-A:SP$ RollDice | Cost$ R | On2$ GetGobTut | On3$ GetEnch | On4$ GetArtif | On5$ GetCreat| On6$ GetSpell | SpellDescription$ Roll a six-sided die. Search your library for the indicated card, reveal it, put it into your hand, then shuffle.\n2 — A card named Goblin Tutor\n3 — An enchantment\n4 — An artifact\n5 — A creature\n6 — An instant or sorcery
-SVar:GetGobTut:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Card.namedGoblin Tutor | ChangeNum$ 1
-SVar:GetEnch:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Enchantment | ChangeNum$ 1
-SVar:GetArtif:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Artifact | ChangeNum$ 1
-SVar:GetCreat:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Creature | ChangeNum$ 1
-SVar:GetSpell:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Instant,Sorcery | ChangeNum$ 1
+A:SP$ RollDice | Cost$ R | ResultSubAbilities$ 2:GetGobTut,3:GetEnch,4:GetArtif,5:GetCreat,6:GetSpell | SpellDescription$ Roll a six-sided die. If you roll a 1, CARDNAME has no effect. Otherwise, search your library for the indicated card, reveal it, put it into your hand, then shuffle.
+SVar:GetGobTut:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Card.namedGoblin Tutor | ChangeNum$ 1 | SpellDescription$ 2 — A card named Goblin Tutor
+SVar:GetEnch:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Enchantment | ChangeNum$ 1 | SpellDescription$ 3 — An enchantment
+SVar:GetArtif:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Artifact | ChangeNum$ 1 | SpellDescription$ 4 — An artifact
+SVar:GetCreat:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Creature | ChangeNum$ 1 | SpellDescription$ 5 — A creature
+SVar:GetSpell:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Instant,Sorcery | ChangeNum$ 1 | SpellDescription$ 6 — An instant or sorcery
Oracle:Roll a six-sided die. If you roll a 1, Goblin Tutor has no effect. Otherwise, search your library for the indicated card, reveal it, put it into your hand, then shuffle.\n2 — A card named Goblin Tutor\n3 — An enchantment\n4 — An artifact\n5 — A creature\n6 — An instant or sorcery
diff --git a/forge-gui/res/cardsfolder/h/hydradoodle.txt b/forge-gui/res/cardsfolder/h/hydradoodle.txt
index 9b0db0b9574..2da6084ff7f 100644
--- a/forge-gui/res/cardsfolder/h/hydradoodle.txt
+++ b/forge-gui/res/cardsfolder/h/hydradoodle.txt
@@ -4,7 +4,7 @@ Types:Creature Hydra Dog
PT:0/0
K:ETBReplacement:Other:RollCounters
SVar:X:Count$xPaid
-SVar:RollCounters:DB$ RollDice | Amt$ X | ETB$ True | ResultSVar$ Result | SubAbility$ DBCounters | SpellDescription$ As CARDNAME enters the battlefield, roll X six-sided dice. CARDNAME enters the battlefield with a number of +1/+1 counters on it equal to the total of those results.
+SVar:RollCounters:DB$ RollDice | Amount$ X | ETB$ True | ResultSVar$ Result | SubAbility$ DBCounters | SpellDescription$ As CARDNAME enters the battlefield, roll X six-sided dice. CARDNAME enters the battlefield with a number of +1/+1 counters on it equal to the total of those results.
SVar:DBCounters:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ Result | ETB$ True
K:Reach
K:Trample
diff --git a/forge-gui/res/cardsfolder/i/inhumaniac.txt b/forge-gui/res/cardsfolder/i/inhumaniac.txt
index 47e60a8e386..be2ff08db0c 100644
--- a/forge-gui/res/cardsfolder/i/inhumaniac.txt
+++ b/forge-gui/res/cardsfolder/i/inhumaniac.txt
@@ -3,7 +3,7 @@ ManaCost:1 B
Types:Creature Brainiac
PT:1/1
T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigRoll | TriggerDescription$ At the beginning of your upkeep, roll a six-sided die. On a 3 or 4, put a +1/+1 counter on Inhumaniac. On a 5 or higher, put two +1/+1 counters on it. On a 1, remove all +1/+1 counters from Inhumaniac.
-SVar:TrigRoll:DB$ RollDice | On3$ DBOne | On4$ DBOne | On5$ DBTwo | On6$ DBTwo | On1$ DBRemove
+SVar:TrigRoll:DB$ RollDice | ResultSubAbilities$ 3-4:DBOne,5-6:DBTwo,1:DBRemove
SVar:DBOne:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ 1
SVar:DBTwo:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ 2
SVar:DBRemove:DB$ RemoveCounterAll | ValidCards$ Card.Self | CounterType$ P1P1 | AllCounters$ True
diff --git a/forge-gui/res/cardsfolder/j/jack_in_the_mox.txt b/forge-gui/res/cardsfolder/j/jack_in_the_mox.txt
index 41a065b0581..8ddbfbfdebb 100644
--- a/forge-gui/res/cardsfolder/j/jack_in_the_mox.txt
+++ b/forge-gui/res/cardsfolder/j/jack_in_the_mox.txt
@@ -1,14 +1,14 @@
Name:Jack-in-the-Mox
ManaCost:0
Types:Artifact
-A:AB$ RollDice | Cost$ T | On1$ DBSac | On2$ AddW | On3$ AddU | On4$ AddB | On5$ AddR | On6$ AddG | InstantSpeed$ True | SubAbility$ Add0 | SpellDescription$ Roll a six-sided die. This ability has the indicated effect. 1—Sacrifice Jack-in-the-Mox and you lose 5 life. 2—Add {W}. 3—Add {U}. 4—Add {B}. 5-Add {R}. 6-Add {G}.
-SVar:DBSac:DB$ Sacrifice | Defined$ Self | SubAbility$ DBLoseLife
+A:AB$ RollDice | Cost$ T | ResultSubAbilities$ 1:DBSac,2:AddW,3:AddU,4:AddB,5:AddR,6:AddG | InstantSpeed$ True | SubAbility$ Add0 | SpellDescription$ Roll a six-sided die. This ability has the indicated effect.
+SVar:DBSac:DB$ Sacrifice | Defined$ Self | SubAbility$ DBLoseLife | SpellDescription$ 1 — Sacrifice CARDNAME and you lose 5 life.
SVar:DBLoseLife:DB$ LoseLife | LifeAmount$ 5
-SVar:AddW:DB$ Mana | Produced$ W
-SVar:AddU:DB$ Mana | Produced$ U
-SVar:AddB:DB$ Mana | Produced$ B
-SVar:AddR:DB$ Mana | Produced$ R
-SVar:AddG:DB$ Mana | Produced$ G
+SVar:AddW:DB$ Mana | Produced$ W | SpellDescription$ 2 — Add {W}.
+SVar:AddU:DB$ Mana | Produced$ U | SpellDescription$ 3 — Add {U}.
+SVar:AddB:DB$ Mana | Produced$ B | SpellDescription$ 4 — Add {B}.
+SVar:AddR:DB$ Mana | Produced$ R | SpellDescription$ 5 — Add {R}.
+SVar:AddG:DB$ Mana | Produced$ G | SpellDescription$ 6 — Add {G}.
SVar:Add0:DB$ Mana | Produced$ C | Amount$ 0
SVar:PlayMain1:TRUE
Oracle:{T}: Roll a six-sided die. This ability has the indicated effect.\n1 — Sacrifice Jack-in-the-Mox and you lose 5 life.\n2 — Add {W}.\n3 — Add {U}.\n4 — Add {B}.\n5 — Add {R}.\n6 — Add {G}.
diff --git a/forge-gui/res/cardsfolder/k/krazy_kow.txt b/forge-gui/res/cardsfolder/k/krazy_kow.txt
index f8f83308370..634694504ff 100644
--- a/forge-gui/res/cardsfolder/k/krazy_kow.txt
+++ b/forge-gui/res/cardsfolder/k/krazy_kow.txt
@@ -3,7 +3,7 @@ ManaCost:3 R
Types:Creature Cow
PT:3/3
T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigRoll | TriggerDescription$ At the beginning of your upkeep, roll a six-sided die. If you a roll a 1, sacrifice Krazy Kow and it deals 3 damage to each creature and each player.
-SVar:TrigRoll:DB$ RollDice | On1$ DBSac
+SVar:TrigRoll:DB$ RollDice | ResultSubAbilities$ 1:DBSac
SVar:DBSac:DB$ Sacrifice | Defined$ Self | SubAbility$ DBDamage
SVar:DBDamage:DB$ DamageAll | ValidCards$ Creature | ValidPlayers$ Player | NumDmg$ 3
Oracle:At the beginning of your upkeep, roll a six-sided die. If you a roll a 1, sacrifice Krazy Kow and it deals 3 damage to each creature and each player.
diff --git a/forge-gui/res/cardsfolder/l/lobe_lobber.txt b/forge-gui/res/cardsfolder/l/lobe_lobber.txt
index 8e90cc71c4b..5d5c977b5a7 100644
--- a/forge-gui/res/cardsfolder/l/lobe_lobber.txt
+++ b/forge-gui/res/cardsfolder/l/lobe_lobber.txt
@@ -4,6 +4,6 @@ Types:Artifact Equipment
K:Equip:2
S:Mode$ Continuous | Affected$ Creature.EquippedBy | AddAbility$ WandDamage | AddSVar$ DBWandDmg | Description$ Equipped creature has "{T}: This creature deals 1 damage to target player or planeswalker. Roll a six-sided die. On a 5 or higher, untap it."
SVar:WandDamage:AB$ DealDamage | Cost$ T | ValidTgts$ Planeswalker,Player | TgtPrompt$ Select target Planeswalker or player | NumDmg$ 1 | SubAbility$ DBRoll | SpellDescription$ This creature deals 1 damage to target player or planeswalker. Roll a six-sided die. On a 5 or higher, untap it.
-SVar:DBRoll:DB$ RollDice | On5$ DBUntap | On6$ DBUntap
+SVar:DBRoll:DB$ RollDice | ResultSubAbilities$ 5-6:DBUntap
SVar:DBUntap:DB$ Untap
Oracle:Equipped creature has "{T}: This creature deals 1 damage to target player or planeswalker. Roll a six-sided die. On a 5 or higher, untap it."\nEquip {2}
diff --git a/forge-gui/res/cardsfolder/m/mad_science_fair_project.txt b/forge-gui/res/cardsfolder/m/mad_science_fair_project.txt
index b329c61016c..d897784c12b 100644
--- a/forge-gui/res/cardsfolder/m/mad_science_fair_project.txt
+++ b/forge-gui/res/cardsfolder/m/mad_science_fair_project.txt
@@ -2,7 +2,7 @@ Name:Mad Science Fair Project
ManaCost:3
Types:Artifact
A:AB$ Pump | Cost$ T | ValidTgts$ Player | SubAbility$ DBRoll
-SVar:DBRoll:DB$ RollDice | On1$ AddC | On2$ AddC | On3$ AddC | Else$ AddAny | AILogic$ Main2 | SpellDescription$ {T}: Roll a six-sided die. On a 3 or lower, target player adds {C}. Otherwise, that player adds one mana of any color they choose.
+SVar:DBRoll:DB$ RollDice | ResultSubAbilities$ 1-3:AddC,Else:AddAny | AILogic$ Main2 | SpellDescription$ {T}: Roll a six-sided die. On a 3 or lower, target player adds {C}. Otherwise, that player adds one mana of any color they choose.
SVar:AddC:DB$ Mana | Produced$ C | Defined$ Targeted
SVar:AddAny:DB$ Mana | Produced$ Any | Defined$ Targeted
SVar:PlayMain1:TRUE
diff --git a/forge-gui/res/cardsfolder/p/poultrygeist.txt b/forge-gui/res/cardsfolder/p/poultrygeist.txt
index 714b1f2fda5..69bdad81919 100644
--- a/forge-gui/res/cardsfolder/p/poultrygeist.txt
+++ b/forge-gui/res/cardsfolder/p/poultrygeist.txt
@@ -4,7 +4,7 @@ Types:Creature Bird Spirit
PT:1/1
K:Flying
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Creature | TriggerZones$ Battlefield | Execute$ TrigRoll | TriggerDescription$ Whenever a creature dies, you may roll a six-sided die. If you roll a 1, sacrifice CARDNAME. Otherwise, put a +1/+1 counter on CARDNAME.
-SVar:TrigRoll:DB$ RollDice | On1$ DBSac | Else$ DBCounter
+SVar:TrigRoll:DB$ RollDice | ResultSubAbilities$ 1:DBSac,Else:DBCounter
SVar:DBSac:DB$ Sacrifice | Defined$ Self
SVar:DBCounter:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ 1
DeckHas:Ability$Counters
diff --git a/forge-gui/res/cardsfolder/s/spark_fiend.txt b/forge-gui/res/cardsfolder/s/spark_fiend.txt
index 22766f3b029..8e105dacc25 100644
--- a/forge-gui/res/cardsfolder/s/spark_fiend.txt
+++ b/forge-gui/res/cardsfolder/s/spark_fiend.txt
@@ -3,13 +3,13 @@ ManaCost:4 R
Types:Creature Beast
PT:5/6
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigRollETB | TriggerDescription$ When Spark Fiend enters the battlefield, roll two six-sided dice. If you rolled 2, 3, or 12, sacrifice Spark Fiend. If you rolled 7 or 11, don’t roll dice for Spark Fiend during any of your following upkeeps. If you rolled any other total, note that total.
-SVar:TrigRollETB:DB$ RollDice | Amt$ 2 | On2$ DBSac | On3$ DBSac | On12$ DBSac | On7$ DBSafe | Else$ DBNote | ResultSVar$ Result
+SVar:TrigRollETB:DB$ RollDice | Amount$ 2 | ResultSubAbilities$ 2-3:DBSac,12:DBSac,7:DBSafe,Else:DBNote | ResultSVar$ Result
SVar:DBSac:DB$ Sacrifice | Defined$ Self
SVar:Safe:Number$1
SVar:Noted:Number$0
SVar:DBSafe:DB$ StoreSVar | SVar$ Safe | Type$ Number | Expression$ 0
SVar:DBNote:DB$ StoreSVar | SVar$ Noted | Type$ CountSVar | Expression$ Result
T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | CheckSVar$ Safe | SVarCompare$ NE0 | TriggerZones$ Battlefield | Execute$ TrigRoll | TriggerDescription$ At the beginning of your upkeep, roll two six-sided dice. If you rolled 7, sacrifice Spark Fiend. If you roll the noted total, don’t roll dice for Spark Fiend during any of your following upkeeps. Otherwise, do nothing.
-SVar:TrigRoll:DB$ RollDice | Amt$ 2 | On7$ DBSac | ResultSVar$ Result | SubAbility$ DBCheck
+SVar:TrigRoll:DB$ RollDice | Amount$ 2 | ResultSubAbilities$ 7:DBSac | ResultSVar$ Result | SubAbility$ DBCheck
SVar:DBCheck:DB$ StoreSVar | SVar$ Safe | Type$ CountSVar | Expression$ Result/Minus.Noted
Oracle:When Spark Fiend enters the battlefield, roll two six-sided dice. If you rolled 2, 3, or 12, sacrifice Spark Fiend. If you rolled 7 or 11, don't roll dice for Spark Fiend during any of your following upkeeps. If you rolled any other total, note that total.\nAt the beginning of your upkeep, roll two six-sided dice. If you rolled 7, sacrifice Spark Fiend. If you roll the noted total, don't roll dice for Spark Fiend during any of your following upkeeps. Otherwise, do nothing.
diff --git a/forge-gui/res/cardsfolder/s/strategy_schmategy.txt b/forge-gui/res/cardsfolder/s/strategy_schmategy.txt
index 5bce9eb6050..4d32dd22cd6 100644
--- a/forge-gui/res/cardsfolder/s/strategy_schmategy.txt
+++ b/forge-gui/res/cardsfolder/s/strategy_schmategy.txt
@@ -2,13 +2,14 @@ Name:Strategy, Schmategy
ManaCost:1 R
Types:Sorcery
A:SP$ StoreSVar | Cost$ 1 R | SVar$ Left | Type$ Number | Expression$ 1 | SubAbility$ DBRepeat
-SVar:DBRepeat:DB$ Repeat | RepeatCheckSVar$ Left | RepeatSVarCompare$ GT0 | RepeatSubAbility$ Roll | StackDescription$ Roll a six-sided die. Strategy, Schmategy has the indicated effect. 1-Do nothing. 2-Destroy all artifacts. 3-Destroy all lands. 4-Strategy, Schmategy deals 3 damage to each creature and each player. 5-Each player discards their hand and draws seven cards. 6-Repeat this process two more times.
-SVar:Roll:DB$ RollDice | On2$ DBArtif | On3$ DBLand | On4$ DBDamage | On5$ DBWheel | On6$ DBTwoMore | SubAbility$ DBDecr
-SVar:DBArtif:DB$ DestroyAll | ValidCards$ Artifact
-SVar:DBLand:DB$ DestroyAll | ValidCards$ Land
-SVar:DBDamage:DB$ DamageAll | ValidCards$ Creature | ValidPlayers$ Player | NumDmg$ 3
-SVar:DBWheel:DB$ Discard | Mode$ Hand | Defined$ Player | SubAbility$ DBEachDraw
+SVar:DBRepeat:DB$ Repeat | RepeatCheckSVar$ Left | RepeatSVarCompare$ GT0 | RepeatSubAbility$ Roll | StackDescription$ Roll a six-sided die. CARDNAME has the indicated effect.
+SVar:Roll:DB$ RollDice | ResultSubAbilities$ 1:DBNothing,2:DBArtif,3:DBLand,4:DBDamage,5:DBWheel,6:DBTwoMore | SubAbility$ DBDecr
+SVar:DBNothing:DB$ Pump | SpellDescription$ 1 — Do nothing.
+SVar:DBArtif:DB$ DestroyAll | ValidCards$ Artifact | SpellDescription$ 2 — Destroy all artifacts.
+SVar:DBLand:DB$ DestroyAll | ValidCards$ Land | SpellDescription$ 3 — Destroy all lands.
+SVar:DBDamage:DB$ DamageAll | ValidCards$ Creature | ValidPlayers$ Player | NumDmg$ 3 | SpellDescription$ 4 — CARDNAME deals 3 damage to each creature and each player.
+SVar:DBWheel:DB$ Discard | Mode$ Hand | Defined$ Player | SubAbility$ DBEachDraw | SpellDescription$ 5 — Each player discards their hand and draws seven cards.
SVar:DBEachDraw:DB$ Draw | Defined$ Player | NumCards$ 7
-SVar:DBTwoMore:DB$ StoreSVar | SVar$ Left | Type$ CountSVar | Expression$ Left/Plus.2
+SVar:DBTwoMore:DB$ StoreSVar | SVar$ Left | Type$ CountSVar | Expression$ Left/Plus.2 | SpellDescription$ 6 — Repeat this process two more times.
SVar:DBDecr:DB$ StoreSVar | SVar$ Left | Type$ CountSVar | Expression$ Left/Minus.1
Oracle:Roll a six-sided die. Strategy, Schmategy has the indicated effect.\n1 — Do nothing.\n2 — Destroy all artifacts.\n3 — Destroy all lands.\n4 — Strategy, Schmategy deals 3 damage to each creature and each player.\n5 — Each player discards their hand and draws seven cards.\n6 — Repeat this process two more times.
diff --git a/forge-gui/res/cardsfolder/s/sword_of_dungeons_dragons.txt b/forge-gui/res/cardsfolder/s/sword_of_dungeons_dragons.txt
index b03103e7670..70f55085813 100644
--- a/forge-gui/res/cardsfolder/s/sword_of_dungeons_dragons.txt
+++ b/forge-gui/res/cardsfolder/s/sword_of_dungeons_dragons.txt
@@ -6,7 +6,7 @@ S:Mode$ Continuous | Affected$ Creature.EquippedBy | AddPower$ 2 | AddToughness$
T:Mode$ DamageDone | ValidSource$ Creature.EquippedBy | ValidTarget$ Player | CombatDamage$ True | Execute$ TrigLoop | TriggerZones$ Battlefield | TriggerDescription$ Whenever equipped creature deals combat damage to a player, create a 4/4 gold Dragon creature token with flying and roll a d20 (a twenty-sided die). If you roll a 20, repeat this process.
SVar:TrigLoop:DB$ Repeat | RepeatCheckSVar$ RepeatCheck | RepeatSVarCompare$ GT0 | RepeatSubAbility$ TrigToken | StackDescription$ Create a 4/4 gold Dragon creature token with flying and roll a d20. If you roll a 20, repeat this process.
SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ c_4_4_dragon_flying | TokenOwner$ You | SubAbility$ RollDie
-SVar:RollDie:DB$ RollDice | Sides$ 20 | On20$ Win | Else$ Lose
+SVar:RollDie:DB$ RollDice | Sides$ 20 | ResultSubAbilities$ 20:Win,Else:Lose
SVar:Win:DB$ StoreSVar | SVar$ RepeatCheck | Type$ Number | Expression$ 1
SVar:Lose:DB$ StoreSVar | SVar$ RepeatCheck | Type$ Number | Expression$ 0
SVar:RepeatCheck:Number$ 1
diff --git a/forge-gui/res/cardsfolder/u/urzas_science_fair_project.txt b/forge-gui/res/cardsfolder/u/urzas_science_fair_project.txt
index 2484884ab67..dc3f0eef86d 100644
--- a/forge-gui/res/cardsfolder/u/urzas_science_fair_project.txt
+++ b/forge-gui/res/cardsfolder/u/urzas_science_fair_project.txt
@@ -2,11 +2,11 @@ Name:Urza's Science Fair Project
ManaCost:6
Types:Artifact Creature Construct
PT:4/4
-A:AB$ RollDice | Cost$ 2 | On1$ M2 | On2$ Fog | On3$ Vig | On4$ FS | On5$ Fly | On6$ P2 | AILogic$ CombatEarly | SpellDescription$ Roll a six-sided die. CARDNAME gets the indicated result: 1-It gets -2/-2 until end of turn. 2-Prevent all combat damage it would deal this turn. 3-It gains vigilance until end of turn. 4-It gains first strike until end of turn. 5-It gains flying until end of turn. 6-It gets +2/+2 until end of turn.
-SVar:M2:DB$ Pump | Defined$ Self | NumAtt$ -2 | NumDef$ -2
-SVar:Fog:DB$ Pump | Defined$ Self | KW$ Prevent all combat damage that would be dealt by CARDNAME.
-SVar:Vig:DB$ Pump | Defined$ Self | KW$ Vigilance
-SVar:FS:DB$ Pump | Defined$ Self | KW$ First Strike
-SVar:Fly:DB$ Pump | Defined$ Self | KW$ Flying
-SVar:P2:DB$ Pump | Defined$ Self | NumAtt$ 2 | NumDef$ 2
+A:AB$ RollDice | Cost$ 2 | ResultSubAbilities$ 1:M2,2:Fog,3:Vig,4:FS,5:Fly,6:P2 | AILogic$ CombatEarly | SpellDescription$ Roll a six-sided die. CARDNAME gets the indicated result.
+SVar:M2:DB$ Pump | Defined$ Self | NumAtt$ -2 | NumDef$ -2 | SpellDescription$ 1 — It gets -2/-2 until end of turn.
+SVar:Fog:DB$ Pump | Defined$ Self | KW$ Prevent all combat damage that would be dealt by CARDNAME. | SpellDescription$ 2 — Prevent all combat damage it would deal this turn.
+SVar:Vig:DB$ Pump | Defined$ Self | KW$ Vigilance | SpellDescription$ 3 — It gains vigilance until end of turn.
+SVar:FS:DB$ Pump | Defined$ Self | KW$ First Strike | SpellDescription$ 4 — It gains first strike until end of turn.
+SVar:Fly:DB$ Pump | Defined$ Self | KW$ Flying | SpellDescription$ 5 — It gains flying until end of turn.
+SVar:P2:DB$ Pump | Defined$ Self | NumAtt$ 2 | NumDef$ 2 | SpellDescription$ 6 — It gets +2/+2 until end of turn.
Oracle:{2}: Roll a six-sided die. Urza's Science Fair Project gets the indicated result.\n1 — It gets -2/-2 until end of turn.\n2 — Prevent all combat damage it would deal this turn.\n3 — It gains vigilance until end of turn.\n4 — It gains first strike until end of turn.\n5 — It gains flying until end of turn.\n6 — It gets +2/+2 until end of turn.
diff --git a/forge-gui/res/cardsfolder/upcoming/critical_hit.txt b/forge-gui/res/cardsfolder/upcoming/critical_hit.txt
new file mode 100644
index 00000000000..770600b5268
--- /dev/null
+++ b/forge-gui/res/cardsfolder/upcoming/critical_hit.txt
@@ -0,0 +1,7 @@
+Name:Critical Hit
+ManaCost:1 R
+Types:Instant
+A:SP$ Pump | KW$ Double Strike | ValidTgts$ Creature
+T:Mode$ RolledDie | TriggerZones$ Graveyard | ValidResult$ 20 | ValidPlayer$ You | Execute$ TrigReturn | TriggerDescription$ When you roll a natural 20, return CARDNAME from your graveyard to your hand. (A natural 20 is a roll that displays 20 on the die.)
+SVar:TrigReturn:DB$ ChangeZone | Defined$ Self | Origin$ Graveyard | Destination$ Hand
+Oracle:Target creature gains double strike until end of turn.\nWhen you roll a natural 20, return Critical Hit from your graveyard to your hand. (A natural 20 is a roll that displays 20 on the die.)
\ No newline at end of file
diff --git a/forge-gui/res/cardsfolder/upcoming/delina_wild_mage.txt b/forge-gui/res/cardsfolder/upcoming/delina_wild_mage.txt
new file mode 100644
index 00000000000..f4c665d2972
--- /dev/null
+++ b/forge-gui/res/cardsfolder/upcoming/delina_wild_mage.txt
@@ -0,0 +1,15 @@
+Name:Delina, Wild Mage
+ManaCost:3 R
+Types:Legendary Creature Elf Shaman
+PT:3/2
+T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ DBLoop | TriggerDescription$ Whenever CARDNAME attacks, choose target creature you control, then ABILITY
+SVar:DBLoop:DB$ Repeat | ValidTgts$ Creature.YouCtrl | RepeatCheckSVar$ RepeatCheck | RepeatSVarCompare$ GT0 | RepeatSubAbility$ DBRollDice | RepeatOptional$ True
+SVar:DBRollDice:DB$ RollDice | Sides$ 20 | ResultSubAbilities$ 1-14:DBCopy,15-20:DBCopyRepeat | SpellDescription$ roll a d20.
+SVar:DBCopy:DB$ CopyPermanent | Defined$ Targeted | TokenTapped$ True | TokenAttacking$ True | NonLegendary$ True | AtEOT$ ExileCombat | SubAbility$ DBNotRepeat | SpellDescription$ 1-14 VERT Create a tapped and attacking token that's a copy of that creature, except it's not legendary and it has "Exile this creature at end of combat.
+SVar:DBNotRepeat:DB$ StoreSVar | SVar$ RepeatCheck | Type$ Number | Expression$ 0
+SVar:DBCopyRepeat:DB$ CopyPermanent | Defined$ Targeted | TokenTapped$ True | TokenAttacking$ True | NonLegendary$ True | AtEOT$ ExileCombat | SubAbility$ DBRepeat | SpellDescription$ 15-20 VERT Create one of those tokens. You may roll again.
+SVar:DBRepeat:DB$ StoreSVar | SVar$ RepeatCheck | Type$ Number | Expression$ 1
+SVar:RepeatCheck:Number$1
+SVar:HasAttackEffect:TRUE
+DeckHas:Ability$Token
+Oracle:Whenever Delina, Wild Mage attacks, choose target creature you control, then roll a d20.\n1-14 | Create a tapped and attacking token that's a copy of that creature, except it's not legendary and it has "Exile this creature at end of combat."\n15-20 | Create one of those tokens. You may roll again.
diff --git a/forge-gui/res/cardsfolder/upcoming/farideh_devils_chosen.txt b/forge-gui/res/cardsfolder/upcoming/farideh_devils_chosen.txt
new file mode 100644
index 00000000000..b8e01dd2376
--- /dev/null
+++ b/forge-gui/res/cardsfolder/upcoming/farideh_devils_chosen.txt
@@ -0,0 +1,9 @@
+Name:Farideh, Devil's Chosen
+ManaCost:2 U R
+Types:Legendary Creature Tiefling Warlock
+PT:3/3
+T:Mode$ RolledDieOnce | TriggerZones$ Battlefield | ValidPlayer$ You | Execute$ TrigPump | TriggerDescription$ Dark One's Own Luck — Whenever you roll one or more dice, Farideh, Devil's Chosen gains flying and menace until end of turn. If any of those results was 10 or higher, draw a card.
+SVar:TrigPump:DB$ Pump | Defined$ Self | KW$ Flying & Menace | SubAbility$ DBDraw
+SVar:DBDraw:DB$ Draw | NumCards$ 1 | ConditionCheckSVar$ DiceResult | ConditionSVarCompare$ GE10
+SVar:DiceResult:TriggerCountMax$Result
+Oracle:Dark One's Own Luck — Whenever you roll one or more dice, Farideh, Devil's Chosen gains flying and menace until end of turn. If any of those results was 10 or higher, draw a card.
diff --git a/forge-gui/res/cardsfolder/upcoming/feywild_trickster.txt b/forge-gui/res/cardsfolder/upcoming/feywild_trickster.txt
new file mode 100644
index 00000000000..5e07f1e067c
--- /dev/null
+++ b/forge-gui/res/cardsfolder/upcoming/feywild_trickster.txt
@@ -0,0 +1,7 @@
+Name:Feywild Trickster
+ManaCost:2 U
+Types:Creature Gnome Warlock
+PT:2/2
+T:Mode$ RolledDieOnce | TriggerZones$ Battlefield | ValidPlayer$ You | Execute$ TrigToken | TriggerDescription$ Whenever you roll one or more dice, create a 1/1 blue Faerie Dragon creature token with flying.
+SVar:TrigToken:DB$ Token | TokenScript$ u_1_1_faerie_dragon_flying | TokenAmount$ 1 | TokenOwner$ You
+Oracle:Whenever you roll one or more dice, create a 1/1 blue Faerie Dragon creature token with flying.
diff --git a/forge-gui/res/cardsfolder/upcoming/pixie_guide.txt b/forge-gui/res/cardsfolder/upcoming/pixie_guide.txt
new file mode 100644
index 00000000000..54799ca966b
--- /dev/null
+++ b/forge-gui/res/cardsfolder/upcoming/pixie_guide.txt
@@ -0,0 +1,7 @@
+Name:Pixie Guide
+ManaCost:1 U
+Types:Creature Faerie
+PT:1/3
+K:Flying
+S:Mode$ Continuous | Affected$ You | AddKeyword$ If you would roll one or more dice, instead roll that many dice plus one and ignore the lowest roll. | Description$ Grant an Advantage — if you would roll one or more dice, instead roll that many dice plus one and ignore the lowest roll.
+Oracle:Flying\nGrant an Advantage — if you would roll one or more dice, instead roll that many dice plus one and ignore the lowest roll.
diff --git a/forge-gui/res/cardsfolder/upcoming/the_deck_of_many_things.txt b/forge-gui/res/cardsfolder/upcoming/the_deck_of_many_things.txt
new file mode 100644
index 00000000000..bed0629901e
--- /dev/null
+++ b/forge-gui/res/cardsfolder/upcoming/the_deck_of_many_things.txt
@@ -0,0 +1,16 @@
+Name:The Deck of Many Things
+ManaCost:5
+Types:Legendary Artifact
+A:AB$ RollDice | Cost$ 2 T | Sides$ 20 | Modifier$ CardsInHand | ResultSubAbilities$ Else:DBDiscard,1-9:DBReturn,10-19:DBDrawTwo,20:DBReanimate | SpellDescription$ Roll a d20 and subtract the number of cards in your hand. If the result is 0 or less, discard your hand.
+SVar:CardsInHand:Count$InYourHand/Negative
+SVar:DBDiscard:DB$ Discard | Mode$ Hand
+SVar:DBReturn:DB$ ChangeZone | ChangeType$ Card.YouCtrl | ChangeNum$ 1 | Hidden$ True | Origin$ Graveyard | AtRandom$ True | Destination$ Hand | SpellDescription$ 1-9 VERT Return a card at random from your graveyard to your hand.
+SVar:DBDrawTwo:DB$ Draw | NumCards$ 2 | SpellDescription$ 10-19 VERT Draw two cards.
+SVar:DBReanimate:DB$ ChooseCard | Choices$ Creature | ChoiceZone$ Graveyard | Amount$ 1 | SubAbility$ DBChangeZone | SpellDescription$ 20 VERT Put a creature card from any graveyard onto the battlefield under your control. When that creature dies, its owner loses the game.
+SVar:DBChangeZone:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | Defined$ ChosenCard | GainControl$ True | RememberChanged$ True | SubAbility$ DBLoseEffect
+SVar:DBLoseEffect:DB$ Effect | Triggers$ TrigDie | RememberObjects$ ChosenCard | Duration$ Permanent | ExileOnMoved$ Battlefield | SubAbility$ DBCleanup
+SVar:TrigDie:Mode$ ChangesZone | ValidCard$ Creature.IsRemembered | Origin$ Battlefield | Destination$ Graveyard | Execute$ DBLose | TriggerDescription$ When that creature dies, its owner loses the game.
+SVar:DBLose:DB$ LosesGame | Defined$ RememberedOwner
+SVar:DBCleanup:DB$ Cleanup | ClearChosenCard$ True
+DeckHas:Ability$Discard & Ability$Graveyard
+Oracle:{2}, {T}: Roll a d20 and subtract the number of cards in your hand. If the result is 0 or less, discard your hand.\n1-9 | Return a card at random from your graveyard to your hand.\n10-19 | Draw two cards.\n20 | Put a creature card from any graveyard onto the battlefield under your control. When that creature dies, its owner loses the game.
diff --git a/forge-gui/res/cardsfolder/upcoming/wizards_spellbook.txt b/forge-gui/res/cardsfolder/upcoming/wizards_spellbook.txt
new file mode 100644
index 00000000000..4163dc1f6e2
--- /dev/null
+++ b/forge-gui/res/cardsfolder/upcoming/wizards_spellbook.txt
@@ -0,0 +1,11 @@
+Name:Wizard's Spellbook
+ManaCost:5 U U
+Types:Artifact
+A:AB$ ChangeZone | Cost$ T | SorcerySpeed$ True | Origin$ Graveyard | Destination$ Exile | Mandatory$ True | ValidTgts$ Instant,Sorcery | TgtPrompt$ Select target instant or sorcery card in a graveyard | RememberChanged$ True | Imprint$ True | SubAbility$ DBRollDice | SpellDescription$ Exile target instant or sorcery card from a graveyard. Roll a d20. Activate only as a sorcery.
+SVar:DBRollDice:DB$ RollDice | Sides$ 20 | ResultSubAbilities$ 1-9:PlayCopy,10-19:PlayCost1,20:PlayAny
+SVar:PlayCopy:DB$ Play | Valid$ Card.IsRemembered+ExiledWithSource | ValidZone$ Exile | Amount$ All | CopyOnce$ True | CopyCard$ True | Optional$ True | SubAbility$ DBCleanup | SpellDescription$ 1-9 VERT Copy that card. You may cast the copy.
+SVar:PlayCost1:DB$ Play | Valid$ Card.IsRemembered+ExiledWithSource | ValidZone$ Exile | Amount$ All | CopyOnce$ True | CopyCard$ True | Optional$ True | PlayCost$ 1 | SubAbility$ DBCleanup | SpellDescription$ 10-19 VERT Copy that card. You may cast the copy by paying {1} rather than paying its mana cost.
+SVar:PlayAny:DB$ Play | Valid$ Card.IsImprinted+ExiledWithSource | ValidZone$ Exile | Amount$ All | CopyOnce$ True | CopyCard$ True | Optional$ True | WithoutManaCost$ True | SubAbility$ DBCleanup | SpellDescription$ 20 VERT Copy each card exiled with Wizard's Spellbook. You may cast any number of the copies without paying their mana costs.
+SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
+DeckHas:Ability$Graveyard
+Oracle:{T}: Exile target instant or sorcery card from a graveyard. Roll a d20. Activate only as a sorcery.\n1-9 | Copy that card. You may cast the copy.\n10-19 | Copy that card. You may cast the copy by paying {1} rather than paying its mana cost.\n20 | Copy each card exiled with Wizard's Spellbook. You may cast any number of the copies without paying their mana costs.
diff --git a/forge-gui/res/lists/TypeLists.txt b/forge-gui/res/lists/TypeLists.txt
index 7129eee7b5c..feb85ee3f8a 100644
--- a/forge-gui/res/lists/TypeLists.txt
+++ b/forge-gui/res/lists/TypeLists.txt
@@ -257,6 +257,7 @@ Tetravite:Tetravites
Thalakos:Thalakoses
Thopter:Thopters
Thrull:Thrulls
+Tiefling:Tieflings
Treefolk:Treefolks
Trilobite:Trilobites
Triskelavite:Triskelavites
diff --git a/forge-gui/res/tokenscripts/u_1_1_faerie_dragon_flying.txt b/forge-gui/res/tokenscripts/u_1_1_faerie_dragon_flying.txt
new file mode 100644
index 00000000000..23f14a9f4ea
--- /dev/null
+++ b/forge-gui/res/tokenscripts/u_1_1_faerie_dragon_flying.txt
@@ -0,0 +1,7 @@
+Name:Faerie Dragon
+ManaCost:no cost
+Types:Creature Faerie Dragon
+Colors:blue
+PT:1/1
+K:Flying
+Oracle:Flying