1 #include "deck_loader.h"
2
3 #include "carddatabase.h"
4 #include "decklist.h"
5 #include "main.h"
6
7 #include <QDebug>
8 #include <QFile>
9 #include <QStringList>
10
11 const QStringList DeckLoader::fileNameFilters = QStringList()
12 << QObject::tr("Common deck formats (*.cod *.dec *.dek *.txt *.mwDeck)")
13 << QObject::tr("All files (*.*)");
14
DeckLoader()15 DeckLoader::DeckLoader() : DeckList(), lastFileName(QString()), lastFileFormat(CockatriceFormat), lastRemoteDeckId(-1)
16 {
17 }
18
DeckLoader(const QString & nativeString)19 DeckLoader::DeckLoader(const QString &nativeString)
20 : DeckList(nativeString), lastFileName(QString()), lastFileFormat(CockatriceFormat), lastRemoteDeckId(-1)
21 {
22 }
23
DeckLoader(const DeckList & other)24 DeckLoader::DeckLoader(const DeckList &other)
25 : DeckList(other), lastFileName(QString()), lastFileFormat(CockatriceFormat), lastRemoteDeckId(-1)
26 {
27 }
28
DeckLoader(const DeckLoader & other)29 DeckLoader::DeckLoader(const DeckLoader &other)
30 : DeckList(other), lastFileName(other.lastFileName), lastFileFormat(other.lastFileFormat),
31 lastRemoteDeckId(other.lastRemoteDeckId)
32 {
33 }
34
loadFromFile(const QString & fileName,FileFormat fmt)35 bool DeckLoader::loadFromFile(const QString &fileName, FileFormat fmt)
36 {
37 QFile file(fileName);
38 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
39 return false;
40 }
41
42 bool result = false;
43 switch (fmt) {
44 case PlainTextFormat:
45 result = loadFromFile_Plain(&file);
46 break;
47 case CockatriceFormat: {
48 result = loadFromFile_Native(&file);
49 qDebug() << "Loaded from" << fileName << "-" << result;
50 if (!result) {
51 qDebug() << "Retying as plain format";
52 file.seek(0);
53 result = loadFromFile_Plain(&file);
54 fmt = PlainTextFormat;
55 }
56 break;
57 }
58
59 default:
60 break;
61 }
62
63 if (result) {
64 lastFileName = fileName;
65 lastFileFormat = fmt;
66
67 emit deckLoaded();
68 }
69
70 qDebug() << "Deck was loaded -" << result;
71 return result;
72 }
73
loadFromRemote(const QString & nativeString,int remoteDeckId)74 bool DeckLoader::loadFromRemote(const QString &nativeString, int remoteDeckId)
75 {
76 bool result = loadFromString_Native(nativeString);
77 if (result) {
78 lastFileName = QString();
79 lastFileFormat = CockatriceFormat;
80 lastRemoteDeckId = remoteDeckId;
81
82 emit deckLoaded();
83 }
84 return result;
85 }
86
saveToFile(const QString & fileName,FileFormat fmt)87 bool DeckLoader::saveToFile(const QString &fileName, FileFormat fmt)
88 {
89 QFile file(fileName);
90 if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
91 return false;
92 }
93
94 bool result = false;
95 switch (fmt) {
96 case PlainTextFormat:
97 result = saveToFile_Plain(&file);
98 break;
99 case CockatriceFormat:
100 result = saveToFile_Native(&file);
101 break;
102 }
103
104 if (result) {
105 lastFileName = fileName;
106 lastFileFormat = fmt;
107 }
108 return result;
109 }
110
111 // This struct is here to support the forEachCard function call, defined in decklist. It
112 // requires a function to be called for each card, and passes an inner node and a card for
113 // each card in the decklist.
114 struct FormatDeckListForExport
115 {
116 // Create refrences for the strings that will be passed in.
117 QString &mainBoardCards;
118 QString &sideBoardCards;
119 // create main operator for struct, allowing the foreachcard to work.
FormatDeckListForExportFormatDeckListForExport120 FormatDeckListForExport(QString &_mainBoardCards, QString &_sideBoardCards)
121 : mainBoardCards(_mainBoardCards), sideBoardCards(_sideBoardCards){};
122
operator ()FormatDeckListForExport123 void operator()(const InnerDecklistNode *node, const DecklistCardNode *card) const
124 {
125 // Get the card name
126 CardInfoPtr dbCard = db->getCard(card->getName());
127 if (!dbCard || dbCard->getIsToken()) {
128 // If it's a token, we don't care about the card.
129 return;
130 }
131
132 // Check if it's a sideboard card.
133 if (node->getName() == DECK_ZONE_SIDE) {
134 // Get the number of cards and add the card name
135 sideBoardCards += QString::number(card->getNumber());
136 // Add a space between card num and name
137 sideBoardCards += "%20";
138 // Add card name
139 sideBoardCards += card->getName();
140 // Add a return at the end of the card
141 sideBoardCards += "%0A";
142 } else // If it's a mainboard card, do the same thing, but for the mainboard card string
143 {
144 mainBoardCards += QString::number(card->getNumber());
145 mainBoardCards += "%20";
146 mainBoardCards += card->getName();
147 mainBoardCards += "%0A";
148 }
149 }
150 };
151
152 // Export deck to decklist function, called to format the deck in a way to be sent to a server
exportDeckToDecklist()153 QString DeckLoader::exportDeckToDecklist()
154 {
155 // Add the base url
156 QString deckString = "https://www.decklist.org/?";
157 // Create two strings to pass to function
158 QString mainBoardCards, sideBoardCards;
159 // Set up the struct to call.
160 FormatDeckListForExport formatDeckListForExport(mainBoardCards, sideBoardCards);
161 // call our struct function for each card in the deck
162 forEachCard(formatDeckListForExport);
163 // Remove the extra return at the end of the last cards
164 mainBoardCards.chop(3);
165 sideBoardCards.chop(3);
166 // if after we've called it for each card, and the strings are empty, we know that
167 // there were no non-token cards in the deck, so show an error message.
168 if ((QString::compare(mainBoardCards, "", Qt::CaseInsensitive) == 0) &&
169 (QString::compare(sideBoardCards, "", Qt::CaseInsensitive) == 0)) {
170 return "";
171 }
172 // return a string with the url for decklist export
173 deckString += "deckmain=" + mainBoardCards + "&deckside=" + sideBoardCards;
174 return deckString;
175 }
176
getFormatFromName(const QString & fileName)177 DeckLoader::FileFormat DeckLoader::getFormatFromName(const QString &fileName)
178 {
179 if (fileName.endsWith(".cod", Qt::CaseInsensitive)) {
180 return CockatriceFormat;
181 }
182 return PlainTextFormat;
183 }
184
saveToStream_Plain(QTextStream & out,bool addComments)185 bool DeckLoader::saveToStream_Plain(QTextStream &out, bool addComments)
186 {
187 if (addComments) {
188 saveToStream_DeckHeader(out);
189 }
190
191 // loop zones
192 for (int i = 0; i < getRoot()->size(); i++) {
193 const auto *zoneNode = dynamic_cast<InnerDecklistNode *>(getRoot()->at(i));
194
195 saveToStream_DeckZone(out, zoneNode, addComments);
196
197 // end of zone
198 out << "\n";
199 }
200
201 return true;
202 }
203
saveToStream_DeckHeader(QTextStream & out)204 void DeckLoader::saveToStream_DeckHeader(QTextStream &out)
205 {
206 if (!getName().isEmpty()) {
207 out << "// " << getName() << "\n\n";
208 }
209
210 if (!getComments().isEmpty()) {
211 QStringList commentRows = getComments().split(QRegExp("\n|\r\n|\r"));
212 foreach (QString row, commentRows) {
213 out << "// " << row << "\n";
214 }
215 out << "\n";
216 }
217 }
218
saveToStream_DeckZone(QTextStream & out,const InnerDecklistNode * zoneNode,bool addComments)219 void DeckLoader::saveToStream_DeckZone(QTextStream &out, const InnerDecklistNode *zoneNode, bool addComments)
220 {
221 // group cards by card type and count the subtotals
222 QMultiMap<QString, DecklistCardNode *> cardsByType;
223 QMap<QString, int> cardTotalByType;
224 int cardTotal = 0;
225
226 for (int j = 0; j < zoneNode->size(); j++) {
227 auto *card = dynamic_cast<DecklistCardNode *>(zoneNode->at(j));
228
229 CardInfoPtr info = db->getCard(card->getName());
230 QString cardType = info ? info->getMainCardType() : "unknown";
231
232 cardsByType.insert(cardType, card);
233
234 if (cardTotalByType.contains(cardType)) {
235 cardTotalByType[cardType] += card->getNumber();
236 } else {
237 cardTotalByType[cardType] = card->getNumber();
238 }
239
240 cardTotal += card->getNumber();
241 }
242
243 if (addComments) {
244 out << "// " << cardTotal << " " << zoneNode->getVisibleName() << "\n";
245 }
246
247 // print cards to stream
248 foreach (QString cardType, cardsByType.uniqueKeys()) {
249 if (addComments) {
250 out << "// " << cardTotalByType[cardType] << " " << cardType << "\n";
251 }
252
253 QList<DecklistCardNode *> cards = cardsByType.values(cardType);
254
255 saveToStream_DeckZoneCards(out, zoneNode, cards, addComments);
256
257 if (addComments) {
258 out << "\n";
259 }
260 }
261 }
262
saveToStream_DeckZoneCards(QTextStream & out,const InnerDecklistNode * zoneNode,QList<DecklistCardNode * > cards,bool addComments)263 void DeckLoader::saveToStream_DeckZoneCards(QTextStream &out,
264 const InnerDecklistNode *zoneNode,
265 QList<DecklistCardNode *> cards,
266 bool addComments)
267 {
268 // QMultiMap sorts values in reverse order
269 for (int i = cards.size() - 1; i >= 0; --i) {
270 DecklistCardNode *card = cards[i];
271
272 if (zoneNode->getName() == DECK_ZONE_SIDE && addComments) {
273 out << "SB: ";
274 }
275
276 out << card->getNumber() << " " << card->getName() << "\n";
277 }
278 }
279
getCardZoneFromName(QString cardName,QString currentZoneName)280 QString DeckLoader::getCardZoneFromName(QString cardName, QString currentZoneName)
281 {
282 CardInfoPtr card = db->getCard(cardName);
283
284 if (card && card->getIsToken()) {
285 return DECK_ZONE_TOKENS;
286 }
287
288 return currentZoneName;
289 }
290
getCompleteCardName(const QString cardName) const291 QString DeckLoader::getCompleteCardName(const QString cardName) const
292 {
293 if (db) {
294 CardInfoPtr temp = db->guessCard(cardName);
295 if (temp) {
296 return temp->getName();
297 }
298 }
299
300 return cardName;
301 }
302