diff --git a/libcockatrice_card/libcockatrice/card/game_specific_terms.h b/libcockatrice_card/libcockatrice/card/game_specific_terms.h index e9160e514..5be8ec6d5 100644 --- a/libcockatrice_card/libcockatrice/card/game_specific_terms.h +++ b/libcockatrice_card/libcockatrice/card/game_specific_terms.h @@ -28,6 +28,7 @@ QString const PowTough("pt"); QString const Side("side"); QString const Layout("layout"); QString const ColorIdentity("coloridentity"); +QString const Tags("tags"); inline static const QString getNicePropertyName(QString key) { @@ -61,6 +62,9 @@ inline static const QString getNicePropertyName(QString key) if (key == ColorIdentity) { return QCoreApplication::translate("Mtg", "Color Identity"); } + if (key == Tags) { + return QCoreApplication::translate("Mtg", "Tags"); + } return key; } } // namespace Mtg diff --git a/oracle/src/oracleimporter.cpp b/oracle/src/oracleimporter.cpp index 88b522197..4b72817dc 100644 --- a/oracle/src/oracleimporter.cpp +++ b/oracle/src/oracleimporter.cpp @@ -505,6 +505,21 @@ int OracleImporter::importCardsFromSet(const CardSetPtr ¤tSet, const QJson properties.insert("coloridentity", colorIdentity); } + // Scryfall Tagger tags (resolved by MTGJSON into the `tags` array). + // Stored space-separated so the `tags:` search can match each slug as a + // discrete token; slugs never contain whitespace. + QStringList tags; + for (const QJsonValue &tag : card.value("tags").toArray()) { + const QString tagSlug = tag.toString().trimmed().toLower(); + if (!tagSlug.isEmpty() && !tags.contains(tagSlug)) { + tags.append(tagSlug); + } + } + if (!tags.isEmpty()) { + tags.sort(); + properties.insert("tags", tags.join(" ")); + } + const auto &mainCardType = getMainCardType(card.value("types").toVariant().toStringList()); if (mainCardType.isEmpty()) { qDebug() << "warning: no mainCardType for card:" << name; @@ -651,6 +666,14 @@ int OracleImporter::importCardsFromSet(const CardSetPtr ¤tSet, const QJson if (newPriority >= 0 && (currentPriority < 0 || newPriority < currentPriority)) { properties.insert(prop, thisCardPropertyValue); } + } else if (prop == "tags") { + // Tags are oracle-level: union both faces instead of + // concatenating them with the split-card separator. + QStringList merged = + (originalPropertyValue + " " + thisCardPropertyValue).split(" ", Qt::SkipEmptyParts); + merged.removeDuplicates(); + merged.sort(); + properties.insert(prop, merged.join(" ")); } else { properties.insert(prop, originalPropertyValue + splitCardPropSeparator + thisCardPropertyValue); diff --git a/tests/oracle/oracle_importer_test.cpp b/tests/oracle/oracle_importer_test.cpp index f66616e37..2b902a10e 100644 --- a/tests/oracle/oracle_importer_test.cpp +++ b/tests/oracle/oracle_importer_test.cpp @@ -523,6 +523,64 @@ TEST_F(OracleImporterTest, LegacyConvertedManaCostCoercedToCmc) ASSERT_EQ(result->getProperty("cmc"), "3"); } +// ============================================================================ +// Scryfall Tagger tag tests +// ============================================================================ + +TEST_F(OracleImporterTest, ImportsScryfallTags) +{ + QJsonObject card = makeCard("Ramp Card"); + card["tags"] = QJsonArray{"ramp", "removal"}; + QJsonArray cards{card}; + + importer->importCardsFromSet(set, cards); + auto result = importer->getCardList().value("Ramp Card"); + ASSERT_FALSE(result.isNull()); + ASSERT_EQ(result->getProperty("tags"), "ramp removal"); +} + +TEST_F(OracleImporterTest, TagsAreNormalizedDedupedAndSorted) +{ + QJsonObject card = makeCard("Messy Tags"); + card["tags"] = QJsonArray{"Ramp", " removal ", "ramp", ""}; + QJsonArray cards{card}; + + importer->importCardsFromSet(set, cards); + auto result = importer->getCardList().value("Messy Tags"); + ASSERT_FALSE(result.isNull()); + ASSERT_EQ(result->getProperty("tags"), "ramp removal"); +} + +TEST_F(OracleImporterTest, CardsWithoutTagsHaveNoTagsProperty) +{ + QJsonArray cards{makeCard("Untagged Card")}; + importer->importCardsFromSet(set, cards); + + auto result = importer->getCardList().value("Untagged Card"); + ASSERT_FALSE(result.isNull()); + ASSERT_FALSE(result->hasProperty("tags")); +} + +TEST_F(OracleImporterTest, SplitCardTagsAreUnioned) +{ + QJsonObject face1 = makeCard("Fire // Ice"); + face1["layout"] = "split"; + face1["side"] = "a"; + face1["faceName"] = "Fire"; + face1["tags"] = QJsonArray{"removal"}; + QJsonObject face2 = makeCard("Fire // Ice"); + face2["layout"] = "split"; + face2["side"] = "b"; + face2["faceName"] = "Ice"; + face2["tags"] = QJsonArray{"card-advantage", "removal"}; + QJsonArray cards{face1, face2}; + + importer->importCardsFromSet(set, cards); + auto result = importer->getCardList().value("Fire // Ice"); + ASSERT_FALSE(result.isNull()); + ASSERT_EQ(result->getProperty("tags"), "card-advantage removal"); +} + // ============================================================================ // Card deduplication tests // ============================================================================