Implemented Commander variant of Adventure Mode (#8970)

* Added UI elements for commander mode in NewGame screen.
- still missing random pool of commander candidates sharing a color with each selected color identity.

* Added rudimentary commander choice with preview image.
For now, it is 5 suggestions per color identity with the requirement that the chosen color is part of the commander's identity.

* Commander card is now added to the player inventory and cannot be sold.
The save game adds a flag whether the game is in commander mode, which will be important for the future changes.

* Commander is now auto-added to the initial deck. Commander cannot be removed. Commander is properly saved and loaded.

* Duels now feature the command zone and all rules

* The chosen commander is now set to every deck if the game mode is commander.

* Only cards can be added to the deck that fulfill the color identity.

* heuristically add a pile of singleton cards to function as the starter for the commander's journey.

* Fix land color identity bug in commander pile

* Items are put on autosell if they do not match commander identity.

* Autosell is now properly set with the card flip timing rules. When visiting a shop, the deck conformaty message does not show anymore.

* Remove strict necessity for a fixed commander. This way, a player may choose to switch to a different legendary creature they find.

* 15 Streamlined commander choices without having to swap colors individually.

* Clean up to remove now redundant i18n label. More focus on creatures for starter commander decks.

* Refactor adventure commander deck generation into the DeckgenUtil class

* Refactorings as suggested by @Jetz72.

* Remove custom deck generator

* handle DeckFormat logic through AdventureDeckEditor.
Fix bugs, where partner commanders would be overwritten when using the stock DeckgenUtil.

* Remove random new line

* Code cleanup

* Fix unused import error

* Fix unused import error

* Revert "organise import" changes and random space linebreak changes.

* Removed another import package.* statement

* Implement fixed commander deck to start with. Removed UI edits and random commander choice.

* Add commander-specific cards and a two white overrun effects

* Revert to prior string formatting.

* Remove import *

* Formatting nitpicking to change as little original code as possible.

---------

Co-authored-by: Agetian <stavdev@mail.ru>
This commit is contained in:
Janek Gröhl
2025-11-01 17:56:55 +01:00
committed by GitHub
parent 54b8094211
commit ab4c91dd2d
15 changed files with 566 additions and 21 deletions

View File

@@ -25,5 +25,6 @@ public class DifficultyData {
public ObjectMap<String,String> starterDecks = null;
public ObjectMap<String,String> constructedStarterDecks= null;
public ObjectMap<String,String> pileDecks= null;
public ObjectMap<String,String> commanderDecks = null;
}

View File

@@ -49,10 +49,13 @@ public class AdventurePlayer implements Serializable, SaveFileContent {
// Deck data
private Deck deck;
private final ArrayList<Deck> decks = new ArrayList<Deck>(MIN_DECK_COUNT);
private final ArrayList<Deck> decks = new ArrayList<>(MIN_DECK_COUNT);
private int selectedDeckIndex = 0;
private final DifficultyData difficultyData = new DifficultyData();
// Commander mode
private AdventureModes adventureMode;
// Game data.
private float worldPosX;
private float worldPosY;
@@ -114,6 +117,7 @@ public class AdventurePlayer implements Serializable, SaveFileContent {
fantasyMode = false;
announceFantasy = false;
usingCustomDeck = false;
adventureMode = null;
blessing = null;
gold = 0;
maxLife = 20;
@@ -147,11 +151,14 @@ public class AdventurePlayer implements Serializable, SaveFileContent {
public final ItemPool<PaperCard> autoSellCards = new ItemPool<>(PaperCard.class);
public final Set<PaperCard> favoriteCards = new HashSet<>();
public void create(String n, Deck startingDeck, boolean male, int race, int avatar, boolean isFantasy, boolean isUsingCustomDeck, DifficultyData difficultyData) {
public void create(String n, Deck startingDeck, boolean male, int race, int avatar, boolean isFantasy,
boolean isUsingCustomDeck, DifficultyData difficultyData, AdventureModes adventureMode) {
clear();
this.adventureMode = adventureMode;
announceFantasy = fantasyMode = isFantasy; //Set Chaos mode first.
announceCustom = usingCustomDeck = isUsingCustomDeck;
clearDecks(); // Reset the empty decks to now already have the commander in the command zone.
deck = startingDeck;
decks.set(0, deck);
@@ -288,6 +295,10 @@ public class AdventurePlayer implements Serializable, SaveFileContent {
return life;
}
public AdventureModes getAdventureMode(){
return adventureMode;
}
public int getMaxLife() {
return maxLife;
}
@@ -334,7 +345,6 @@ public class AdventurePlayer implements Serializable, SaveFileContent {
this.colorIdentity = set;
}
@Override
public void load(SaveFileData data) {
boolean migration = false;
@@ -391,6 +401,13 @@ public class AdventurePlayer implements Serializable, SaveFileContent {
heroRace = data.readInt("heroRace");
avatarIndex = data.readInt("avatarIndex");
isFemale = data.readBool("isFemale");
String _mode = data.readString("adventure_mode");
if (_mode == null)
adventureMode = AdventureModes.Standard;
else
adventureMode = AdventureModes.valueOf(_mode);
if (data.containsKey("colorIdentity")) {
String temp = data.readString("colorIdentity");
if (temp != null)
@@ -505,6 +522,11 @@ public class AdventurePlayer implements Serializable, SaveFileContent {
deck.getOrCreate(DeckSection.Contraptions).addAll(contraptionDeckCards.getFilteredPool(isValid));
unsupportedCards.addAll(contraptionDeckCards.getFilteredPool(isUnsupported).toFlatList());
}
if (data.containsKey("commanderCards")) {
CardPool commanderCards = CardPool.fromCardList(List.of((String[]) data.readObject("commanderCards")));
deck.getOrCreate(DeckSection.Commander).addAll(commanderCards.getFilteredPool(isValid));
unsupportedCards.addAll(commanderCards.getFilteredPool(isUnsupported).toFlatList());
}
if (data.containsKey("characterFlagsKey") && data.containsKey("characterFlagsValue")) {
String[] keys = (String[]) data.readObject("characterFlagsKey");
Byte[] values = (Byte[]) data.readObject("characterFlagsValue");
@@ -573,6 +595,11 @@ public class AdventurePlayer implements Serializable, SaveFileContent {
decks.get(i).getOrCreate(DeckSection.Contraptions).addAll(contraptionCards.getFilteredPool(isValid));
unsupportedCards.addAll(contraptionCards.getFilteredPool(isUnsupported).toFlatList());
}
if (data.containsKey("commanderCards_" + i)) {
CardPool commanderCards = CardPool.fromCardList(List.of((String[]) data.readObject("commanderCards_" + i)));
decks.get(i).getOrCreate(DeckSection.Commander).addAll(commanderCards.getFilteredPool(isValid));
unsupportedCards.addAll(commanderCards.getFilteredPool(isUnsupported).toFlatList());
}
}
// In case we allow removing decks from the deck selection GUI, populate up to the minimum
for (int i = dynamicDeckCount++; i < MIN_DECK_COUNT; i++) {
@@ -595,6 +622,11 @@ public class AdventurePlayer implements Serializable, SaveFileContent {
decks.get(i).getOrCreate(DeckSection.Sideboard).addAll(sideBoardCards.getFilteredPool(isValid));
unsupportedCards.addAll(sideBoardCards.getFilteredPool(isUnsupported).toFlatList());
}
if (data.containsKey("commanderCards_" + i)) {
CardPool commanderCards = CardPool.fromCardList(List.of((String[]) data.readObject("commanderCards_" + i)));
decks.get(i).getOrCreate(DeckSection.Commander).addAll(commanderCards.getFilteredPool(isValid));
unsupportedCards.addAll(commanderCards.getFilteredPool(isUnsupported).toFlatList());
}
}
}
@@ -715,6 +747,8 @@ public class AdventurePlayer implements Serializable, SaveFileContent {
data.store("isFemale", isFemale);
data.store("colorIdentity", colorIdentity.getColor());
data.store("adventure_mode", adventureMode.toString());
data.store("fantasyMode", fantasyMode);
data.store("announceFantasy", announceFantasy);
data.store("usingCustomDeck", usingCustomDeck);
@@ -772,6 +806,8 @@ public class AdventurePlayer implements Serializable, SaveFileContent {
data.storeObject("attractionDeckCards", deck.get(DeckSection.Attractions).toCardList("\n").split("\n"));
if (deck.get(DeckSection.Contraptions) != null)
data.storeObject("contraptionDeckCards", deck.get(DeckSection.Contraptions).toCardList("\n").split("\n"));
if (deck.get(DeckSection.Commander) != null)
data.storeObject("commanderCards", deck.get(DeckSection.Commander).toCardList("\n").split("\n"));
// save decks dynamically
data.store("deckCount", getDeckCount());
@@ -784,6 +820,8 @@ public class AdventurePlayer implements Serializable, SaveFileContent {
data.storeObject("attractionDeckCards_" + i, decks.get(i).get(DeckSection.Attractions).toCardList("\n").split("\n"));
if (decks.get(i).get(DeckSection.Contraptions) != null)
data.storeObject("contraptionDeckCards_" + i, decks.get(i).get(DeckSection.Contraptions).toCardList("\n").split("\n"));
if (decks.get(i).get(DeckSection.Commander) != null)
data.storeObject("commanderCards_" + i, decks.get(i).get(DeckSection.Commander).toCardList("\n").split("\n"));
}
data.store("selectedDeckIndex", selectedDeckIndex);
data.storeObject("cards", cards.toCardList("\n").split("\n"));

View File

@@ -9,6 +9,7 @@ import forge.adventure.data.AdventureEventData;
import forge.adventure.data.ItemData;
import forge.adventure.player.AdventurePlayer;
import forge.adventure.util.AdventureEventController;
import forge.adventure.util.AdventureModes;
import forge.adventure.util.Config;
import forge.adventure.util.Current;
import forge.assets.FImage;
@@ -50,7 +51,7 @@ public class AdventureDeckEditor extends FDeckEditor {
@Override
public DeckFormat getDeckFormat() {
return DeckFormat.Adventure;
return AdventurePlayer.current().getAdventureMode() == AdventureModes.Commander ? DeckFormat.Commander : DeckFormat.Adventure;
}
@Override
@@ -65,17 +66,29 @@ public class AdventureDeckEditor extends FDeckEditor {
@Override
protected DeckEditorPage[] getInitialPages() {
return new DeckEditorPage[]{
new CollectionCatalogPage(),
new AdventureDeckSectionPage(DeckSection.Main, ItemManagerConfig.ADVENTURE_EDITOR_POOL),
new AdventureDeckSectionPage(DeckSection.Sideboard, ItemManagerConfig.ADVENTURE_SIDEBOARD),
new CollectionAutoSellPage()
};
if (AdventurePlayer.current().getAdventureMode() == AdventureModes.Commander)
return new DeckEditorPage[]{
new CollectionCatalogPage(),
new AdventureDeckSectionPage(DeckSection.Commander, ItemManagerConfig.ADVENTURE_EDITOR_POOL),
new AdventureDeckSectionPage(DeckSection.Main, ItemManagerConfig.ADVENTURE_EDITOR_POOL),
new AdventureDeckSectionPage(DeckSection.Sideboard, ItemManagerConfig.ADVENTURE_SIDEBOARD),
new CollectionAutoSellPage()
};
else {
return new DeckEditorPage[]{
new CollectionCatalogPage(),
new AdventureDeckSectionPage(DeckSection.Main, ItemManagerConfig.ADVENTURE_EDITOR_POOL),
new AdventureDeckSectionPage(DeckSection.Sideboard, ItemManagerConfig.ADVENTURE_SIDEBOARD),
new CollectionAutoSellPage()
};
}
}
@Override
public ItemPool<PaperCard> getCardPool(boolean wantUnique) {
return Current.player().getCards();
ItemPool<PaperCard> pool = new ItemPool<>(PaperCard.class);
pool.addAll(Current.player().getCards());
return pool;
}
@Override
@@ -113,6 +126,13 @@ public class AdventureDeckEditor extends FDeckEditor {
}
}
@Override
public boolean isCommanderEditor() {
if (AdventurePlayer.current().getAdventureMode() == AdventureModes.Commander)
return true;
return super.isCommanderEditor();
}
protected static class ShopConfig extends AdventureEditorConfig {
@Override
protected DeckEditorPage[] getInitialPages() {
@@ -896,12 +916,17 @@ public class AdventureDeckEditor extends FDeckEditor {
return;
}
String deckError = GameType.Adventure.getDeckFormat().getDeckConformanceProblem(getDeck());
if (deckError != null) {
//Allow the player to close the editor with an invalid deck, but warn them that cards may be swapped out.
String warning = localizer.getMessage("lblAdventureDeckError", deckError);
FOptionPane.showConfirmDialog(warning, localizer.getMessage("lblInvalidDeck"), false, result -> resolveClose(canCloseCallback, result == true));
return;
String deckError;
if (!(getEditorConfig() instanceof ShopConfig))
{
deckError = getEditorConfig().getDeckFormat().getDeckConformanceProblem(getDeck());
if (deckError != null) {
//Allow the player to close the editor with an invalid deck, but warn them that cards may be swapped out.
String warning = localizer.getMessage("lblAdventureDeckError", deckError);
FOptionPane.showConfirmDialog(warning, localizer.getMessage("lblInvalidDeck"), false, result -> resolveClose(canCloseCallback, result == true));
return;
}
}
resolveClose(canCloseCallback, true);

View File

@@ -14,6 +14,7 @@ import forge.adventure.player.AdventurePlayer;
import forge.adventure.stage.GameHUD;
import forge.adventure.stage.IAfterMatch;
import forge.adventure.util.AdventureEventController;
import forge.adventure.util.AdventureModes;
import forge.adventure.util.Config;
import forge.adventure.util.Current;
import forge.assets.FBufferedImage;
@@ -197,6 +198,8 @@ public class DuelScene extends ForgeScene {
String isDeckMissingMsg = "";
if (eventData != null && eventData.eventRules != null) {
mainGameType = eventData.eventRules.gameType;
} else if (AdventurePlayer.current().getAdventureMode() == AdventureModes.Commander){
mainGameType = GameType.Commander;
} else {
mainGameType = GameType.Adventure;
}

View File

@@ -9,6 +9,7 @@ import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener;
import com.badlogic.gdx.scenes.scene2d.utils.ClickListener;
import com.badlogic.gdx.scenes.scene2d.utils.TextureRegionDrawable;
import com.badlogic.gdx.utils.Array;
import com.github.tommyettinger.textra.TextraLabel;
import forge.Forge;
import forge.adventure.data.DialogData;
@@ -34,6 +35,7 @@ import java.util.Random;
* NewGame scene that contains the character creation
*/
public class NewGameScene extends MenuScene {
TextField selectedName;
ColorSet[] colorIds;
CardEdition[] editionIds;
@@ -97,6 +99,11 @@ public class NewGameScene extends MenuScene {
AdventureModes.Pile.setSelectionName(colorIdLabel);
AdventureModes.Pile.setModes(colorNames);
}
if (diff.commanderDecks != null) {
modes.add(AdventureModes.Commander);
AdventureModes.Commander.setSelectionName(colorIdLabel);
AdventureModes.Commander.setModes(colorNames);
}
break;
}
@@ -122,6 +129,12 @@ public class NewGameScene extends MenuScene {
AdventureModes.Custom.setSelectionName("[BLACK]" + Forge.getLocalizer().getMessage("lblDeck") + ":");
AdventureModes.Custom.setModes(custom);
}
// Commander game mode in selection screen
modes.add(AdventureModes.Commander);
AdventureModes.Commander.setSelectionName(colorIdLabel);
AdventureModes.Commander.setModes(colorNames);
String[] modeNames = new String[modes.size];
int constructedIndex = -1;
@@ -205,6 +218,9 @@ public class NewGameScene extends MenuScene {
});
}
// class field
private RewardActor previewActor;
private static NewGameScene object;
public static NewGameScene instance() {
@@ -302,7 +318,6 @@ public class NewGameScene extends MenuScene {
@Override
public void enter() {
updateAvatar();
if (Forge.createNewAdventureMap) {
FModel.getPreferences().setPref(ForgePreferences.FPref.UI_ENABLE_MUSIC, false);
WorldSave.generateNewWorld(selectedName.getText(),
@@ -451,6 +466,9 @@ public class NewGameScene extends MenuScene {
case Custom:
summaryText.append("Mode: Custom\n\nChoose your own preconstructed deck. Enemies can receive a random genetic AI deck (difficult).\n\nWarning: This will make encounter difficulty vary wildly from the developers' intent");
break;
case Commander:
summaryText.append("Mode: Commander\n\nYou will given a preconstructed commander deck based on the chosen color theme to start the playthrough.\n\nGood luck on your quest of creating a coherent deck that can win consistently and defeat the bosses.");
break;
default:
summaryText.append("No summary available for your this game mode.");
break;

View File

@@ -8,7 +8,8 @@ public enum AdventureModes {
Constructed(Forge.getLocalizer().getMessage("lblConstructed")),
Chaos("[GOLD]"+Forge.getLocalizer().getMessage("lblChaos")),
Pile(Forge.getLocalizer().getMessage("lblPile")),
Custom(Forge.getLocalizer().getMessage("lblCustom"));
Custom(Forge.getLocalizer().getMessage("lblCustom")),
Commander(Forge.getLocalizer().getMessage("lblCommander"));
private final String name;
private String selectionName;

View File

@@ -255,6 +255,12 @@ public class Config {
return CardUtil.getDeck(entry.value, false, false, "", false, false);
}
}
case Commander:
for (ObjectMap.Entry<String, String> entry : difficultyData.commanderDecks) {
if (ColorSet.fromNames(entry.key.toCharArray()).getColor() == color.getColor()) {
return CardUtil.getDeck(entry.value, false, false, "", false, false);
}
};
}
return null;
}

View File

@@ -33,6 +33,7 @@ import forge.Graphics;
import forge.ImageKeys;
import forge.StaticData;
import forge.adventure.data.ItemData;
import forge.adventure.player.AdventurePlayer;
import forge.adventure.scene.RewardScene;
import forge.adventure.scene.Scene;
import forge.adventure.scene.UIScene;
@@ -925,6 +926,16 @@ public class RewardActor extends Actor implements Disposable, ImageFetcher.Callb
getColor().a = 0.5f;
}
private static boolean inCollectionLike(PaperCard pc) {
var coll = AdventurePlayer.current().getCollectionCards(true).toFlatList();
String name = pc.getName();
for (PaperCard c : coll) {
if (c.equals(pc) || c.getName().equals(name))
return true;
}
return false;
}
@Override
public void act(float delta) {
super.act(delta);
@@ -943,8 +954,15 @@ public class RewardActor extends Actor implements Disposable, ImageFetcher.Callb
addListener(tooltip);
}
}
if (autoSell != null && !autoSell.isVisible() && flipProcess == 1)
if (autoSell != null && !autoSell.isVisible() && flipProcess == 1) {
autoSell.setVisible(true);
if (AdventurePlayer.current().getAdventureMode() == AdventureModes.Commander) {
PaperCard pc = reward.getCard();
if (pc != null) {
setAutoSell(inCollectionLike(pc));
}
}
}
// flipProcess=(float)Gdx.input.getX()/ (float)Gdx.graphics.getWidth();
}

View File

@@ -132,8 +132,10 @@ public class WorldSave {
currentSave.pointOfInterestChanges.clear();
boolean chaos = mode == AdventureModes.Chaos;
boolean custom = mode == AdventureModes.Custom;
Deck starterDeck = Config.instance().starterDeck(startingColorIdentity, diff, mode, customDeckIndex, starterEdition);
currentSave.player.create(name, starterDeck, male, race, avatarIndex, chaos, custom, diff);
currentSave.player.create(name, starterDeck, male, race, avatarIndex, chaos, custom, diff, mode);
currentSave.player.setWorldPosY((int) (currentSave.world.getData().playerStartPosY * currentSave.world.getData().height * currentSave.world.getTileSize()));
currentSave.player.setWorldPosX((int) (currentSave.world.getData().playerStartPosX * currentSave.world.getData().width * currentSave.world.getTileSize()));
currentSave.onLoadList.emit();

View File

@@ -132,6 +132,13 @@
"R":"decks/starter/pile_red_e.json",
"G":"decks/starter/pile_green_e.json"
},
"commanderDecks": {
"W":"decks/starter/commander/white_01.dck",
"B":"decks/starter/commander/black_01.dck",
"U":"decks/starter/commander/blue_01.dck",
"R":"decks/starter/commander/red_01.dck",
"G":"decks/starter/commander/green_01.dck"
},
"startItems": [
"Manasight Amulet",
"Leather Boots"
@@ -170,6 +177,13 @@
"R":"decks/starter/pile_red_n.json",
"G":"decks/starter/pile_green_n.json"
},
"commanderDecks": {
"W":"decks/starter/commander/white_01.dck",
"B":"decks/starter/commander/black_01.dck",
"U":"decks/starter/commander/blue_01.dck",
"R":"decks/starter/commander/red_01.dck",
"G":"decks/starter/commander/green_01.dck"
},
"startItems": [
"Leather Boots"
]
@@ -205,6 +219,13 @@
"U":"decks/starter/pile_blue_h.json",
"R":"decks/starter/pile_red_h.json",
"G":"decks/starter/pile_green_h.json"
},
"commanderDecks": {
"W":"decks/starter/commander/white_01.dck",
"B":"decks/starter/commander/black_01.dck",
"U":"decks/starter/commander/blue_01.dck",
"R":"decks/starter/commander/red_01.dck",
"G":"decks/starter/commander/green_01.dck"
}
},{
"name": "Insane",
@@ -238,6 +259,13 @@
"U":"decks/starter/pile_blue_h.json",
"R":"decks/starter/pile_red_h.json",
"G":"decks/starter/pile_green_h.json"
},
"commanderDecks": {
"W":"decks/starter/commander/white_01.dck",
"B":"decks/starter/commander/black_01.dck",
"U":"decks/starter/commander/blue_01.dck",
"R":"decks/starter/commander/red_01.dck",
"G":"decks/starter/commander/green_01.dck"
}
}
],

View File

@@ -0,0 +1,81 @@
[metadata]
Name=Starter Commander - Black
[Avatar]
[Commander]
1 Tymaret, Chosen from Death|thb|1
[Main]
1 Blood Artist|inr|1
1 Burglar Rat|fdn|1
1 Changeling Outcast|otc|1
1 Creeping Bloodsucker|j25|1
1 Gifted Aetherborn|jmp|1
1 Gravedigger|m20|1
1 Graveshifter|moc|1
1 Gray Merchant of Asphodel|dsc|1
1 Infestation Sage|fdn|1
1 Marauding Blight-Priest|fdn|1
1 Vampire Nighthawk|fdn|1
1 Vampire of the Dire Moon|m20|1
1 Vengeful Bloodwitch|fdn|1
1 Vindictive Vampire|clu|1
1 Yargle, Glutton of Urborg|cmm|1
1 Zulaport Cutthroat|clb|1
1 Arbiter of Woe|fdn|1
1 Dockside Chef|neo|1
1 Dusk Legion Zealot|lcc|1
1 Faerie Dreamthief|woe|1
1 Foulmire Knight|moc|1
1 Indulgent Tormentor|ima|1
1 Morbid Opportunist|tdc|1
1 Novice Occultist|mid|1
1 Phyrexian Rager|moc|1
1 Refurbished Familiar|mh3|1
1 Silversmote Ghoul|c21|1
1 Troll of Khazad-dûm|ltr|1
1 Vampire Gourmand|fdn|1
1 Command Beacon|tdc|1
1 Command Tower|eoc|1
37 Swamp|tla|1
1 Lightning Greaves|tdc|1
1 Swiftfoot Boots|tdc|1
1 Arcane Signet|eoc|1
1 Burnished Hart|dsc|1
1 Commander's Sphere|drc|1
1 Darksteel Ingot|otc|1
1 Manalith|m19|1
1 Mind Stone|dsc|1
1 Patchwork Banner|blb|1
1 Prismatic Lens|otc|1
1 Relic of Legends|dmu|1
1 Sol Ring|eoc|1
1 Solemn Simulacrum|tdc|1
1 Thought Vessel|dsc|1
1 Accursed Marauder|mh3|1
1 Bake into a Pie|fdn|1
1 Banewhip Punisher|hou|1
1 Black Dragon|afr|1
1 Doom Blade|ima|1
1 Elspeth's Nightmare|thb|1
1 Hero's Downfall|fdn|1
1 Murder|dsk|1
1 Nekrataal|ema|1
1 Ob Nixilis, the Hate-Twisted|war|1
1 Ravenous Chupacabra|mkc|1
1 Sephiroth's Intervention|fin|1
1 Stinkweed Imp|dvd|1
1 Withering Torment|dsk|1
1 Commander's Plate|cmr|1
1 Path of Ancestry|tdc|1
1 Campfire|cmm|1
[Sideboard]
[Planes]
[Schemes]
[Conspiracy]
[Dungeon]

View File

@@ -0,0 +1,81 @@
[metadata]
Name=Starter Commander - Blue
[Avatar]
[Commander]
1 Callaphe, Beloved of the Sea|thb|1
[Main]
1 Brineborn Cutthroat|fdn|1
1 Cloudfin Raptor|rvr|1
1 Air Elemental|m20|1
1 Augury Raven|khm|1
1 Aven Fateshaper|dmr|1
1 Aven Reedstalker|hou|1
1 Baithook Angler|mid|1
1 Bay Falcon|mir|1
1 Boreal Elemental|m20|1
1 Curio Vendor|kld|1
1 Foul Watcher|mh2|1
1 Gossamer Phantasm|tsr|1
1 Ancestral Reminiscence|lci|1
1 Arrester's Admonition|rna|1
1 Artificer's Assistant|dom|1
1 Artificer's Epiphany|ddu|1
1 Azure Mage|mm3|1
1 Behold the Multiverse|khm|1
1 Bellowing Crier|blb|1
1 Birthday Escape|ltr|1
1 Chart a Course|fdn|1
1 Cloudkin Seer|clu|1
1 Cryogen Relic|eoe|1
1 Curate|mkc|1
1 Spectral Sailor|fdn|1
1 Delver of Secrets|inr|1
1 Spyglass Siren|lci|1
1 Command Beacon|tdc|1
1 Command Tower|eoc|1
37 Island|tla|1
1 Aegis Turtle|fdn|1
1 Cogwork Wrestler|lci|1
1 Lightning Greaves|tdc|1
1 Swiftfoot Boots|tdc|1
1 Animating Faerie|eld|1
1 Aven Wind Mage|gn2|1
1 Aarakocra Sneak|clb|1
1 Arcane Signet|eoc|1
1 Burnished Hart|dsc|1
1 Commander's Sphere|drc|1
1 Darksteel Ingot|otc|1
1 Manalith|m19|1
1 Mind Stone|dsc|1
1 Patchwork Banner|blb|1
1 Prismatic Lens|otc|1
1 Relic of Legends|dmu|1
1 Sol Ring|eoc|1
1 Solemn Simulacrum|tdc|1
1 Thought Vessel|dsc|1
1 Academy Journeymage|dom|1
1 Aether Adept|ddm|1
1 Aetherize|fdn|1
1 Angler Drake|akh|1
1 Aven Augur|fut|1
1 Banishing Knack|eve|1
1 Beluna's Gatekeeper|woe|1
1 Bounce Off|dft|1
1 Clutch of Currents|bfz|1
1 Cunning Geysermage|znr|1
1 Exclusion Mage|fdn|1
1 Commander's Plate|cmr|1
1 Path of Ancestry|tdc|1
1 Campfire|cmm|1
[Sideboard]
[Planes]
[Schemes]
[Conspiracy]
[Dungeon]

View File

@@ -0,0 +1,81 @@
[metadata]
Name=Starter Commander - Green
[Avatar]
[Commander]
1 Renata, Called to the Hunt|thb|1
[Main]
1 Bashful Beastie|dsk|1
1 Beanstalk Giant|dsc|1
1 Bitterbow Sharpshooters|hou|1
1 Candlelit Cavalry|mid|1
1 Coliseum Behemoth|fin|1
1 Colossal Dreadmaw|m21|1
1 Crash of Rhino Beetles|cmm|1
1 Disciple of Freyalise|mh3|1
1 Drove of Elves|cma|1
1 Duskdale Wurm|ima|1
1 Generous Ent|ltr|1
1 Adventure Awaits|znr|1
1 Carven Caryatid|tdc|1
1 Earthshaker Dreadmaw|lci|1
1 Elven Farsight|ltr|1
1 Fecundity|uma|1
1 Joraga Visionary|znr|1
1 Masked Admirers|mma|1
1 Mild-Mannered Librarian|fdn|1
1 Owlbear Shepherd|clb|1
1 Summon: Fenrir|fin|1
1 Command Beacon|tdc|1
1 Command Tower|eoc|1
37 Forest|tla|1
1 Lightning Greaves|tdc|1
1 Swiftfoot Boots|tdc|1
1 Arcane Signet|eoc|1
1 Boseiju Reaches Skyward|neo|1
1 Burnished Hart|dsc|1
1 Commander's Sphere|drc|1
1 Darksteel Ingot|otc|1
1 Druid of the Cowl|fdn|1
1 Elvish Mystic|cmm|1
1 Golden Hind|jou|1
1 Goobbue Gardener|fin|1
1 Hardbristle Bandit|otj|1
1 Ilysian Caryatid|cmm|1
1 Leafkin Druid|ncc|1
1 Llanowar Elves|fdn|1
1 Manalith|m19|1
1 Mind Stone|dsc|1
1 Overgrown Battlement|roe|1
1 Patchwork Banner|blb|1
1 Prismatic Lens|otc|1
1 Relic of Legends|dmu|1
1 Sol Ring|eoc|1
1 Solemn Simulacrum|tdc|1
1 Thought Vessel|dsc|1
1 Vine Trellis|gvl|1
1 Affectionate Indrik|fdn|1
1 Aggressive Instinct|gs1|1
1 Atzocan Archer|xln|1
1 Bite Down|fdn|1
1 Bushwhack|fdn|1
1 Clash of the Eikons|fin|1
1 Ent's Fury|ltr|1
1 Hunter's Talent|blb|1
1 Kapow!|spm|1
1 Longstalk Brawl|blb|1
1 Rabid Bite|blb|1
1 Commander's Plate|cmr|1
1 Path of Ancestry|tdc|1
1 Campfire|cmm|1
[Sideboard]
[Planes]
[Schemes]
[Conspiracy]
[Dungeon]

View File

@@ -0,0 +1,81 @@
[metadata]
Name=Starter Commander - Red
[Avatar]
[Commander]
1 Anax, Hardened in the Forge|cmm|1
[Main]
1 Viashino Pyromancer|fdn|1
1 Bolrac-Clan Basher|mkm|1
1 Charging Monstrosaur|xln|1
1 Sabotender|fin|1
1 Akoum Hellhound|znr|1
1 Goblin Bushwhacker|zen|1
1 Skitter of Lizards|cns|1
1 Alania's Pathmaker|blb|1
1 Anep, Vizier of Hazoret|j25|1
1 Battlefield Scavenger|akh|1
1 Blazing Crescendo|one|1
1 Boundary Lands Ranger|woe|1
1 Burning-Tree Vandal|rvr|1
1 Cleon, Merry Champion|j25|1
1 Clockwork Percussionist|dsk|1
1 Dragon Mage|fdn|1
1 Experimental Synthesizer|neo|1
1 Fissure Wizard|znr|1
1 Goblin Researcher|j25|1
1 Heroes' Hangout|spm|1
1 Irascible Wolverine|otj|1
1 Light Up the Stage|dsc|1
1 Tuskeri Firewalker|khm|1
1 Voldaren Epicure|inr|1
1 Command Beacon|tdc|1
1 Command Tower|eoc|1
37 Mountain|tle|1
1 Belligerent Whiptail|bfz|1
1 Lightning Greaves|tdc|1
1 Swiftfoot Boots|tdc|1
1 Battle Squadron|ddt|1
1 Most Valuable Slayer|dsk|1
1 Arcane Signet|eoc|1
1 Burnished Hart|dsc|1
1 Commander's Sphere|drc|1
1 Darksteel Ingot|otc|1
1 Manalith|m19|1
1 Mind Stone|dsc|1
1 Patchwork Banner|blb|1
1 Prismatic Lens|otc|1
1 Relic of Legends|dmu|1
1 Sol Ring|eoc|1
1 Solemn Simulacrum|tdc|1
1 Thought Vessel|dsc|1
1 Young Red Dragon|clb|1
1 Abrade|tdc|1
1 Fear of Being Hunted|dsk|1
1 Fire Prophecy|j25|1
1 Flame Slash|plst|1
1 Forge Devil|jmp|1
1 Lava Coil|2x2|1
1 Lightning Axe|inr|1
1 Lightning Strike|tla|1
1 Magma Spray|plst|1
1 Rolling Thunder|plst|1
1 Shatter|plst|1
1 Shock|spm|1
1 Smash to Smithereens|plst|1
1 Stoke the Flames|mom|1
1 Goblin Instigator|moc|1
1 Commander's Plate|cmr|1
1 Path of Ancestry|tdc|1
1 Campfire|cmm|1
[Sideboard]
[Planes]
[Schemes]
[Conspiracy]
[Dungeon]

View File

@@ -0,0 +1,81 @@
[metadata]
Name=Starter Commander - White
[Avatar]
[Commander]
1 Daxos, Blessed by the Sun|plst|1
[Main]
1 Accorder Paladin|mbs|1
1 Ajani's Pridemate|fdn|1
1 Akroan Jailer|ori|1
1 Alpine Watchdog|m21|1
1 Battlefield Raptor|khm|1
1 Cheeky House-Mouse|woe|1
1 Doomed Traveler|clu|1
1 Faerie Guidemother|eld|1
1 Goldnight Commander|tdc|1
1 Healer's Hawk|fdn|1
1 Hinterland Sanctifier|fdn|1
1 Lunarch Veteran|inr|1
1 Selfless Savior|m21|1
1 Stonehorn Dignitary|m12|1
1 Thraben Watcher|mh2|1
1 Valiant Changeling|clb|1
1 Errand-Rider of Gondor|ltr|1
1 Helpful Hunter|fdn|1
1 Inspiring Overseer|fdn|1
1 Mentor of the Meek|inr|1
1 Outlaw Medic|otj|1
1 Roving Harper|clb|1
1 Rumor Gatherer|clb|1
1 Salt Road Packbeast|tdm|1
1 Search Party Captain|mid|1
1 Spirited Companion|cmm|1
1 The Fall of Lord Konda|neo|1
1 Wall of Omens|tdc|1
1 Command Beacon|tdc|1
1 Command Tower|eoc|1
37 Plains|tle|1
1 Lightning Greaves|tdc|1
1 Swiftfoot Boots|tdc|1
1 Arcane Signet|eoc|1
1 Burnished Hart|dsc|1
1 Commander's Sphere|drc|1
1 Darksteel Ingot|otc|1
1 Manalith|m19|1
1 Mind Stone|dsc|1
1 Patchwork Banner|blb|1
1 Prismatic Lens|otc|1
1 Relic of Legends|dmu|1
1 Sol Ring|eoc|1
1 Solemn Simulacrum|tdc|1
1 Thought Vessel|dsc|1
1 Alabaster Host Intercessor|mom|1
1 Angelic Edict|jmp|1
1 Baffling End|rix|1
1 Banish from Edoras|ltr|1
1 Banisher Priest|moc|1
1 Borrowed Time|mid|1
1 Cathar Commando|inr|1
1 Celestial Purge|mm2|1
1 Circle of Confinement|vow|1
1 Citizen's Arrest|dmu|1
1 Citizen's Crowbar|snc|1
1 Githzerai Monk|clb|1
1 Palace Jailer|cmm|1
1 Commander's Plate|cmr|1
1 Path of Ancestry|tdc|1
1 Campfire|cmm|1
1 Inspired Charge|mom|1
1 Coordinated Charge|iko|1
[Sideboard]
[Planes]
[Schemes]
[Conspiracy]
[Dungeon]