diff --git a/cockatrice/src/carddatabase.cpp b/cockatrice/src/carddatabase.cpp index 6a7988d1..a52d5cd5 100644 --- a/cockatrice/src/carddatabase.cpp +++ b/cockatrice/src/carddatabase.cpp @@ -3,22 +3,47 @@ #include #include #include +#include -CardInfo::CardInfo(const QString &_name, const QString &_manacost, const QString &_cardtype, const QString &_powtough, const QStringList &_text) - : name(_name), manacost(_manacost), cardtype(_cardtype), powtough(_powtough), text(_text), pixmap(NULL) +CardSet::CardSet(const QString &_shortName, const QString &_longName) + : shortName(_shortName), longName(_longName) { - + updateSortKey(); } -CardInfo::CardInfo(QDataStream &stream) - : pixmap(NULL) +void CardSet::loadFromStream(QDataStream &stream) +{ + stream >> shortName >> longName; + updateSortKey(); + qDebug(QString("set loaded: %1, %2").arg(shortName).arg(longName).toLatin1()); +} + +void CardSet::saveToStream(QDataStream &stream) +{ + stream << shortName << longName; +} + +void CardSet::setSortKey(unsigned int _sortKey) +{ + sortKey = _sortKey; + + QSettings settings; + settings.beginGroup("sets"); + settings.beginGroup(shortName); + settings.setValue("sortkey", sortKey); +} + +void CardSet::updateSortKey() +{ + QSettings settings; + settings.beginGroup("sets"); + settings.beginGroup(shortName); + sortKey = settings.value("sortkey", 0).toInt(); +} + +CardInfo::CardInfo(CardDatabase *_db, const QString &_name, const QString &_manacost, const QString &_cardtype, const QString &_powtough, const QStringList &_text) + : db(_db), name(_name), manacost(_manacost), cardtype(_cardtype), powtough(_powtough), text(_text), pixmap(NULL) { - stream >> name - >> editions - >> manacost - >> cardtype - >> powtough - >> text; } CardInfo::~CardInfo() @@ -42,7 +67,7 @@ QString CardInfo::getMainCardType() const Legendary Artifact Creature - Golem Instant // Instant */ - + int pos; if ((pos = result.indexOf('-')) != -1) result.remove(pos, result.length()); @@ -53,23 +78,31 @@ QString CardInfo::getMainCardType() const Legendary Artifact Creature Instant */ - + if ((pos = result.lastIndexOf(' ')) != -1) result = result.mid(pos + 1); /* Creature Instant */ - + return result; } -void CardInfo::addEdition(const QString &edition) +void CardInfo::addToSet(CardSet *set) { - if (!editions.contains(edition)) - editions << edition; + set->append(this); + sets << set; } +class CardInfo::SetCompareFunctor { +public: + inline bool operator()(CardSet *a, CardSet *b) const + { + return a->getSortKey() < b->getSortKey(); + } +}; + QPixmap *CardInfo::loadPixmap() { if (pixmap) @@ -79,13 +112,19 @@ QPixmap *CardInfo::loadPixmap() pixmap->load("../pics/back.jpg"); return pixmap; } - qDebug(QString("CardDatabase: loading pixmap for '%1'").arg(getName()).toLatin1()); - for (int i = 0; i < editions.size(); i++) { + qSort(sets.begin(), sets.end(), SetCompareFunctor()); + + QString debugOutput = QString("CardDatabase: loading pixmap for '%1' from ").arg(getName()); + for (int i = 0; i < sets.size(); i++) + debugOutput.append(QString("%1, ").arg(sets[i]->getShortName())); + qDebug(debugOutput.toLatin1()); + + for (int i = 0; i < sets.size(); i++) { // Fire // Ice, Circle of Protection: Red QString correctedName = getName().remove(" // ").remove(":"); - if (pixmap->load(QString("../pics/%1/%2.full.jpg").arg(editions.at(i)).arg(correctedName))) + if (pixmap->load(QString("../pics/%1/%2.full.jpg").arg(sets[i]->getShortName()).arg(correctedName))) return pixmap; - if (pixmap->load(QString("../pics/%1/%2%3.full.jpg").arg(editions.at(i)).arg(correctedName).arg(1))) + if (pixmap->load(QString("../pics/%1/%2%3.full.jpg").arg(sets[i]->getShortName()).arg(correctedName).arg(1))) return pixmap; } return pixmap; @@ -95,11 +134,8 @@ QPixmap *CardInfo::getPixmap(QSize size) { qDebug(QString("CardInfo::getPixmap(%1, %2) for %3").arg(size.width()).arg(size.height()).arg(getName()).toLatin1()); QPixmap *cachedPixmap = scaledPixmapCache.value(size.width()); - if (cachedPixmap) { - qDebug("cache HIT"); + if (cachedPixmap) return cachedPixmap; - } - qDebug("cache MISS"); QPixmap *bigPixmap = loadPixmap(); if (bigPixmap->isNull()) return 0; @@ -108,10 +144,28 @@ QPixmap *CardInfo::getPixmap(QSize size) return result; } +void CardInfo::loadFromStream(QDataStream &stream) +{ + QStringList setNames; + stream >> name + >> setNames + >> manacost + >> cardtype + >> powtough + >> text; + + for (int i = 0; i < setNames.size(); i++) + addToSet(db->getSet(setNames[i])); +} + void CardInfo::saveToStream(QDataStream &stream) { + QStringList setNames; + for (int i = 0; i < sets.size(); i++) + setNames << sets[i]->getShortName(); + stream << name - << editions + << setNames << manacost << cardtype << powtough @@ -120,6 +174,8 @@ void CardInfo::saveToStream(QDataStream &stream) CardDatabase::CardDatabase() { + noCard = new CardInfo(this); + noCard->loadPixmap(); // cache pixmap for card back } CardDatabase::~CardDatabase() @@ -129,39 +185,90 @@ CardDatabase::~CardDatabase() void CardDatabase::clear() { - QHashIterator i(hash); + QHashIterator setIt(setHash); + while (setIt.hasNext()) { + setIt.next(); + delete setIt.value(); + } + setHash.clear(); + + QHashIterator i(cardHash); while (i.hasNext()) { i.next(); delete i.value(); } - hash.clear(); + cardHash.clear(); } CardInfo *CardDatabase::getCard(const QString &cardName) { - if (hash.contains(cardName)) - return hash.value(cardName); + if (cardName.isEmpty()) + return noCard; + else if (cardHash.contains(cardName)) + return cardHash.value(cardName); else { qDebug(QString("CardDatabase: card not found: %1").arg(cardName).toLatin1()); - CardInfo *newCard = new CardInfo(cardName); - newCard->addEdition("TK"); - hash.insert(cardName, newCard); + CardInfo *newCard = new CardInfo(this, cardName); + newCard->addToSet(getSet("TK")); + cardHash.insert(cardName, newCard); return newCard; } } -QListCardDatabase::getCardList() +CardSet *CardDatabase::getSet(const QString &setName) { - QList cardList; - QHashIterator i(hash); - while (i.hasNext()) { - i.next(); - cardList.append(i.value()); + if (setHash.contains(setName)) + return setHash.value(setName); + else { + qDebug(QString("CardDatabase: set not found: %1").arg(setName).toLatin1()); + CardSet *newSet = new CardSet(setName); + setHash.insert(setName, newSet); + return newSet; } - return cardList; } -void CardDatabase::importOracle() +void CardDatabase::importOracleFile(const QString &fileName, CardSet *set) +{ + QFile file(fileName); + file.open(QIODevice::ReadOnly | QIODevice::Text); + QTextStream in(&file); + while (!in.atEnd()) { + QString cardname = in.readLine(); + if (cardname.isEmpty()) + continue; + QString manacost = in.readLine(); + QString cardtype, powtough; + QStringList text; + if (manacost.contains("Land", Qt::CaseInsensitive)) { + cardtype = manacost; + manacost.clear(); + } else { + cardtype = in.readLine(); + powtough = in.readLine(); + // Dirty hack. + // Cards to test: Any creature, any basic land, Ancestral Vision, Fire // Ice. + if (!powtough.contains("/") || powtough.size() > 5) { + text << powtough; + powtough = QString(); + } + } + QString line = in.readLine(); + while (!line.isEmpty()) { + text << line; + line = in.readLine(); + } + CardInfo *card; + if (cardHash.contains(cardname)) + card = cardHash.value(cardname); + else { + card = new CardInfo(this, cardname, manacost, cardtype, powtough, text); + cardHash.insert(cardname, card); + } + card->addToSet(set); + } +} + +void CardDatabase::importOracleDir() { clear(); QDir dir("../db"); @@ -173,50 +280,16 @@ void CardDatabase::importOracle() QFileInfoList files = dir.entryInfoList(QStringList() << "*.txt"); for (int k = 0; k < files.size(); k++) { QFileInfo i = files[k]; - QString edition = i.fileName().mid(i.fileName().indexOf('_') + 1); - edition = edition.left(edition.indexOf('.')); - QFile file(i.filePath()); - file.open(QIODevice::ReadOnly | QIODevice::Text); - QTextStream in(&file); - while (!in.atEnd()) { - QString cardname = in.readLine(); - QString manacost = in.readLine(); - QString cardtype, powtough; - QStringList text; - if (manacost.contains("Land", Qt::CaseInsensitive)) { - cardtype = manacost; - manacost.clear(); - } else { - cardtype = in.readLine(); - powtough = in.readLine(); - // Dirty hack. - // Cards to test: Any creature, any basic land, Ancestral Vision, Fire // Ice. - if (!powtough.contains("/") || powtough.size() > 5) { - text << powtough; - powtough = QString(); - } - } - QString line = in.readLine(); - while (!line.isEmpty()) { - text << line; - line = in.readLine(); - } - CardInfo *card; - if (hash.contains(cardname)) - card = hash.value(cardname); - else { - card = new CardInfo(cardname, manacost, cardtype, powtough, text); - hash.insert(cardname, card); - } - card->addEdition(edition); - } + QString shortName = i.fileName().left(i.fileName().indexOf('_')); + QString longName = i.fileName().mid(i.fileName().indexOf('_') + 1); + longName = longName.left(longName.indexOf('.')); + CardSet *set = new CardSet(shortName, longName); + setHash.insert(shortName, set); + + importOracleFile(i.filePath(), set); } - qDebug(QString("CardDatabase: %1 cards imported").arg(hash.size()).toLatin1()); - - CardInfo *empty = new CardInfo(); - empty->loadPixmap(); // cache pixmap for card back - hash.insert("", empty); + qDebug(QString("CardDatabase: %1 cards imported").arg(cardHash.size()).toLatin1()); } int CardDatabase::loadFromFile(const QString &fileName) @@ -226,9 +299,10 @@ int CardDatabase::loadFromFile(const QString &fileName) QDataStream in(&file); in.setVersion(QDataStream::Qt_4_4); - quint32 _magicNumber, _fileVersion, cardCount; + quint32 _magicNumber, _fileVersion, setCount, cardCount; in >> _magicNumber >> _fileVersion + >> setCount >> cardCount; if (_magicNumber != magicNumber) @@ -237,11 +311,20 @@ int CardDatabase::loadFromFile(const QString &fileName) return -2; clear(); - hash.reserve(cardCount); - for (unsigned int i = 0; i < cardCount; i++) { - CardInfo *newCard = new CardInfo(in); - hash.insert(newCard->getName(), newCard); + setHash.reserve(setCount); + qDebug(QString("setCount = %1").arg(setCount).toLatin1()); + for (unsigned int i = 0; i < setCount; i++) { + CardSet *newSet = new CardSet; + newSet->loadFromStream(in); + setHash.insert(newSet->getShortName(), newSet); } + cardHash.reserve(cardCount); + for (unsigned int i = 0; i < cardCount; i++) { + CardInfo *newCard = new CardInfo(this); + newCard->loadFromStream(in); + cardHash.insert(newCard->getName(), newCard); + } + qDebug(QString("%1 cards in %2 sets loaded").arg(cardCount).arg(setHash.size()).toLatin1()); return cardCount; } @@ -255,9 +338,15 @@ bool CardDatabase::saveToFile(const QString &fileName) out << (quint32) magicNumber << (quint32) fileVersion - << (quint32) hash.size(); + << (quint32) setHash.size() + << (quint32) cardHash.size(); - QHashIterator i(hash); + QHashIterator setIt(setHash); + while (setIt.hasNext()) { + setIt.next(); + setIt.value()->saveToStream(out); + } + QHashIterator i(cardHash); while (i.hasNext()) { i.next(); i.value()->saveToStream(out); diff --git a/cockatrice/src/carddatabase.h b/cockatrice/src/carddatabase.h index 13f97d86..6e806095 100644 --- a/cockatrice/src/carddatabase.h +++ b/cockatrice/src/carddatabase.h @@ -5,11 +5,33 @@ #include #include #include +#include + +class CardDatabase; +class CardInfo; + +class CardSet : public QList { +private: + QString shortName, longName; + unsigned int sortKey; +public: + CardSet(const QString &_shortName = QString(), const QString &_longName = QString()); + QString getShortName() const { return shortName; } + QString getLongName() const { return longName; } + int getSortKey() const { return sortKey; } + void setSortKey(unsigned int _sortKey); + void updateSortKey(); + void loadFromStream(QDataStream &stream); + void saveToStream(QDataStream &stream); +}; class CardInfo { private: + class SetCompareFunctor; + CardDatabase *db; + QString name; - QStringList editions; + QList sets; QString manacost; QString cardtype; QString powtough; @@ -17,29 +39,32 @@ private: QPixmap *pixmap; QMap scaledPixmapCache; public: - CardInfo(const QString &_name = QString(), + CardInfo(CardDatabase *_db, + const QString &_name = QString(), const QString &_manacost = QString(), const QString &_cardtype = QString(), const QString &_powtough = QString(), const QStringList &_text = QStringList()); - CardInfo(QDataStream &stream); ~CardInfo(); QString getName() const { return name; } - QStringList getEditions() const { return editions; } + QList getSets() const { return sets; } QString getManacost() const { return manacost; } QString getCardType() const { return cardtype; } QString getPowTough() const { return powtough; } QStringList getText() const { return text; } QString getMainCardType() const; - void addEdition(const QString &edition); + void addToSet(CardSet *set); QPixmap *loadPixmap(); QPixmap *getPixmap(QSize size); + void loadFromStream(QDataStream &stream); void saveToStream(QDataStream &stream); }; class CardDatabase { private: - QHash hash; + QHash cardHash; + QHash setHash; + CardInfo *noCard; static const unsigned int magicNumber = 0x12345678; static const unsigned int fileVersion = 1; public: @@ -47,8 +72,10 @@ public: ~CardDatabase(); void clear(); CardInfo *getCard(const QString &cardName = QString()); - QList getCardList(); - void importOracle(); + CardSet *getSet(const QString &setName); + QList getCardList() { return cardHash.values(); } + void importOracleFile(const QString &fileName, CardSet *set); + void importOracleDir(); int loadFromFile(const QString &fileName); bool saveToFile(const QString &fileName); }; diff --git a/cockatrice/src/carddatabasemodel.cpp b/cockatrice/src/carddatabasemodel.cpp index 4de61f56..95c94dc0 100644 --- a/cockatrice/src/carddatabasemodel.cpp +++ b/cockatrice/src/carddatabasemodel.cpp @@ -29,11 +29,17 @@ QVariant CardDatabaseModel::data(const QModelIndex &index, int role) const return QVariant(); if (role != Qt::DisplayRole) return QVariant(); - + CardInfo *card = cardList.at(index.row()); switch (index.column()){ case 0: return card->getName(); - case 1: return card->getEditions().join(", "); + case 1: { + QStringList setList; + QList sets = card->getSets(); + for (int i = 0; i < sets.size(); i++) + setList << sets[i]->getShortName(); + return setList.join(", "); + } case 2: return card->getManacost(); case 3: return card->getCardType(); case 4: return card->getPowTough(); @@ -68,7 +74,7 @@ public: bool result; switch (column) { case 0: result = (a->getName() < b->getName()); break; - case 1: result = (a->getEditions().join("") < b->getEditions().join("")); break; + case 1: result = (a->getSets().at(0)->getShortName() < b->getSets().at(0)->getShortName()); break; case 2: result = (a->getManacost() < b->getManacost()); break; case 3: result = (a->getCardType() < b->getCardType()); break; case 4: result = (a->getPowTough() < b->getPowTough()); break; diff --git a/cockatrice/src/game.cpp b/cockatrice/src/game.cpp index b91385c4..fdf5c247 100644 --- a/cockatrice/src/game.cpp +++ b/cockatrice/src/game.cpp @@ -12,8 +12,8 @@ #include "playerarea.h" #include "counter.h" -Game::Game(CardDatabase *_db, Client *_client, QGraphicsScene *_scene, QMenu *_actionsMenu, QMenu *_cardMenu, int playerId, const QString &playerName) - : QObject(), actionsMenu(_actionsMenu), cardMenu(_cardMenu), db(_db), client(_client), scene(_scene), started(false) +Game::Game(CardDatabase *_db, Client *_client, QGraphicsScene *_scene, QMenu *_actionsMenu, QMenu *_cardMenu, int playerId, const QString &playerName, QObject *parent) + : QObject(parent), actionsMenu(_actionsMenu), cardMenu(_cardMenu), db(_db), client(_client), scene(_scene), started(false) { QRectF sr = scene->sceneRect(); localPlayer = addPlayer(playerId, playerName, QPointF(0, sr.y() + sr.height() / 2 + 2), true); diff --git a/cockatrice/src/game.h b/cockatrice/src/game.h index 9d85dde8..0d36cbe6 100644 --- a/cockatrice/src/game.h +++ b/cockatrice/src/game.h @@ -18,7 +18,7 @@ private: QAction *aTap, *aUntap, *aDoesntUntap, *aFlip, *aAddCounter, *aRemoveCounter, *aSetCounters, *aRearrange, *aUntapAll, *aDecLife, *aIncLife, *aSetLife, *aShuffle, *aDraw, *aDrawCards, *aRollDice, *aCreateToken; DlgStartGame *dlgStartGame; - + CardDatabase *db; Client *client; QGraphicsScene *scene; @@ -74,7 +74,7 @@ signals: void logSetDoesntUntap(QString playerName, QString cardName, bool doesntUntap); void logDumpZone(QString playerName, QString zoneName, QString zoneOwner, int numberCards); public: - Game(CardDatabase *_db, Client *_client, QGraphicsScene *_scene, QMenu *_actionsMenu, QMenu *_cardMenu, int playerId, const QString &playerName); + Game(CardDatabase *_db, Client *_client, QGraphicsScene *_scene, QMenu *_actionsMenu, QMenu *_cardMenu, int playerId, const QString &playerName, QObject *parent = 0); ~Game(); Player *getLocalPlayer() const { return localPlayer; } void restartGameDialog(); diff --git a/cockatrice/src/main.cpp b/cockatrice/src/main.cpp index 0d0822f1..141ed90b 100644 --- a/cockatrice/src/main.cpp +++ b/cockatrice/src/main.cpp @@ -22,9 +22,10 @@ #include #include #include -#include "window_main.h" #include +#include "window_main.h" + //Q_IMPORT_PLUGIN(qjpeg) void myMessageOutput(QtMsgType type, const char *msg) diff --git a/cockatrice/src/window_main.cpp b/cockatrice/src/window_main.cpp index d16db167..382727a2 100644 --- a/cockatrice/src/window_main.cpp +++ b/cockatrice/src/window_main.cpp @@ -120,6 +120,14 @@ void MainWindow::actDeckEditor() deckEditor->show(); } +void MainWindow::actFullScreen(bool checked) +{ + if (checked) + setWindowState(windowState() | Qt::WindowFullScreen); + else + setWindowState(windowState() & ~Qt::WindowFullScreen); +} + void MainWindow::actExit() { close(); @@ -149,7 +157,7 @@ void MainWindow::buttonSay() void MainWindow::playerIdReceived(int id, QString name) { - game = new Game(db, client, scene, actionsMenu, cardMenu, id, name); + game = new Game(db, client, scene, actionsMenu, cardMenu, id, name, this); connect(game, SIGNAL(hoverCard(QString)), this, SLOT(hoverCard(QString))); connect(game, SIGNAL(playerAdded(Player *)), this, SLOT(playerAdded(Player *))); connect(game, SIGNAL(playerRemoved(Player *)), this, SLOT(playerRemoved(Player *))); @@ -187,6 +195,10 @@ void MainWindow::createActions() connect(aLeaveGame, SIGNAL(triggered()), this, SLOT(actLeaveGame())); aDeckEditor = new QAction(tr("&Deck editor"), this); connect(aDeckEditor, SIGNAL(triggered()), this, SLOT(actDeckEditor())); + aFullScreen = new QAction(tr("&Full screen"), this); + aFullScreen->setShortcut(tr("F4")); + aFullScreen->setCheckable(true); + connect(aFullScreen, SIGNAL(toggled(bool)), this, SLOT(actFullScreen(bool))); aExit = new QAction(tr("&Exit"), this); connect(aExit, SIGNAL(triggered()), this, SLOT(actExit())); @@ -208,8 +220,10 @@ void MainWindow::createMenus() gameMenu->addSeparator(); gameMenu->addAction(aDeckEditor); gameMenu->addSeparator(); + gameMenu->addAction(aFullScreen); + gameMenu->addSeparator(); gameMenu->addAction(aExit); - + actionsMenu = menuBar()->addMenu(tr("&Actions")); cardMenu = menuBar()->addMenu(tr("&Card")); @@ -224,7 +238,8 @@ MainWindow::MainWindow(QWidget *parent) db = new CardDatabase; int cardCount = db->loadFromFile("../cards.dat"); - qDebug(QString("%1 cards loaded").arg(cardCount).toLatin1()); +// db->importOracleDir(); +// db->saveToFile("../cards.dat"); scene = new QGraphicsScene(0, 0, 952, 1024, this); view = new GameView(scene); @@ -253,7 +268,7 @@ MainWindow::MainWindow(QWidget *parent) QHBoxLayout *mainLayout = new QHBoxLayout; mainLayout->addWidget(view); - mainLayout->setStretchFactor(view, 10); +// mainLayout->setStretchFactor(view, 10); mainLayout->addLayout(verticalLayout); QWidget *centralWidget = new QWidget; @@ -280,6 +295,4 @@ MainWindow::MainWindow(QWidget *parent) void MainWindow::closeEvent(QCloseEvent */*event*/) { - delete game; - delete db; } diff --git a/cockatrice/src/window_main.h b/cockatrice/src/window_main.h index 9b582a35..9ee0f52a 100644 --- a/cockatrice/src/window_main.h +++ b/cockatrice/src/window_main.h @@ -56,6 +56,7 @@ private slots: void actRestartGame(); void actLeaveGame(); void actDeckEditor(); + void actFullScreen(bool checked); void actExit(); void updateSceneSize(); @@ -66,7 +67,7 @@ private: void createActions(); void createMenus(); QMenu *gameMenu, *actionsMenu, *cardMenu; - QAction *aConnect, *aDisconnect, *aGames, *aRestartGame, *aLeaveGame, *aDeckEditor, *aExit; + QAction *aConnect, *aDisconnect, *aGames, *aRestartGame, *aLeaveGame, *aDeckEditor, *aFullScreen, *aExit; QAction *aCloseMostRecentZoneView; CardInfoWidget *cardInfo; diff --git a/crystalkeep.sh b/crystalkeep.sh new file mode 100644 index 00000000..cb36c608 --- /dev/null +++ b/crystalkeep.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +while read; do + filename=`echo $REPLY | cut '-d,' -f 1` + edition=`echo $REPLY | cut '-d,' -f 2` + editionLong=`echo $REPLY | cut '-d,' -f 3` + + wget http://www.crystalkeep.com/magic/rules/oracle/oracle-$filename.txt + mv oracle-$filename.txt "${edition}_${editionLong}.txt" +done diff --git a/crystalkeep.txt b/crystalkeep.txt new file mode 100644 index 00000000..a1969930 --- /dev/null +++ b/crystalkeep.txt @@ -0,0 +1,67 @@ +10,10E,Tenth Edition +ar,ARB,Alara Reborn +cf,CFX,Conflux +sa,ALA,Shards of Alara +et,EVE,Eventide +sm,SHM,Shadowmoor +mt,MOR,Morningtide +lo,LRW,Lorwyn +9th,9E,Ninth Edition +8th,8E,Eighth Edition +fs,FUT,Future Sight +pc,PLC,Planar Chaos +ts,TSP,Time Spiral +cs,CS,Coldsnap +di,DIS,Dissension +gp,GP,Guildpact +ra,RAV,Ravnica +sk,SOK,Saviors of Kamigawa +bk,BOK,Betrayers of Kamigawa +ck,CHK,Champions of Kamigawa +fd,FD,Fifth Dawn +ds,DS,Darksteel +mr,MR,Mirrodin +sc,SC,Scourge +le,LE,Legions +on,ON,Onslaught +7th,7E,Seventh Edition +6th,6E,Sixth Edition +5th,5E,Fifth Edition +4th,4E,Fourth Edition +rv,R,Revised Edition +lu,U,Unlimited Edition +lu,B,Limited Edition +ju,JU,Judgment +to,TO,Torment +od,OD,Odyssey +ap,AP,Apocalypse +ps,PS,Planeshift +in,IN,Invasion +py,PY,Prophecy +ne,NE,Nemesis +mm,MM,Mercadian Masques +ud,UD,Urza's Destiny +ul,UL,Urza's Legacy +us,US,Urza's Saga +ex,EX,Exodus +sh,SH,Stronghold +te,TE,Tempest +wl,WL,Weatherlight +vi,VI,Visions +mi,MI,Mirage +al,AL,Alliances +hl,HL,Homelands +ia,IA,Ice Age +ch,CH,Chronicles +fe,FE,Fallen Empires +dk,DK,The Dark +lg,LG,Legends +aq,AQ,Antiquities +an,AN,Arabian Nights +pt,PT,Portal +pt2,P2,Portal: Second Age +pt3,P3,Portal: Three Kingdoms +st,ST,Starter +st2,ST2K,Starter 2000 +ug,UG,Unglued +uh,UNH,Unhinged