mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-09-21 09:05:10 -07:00
Merge branch 'master' into tooomm-qt5
This commit is contained in:
commit
b3813c202b
162 changed files with 4862 additions and 653 deletions
|
|
@ -12,6 +12,7 @@
|
|||
#include <QSet>
|
||||
#include <algorithm>
|
||||
#include <climits>
|
||||
#include <libcockatrice/card/card_localization.h>
|
||||
#include <libcockatrice/card/database/parser/cockatrice_xml_4.h>
|
||||
#include <libcockatrice/card/relation/card_relation.h>
|
||||
|
||||
|
|
@ -22,8 +23,9 @@ static const QList<AllowedCount> kSingletonCounts = {{1, "legal"}, {0, "banned"}
|
|||
SplitCardPart::SplitCardPart(const QString &_name,
|
||||
const QString &_text,
|
||||
const QHash<QString, QString> &_properties,
|
||||
const PrintingInfo &_printingInfo)
|
||||
: name(_name), text(_text), properties(_properties), printingInfo(_printingInfo)
|
||||
const PrintingInfo &_printingInfo,
|
||||
const QString &_localizedText)
|
||||
: name(_name), text(_text), localizedText(_localizedText), properties(_properties), printingInfo(_printingInfo)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
@ -33,6 +35,42 @@ OracleImporter::OracleImporter(QObject *parent) : QObject(parent)
|
|||
{
|
||||
}
|
||||
|
||||
void OracleImporter::setCardLang(const QString &lang)
|
||||
{
|
||||
cardLang = lang.trimmed().toLower();
|
||||
localizationEnabled = cardLang != "en" && CardLocalization::supportedLanguages().contains(cardLang);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Maps the MTGJSON foreignData language names to the short codes used by
|
||||
* Scryfall and stored in cards.xml (e.g. "German" -> "de").
|
||||
* @param language The language name found in the MTGJSON foreignData entries.
|
||||
* @return The short language code, or an empty string if unknown.
|
||||
*/
|
||||
static QString mtgjsonLanguageToCode(const QString &language)
|
||||
{
|
||||
static const QHash<QString, QString> map = {
|
||||
{"Chinese Simplified", "zhs"},
|
||||
{"Chinese Traditional", "zht"},
|
||||
{"English", "en"},
|
||||
{"French", "fr"},
|
||||
{"German", "de"},
|
||||
{"Greek", "grc"},
|
||||
{"Ancient Greek", "grc"},
|
||||
{"Hebrew", "he"},
|
||||
{"Italian", "it"},
|
||||
{"Japanese", "ja"},
|
||||
{"Korean", "ko"},
|
||||
{"Latin", "la"},
|
||||
{"Phyrexian", "ph"},
|
||||
{"Portuguese (Brazil)", "pt"},
|
||||
{"Russian", "ru"},
|
||||
{"Sanskrit", "sa"},
|
||||
{"Spanish", "es"},
|
||||
};
|
||||
return map.value(language);
|
||||
}
|
||||
|
||||
static CardSet::Priority getSetPriority(const QString &setType, const QString &shortName)
|
||||
{
|
||||
if (!setTypePriorities.contains(setType.toLower())) {
|
||||
|
|
@ -254,6 +292,101 @@ static QString getJsonString(const QJsonObject &obj, const QString &key)
|
|||
return obj.value(key).toVariant().toString();
|
||||
}
|
||||
|
||||
static QString normalizeCardName(QString name)
|
||||
{
|
||||
// Mirror of the name cleanup applied in addCard(), so collected localization
|
||||
// keys line up with the card map keys (Æ → AE, curly apostrophe → straight).
|
||||
name = name.replace("Æ", "AE");
|
||||
name = name.replace("’", "'");
|
||||
return name;
|
||||
}
|
||||
|
||||
static QString matchingForeignEntryText(const QJsonObject &card, const QString &cardLang)
|
||||
{
|
||||
// Multi-face cards (split/aftermath/adventure/prepare) expose each face as a
|
||||
// separate card object, each with its own foreignData entry carrying that
|
||||
// face's rules text; single-face cards carry the full text in one entry.
|
||||
const QJsonArray foreignData = card.value("foreignData").toArray();
|
||||
for (const QJsonValue &entryValue : foreignData) {
|
||||
const QJsonObject entry = entryValue.toObject();
|
||||
// MTGJSON reports languages by long-form name ("German"); match the
|
||||
// short code ("de") that Scryfall and cards.xml use.
|
||||
if (mtgjsonLanguageToCode(getJsonString(entry, "language")) == cardLang) {
|
||||
return getJsonString(entry, "text");
|
||||
}
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
|
||||
void OracleImporter::collectForeignData(const QString &cardKey,
|
||||
const CardSetPtr ¤tSet,
|
||||
const QJsonObject &card,
|
||||
bool collectText)
|
||||
{
|
||||
if (!localizationEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
LocalizedCardEntry incoming;
|
||||
bool found = false;
|
||||
const QJsonArray foreignData = card.value("foreignData").toArray();
|
||||
for (const QJsonValue &entryValue : foreignData) {
|
||||
const QJsonObject entry = entryValue.toObject();
|
||||
// MTGJSON reports languages by long-form name ("German"); match the
|
||||
// short code ("de") that Scryfall and cards.xml use.
|
||||
if (mtgjsonLanguageToCode(getJsonString(entry, "language")) != cardLang) {
|
||||
continue;
|
||||
}
|
||||
incoming.name = getJsonString(entry, "name");
|
||||
incoming.text = collectText ? getJsonString(entry, "text") : QString();
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
if (!found) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Prefer the entry from the highest-priority set (lower enum value = more
|
||||
// authoritative); printings of equal priority keep the first one seen.
|
||||
incoming.priority = currentSet->getPriority();
|
||||
const auto existing = localizedEntries.constFind(cardKey);
|
||||
if (existing == localizedEntries.constEnd() || incoming.priority < existing->priority) {
|
||||
localizedEntries.insert(cardKey, incoming);
|
||||
}
|
||||
}
|
||||
|
||||
void OracleImporter::applyLocalizedData()
|
||||
{
|
||||
if (!localizationEnabled) {
|
||||
return;
|
||||
}
|
||||
for (auto it = localizedEntries.constBegin(); it != localizedEntries.constEnd(); ++it) {
|
||||
CardInfoPtr card = cards.value(it.key());
|
||||
if (card.isNull()) {
|
||||
continue;
|
||||
}
|
||||
const LocalizedCardEntry &entry = it.value();
|
||||
if (!entry.name.isEmpty()) {
|
||||
card->setLocalizedName(cardLang, entry.name);
|
||||
}
|
||||
if (!entry.text.isEmpty()) {
|
||||
card->setLocalizedText(cardLang, entry.text);
|
||||
}
|
||||
}
|
||||
localizedEntries.clear();
|
||||
|
||||
for (auto it = splitLocalizedTexts.constBegin(); it != splitLocalizedTexts.constEnd(); ++it) {
|
||||
CardInfoPtr card = cards.value(it.key());
|
||||
if (card.isNull()) {
|
||||
continue;
|
||||
}
|
||||
if (!it.value().text.isEmpty()) {
|
||||
card->setLocalizedText(cardLang, it.value().text);
|
||||
}
|
||||
}
|
||||
splitLocalizedTexts.clear();
|
||||
}
|
||||
|
||||
int OracleImporter::importCardsFromSet(const CardSetPtr ¤tSet, const QJsonArray &cardsList)
|
||||
{
|
||||
// mtgjson name => xml name
|
||||
|
|
@ -397,13 +530,21 @@ int OracleImporter::importCardsFromSet(const CardSetPtr ¤tSet, const QJson
|
|||
// split cards are considered a single card, enqueue for later merging
|
||||
if (layout == "split" || layout == "aftermath" || layout == "adventure" || layout == "prepare") {
|
||||
auto _faceName = getJsonString(card, "faceName");
|
||||
SplitCardPart split(_faceName, text, properties, printingInfo);
|
||||
// MTGJSON exposes each face as a separate card object, each with its
|
||||
// own foreignData entry holding that face's rules text; collect it so
|
||||
// the per-face texts can be joined the same way as the English text.
|
||||
const QString faceLocalizedText =
|
||||
localizationEnabled ? matchingForeignEntryText(card, cardLang) : QString();
|
||||
SplitCardPart split(_faceName, text, properties, printingInfo, faceLocalizedText);
|
||||
auto found_iter = splitCards.find(name + numProperty);
|
||||
if (found_iter == splitCards.end()) {
|
||||
splitCards.insert(name + numProperty, {{split}, name});
|
||||
} else {
|
||||
found_iter->first.append(split);
|
||||
}
|
||||
// MTGJSON's foreignData name is the joined name present on every
|
||||
// face, so collect the name once.
|
||||
collectForeignData(normalizeCardName(name), currentSet, card, false);
|
||||
} else {
|
||||
// relations
|
||||
QList<CardRelation *> relatedCards;
|
||||
|
|
@ -446,6 +587,8 @@ int OracleImporter::importCardsFromSet(const CardSetPtr ¤tSet, const QJson
|
|||
}
|
||||
}
|
||||
|
||||
collectForeignData(normalizeCardName(name + numComponent), currentSet, card);
|
||||
|
||||
CardInfoPtr newCard =
|
||||
addCard(name + numComponent, text, isToken, std::move(properties), relatedCards, printingInfo);
|
||||
numCards++;
|
||||
|
|
@ -459,6 +602,8 @@ int OracleImporter::importCardsFromSet(const CardSetPtr ¤tSet, const QJson
|
|||
QList<QPair<QList<SplitCardPart>, QString>> partsAndNames = splitCards.values();
|
||||
for (auto [splitCardParts, name] : partsAndNames) {
|
||||
QString text;
|
||||
QString localizedText;
|
||||
bool localizedTextComplete = true;
|
||||
QHash<QString, QString> properties;
|
||||
PrintingInfo printingInfo;
|
||||
|
||||
|
|
@ -468,6 +613,21 @@ int OracleImporter::importCardsFromSet(const CardSetPtr ¤tSet, const QJson
|
|||
}
|
||||
text.append(tmp.getText());
|
||||
|
||||
// Build the cardLang text by joining each face's translated text with
|
||||
// the same separator as the English text. Any face missing a complete
|
||||
// translation abandons the whole join, falling back to the English text.
|
||||
if (localizedTextComplete) {
|
||||
const QString partLocalizedText = tmp.getLocalizedText();
|
||||
if (partLocalizedText.isEmpty()) {
|
||||
localizedTextComplete = false;
|
||||
} else {
|
||||
if (!localizedText.isEmpty()) {
|
||||
localizedText.append(splitCardTextSeparator);
|
||||
}
|
||||
localizedText.append(partLocalizedText);
|
||||
}
|
||||
}
|
||||
|
||||
if (properties.isEmpty()) {
|
||||
properties = tmp.getProperties();
|
||||
printingInfo = tmp.getPrintingInfo();
|
||||
|
|
@ -500,6 +660,18 @@ int OracleImporter::importCardsFromSet(const CardSetPtr ¤tSet, const QJson
|
|||
}
|
||||
}
|
||||
CardInfoPtr newCard = addCard(name, text, isToken, std::move(properties), {}, printingInfo);
|
||||
if (localizationEnabled && localizedTextComplete && !localizedText.isEmpty()) {
|
||||
// Same priority policy as collectForeignData(): the joined text from the
|
||||
// highest-priority set seen so far wins, applied once all printings are in.
|
||||
LocalizedCardEntry entry;
|
||||
entry.text = localizedText;
|
||||
entry.priority = currentSet->getPriority();
|
||||
const QString entryKey = normalizeCardName(name);
|
||||
const auto existing = splitLocalizedTexts.constFind(entryKey);
|
||||
if (existing == splitLocalizedTexts.constEnd() || entry.priority < existing->priority) {
|
||||
splitLocalizedTexts.insert(entryKey, entry);
|
||||
}
|
||||
}
|
||||
numCards++;
|
||||
}
|
||||
|
||||
|
|
@ -654,6 +826,8 @@ int OracleImporter::startImport()
|
|||
emit setIndexChanged(numCardsInSet, setIndex, curSetToParse.getLongName());
|
||||
}
|
||||
|
||||
applyLocalizedData();
|
||||
|
||||
emit setIndexChanged(0, setIndex, QString());
|
||||
|
||||
// total number of sets
|
||||
|
|
@ -679,4 +853,6 @@ void OracleImporter::clear()
|
|||
cards.clear();
|
||||
allSets.clear();
|
||||
rawSetsData.clear();
|
||||
localizedEntries.clear();
|
||||
splitLocalizedTexts.clear();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -107,7 +107,8 @@ public:
|
|||
SplitCardPart(const QString &_name,
|
||||
const QString &_text,
|
||||
const QHash<QString, QString> &_properties,
|
||||
const PrintingInfo &_printingInfo);
|
||||
const PrintingInfo &_printingInfo,
|
||||
const QString &_localizedText = QString());
|
||||
inline const QString &getName() const
|
||||
{
|
||||
return name;
|
||||
|
|
@ -116,6 +117,13 @@ public:
|
|||
{
|
||||
return text;
|
||||
}
|
||||
/**
|
||||
* @brief The cardLang rules text of this face's foreignData entry, if any.
|
||||
*/
|
||||
inline const QString &getLocalizedText() const
|
||||
{
|
||||
return localizedText;
|
||||
}
|
||||
inline const QHash<QString, QString> &getProperties() const
|
||||
{
|
||||
return properties;
|
||||
|
|
@ -128,10 +136,18 @@ public:
|
|||
private:
|
||||
QString name;
|
||||
QString text;
|
||||
QString localizedText;
|
||||
QHash<QString, QString> properties;
|
||||
PrintingInfo printingInfo;
|
||||
};
|
||||
|
||||
struct LocalizedCardEntry
|
||||
{
|
||||
QString name;
|
||||
QString text;
|
||||
CardSet::Priority priority = CardSet::PriorityLowest;
|
||||
};
|
||||
|
||||
class OracleImporter : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
|
@ -171,12 +187,53 @@ private:
|
|||
*/
|
||||
QAtomicInt importCancelled;
|
||||
|
||||
/**
|
||||
* The ISO-639 language code whose foreignData is imported; "en" by default.
|
||||
*/
|
||||
QString cardLang = "en";
|
||||
|
||||
/**
|
||||
* Whether cardLang is a supported language other than English, so per-card
|
||||
* foreignData scanning can be skipped entirely when disabled.
|
||||
*/
|
||||
bool localizationEnabled = false;
|
||||
|
||||
/**
|
||||
* Localized name/text collected per imported card key while parsing sets,
|
||||
* applied to the CardInfo objects by applyLocalizedData() once all
|
||||
* printings have been seen so the best-priority one wins.
|
||||
*/
|
||||
QMap<QString, LocalizedCardEntry> localizedEntries;
|
||||
|
||||
/**
|
||||
* cardLang rules text collected for split-card names while parsing sets,
|
||||
* applied by applyLocalizedData(). Kept apart from localizedEntries because
|
||||
* MTGJSON emits each split face as its own card object with the joined name
|
||||
* on every foreignData entry: names and the per-face text join have different
|
||||
* completeness and must not overwrite each other under the same key.
|
||||
*/
|
||||
QMap<QString, LocalizedCardEntry> splitLocalizedTexts;
|
||||
|
||||
CardInfoPtr addCard(QString name,
|
||||
const QString &text,
|
||||
bool isToken,
|
||||
QHash<QString, QString> properties,
|
||||
const QList<CardRelation *> &relatedCards,
|
||||
const PrintingInfo &printingInfo);
|
||||
|
||||
/**
|
||||
* Records the first foreignData entry matching cardLang for the given card
|
||||
* key, keeping the entry from the highest-priority set seen so far.
|
||||
*
|
||||
* Multi-face cards (split, adventure, aftermath, prepare) pass collectText =
|
||||
* false: MTGJSON emits one foreignData entry per face with the same joined
|
||||
* name but only that face's text, so the name is collected here while the
|
||||
* per-face texts are joined during the split-card merge.
|
||||
*/
|
||||
void collectForeignData(const QString &cardKey,
|
||||
const CardSetPtr ¤tSet,
|
||||
const QJsonObject &card,
|
||||
bool collectText = true);
|
||||
signals:
|
||||
void setIndexChanged(int cardsImported, int setIndex, const QString &setName);
|
||||
void dataReadProgress(int bytesRead, int totalBytes);
|
||||
|
|
@ -195,6 +252,15 @@ public:
|
|||
{
|
||||
progressReporting = enabled;
|
||||
}
|
||||
/**
|
||||
* Selects the ISO-639 language code whose foreignData is imported.
|
||||
* English (the default) and unsupported codes disable localization.
|
||||
*/
|
||||
void setCardLang(const QString &lang);
|
||||
const QString &getCardLang() const
|
||||
{
|
||||
return cardLang;
|
||||
}
|
||||
/**
|
||||
* Scans the given JSON document for set metadata. Takes the data by value so
|
||||
* the wizard can hand over its decompressed buffer without copying it.
|
||||
|
|
@ -212,6 +278,12 @@ public:
|
|||
{
|
||||
importCancelled.storeRelease(1);
|
||||
}
|
||||
/**
|
||||
* Applies the collected localized names/texts to the imported cards.
|
||||
* Called automatically at the end of startImport(); exposed separately so
|
||||
* tests can drive it after importing sets directly.
|
||||
*/
|
||||
void applyLocalizedData();
|
||||
bool saveToFile(const QString &fileName, const QString &sourceUrl, const QString &sourceVersion);
|
||||
int importCardsFromSet(const CardSetPtr ¤tSet, const QJsonArray &cardsList);
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
#include <QScrollBar>
|
||||
#include <QtConcurrent>
|
||||
#include <QtGui>
|
||||
#include <libcockatrice/settings/cards_display_settings.h>
|
||||
#include <libcockatrice/settings/personal_settings.h>
|
||||
|
||||
OracleWizard::OracleWizard(QWidget *parent) : QWizard(parent)
|
||||
|
|
@ -37,6 +38,9 @@ OracleWizard::OracleWizard(QWidget *parent) : QWizard(parent)
|
|||
connect(&SettingsCache::instance().personal(), &PersonalSettings::langChanged, this, &OracleWizard::updateLanguage);
|
||||
|
||||
importer = new OracleImporter(this);
|
||||
// Import card text in the language the client displays, if supported:
|
||||
// foreignData for any other language is never imported.
|
||||
importer->setCardLang(SettingsCache::instance().cardsDisplay().getCardLang());
|
||||
|
||||
nam = new QNetworkAccessManager(this);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue