diff --git a/forge-ai/src/main/java/forge/ai/ability/AnimateAi.java b/forge-ai/src/main/java/forge/ai/ability/AnimateAi.java
index b64a1fd84c5..cb0c37bf3f4 100644
--- a/forge-ai/src/main/java/forge/ai/ability/AnimateAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/AnimateAi.java
@@ -340,7 +340,7 @@ public class AnimateAi extends SpellAbilityAi {
// select the worst of the best
final Card worst = ComputerUtilCard.getWorstAI(maxList);
- if (worst.isLand()) {
+ if (worst != null && worst.isLand()) {
// e.g. Clan Guildmage, make sure we're not using the same land we want to animate to activate the ability
this.holdAnimatedTillMain2(ai, worst);
if (!ComputerUtilMana.canPayManaCost(sa, ai, 0)) {
diff --git a/forge-game/src/main/java/forge/game/GameOutcome.java b/forge-game/src/main/java/forge/game/GameOutcome.java
index d6f3481c3d6..2cb077ef14a 100644
--- a/forge-game/src/main/java/forge/game/GameOutcome.java
+++ b/forge-game/src/main/java/forge/game/GameOutcome.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 .
*/
@@ -33,7 +33,7 @@ import java.util.Map.Entry;
*
* GameInfo class.
*
- *
+ *
* @author Forge
* @version $Id: GameOutcome.java 17608 2012-10-20 22:27:27Z Max mtg $
*/
@@ -47,7 +47,7 @@ public final class GameOutcome implements Iterable lostCards;
public final List wonCards;
-
+
private AnteResult(List cards, boolean won) {
// Need empty lists for other results for addition of change ownership cards
if (won) {
@@ -67,15 +67,20 @@ public final class GameOutcome implements Iterable cards) { return new AnteResult(cards, true); }
- public static AnteResult lost(List cards) { return new AnteResult(cards, false); }
+ public static AnteResult won(List cards) {
+ return new AnteResult(cards, true);
+ }
+
+ public static AnteResult lost(List cards) {
+ return new AnteResult(cards, false);
+ }
}
private int lastTurnNumber = 0;
private int lifeDelta = 0;
private int winningTeam = -1;
- private final HashMap playerRating = new HashMap<>();
+ private final HashMap playerRating = new HashMap<>();
private final HashMap playerNames = new HashMap<>();
public final Map anteResult = new HashMap<>();
@@ -83,21 +88,22 @@ public final class GameOutcome implements Iterable players) {
winCondition = reason;
- calculateLifeDelta(players);
-
- int winnersHealth = 0;
- int opponentsHealth = 0;
for (final Player p : players) {
this.playerRating.put(p.getRegisteredPlayer(), p.getStats());
this.playerNames.put(p.getRegisteredPlayer(), p.getName());
- if (p.getOutcome().hasWon() && winCondition == GameEndReason.AllOpposingTeamsLost) {
+ if (winCondition == GameEndReason.AllOpposingTeamsLost && p.getOutcome().hasWon()) {
// Only mark the WinningTeam when "Team mode" is on.
winningTeam = p.getTeam();
}
}
+
+ // Unable to calculate lifeDelta between a winning and losing player whe a draw is in place
+ if (winCondition == GameEndReason.Draw) return;
+ int winnersHealth = 0;
+ int opponentsHealth = 0;
for (final Player p : players) {
if (p.getTeam() == winningTeam) {
winnersHealth += p.getLife();
@@ -106,22 +112,22 @@ public final class GameOutcome implements Iterable players) {
int opponentsHealth = 0;
int winnersHealth = 0;
-
+
for (Player p : players) {
if (p.getOutcome().hasWon()) {
winnersHealth += p.getLife();
- }
- else {
+ } else {
opponentsHealth += p.getLife();
}
}
-
+
lifeDelta = Math.max(0, winnersHealth - opponentsHealth);
}
@@ -150,7 +156,7 @@ public final class GameOutcome implements Iterable pair : playerRating.entrySet()) {
+ for (Entry pair : playerRating.entrySet()) {
if (pair.getValue().getOutcome().hasWon()) {
return pair.getKey();
}
@@ -196,7 +202,7 @@ public final class GameOutcome implements Iterable getOutcomeStrings() {
List outcomes = Lists.newArrayList();
- for(RegisteredPlayer player : playerNames.keySet()) {
+ for (RegisteredPlayer player : playerNames.keySet()) {
outcomes.add(getOutcomeString(player));
}
return outcomes;
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 ade6156cdc5..6e8a3bbe680 100644
--- a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java
+++ b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java
@@ -4686,7 +4686,7 @@ public class CardFactoryUtil {
altCostSA.setRestrictions(restriction);
String costDescription = TextUtil.fastReplace(params.get("Description"),"CARDNAME", card.getName());
- if (costDescription.isEmpty()) {
+ if (costDescription == null || costDescription.isEmpty()) {
costDescription = TextUtil.concatWithSpace("You may", abCost.toStringAlt(), "rather than pay", TextUtil.addSuffix(card.getName(), "'s mana cost."));
}
diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerTapsForMana.java b/forge-game/src/main/java/forge/game/trigger/TriggerTapsForMana.java
index 499e5426022..6b50f104eb6 100644
--- a/forge-game/src/main/java/forge/game/trigger/TriggerTapsForMana.java
+++ b/forge-game/src/main/java/forge/game/trigger/TriggerTapsForMana.java
@@ -101,10 +101,10 @@ public class TriggerTapsForMana extends Trigger {
if (!this.getHostCard().hasChosenColor() || !produced.contains(MagicColor.toShortString(this.getHostCard().getChosenColor()))) {
return false;
}
- if (!produced.contains(MagicColor.toShortString(this.getParam("Produced")))) {
+ }
+ if (!produced.contains(MagicColor.toShortString(this.getParam("Produced")))) {
return false;
}
- }
}
return true;
diff --git a/forge-gui-desktop/src/main/java/forge/view/SimulateMatch.java b/forge-gui-desktop/src/main/java/forge/view/SimulateMatch.java
index 49e3a1a6acc..35d2a2dbc35 100644
--- a/forge-gui-desktop/src/main/java/forge/view/SimulateMatch.java
+++ b/forge-gui-desktop/src/main/java/forge/view/SimulateMatch.java
@@ -1,34 +1,33 @@
package forge.view;
+import forge.LobbyPlayer;
+import forge.deck.Deck;
+import forge.deck.DeckGroup;
+import forge.deck.io.DeckSerializer;
+import forge.game.*;
+import forge.game.player.RegisteredPlayer;
+import forge.model.FModel;
+import forge.player.GamePlayerUtil;
+import forge.properties.ForgeConstants;
+import forge.tournament.system.*;
+import forge.util.Lang;
+import forge.util.TextUtil;
+import forge.util.WordUtil;
+import forge.util.storage.IStorage;
+import org.apache.commons.lang3.time.StopWatch;
+
import java.io.File;
import java.io.FilenameFilter;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
-import forge.LobbyPlayer;
-import forge.deck.DeckGroup;
-import forge.game.*;
-import forge.properties.ForgeConstants;
-import forge.tournament.system.*;
-import forge.util.TextUtil;
-import forge.util.WordUtil;
-import forge.util.storage.IStorage;
-import org.apache.commons.lang3.time.StopWatch;
-
-import forge.deck.Deck;
-import forge.deck.io.DeckSerializer;
-import forge.game.player.RegisteredPlayer;
-import forge.model.FModel;
-import forge.player.GamePlayerUtil;
-import forge.util.Lang;
-
public class SimulateMatch {
public static void simulate(String[] args) {
FModel.initialize(null, null);
System.out.println("Simulation mode");
- if(args.length < 4) {
+ if (args.length < 4) {
argumentHelp();
return;
}
@@ -49,11 +48,9 @@ public class SimulateMatch {
options = new ArrayList<>();
params.put(a.substring(1), options);
- }
- else if (options != null) {
+ } else if (options != null) {
options.add(a);
- }
- else {
+ } else {
System.err.println("Illegal parameter usage");
return;
}
@@ -97,7 +94,7 @@ public class SimulateMatch {
int i = 1;
if (params.containsKey("d")) {
- for(String deck : params.get("d")) {
+ for (String deck : params.get("d")) {
Deck d = deckFromCommandLineParameter(deck, type);
if (d == null) {
System.out.println(TextUtil.concatNoSpace("Could not load deck - ", deck, ", match cannot start"));
@@ -130,7 +127,7 @@ public class SimulateMatch {
if (matchSize != 0) {
int iGame = 0;
- while(!mc.isMatchOver()) {
+ while (!mc.isMatchOver()) {
// play games until the match ends
simulateSingleMatch(mc, iGame, outputGamelog);
iGame++;
@@ -159,38 +156,26 @@ public class SimulateMatch {
}
-
public static void simulateSingleMatch(final Match mc, int iGame, boolean outputGamelog) {
final StopWatch sw = new StopWatch();
sw.start();
final Game g1 = mc.createGame();
// will run match in the same thread
-
- long startTime = System.currentTimeMillis();
try {
- TimeLimitedCodeBlock.runWithTimeout(new Runnable() {
- @Override
- public void run() {
- mc.startGame(g1);
- sw.stop();
- }
+ TimeLimitedCodeBlock.runWithTimeout(() -> {
+ mc.startGame(g1);
+ sw.stop();
}, 120, TimeUnit.SECONDS);
- }
- catch (TimeoutException e) {
+ } catch (TimeoutException e) {
System.out.println("Stopping slow match as draw");
- g1.setGameOver(GameEndReason.Draw);
- sw.stop();
- }catch (Exception e){
+ } catch (Exception | StackOverflowError e) {
e.printStackTrace();
- g1.setGameOver(GameEndReason.Draw);
- sw.stop();
- }catch(StackOverflowError e){
+ } finally {
g1.setGameOver(GameEndReason.Draw);
sw.stop();
}
-
List log;
if (outputGamelog) {
log = g1.getGameLog().getLogEntries(null);
@@ -198,15 +183,15 @@ public class SimulateMatch {
log = g1.getGameLog().getLogEntries(GameLogEntryType.MATCH_RESULTS);
}
Collections.reverse(log);
- for(GameLogEntry l : log) {
+ for (GameLogEntry l : log) {
System.out.println(l);
}
// If both players life totals to 0 in a single turn, the game should end in a draw
- if(g1.getOutcome().isDraw()){
- System.out.println(String.format("Game %d ended in a Draw! Took %d ms.", 1+iGame, sw.getTime()));
+ if (g1.getOutcome().isDraw()) {
+ System.out.printf("\nGame Result: Game %d ended in a Draw! Took %d ms.%n", 1 + iGame, sw.getTime());
} else {
- System.out.println(String.format("\nGame %d ended in %d ms. %s has won!\n", 1+iGame, sw.getTime(), g1.getOutcome().getWinningLobbyPlayer().getName()));
+ System.out.printf("\nGame Result: Game %d ended in %d ms. %s has won!\n%n", 1 + iGame, sw.getTime(), g1.getOutcome().getWinningLobbyPlayer().getName());
}
}
@@ -219,7 +204,7 @@ public class SimulateMatch {
List players = new ArrayList<>();
int numPlayers = 0;
if (params.containsKey("d")) {
- for(String deck : params.get("d")) {
+ for (String deck : params.get("d")) {
Deck d = deckFromCommandLineParameter(deck, rules.getGameType());
if (d == null) {
System.out.println(TextUtil.concatNoSpace("Could not load deck - ", deck, ", match cannot start"));
@@ -239,7 +224,7 @@ public class SimulateMatch {
if (!folder.isDirectory()) {
System.out.println("Directory not found - " + foldName);
} else {
- for(File deck : folder.listFiles(new FilenameFilter() {
+ for (File deck : folder.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.endsWith(".dck");
@@ -281,16 +266,16 @@ public class SimulateMatch {
System.out.println(TextUtil.concatNoSpace("Starting a ", tournament, " tournament with ",
String.valueOf(numPlayers), " players over ",
String.valueOf(tourney.getTotalRounds()), " rounds"));
- while(!tourney.isTournamentOver()) {
+ while (!tourney.isTournamentOver()) {
if (tourney.getActiveRound() != curRound) {
if (curRound != 0) {
System.out.println(TextUtil.concatNoSpace("End Round - ", String.valueOf(curRound)));
}
curRound = tourney.getActiveRound();
System.out.println();
- System.out.println(TextUtil.concatNoSpace("Round ", String.valueOf(curRound) ," Pairings:"));
+ System.out.println(TextUtil.concatNoSpace("Round ", String.valueOf(curRound), " Pairings:"));
- for(TournamentPairing pairing : tourney.getActivePairings()) {
+ for (TournamentPairing pairing : tourney.getActivePairings()) {
System.out.println(pairing.outputHeader());
}
System.out.println();
@@ -311,10 +296,10 @@ public class SimulateMatch {
int iGame = 0;
while (!mc.isMatchOver()) {
// play games until the match ends
- try{
+ try {
simulateSingleMatch(mc, iGame, outputGamelog);
iGame++;
- } catch(Exception e) {
+ } catch (Exception e) {
exceptions++;
System.out.println(e.toString());
if (exceptions > 5) {
@@ -349,10 +334,10 @@ public class SimulateMatch {
private static Deck deckFromCommandLineParameter(String deckname, GameType type) {
int dotpos = deckname.lastIndexOf('.');
- if(dotpos > 0 && dotpos == deckname.length()-4) {
+ if (dotpos > 0 && dotpos == deckname.length() - 4) {
String baseDir = type.equals(GameType.Commander) ?
ForgeConstants.DECK_COMMANDER_DIR : ForgeConstants.DECK_CONSTRUCTED_DIR;
- return DeckSerializer.fromFile(new File(baseDir+deckname));
+ return DeckSerializer.fromFile(new File(baseDir + deckname));
}
IStorage deckStore = null;
diff --git a/forge-gui/res/cardsfolder/j/jace_mirror_mage.txt b/forge-gui/res/cardsfolder/j/jace_mirror_mage.txt
index 870ed1df4d9..6bfc5d248cf 100644
--- a/forge-gui/res/cardsfolder/j/jace_mirror_mage.txt
+++ b/forge-gui/res/cardsfolder/j/jace_mirror_mage.txt
@@ -3,10 +3,10 @@ ManaCost:1 U U
Types:Legendary Planeswalker Jace
Loyalty:4
K:Kicker:2
-T:Mode$ ChangesZone | ValidCard$ Card.Self+kicked | Origin$ Any | Destination$ Battlefield | Execute$ TrigCopy | TriggerDescription$ When CARDNAME enters the battlefield, if it was kicked, create a token that's a copy of CARDNAME, except it's not legendary and its starting loyalty is 1.
+T:Mode$ ChangesZone | ValidCard$ Card.Self+kicked | Origin$ Any | Destination$ Battlefield | Execute$ TrigCopy | TriggerDescription$ When CARDNAME enters the battlefield, if NICKNAME was kicked, create a token that's a copy of CARDNAME, except it's not legendary and its starting loyalty is 1.
SVar:TrigCopy:DB$ CopyPermanent | Defined$ Self | NonLegendary$ True | SetLoyalty$ 1
A:AB$ Scry | Cost$ AddCounter<1/LOYALTY> | Planeswalker$ True | ScryNum$ 1 | SpellDescription$ Scry 1.
-A:AB$ Draw | Cost$ AddCounter<0/LOYALTY> | Planeswalker$ True | NumCards$ 1 | Reveal$ True | RememberDrawn$ True | SubAbility$ DBRemoveCounters | SpellDescription$ Draw a card and reveal it. Remove a number of loyalty counters equal to that card's converted mana cost from CARDNAME.
+A:AB$ Draw | Cost$ AddCounter<0/LOYALTY> | Planeswalker$ True | Ultimate$ True | NumCards$ 1 | Reveal$ True | RememberDrawn$ True | SubAbility$ DBRemoveCounters | SpellDescription$ Draw a card and reveal it. Remove a number of loyalty counters equal to that card's converted mana cost from CARDNAME.
SVar:DBRemoveCounters:DB$ RemoveCounter | Defined$ Self | CounterType$ LOYALTY | CounterNum$ X | References$ X | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
SVar:X:Remembered$CardManaCost
diff --git a/forge-gui/res/cardsfolder/l/light_of_promise.txt b/forge-gui/res/cardsfolder/l/light_of_promise.txt
index 4d55340a156..60488e861a8 100644
--- a/forge-gui/res/cardsfolder/l/light_of_promise.txt
+++ b/forge-gui/res/cardsfolder/l/light_of_promise.txt
@@ -3,10 +3,10 @@ ManaCost:2 W
Types:Enchantment Aura
K:Enchant creature
A:SP$ Attach | Cost$ 2 W | ValidTgts$ Creature
-S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddTrigger$ LightOfPromiseTrig | AddSVar$ LightOfPromisePutCounter & X | Description$ Enchanted creature has "Whenever you gain life, put that many +1/+1 counters on this creature."
-SVar:LightOfPromiseTrig:Mode$ LifeGained | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ LightOfPromisePutCounter | TriggerDescription$ Whenever you gain life, put that many +1/+1 counters on CARDNAME.
-SVar:LightOfPromisePutCounter:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ X | References$ X
-SVar:X:TriggerCount$LifeAmount
-DeckHints:Ability$LifeGain
+S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddTrigger$ LightOfPromiseTrig | AddSVar$ LightOfPromisePutCounter & LightOfPromiseAmount | Description$ Enchanted creature has "Whenever you gain life, put that many +1/+1 counters on this creature."
+SVar:LightOfPromiseTrig:Mode$ LifeGained | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ LightOfPromisePutCounter | TriggerDescription$ Whenever you gain life, put that many +1/+1 counters on this creature.
+SVar:LightOfPromisePutCounter:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ LightOfPromiseAmount | References$ LightOfPromiseAmount
+SVar:LightOfPromiseAmount:TriggerCount$LifeAmount
+DeckNeeds:Ability$LifeGain
DeckHas:Ability$Counters
Oracle:Enchant creature\nEnchanted creature has "Whenever you gain life, put that many +1/+1 counters on this creature."
diff --git a/forge-gui/res/cardsfolder/o/orah_skyclave_hierophant.txt b/forge-gui/res/cardsfolder/o/orah_skyclave_hierophant.txt
index f12bb686902..efbdbdc6a1b 100755
--- a/forge-gui/res/cardsfolder/o/orah_skyclave_hierophant.txt
+++ b/forge-gui/res/cardsfolder/o/orah_skyclave_hierophant.txt
@@ -4,7 +4,7 @@ Types:Legendary Creature Kor Cleric
PT:3/3
K:Lifelink
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigReturn | TriggerDescription$ Whenever CARDNAME or another Cleric you control dies, return target Cleric with lesser converted mana cost from your graveyard to the battlefield.
-T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | TriggerZone$ Battlefield | ValidCard$ Cleric.YouCtrl+Other | Execute$ TrigReturn | Secondary$ True | TriggerDescription$ Whenever CARDNAME or another Cleric you control dies, return target Cleric card with lesser converted mana cost from your graveyard to the battlefield.
+T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Cleric.YouCtrl+Other | Execute$ TrigReturn | Secondary$ True | TriggerZones$ Battlefield | TriggerDescription$ Whenever CARDNAME or another Cleric you control dies, return target Cleric card with lesser converted mana cost from your graveyard to the battlefield.
SVar:TrigReturn:DB$ ChangeZone | ValidTgts$ Cleric.cmcLTX+YouOwn | TgtPrompt$ Choose target Cleric card with lesser converted mana cost | References$ X | Origin$ Graveyard | Destination$ Battlefield
SVar:X:TriggeredCard$CardManaCost
DeckHas:Ability$Graveyard
diff --git a/forge-gui/res/cube/MTGA Cube 2020 April.dck b/forge-gui/res/cube/MTGA Cube 2020 April.dck
new file mode 100644
index 00000000000..2dcab70784b
--- /dev/null
+++ b/forge-gui/res/cube/MTGA Cube 2020 April.dck
@@ -0,0 +1,558 @@
+[metadata]
+Name=MTGA Cube 2020 April
+[main]
+1 Adanto Vanguard|XLN
+1 Admiral's Order|RIX
+1 Agent of Treachery|M20
+1 Agonizing Remorse|THB
+1 Ajani's Pridemate|WAR
+1 Ajani, Strength of the Pride|M20
+1 Ajani, the Greathearted|WAR
+1 Alirios, Enraptured|THB
+1 All That Glitters|ELD
+1 Alseid of Life's Bounty|THB
+1 Anax, Hardened in the Forge|THB
+1 Angrath's Rampage|WAR
+1 Angrath, Captain of Chaos|WAR
+1 Animating Faerie|ELD
+1 Anticipate|IKO
+1 Aphemia, the Cacophony|THB
+1 Arasta of the Endless Web|THB
+1 Arcane Encyclopedia|M19
+1 Arcanist's Owl|ELD
+1 Arch of Orazca|RIX
+1 Archon of Sun's Grace|THB
+1 Ardenvale Tactician|ELD
+1 Arguel's Blood Fast|XLN
+1 Arrester's Zeal|RNA
+1 Aryel, Knight of Windgrace|DOM
+1 Ashiok, Dream Render|WAR
+1 Ashiok, Nightmare Muse|THB
+1 Assassin's Trophy|GRN
+1 Atris, Oracle of Half-Truths|THB
+1 Audacious Thief|M20
+1 Augur of Bolas|WAR
+1 Aurelia, Exemplar of Justice|GRN
+1 Baffling End|RIX
+1 Bake into a Pie|ELD
+1 Banefire|M19
+1 Banishing Light|THB
+1 Barkhide Troll|M20
+1 Barren Moor|MH1
+1 Beanstalk Giant|ELD
+1 Beast Whisperer|GRN
+1 Benthic Biomancer|RNA
+1 Biogenic Ooze|RNA
+1 Blackblade Reforged|DOM
+1 Blacklance Paragon|ELD
+1 Blade Juggler|RNA
+1 Blast Zone|WAR
+1 Blink of an Eye|DOM
+1 Blood Aspirant|THB
+1 Blood Crypt|RNA
+1 Blood Divination|M19
+1 Blood for Bones|M20
+1 Bloodfell Caves|M21
+1 Bloom Hulk|WAR
+1 Blossoming Sands|M21
+1 Board the Weatherlight|DOM
+1 Bolas's Citadel|WAR
+1 Bond of Insight|WAR
+1 Bonecrusher Giant|ELD
+1 Brain Maggot|JOU
+1 Brazen Borrower|ELD
+1 Breeding Pool|RNA
+1 Brineborn Cutthroat|M20
+1 Burning-Tree Emissary|MM3
+1 Captain Lannery Storm|XLN
+1 Captain Sisay|INV
+1 Captivating Crew|XLN
+1 Careless Celebrant|THB
+1 Cast Down|2XM
+1 Castle Ardenvale|ELD
+1 Castle Embereth|ELD
+1 Castle Garenbrig|ELD
+1 Castle Locthwain|ELD
+1 Castle Vantress|ELD
+1 Casualties of War|WAR
+1 Cauldron Familiar|ELD
+1 Cauldron's Gift|ELD
+1 Cavalcade of Calamity|RNA
+1 Cavalier of Dawn|M20
+1 Cavalier of Flame|M20
+1 Cavalier of Night|M20
+1 Cavalier of Thorns|M20
+1 Cavalry Drillmaster|M19
+1 Chainweb Aracnir|THB
+1 Chandra's Pyrohelix|WAR
+1 Chandra, Acolyte of Flame|M20
+1 Chandra, Awakened Inferno|M20
+1 Charging Monstrosaur|XLN
+1 Charming Prince|ELD
+1 Chart a Course|XLN
+1 Chemister's Insight|GRN
+1 Chromatic Lantern|GRN
+1 Clifftop Retreat|DOM
+1 Clockwork Servant|ELD
+1 Cloudkin Seer|M20
+1 Colossal Majesty|M19
+1 Command the Dreadhorde|WAR
+1 Commence the Endgame|WAR
+1 Conclave Tribunal|GRN
+1 Corpse Knight|M20
+1 Crash Through|AKR
+1 Crucible of Worlds|M19
+1 Cryptbreaker|EMN
+1 Cryptic Caves|M20
+1 Curious Obsession|RIX
+1 Dalakos, Crafter of Wonders|THB
+1 Dark-Dweller Oracle|M19
+1 Dauntless Bodyguard|DOM
+1 Daxos, Blessed by the Sun|THB
+1 Dead Weight|IKO
+1 Deeproot Champion|XLN
+1 Demonlord Belzenlok|DOM
+1 Depose // Deploy|RNA
+1 Despark|WAR
+1 Destiny Spinner|THB
+1 Didn't Say Please|ELD
+1 Dire Fleet Daredevil|RIX
+1 Diregraf Ghoul|M19
+1 Disdainful Stroke|GRN
+1 Disenchant|ZNR
+1 Disfigure|M20
+1 Dismal Backwater|M21
+1 Dive Down|XLN
+1 Divine Visitation|GRN
+1 Domri, Anarch of Bolas|WAR
+1 Doom Whisperer|GRN
+1 Dragonmaster Outcast|BFZ
+1 Dragonskull Summit|XLN
+1 Drakuseth, Maw of Flames|M20
+1 Dread Presence|M20
+1 Dreadhorde Butcher|WAR
+1 Dreadhorde Invasion|WAR
+1 Dream Trawler|THB
+1 Dreamstalker Manticore|THB
+1 Drill Bit|RNA
+1 Drowned Catacomb|XLN
+1 Dryad Greenseeker|M19
+1 Dryad of the Ilysian Grove|THB
+1 Dungeon Geists|M20
+1 Duress|M21
+1 Dusk Legion Zealot|A25
+1 Eat to Extinction|THB
+1 Electrodominance|RNA
+1 Elspeth Conquers Death|THB
+1 Elspeth's Nightmare|THB
+1 Elspeth, Sun's Nemesis|THB
+1 Elvish Reclaimer|M20
+1 Elvish Rejuvenator|M19
+1 Elvish Visionary|ORI
+1 Ember Hauler|M20
+1 Embercleave|ELD
+1 Embereth Shieldbreaker|ELD
+1 Embodiment of Agonies|M20
+1 Empyrean Eagle|M20
+1 Emry, Lurker of the Loch|ELD
+1 End-Raze Forerunners|RNA
+1 Enter the God-Eternals|WAR
+1 Entrancing Lyre|THB
+1 Entrancing Melody|XLN
+1 Erebos's Intervention|THB
+1 Erebos, Bleak-Hearted|THB
+1 Eternal Taskmaster|WAR
+1 Ethereal Absolution|RNA
+1 Evolution Sage|WAR
+1 Evolving Wilds|AKR
+1 Exclusion Mage|M19
+1 Expansion // Explosion|GRN
+1 Experimental Frenzy|GRN
+1 Fabled Passage|M21
+1 Fae of Wishes|ELD
+1 Faeburrow Elder|ELD
+1 Faerie Formation
+1 Faerie Guidemother|ELD
+1 Fanatical Firebrand|RIX
+1 Fauna Shaman|UMA
+1 Favorable Winds|XLN
+1 Fblthp, the Lost|WAR
+1 Field of Ruin|THB
+1 Field of the Dead|M20
+1 Fiery Cannonade|XLN
+1 Fight with Fire|DOM
+1 Finale of Devastation|WAR
+1 Finale of Eternity|WAR
+1 Finale of Glory|WAR
+1 Find // Finality|GRN
+1 Firemind Vessel|WAR
+1 Fires of Invention|ELD
+1 Flame Sweep|M20
+1 Flaxen Intruder|ELD
+1 Fling|ELD
+1 Flood of Tears|M20
+1 Folio of Fancies|ELD
+1 Foreboding Fruit|ELD
+1 Forgotten Cave|MH1
+1 Foulmire Knight|ELD
+1 Fountain of Renewal|M19
+1 Frilled Sandwalla|HOU
+1 Furious Rise|M21
+1 Gallia of the Endless Dance|THB
+1 Garruk, Cursed Huntsman|ELD
+1 Ghalta, Primal Hunger|RIX
+1 Ghitu Lavarunner|DOM
+1 Giant Growth|WAR
+1 Giant Killer|ELD
+1 Gideon Blackblade|WAR
+1 Gilded Goose|ELD
+1 Gilded Lotus|DOM
+1 Gingerbrute|ELD
+1 Glacial Fortress|XLN
+1 Glass Casket|ELD
+1 Gleaming Barrier|2XM
+1 Goblin Banneret|GRN
+1 Goblin Cratermaker|GRN
+1 Goblin Electromancer|GRN
+1 Goblin Instigator|M19
+1 Goblin Motivator|M19
+1 Goblin Ruinblaster|ZEN
+1 Goblin Trashmaster|M19
+1 God-Eternal Bontu|WAR
+1 God-Eternal Kefnet|WAR
+1 God-Eternal Oketra|WAR
+1 Godless Shrine|RNA
+1 Gods Willing|M20
+1 Golden Demise|RIX
+1 Golden Egg|ELD
+1 Golos, Tireless Pilgrim|M20
+1 Goreclaw, Terror of Qal Sisma|M19
+1 Graveyard Marshal|M19
+1 Gray Merchant of Asphodel|THB
+1 Grim Initiate|WAR
+1 Grow from the Ashes|DOM
+1 Growth Spiral|RNA
+1 Gruul Spellbreaker|RNA
+1 Guild Globe|WAR
+1 Gutterbones|RNA
+1 Guttersnipe|M19
+1 Hallowed Fountain|RNA
+1 Hanged Executioner|M20
+1 Harmonious Archon|ELD
+1 Heliod, Sun-Crowned|THB
+1 Helm of the Host|DOM
+1 Heraldic Banner|ELD
+1 Hinterland Harbor|DOM
+1 History of Benalia|DOM
+1 Hostage Taker|XLN
+1 Huatli, Warrior Poet|XLN
+1 Hunted Witness|GRN
+1 Hydroid Krasis|RNA
+1 Hypnotic Specter|M10
+1 Hypnotic Sprite|ELD
+1 Icy Manipulator|DOM
+1 Ilharg, the Raze-Boar|WAR
+1 Ilysian Caryatid|THB
+1 Imperial Aerosaur|XLN
+1 Imperious Perfect|EMA
+1 In Bolas's Clutches|DOM
+1 Incubation Druid|RNA
+1 Inevitable End|THB
+1 Into the Story|ELD
+1 Ionize|GRN
+1 Isareth the Awakener|M19
+1 Isolated Chapel|DOM
+1 Jadelight Ranger|RIX
+1 Jaya's Greeting|WAR
+1 Jaya's Immolating Inferno|DOM
+1 Jhoira, Weatherlight Captain|2XM
+1 Josu Vess, Lich Knight|DOM
+1 Judith, the Scourge Diva|RNA
+1 Juggernaut|DOM
+1 Jungle Hollow|M21
+1 Karn's Bastion|WAR
+1 Karn's Temporal Sundering|DOM
+1 Karn, Scion of Urza|DOM
+1 Keeper of Fables|ELD
+1 Kenrith's Transformation|ELD
+1 Kiln Fiend|IMA
+1 Kinjalli's Sunwing|XLN
+1 Kiora Bests the Sea God|THB
+1 Kiora, Behemoth Beckoner|WAR
+1 Kitesail Freebooter|M21
+1 Klothys, God of Destiny|THB
+1 Knight of Autumn|GRN
+1 Knight of Grace|DOM
+1 Knight of Malice|DOM
+1 Knight of the Ebon Legion|M20
+1 Kraul Harpooner|GRN
+1 Kronch Wrangler|WAR
+1 Kroxa, Titan of Death's Hunger|THB
+1 Kunoros, Hound of Athreos|THB
+1 Labyrinth of Skophos|THB
+1 Lava Coil|GRN
+1 Law-Rune Enforcer|WAR
+1 Lazav, the Multifarious|GRN
+1 Leafkin Druid|M20
+1 Legion Warboss|GRN
+1 Legion's Landing|XLN
+1 Leonin of the Lost Pride|THB
+1 Leonin Vanguard|M19
+1 Leonin Warleader|M19
+1 Light Up the Stage|RNA
+1 Lightning Strike|M19
+1 Liliana, Dreadhorde General|WAR
+1 Llanowar Elves|DOM
+1 Lonely Sandbar|MH1
+1 Lotleth Giant|GRN
+1 Lotus Field|M20
+1 Lovestruck Beast|ELD
+1 Loyal Pegasus|M20
+1 Lyra Dawnbringer|DOM
+1 Mace of the Valiant
+1 Manifold Key|M20
+1 Mantle of the Wolf|THB
+1 Massacre Girl|WAR
+1 Mastermind's Acquisition|RIX
+1 Medomai's Prophecy|THB
+1 Mentor of the Meek|M19
+1 Merfolk Secretkeeper|ELD
+1 Merfolk Trickster|DOM
+1 Midnight Clock|ELD
+1 Midnight Reaper|GRN
+1 Militia Bugler|M19
+1 Mind Stone|IMA
+1 Ministrant of Obligation|RNA
+1 Mire's Grasp|THB
+1 Mist-Cloaked Herald|RIX
+1 Mortify|RNA
+1 Mox Amber|DOM
+1 Murder|M20
+1 Murderous Rider|ELD
+1 Murmuring Mystic|GRN
+1 Nadir Kraken|THB
+1 Negate|ZNR
+1 Nessian Hornbeetle|THB
+1 Nessian Wanderer|THB
+1 Nightmare Shepherd|THB
+1 Nightmare's Thirst|M19
+1 Niv-Mizzet Reborn|WAR
+1 Nylea, Keen-Eyed|THB
+1 Nyx Lotus|THB
+1 Nyxbloom Ancient|THB
+1 Oathsworn Knight|ELD
+1 Omenspeaker|M19
+1 Once and Future|ELD
+1 Once Upon a Time|ELD
+1 Opt|M21
+1 Order of Midnight|ELD
+1 Orzhov Enforcer|RNA
+1 Outlaws' Merriment|ELD
+1 Overgrown Tomb|GRN
+1 Ox of Agonas|THB
+1 Pacifism|IKO
+1 Paradise Druid|WAR
+1 Patient Rebuilding|M19
+1 Pelt Collector|GRN
+1 Phoenix of Ash|THB
+1 Phyrexian Arena|CN2
+1 Piper of the Swarm|ELD
+1 Plaguecrafter|GRN
+1 Planar Cleansing|M20
+1 Planewide Celebration|WAR
+1 Platinum Angel|MPS_KLD
+1 Polukranos, Unchained|THB
+1 Portal of Sanctuary|M20
+1 Prey Upon|UMA
+1 Priest of Forgotten Gods|RNA
+1 Prime Speaker Vannifar|RNA
+1 Prison Realm|WAR
+1 Psychic Corrosion|M19
+1 Pteramander|RNA
+1 Purphoros's Intervention|THB
+1 Quench|RNA
+1 Questing Beast|ELD
+1 Rabid Bite|ZNR
+1 Ral, Izzet Viceroy|GRN
+1 Rampaging Ferocidon|XLN
+1 Rankle, Master of Pranks|ELD
+1 Realm-Cloaked Giant|ELD
+1 Reclamation Sage|2XM
+1 Rekindling Phoenix|RIX
+1 Relentless Pursuit|THB
+1 Relentless Raptor|RIX
+1 Remorseful Cleric|M19
+1 Resplendent Angel|M19
+1 Response // Resurgence|GRN
+1 Return to Nature|M21
+1 Revoke Existence|2XM
+1 Rhys the Redeemed|2XM
+1 Rigging Runner|XLN
+1 Rimrock Knight|ELD
+1 Risen Reef|M20
+1 Risk Factor|GRN
+1 Roalesk, Apex Hybrid|WAR
+1 Robber of the Rich|ELD
+1 Rootbound Crag|XLN
+1 Rotting Regisaur|M20
+1 Rugged Highlands|M21
+1 Ruin Raider|XLN
+1 Rupture Spire|M19
+1 Sacred Foundry|GRN
+1 Saheeli, Sublime Artificer|WAR
+1 Sai, Master Thopterist|M19
+1 Saproling Migration|DOM
+1 Sarkhan the Masterless|WAR
+1 Savage Stomp|XLN
+1 Savvy Hunter|ELD
+1 Scorch Spitter|M20
+1 Scorching Dragonfire|M21
+1 Scoured Barrens|M21
+1 Seal Away|DOM
+1 Search for Azcanta|XLN
+1 Season of Growth|M20
+1 Secluded Steppe|MH1
+1 Sentinel's Eyes|THB
+1 Sentinel's Mark|RNA
+1 Seraph of the Scales|RNA
+1 Setessan Champion|THB
+1 Settle the Wreckage|XLN
+1 Shadowspear|THB
+1 Shanna, Sisay's Legacy|DOM
+1 Shatter the Sky|THB
+1 Shepherd of the Flock|ELD
+1 Shivan Fire|DOM
+1 Shock|M21
+1 Siege-Gang Commander|DOM
+1 Sigil of the Empty Throne|ORI
+1 Sigiled Sword of Valeron|M19
+1 Silverbeak Griffin
+1 Sinister Sabotage|GRN
+1 Siren Stormtamer|XLN
+1 Skarrgan Hellkite|RNA
+1 Skewer the Critics|RNA
+1 Skilled Animator|M19
+1 Sky Terror|XLN
+1 Skymarcher Aspirant|RIX
+1 Slaying Fire|ELD
+1 Soul Warden|MM3
+1 Spark Double|WAR
+1 Spark Harvest|WAR
+1 Sparring Construct|DOM
+1 Spawn of Mayhem|RNA
+1 Spectral Sailor|M20
+1 Spell Pierce|XLN
+1 Sprouting Renewal|GRN
+1 Squee, the Immortal|DOM
+1 Staggering Insight|THB
+1 Starfield Mystic|M20
+1 Starlit Mantle|THB
+1 Steam Vents|GRN
+1 Steel Overseer|M20
+1 Stolen by the Fae|ELD
+1 Stomping Ground|RNA
+1 Stonecoil Serpent|ELD
+1 Storm Fleet Aerialist|XLN
+1 Storm's Wrath|THB
+1 Stormfist Crusader|ELD
+1 Sulfur Falls|DOM
+1 Summary Judgment|RNA
+1 Sunhome Stalwart|GRN
+1 Sunpetal Grove|XLN
+1 Swiftwater Cliffs|M21
+1 Sword-Point Diplomacy|XLN
+1 Syr Faren, the Hengehammer|ELD
+1 Tajic, Legion's Edge|GRN
+1 Talrand, Sky Summoner|M13
+1 Taranika, Akroan Veteran|THB
+1 Taste of Death
+1 Tectonic Giant|THB
+1 Teferi, Hero of Dominaria|DOM
+1 Temple Garden|GRN
+1 Temple of Abandon|THB
+1 Temple of Deceit|THB
+1 Temple of Enlightenment|THB
+1 Temple of Epiphany|M21
+1 Temple of Malady|M21
+1 Temple of Malice|THB
+1 Temple of Mystery|M21
+1 Temple of Plenty|THB
+1 Temple of Silence|M21
+1 Temple of Triumph|M21
+1 Tendershoot Dryad|RIX
+1 Tetsuko Umezawa, Fugitive|DOM
+1 Tezzeret, Artifice Master|M19
+1 Thalia, Guardian of Thraben|A25
+1 Thassa's Intervention|THB
+1 Thassa's Oracle|THB
+1 Thassa, Deep-Dwelling|THB
+1 The Akroan War|THB
+1 The Birth of Meletis|THB
+1 The Circle of Loyalty|ELD
+1 The Eldest Reborn|DOM
+1 The First Iroan Games|THB
+1 The Great Henge|ELD
+1 The Immortal Sun|RIX
+1 The Mending of Dominaria|DOM
+1 The Mirari Conjecture|DOM
+1 Theater of Horrors|RNA
+1 Thirst for Meaning|THB
+1 Thorn Lieutenant|M19
+1 Thorn Mammoth
+1 Thornwood Falls|M21
+1 Thought Erasure|GRN
+1 Thrash // Threat|RNA
+1 Thrashing Brontodon|M21
+1 Threnody Singer|THB
+1 Thryx, the Sudden Storm|THB
+1 Tibalt, Rakish Instigator|WAR
+1 Time Wipe|WAR
+1 Tin Street Dodger|RNA
+1 Tolsimir, Friend to Wolves|WAR
+1 Tomik, Distinguished Advokist|WAR
+1 Tranquil Cove|M21
+1 Tranquil Thicket|MH1
+1 Trapped in the Tower|ELD
+1 Traveler's Amulet|THB
+1 Traxos, Scourge of Kroog|DOM
+1 Treasure Map|XLN
+1 Tymaret, Chosen from Death|THB
+1 Ugin, the Ineffable|WAR
+1 Unbreakable Formation|RNA
+1 Underworld Rage-Hound|THB
+1 Unsummon|M20
+1 Untamed Kavu|DOM
+1 Uro, Titan of Nature's Wrath|THB
+1 Vantress Gargoyle|ELD
+1 Venerable Knight|ELD
+1 Venerated Loxodon|GRN
+1 Verix Bladewing|DOM
+1 Viashino Pyromancer|M19
+1 Vivien's Arkbow|WAR
+1 Vivien, Arkbow Ranger|M20
+1 Voltaic Servant|DOM
+1 Voracious Hydra|M20
+1 Vraska, Golgari Queen|GRN
+1 Warbriar Blessing|THB
+1 Warkite Marauder|RIX
+1 Warlord's Fury|DOM
+1 Watery Grave|GRN
+1 Wavebreak Hippocamp|THB
+1 Wayward Swordtooth|RIX
+1 Weaselback Redcap|ELD
+1 Weatherlight|DOM
+1 Wilderness Reclamation|RNA
+1 Wildwood Tracker|ELD
+1 Wind-Scarred Crag|M21
+1 Winged Words|M20
+1 Witch's Oven|ELD
+1 Witch's Vengeance|ELD
+1 Witching Well|ELD
+1 Woe Strider|THB
+1 Wolfwillow Haven|THB
+1 Woodland Cemetery|DOM
+1 Woodland Champion|2XM
+1 Yawgmoth's Vile Offering|DOM
+1 Zetalpa, Primal Dawn|RIX
+1 Zhalfirin Void|DOM
+1 Zhur-Taa Goblin|RNA
diff --git a/forge-gui/res/cube/MTGO Vintage Cube April 2020.dck b/forge-gui/res/cube/MTGO Vintage Cube April 2020.dck
new file mode 100644
index 00000000000..303fe079b48
--- /dev/null
+++ b/forge-gui/res/cube/MTGO Vintage Cube April 2020.dck
@@ -0,0 +1,543 @@
+[metadata]
+Name=MTGO Vintage Cube April 2020
+[main]
+1 Kytheon, Hero of Akros|ORI
+1 Mother of Runes|EMA
+1 Student of Warfare|ROE
+1 Adanto Vanguard|XLN
+1 Containment Priest|M21
+1 Leonin Relic-Warder|C17
+1 Porcelain Legionnaire|NPH
+1 Selfless Spirit|EMN
+1 Soulfire Grand Master|FRF
+1 Stoneforge Mystic|2XM
+1 Thalia, Guardian of Thraben|A25
+1 Tithe Taker|RNA
+1 Wall of Omens|EMA
+1 Blade Splicer|2XM
+1 Brightling
+1 Brimaz, King of Oreskos|BNG
+1 Fairgrounds Warden|KLD
+1 Flickerwisp|2XM
+1 Monastery Mentor|FRF
+1 Recruiter of the Guard|CN2
+1 Silverblade Paladin|AVR
+1 Emeria Angel|IMA
+1 Hero of Bladehold|MBS
+1 Linvala, Keeper of Silence|MM3
+1 Restoration Angel|IMA
+1 Angel of Invention|KLD
+1 Elspeth Conquers Death|THB
+1 Archangel Avacyn|SOI
+1 Baneslayer Angel|M21
+1 Lyra Dawnbringer|DOM
+1 Reveillark|UMA
+1 Sun Titan|M12
+1 Angel of Serenity|RTR
+1 Elesh Norn, Grand Cenobite|IMA
+1 Iona, Shield of Emeria|MM2
+1 Gideon Blackblade|WAR
+1 Elspeth, Sun's Nemesis|THB
+1 Gideon, Ally of Zendikar|BFZ
+1 Gideon Jura|M12
+1 Elspeth, Sun's Champion|THS
+1 Condemn|M11
+1 Enlightened Tutor|EMA
+1 Mana Tithe|PLC
+1 Path to Exile|2XM
+1 Swords to Plowshares|A25
+1 Disenchant|ZNR
+1 Unexpectedly Absent|EMA
+1 Balance|EMA
+1 Council's Judgment|2XM
+1 Spectral Procession|MM2
+1 Armageddon|A25
+1 Day of Judgment|M12
+1 Ravages of War|PTK
+1 Wrath of God|AKR
+1 Terminus|MM3
+1 Land Tax|2XM
+1 Legion's Landing|XLN
+1 Honor of the Pure|M12
+1 Banishing Light|THB
+1 Oblivion Ring|MM2
+1 Faith's Fetters|M21
+1 Moat|LEG
+1 Parallax Wave|VMA
+1 Spear of Heliod|THS
+1 Karakas|UMA
+1 Thassa's Oracle|THB
+1 Baral, Chief of Compliance|AER
+1 Jace, Vryn's Prodigy|ORI
+1 Looter il-Kor|TSP
+1 Phantasmal Image|MM3
+1 Snapcaster Mage|UMA
+1 Thing in the Ice|SOI
+1 Arcane Artisan
+1 Deceiver Exarch|CN2
+1 Pestermite|MMA
+1 Spellseeker
+1 Trinket Mage|SOM
+1 Vendilion Clique|A25
+1 Glen Elendra Archmage|UMA
+1 Phyrexian Metamorph|2XM
+1 Sower of Temptation|LRW
+1 Venser, Shaper Savant|MM3
+1 Mulldrifter|MM2
+1 Riftwing Cloudskate|MMA
+1 Consecrated Sphinx|IMA
+1 Frost Titan|M12
+1 Torrential Gearhulk|MPS_KLD
+1 Palinchron|VMA
+1 Inkwell Leviathan|2XM
+1 Jace Beleren|M11
+1 Jace, the Mind Sculptor|VMA
+1 Tezzeret the Seeker|MM2
+1 Ancestral Recall|VMA
+1 Brainstorm|2XM
+1 High Tide|VMA
+1 Mystical Tutor|EMA
+1 Spell Pierce|XLN
+1 Brain Freeze|VMA
+1 Counterspell|A25
+1 Daze|MPS_AKH
+1 Impulse|VIS
+1 Mana Drain|IMA
+1 Mana Leak|IMA
+1 Miscalculation|ULG
+1 Remand|MM2
+1 Frantic Search|UMA
+1 Thirst for Knowledge|2XM
+1 Cryptic Command|IMA
+1 Fact or Fiction|MH1
+1 Gifts Ungiven|MM3
+1 Turnabout|VMA
+1 Force of Will|2XM
+1 Gush|VMA
+1 Mystic Confluence|C15
+1 Repeal|IMA
+1 Dig Through Time|UMA
+1 Ancestral Vision|IMA
+1 Gitaxian Probe|NPH
+1 Ponder|M12
+1 Preordain|M11
+1 Chart a Course|XLN
+1 Time Walk|VMA
+1 Show and Tell|CN2
+1 Timetwister|VMA
+1 Tinker|ULG
+1 Bribery|8ED
+1 Time Warp|TPR
+1 Mind's Desire|SCG
+1 Time Spiral|USG
+1 Upheaval|VMA
+1 Treasure Cruise|UMA
+1 Search for Azcanta|XLN
+1 Control Magic|EMA
+1 Opposition|MPS_AKH
+1 Treachery|UDS
+1 Shelldock Isle|LRW
+1 Tolarian Academy|VMA
+1 Putrid Imp|VMA
+1 Dark Confidant|2XM
+1 Kitesail Freebooter|M21
+1 Mesmeric Fiend|A25
+1 Oona's Prowler|LRW
+1 Pack Rat|RTR
+1 Vampire Hexmage|2XM
+1 Bone Shredder|ULG
+1 Hypnotic Specter|M10
+1 Ophiomancer|C13
+1 Plaguecrafter|GRN
+1 Vampire Nighthawk|MM3
+1 Gonti, Lord of Luxury|KLD
+1 Nekrataal|EMA
+1 Ravenous Chupacabra|A25
+1 Shriekmaw|UMA
+1 Grave Titan|M12
+1 Ink-Eyes, Servant of Oni|PCA
+1 Massacre Wurm|M21
+1 Tasigur, the Golden Fang|FRF
+1 Sheoldred, Whispering One|IMA
+1 Griselbrand|MM3
+1 Liliana of the Veil|UMA
+1 Liliana, Death's Majesty|AKR
+1 Dark Ritual|A25
+1 Entomb|UMA
+1 Fatal Push|2XM
+1 Vampiric Tutor|EMA
+1 Cabal Ritual|VMA
+1 Go for the Throat|MBS
+1 Liliana's Triumph|WAR
+1 Shallow Grave|MIR
+1 Ultimate Price|DTK
+1 Corpse Dance|TPR
+1 Dismember|MM2
+1 Hero's Downfall|THS
+1 Makeshift Mannequin|LRW
+1 Duress|M21
+1 Imperial Seal|PTK
+1 Inquisition of Kozilek|MM3
+1 Reanimate|UMA
+1 Thoughtseize|AKR
+1 Collective Brutality|EMN
+1 Demonic Tutor|UMA
+1 Exhume|USG
+1 Hymn to Tourach|EMA
+1 Night's Whisper|EMA
+1 Buried Alive|UMA
+1 Toxic Deluge|2XM
+1 Yawgmoth's Will|USG
+1 Damnation|MPS_AKH
+1 Languish|ORI
+1 Mastermind's Acquisition|RIX
+1 Tendrils of Agony|VMA
+1 Dark Petition|ORI
+1 Living Death|A25
+1 Mind Twist|MPS_AKH
+1 Animate Dead|EMA
+1 Bitterblossom|UMA
+1 Necromancy|VIS
+1 Phyrexian Arena|CN2
+1 Recurring Nightmare|TPR
+1 Yawgmoth's Bargain|UDS
+1 Goblin Guide|2XM
+1 Goblin Welder|ULG
+1 Grim Lavamancer|2XM
+1 Jackal Pup|A25
+1 Monastery Swiftspear|IMA
+1 Zurgo Bellstriker|DTK
+1 Abbot of Keral Keep|ORI
+1 Dire Fleet Daredevil|RIX
+1 Eidolon of the Great Revel|A25
+1 Runaway Steam-Kin|GRN
+1 Young Pyromancer|UMA
+1 Goblin Rabblemaster|M15
+1 Imperial Recruiter|2XM
+1 Magus of the Moon|IMA
+1 Avalanche Riders|TSB
+1 Flametongue Kavu|VMA
+1 Hazoret the Fervent|AKR
+1 Hellrider|MM3
+1 Pia and Kiran Nalaar|ORI
+1 Rekindling Phoenix|RIX
+1 Glorybringer|AKR
+1 Goblin Dark-Dwellers|OGW
+1 Kiki-Jiki, Mirror Breaker|IMA
+1 Siege-Gang Commander|DOM
+1 Thundermaw Hellkite|IMA
+1 Zealous Conscripts|MM3
+1 Inferno Titan|M12
+1 Chandra, Torch of Defiance|KLD
+1 Daretti, Scrap Savant|C16
+1 Koth of the Hammer|SOM
+1 Burst Lightning|MM2
+1 Lightning Bolt|A25
+1 Abrade|AKR
+1 Ancient Grudge|MM3
+1 Desperate Ritual|UMA
+1 Fire // Ice|UMA
+1 Incinerate|M12
+1 Lightning Strike|M19
+1 Pyretic Ritual|M11
+1 Char|RAV
+1 Seething Song|9ED
+1 Through the Breach|UMA
+1 Fireblast|VMA
+1 Chain Lightning|MPS_AKH
+1 Faithless Looting|UMA
+1 Firebolt|MH1
+1 Flame Slash|CN2
+1 Mizzium Mortars|MM3
+1 Pyroclasm|A25
+1 Light Up the Stage|RNA
+1 Underworld Breach|THB
+1 Wheel of Fortune|VMA
+1 Empty the Warrens|MMA
+1 Fiery Confluence|C15
+1 Past in Flames|MM3
+1 Banefire|M19
+1 Burning of Xinye|VMA
+1 Wildfire|MM2
+1 Bonfire of the Damned|MM3
+1 Mana Flare|5ED
+1 Sulfuric Vortex|EMA
+1 Sneak Attack|2XM
+1 Splinter Twin|MM2
+1 Arbor Elf|A25
+1 Avacyn's Pilgrim|MM3
+1 Birds of Paradise|CN2
+1 Elves of Deep Shadow|RAV
+1 Elvish Mystic|M15
+1 Fyndhorn Elves|VMA
+1 Joraga Treespeaker|ROE
+1 Llanowar Elves|DOM
+1 Noble Hierarch|2XM
+1 Den Protector|DTK
+1 Devoted Druid|UMA
+1 Fauna Shaman|UMA
+1 Gilded Goose|ELD
+1 Lotus Cobra|ZNR
+1 Rofellos, Llanowar Emissary|VMA
+1 Sakura-Tribe Elder|CNS
+1 Scavenging Ooze|M21
+1 Sylvan Caryatid|THS
+1 Wall of Blossoms|MH1
+1 Wall of Roots|IMA
+1 Courser of Kruphix|A25
+1 Eternal Witness|UMA
+1 Ramunap Excavator|AKR
+1 Reclamation Sage|2XM
+1 Tireless Tracker|SOI
+1 Yavimaya Elder|VMA
+1 Master of the Wild Hunt|A25
+1 Oracle of Mul Daya|ZEN
+1 Polukranos, World Eater|THS
+1 Acidic Slime|M13
+1 Biogenic Ooze|RNA
+1 Deranged Hermit|VMA
+1 Thragtusk|2XM
+1 Whisperwood Elemental|FRF
+1 Carnage Tyrant|XLN
+1 Primeval Titan|IMA
+1 Avenger of Zendikar|2XM
+1 Craterhoof Behemoth|MM3
+1 Terastodon|2XM
+1 Woodfall Primus|UMA
+1 Dryad of the Ilysian Grove|THB
+1 Garruk Relentless|ISD
+1 Garruk Wildspeaker|M11
+1 Garruk, Primal Hunter|M13
+1 Vivien Reid|M19
+1 Nature's Claim|IMA
+1 Beast Within|CN2
+1 Channel|IMA
+1 Regrowth|MH1
+1 Kodama's Reach|UMA
+1 Search for Tomorrow|IMA
+1 Eureka|VMA
+1 Harmonize|MM3
+1 Natural Order|EMA
+1 Plow Under|8ED
+1 Primal Command|MM3
+1 Green Sun's Zenith|EMA
+1 Finale of Devastation|WAR
+1 Tooth and Nail|MMA
+1 Fastbond|VMA
+1 Oath of Druids|TPR
+1 Survival of the Fittest|TPR
+1 Sylvan Library|EMA
+1 Heartbeat of Spring|2XM
+1 Wilderness Reclamation|RNA
+1 Gaea's Cradle|USG
+1 Geist of Saint Traft|2XM
+1 Teferi, Hero of Dominaria|DOM
+1 Sphinx's Revelation|AKR
+1 Fractured Identity|C17
+1 Celestial Colonnade|UMA
+1 Flooded Strand|EXP
+1 Hallowed Fountain|RNA
+1 Seachrome Coast|SOM
+1 Tundra|VMA
+1 Thief of Sanity|GRN
+1 The Scarab God|AKR
+1 Ashiok, Nightmare Weaver|THS
+1 Baleful Strix|2XM
+1 Creeping Tar Pit|UMA
+1 Darkslick Shores|SOM
+1 Polluted Delta|EXP
+1 Underground Sea|VMA
+1 Watery Grave|GRN
+1 Daretti, Ingenious Iconoclast|CN2
+1 Kroxa, Titan of Death's Hunger|THB
+1 Kolaghan's Command|DTK
+1 Rakdos's Return|RTR
+1 Badlands|VMA
+1 Blackcleave Cliffs|SOM
+1 Blood Crypt|RNA
+1 Bloodstained Mire|EXP
+1 Lavaclaw Reaches|UMA
+1 Bloodbraid Elf|EMA
+1 Huntmaster of the Fells|DKA
+1 Dragonlord Atarka|DTK
+1 Manamorphose|2XM
+1 Copperline Gorge|SOM
+1 Raging Ravine|UMA
+1 Stomping Ground|RNA
+1 Taiga|VMA
+1 Wooded Foothills|EXP
+1 Kitchen Finks|UMA
+1 Knight of Autumn|GRN
+1 Knight of the Reliquary|IMA
+1 Trostani Discordant|GRN
+1 Mirari's Wake|CNS
+1 Razorverge Thicket|SOM
+1 Savannah|VMA
+1 Stirring Wildwood|UMA
+1 Temple Garden|GRN
+1 Windswept Heath|EXP
+1 Ashen Rider|THS
+1 Kaya, Orzhov Usurper|RNA
+1 Tidehollow Sculler|MMA
+1 Anguished Unmaking|SOI
+1 Lingering Souls|MM3
+1 Vindicate|A25
+1 Unburial Rites|UMA
+1 Concealed Courtyard|KLD
+1 Godless Shrine|RNA
+1 Marsh Flats|MM3
+1 Scrubland|VMA
+1 Shambling Vent|BFZ
+1 Vraska, Golgari Queen|GRN
+1 Assassin's Trophy|GRN
+1 Maelstrom Pulse|2XM
+1 Pernicious Deed|A25
+1 Bayou|VMA
+1 Blooming Marsh|KLD
+1 Hissing Quagmire|OGW
+1 Overgrown Tomb|GRN
+1 Verdant Catacombs|MM3
+1 Edric, Spymaster of Trest|VMA
+1 Trygon Predator|EMA
+1 Hydroid Krasis|RNA
+1 Uro, Titan of Nature's Wrath|THB
+1 Botanical Sanctum|KLD
+1 Breeding Pool|RNA
+1 Lumbering Falls|BFZ
+1 Misty Rainforest|MM3
+1 Tropical Island|VMA
+1 Goblin Electromancer|GRN
+1 Dack Fayden|EMA
+1 Thousand-Year Storm|GRN
+1 Scalding Tarn|MM3
+1 Spirebluff Canal|KLD
+1 Steam Vents|GRN
+1 Volcanic Island|VMA
+1 Wandering Fumarole|OGW
+1 Figure of Destiny|MMA
+1 Ajani Vengeant|ALA
+1 Nahiri, the Harbinger|SOI
+1 Wear // Tear|DGM
+1 Lightning Helix|IMA
+1 Arid Mesa|MM3
+1 Inspiring Vantage|KLD
+1 Needle Spires|OGW
+1 Plateau|VMA
+1 Sacred Foundry|GRN
+1 Sphinx of the Steel Wind|EMA
+1 Nicol Bolas, Dragon-God|WAR
+1 Leovold, Emissary of Trest|UMA
+1 Progenitus|MMA
+1 Kozilek, Butcher of Truth|UMA
+1 Ulamog, the Ceaseless Hunger|BFZ
+1 Ulamog, the Infinite Gyre|UMA
+1 Emrakul, the Promised End|EMN
+1 Emrakul, the Aeons Torn|UMA
+1 Karn, Scion of Urza|DOM
+1 Karn Liberated|2XM
+1 Ugin, the Spirit Dragon|FRF
+1 Bomat Courier|KLD
+1 Hangarback Walker|MPS_KLD
+1 Phyrexian Revoker|2XM
+1 Metalworker|UDS
+1 Lodestone Golem|MM2
+1 Solemn Simulacrum|M21
+1 Kuldotha Forgemaster|2XM
+1 Wurmcoil Engine|2XM
+1 Myr Battlesphere|2XM
+1 Sundering Titan|2XM
+1 Walking Ballista|2XM
+1 Blightsteel Colossus|2XM
+1 Black Lotus|VMA
+1 Chrome Mox|2XM
+1 Everflowing Chalice|2XM
+1 Lion's Eye Diamond|MIR
+1 Lotus Bloom|MMA
+1 Mana Crypt|2XM
+1 Mox Diamond|TPR
+1 Mox Emerald|VMA
+1 Mox Jet|VMA
+1 Mox Pearl|VMA
+1 Mox Ruby|VMA
+1 Mox Sapphire|VMA
+1 Mana Vault|UMA
+1 Relic of Progenitus|EMA
+1 Sensei's Divining Top|EMA
+1 Skullclamp|VMA
+1 Sol Ring|MPS_KLD
+1 Azorius Signet|MM3
+1 Boros Signet|MM3
+1 Dimir Signet|MM3
+1 Golgari Signet|MM3
+1 Grim Monolith|ULG
+1 Gruul Signet|MM3
+1 Izzet Signet|MM3
+1 Lightning Greaves|2XM
+1 Orzhov Signet|MM3
+1 Rakdos Signet|MM3
+1 Selesnya Signet|MM3
+1 Shrine of Burning Rage|NPH
+1 Simic Signet|MM3
+1 Smuggler's Copter|KLD
+1 Umezawa's Jitte|BOK
+1 Winter Orb|EMA
+1 Basalt Monolith|2XM
+1 Coalition Relic|A25
+1 Crucible of Worlds|M19
+1 Oblivion Stone|2XM
+1 Sword of Body and Mind|2XM
+1 Sword of Feast and Famine|2XM
+1 Sword of Fire and Ice|2XM
+1 Sword of Light and Shadow|2XM
+1 Sword of War and Peace|2XM
+1 Tangle Wire|NMS
+1 Worn Powerstone|EMA
+1 Coercive Portal|VMA
+1 Smokestack|VMA
+1 Thran Dynamo|IMA
+1 Batterskull|2XM
+1 Memory Jar|VMA
+1 Mindslaver|SOM
+1 Academy Ruins|2XM
+1 Ancient Tomb|UMA
+1 Bazaar of Baghdad|VMA
+1 Blast Zone|WAR
+1 Field of Ruin|THB
+1 Library of Alexandria|VMA
+1 Maze of Ith|2XM
+1 Mishra's Factory|2XM
+1 Mishra's Workshop|ATQ
+1 Mutavault|M14
+1 Nykthos, Shrine to Nyx|THS
+1 Rishadan Port|A25
+1 Strip Mine|EXP
+1 Wasteland|EMA
+1 Expansion // Explosion|GRN
+1 Giver of Runes|MH1
+1 Winds of Abandon|MH1
+1 Hallowed Spiritkeeper|C14
+1 Thraben Inspector|2XM
+1 Narset, Parter of Veils|WAR
+1 Force of Negation|MH1
+1 Urza, Lord High Artificer|MH1
+1 Emry, Lurker of the Loch|ELD
+1 Brazen Borrower|ELD
+1 Bolas's Citadel|WAR
+1 Yawgmoth, Thran Physician|MH1
+1 Rotting Regisaur|M20
+1 Murderous Rider|ELD
+1 Wishclaw Talisman|ELD
+1 Dreadhorde Arcanist|WAR
+1 Seasoned Pyromancer|MH1
+1 Embereth Shieldbreaker|ELD
+1 Nissa, Who Shakes the World|WAR
+1 Questing Beast|ELD
+1 Teferi, Time Raveler|WAR
+1 Angrath's Rampage|WAR
+1 Fallen Shinobi|MH1
+1 Wrenn and Six|MH1
+1 Oko, Thief of Crowns|ELD
+1 Garruk, Cursed Huntsman|ELD
+1 Prismatic Vista|MH1
+1 Golos, Tireless Pilgrim|M20
+1 Stonecoil Serpent|ELD
diff --git a/forge-gui/res/draft/MTGA Cube 2020 April.draft b/forge-gui/res/draft/MTGA Cube 2020 April.draft
new file mode 100644
index 00000000000..2cdf4f5c10e
--- /dev/null
+++ b/forge-gui/res/draft/MTGA Cube 2020 April.draft
@@ -0,0 +1,6 @@
+Name:MTGA Cube 2020 April
+DeckFile:MTGA Cube 2020 April
+Singleton:True
+
+Booster: 15 Any
+NumPacks: 3
diff --git a/forge-gui/res/draft/MTGO Vintage Cube April 2020.draft b/forge-gui/res/draft/MTGO Vintage Cube April 2020.draft
new file mode 100644
index 00000000000..bcfed364059
--- /dev/null
+++ b/forge-gui/res/draft/MTGO Vintage Cube April 2020.draft
@@ -0,0 +1,6 @@
+Name:MTGO Vintage Cube April 2020
+DeckFile:MTGO Vintage Cube April 2020
+Singleton:True
+
+Booster: 15 Any
+NumPacks: 3
diff --git a/forge-gui/res/puzzle/PS_ZNR1.pzl b/forge-gui/res/puzzle/PS_ZNR1.pzl
new file mode 100644
index 00000000000..85b1606cd86
--- /dev/null
+++ b/forge-gui/res/puzzle/PS_ZNR1.pzl
@@ -0,0 +1,16 @@
+[metadata]
+Name:Possibility Storm - Zendikar Rising #01
+URL:https://i2.wp.com/www.possibilitystorm.com/wp-content/uploads/2020/09/157.-ZNR1-scaled.jpg
+Goal:Win
+Turns:1
+Difficulty:Mythic
+Description:Win this turn. Remember that your solution must satisfy all blocking scenarios.
+[state]
+humanlife=15
+ailife=11
+turn=1
+activeplayer=human
+activephase=MAIN1
+humanhand=Dire Tactics;Malakir Blood-Priest;Shadowspear;Light of Hope
+humanbattlefield=Angel of Destiny;Scourge of the Skyclaves;Thundering Chariot;Bastion of Remembrance;Plains;Plains;Plains;Swamp;Swamp;Swamp
+aibattlefield=Loch Dragon;Expedition Diviner;Loch Dragon