deck: improved deck import from clipboard (added support of moxfield and archidekt decks format, related to #13838)

This commit is contained in:
Oleg Agafonov 2025-08-10 22:11:58 +04:00
parent 8a6a3b521f
commit f9fd049ece
2 changed files with 63 additions and 15 deletions

View file

@ -1,5 +1,6 @@
package mage.client.deckeditor; package mage.client.deckeditor;
import mage.cards.decks.importer.MtgaImporter;
import mage.client.MageFrame; import mage.client.MageFrame;
import mage.client.dialog.MageDialog; import mage.client.dialog.MageDialog;
import mage.util.DeckUtil; import mage.util.DeckUtil;
@ -20,11 +21,18 @@ import java.util.Optional;
public class DeckImportClipboardDialog extends MageDialog { public class DeckImportClipboardDialog extends MageDialog {
private static final String FORMAT_TEXT = private static final String FORMAT_TEXT =
"// Example:\n" + "// MTGO format example:\n" +
"//1 Library of Congress\n" + "// 1 Library of Congress\n" +
"//1 Cryptic Gateway\n" + "// 3 Cryptic Gateway\n" +
"//1 Azami, Lady of Scrolls\n" + "//\n" +
"// NB: This is slow as, and will lock your screen :)\n" + "// MTGA, moxfield, archidekt format example:\n" +
"// Deck\n" +
"// 4 Accumulated Knowledge (A25) 40\n" +
"// 2 Adarkar Wastes (EOC) 147\n" +
"// Commander\n" +
"// 1 Grizzly Bears\n" +
"//\n" +
"// Importing deck can take some time to finish\n" +
"\n" + "\n" +
"// Your current clipboard:\n" + "// Your current clipboard:\n" +
"\n"; "\n";
@ -68,17 +76,19 @@ public class DeckImportClipboardDialog extends MageDialog {
} }
private void onOK() { private void onOK() {
String decklist = editData.getText(); String importData = editData.getText();
decklist = decklist.replace(FORMAT_TEXT, ""); importData = importData.replace(FORMAT_TEXT, "").trim();
// find possible data format
String tempDeckPath; String tempDeckPath;
// This dialog also accepts a paste in .mtga format if (MtgaImporter.isMTGA(importData)) {
if (decklist.startsWith("Deck\n")) { // An .mtga list always starts with the first line being "Deck". This kind of paste is processed as .mtga // MTGA or Moxfield
tempDeckPath = DeckUtil.writeTextToTempFile("cbimportdeck", ".mtga", decklist); tempDeckPath = DeckUtil.writeTextToTempFile("cbimportdeck", ".mtga", importData);
} else { } else {
// If the paste is not .mtga format, it's processed as plaintext // text
tempDeckPath = DeckUtil.writeTextToTempFile(decklist); tempDeckPath = DeckUtil.writeTextToTempFile(importData);
} }
if (this.callback != null) { if (this.callback != null) {
callback.onImportDone(tempDeckPath); callback.onImportDone(tempDeckPath);
} }

View file

@ -8,6 +8,7 @@ import mage.cards.repository.CardInfo;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -18,7 +19,11 @@ import java.util.regex.Pattern;
public class MtgaImporter extends PlainTextDeckImporter { public class MtgaImporter extends PlainTextDeckImporter {
private static final Map<String, String> SET_REMAPPING = ImmutableMap.of("DAR", "DOM"); private static final Map<String, String> SET_REMAPPING = ImmutableMap.of("DAR", "DOM");
private static final Pattern MTGA_PATTERN = Pattern.compile(
// example:
// 4 Accumulated Knowledge (A25) 40
// 4 Accumulated Knowledge
public static final Pattern MTGA_PATTERN = Pattern.compile(
"(\\p{Digit}+)" + "(\\p{Digit}+)" +
"\\p{javaWhitespace}+" + "\\p{javaWhitespace}+" +
"(" + CardNameUtil.CARD_NAME_PATTERN.pattern() + ")" + "(" + CardNameUtil.CARD_NAME_PATTERN.pattern() + ")" +
@ -34,12 +39,15 @@ public class MtgaImporter extends PlainTextDeckImporter {
protected void readLine(String line, DeckCardLists deckList, FixedInfo fixedInfo) { protected void readLine(String line, DeckCardLists deckList, FixedInfo fixedInfo) {
line = line.trim(); line = line.trim();
String lowerLine = line.toLowerCase(Locale.ENGLISH);
if (line.equals("Deck")) { // mainboard to support decks from archidekt.com
if (lowerLine.equals("deck") || lowerLine.equals("mainboard")) {
sideboard = false;
return; return;
} }
if (line.equals("Sideboard") || line.equals("")) { if (lowerLine.equals("sideboard") || lowerLine.equals("commander") || lowerLine.equals("maybeboard") || lowerLine.equals("")) {
sideboard = true; sideboard = true;
return; return;
} }
@ -74,4 +82,34 @@ public class MtgaImporter extends PlainTextDeckImporter {
zone.addAll(Collections.nCopies(count, deckCardInfo.copy())); zone.addAll(Collections.nCopies(count, deckCardInfo.copy()));
} }
public static boolean isMTGA(String data) {
// examples:
// 4 Accumulated Knowledge (A25) 40
// 4 Accumulated Knowledge
// Deck
// Commander
// Mainboard - extra mark for archidekt.com
// Sideboard - extra mark for archidekt.com
// Maybeboard - extra mark for archidekt.com
String firstLine = data.split("\\R", 2)[0];
String firstLineLower = firstLine.toLowerCase(Locale.ENGLISH);
// by deck marks
if (firstLineLower.startsWith("deck")
|| firstLineLower.startsWith("mainboard")
|| firstLineLower.startsWith("sideboard")
|| firstLineLower.startsWith("commander")
|| firstLineLower.startsWith("maybeboard")) {
return true;
}
// by card marks
Matcher pattern = MtgaImporter.MTGA_PATTERN.matcher(CardNameUtil.normalizeCardName(firstLine));
return pattern.matches()
&& pattern.groupCount() >= 4
&& pattern.group(3) != null
&& pattern.group(4) != null;
}
} }