From 0929d02b26c2e915ee972162da766a10490342e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20Br=C3=BCbach?= Date: Mon, 21 Sep 2026 13:21:00 +0200 Subject: [PATCH] [Card] Add a Tag attribute to the card filter tree --- .../libcockatrice/filters/filter_card.cpp | 2 ++ .../libcockatrice/filters/filter_card.h | 1 + .../libcockatrice/filters/filter_tree.cpp | 13 +++++++++++++ .../libcockatrice/filters/filter_tree.h | 1 + tests/carddatabase/filter_string_test.cpp | 18 ++++++++++++++++++ 5 files changed, 35 insertions(+) diff --git a/libcockatrice_filters/libcockatrice/filters/filter_card.cpp b/libcockatrice_filters/libcockatrice/filters/filter_card.cpp index 26cddd53c..767989925 100644 --- a/libcockatrice_filters/libcockatrice/filters/filter_card.cpp +++ b/libcockatrice_filters/libcockatrice/filters/filter_card.cpp @@ -87,6 +87,8 @@ const QString CardFilter::attrName(Attr a) return tr("Loyalty"); case AttrFormat: return tr("Format"); + case AttrTag: + return tr("Tag"); case AttrMainType: return tr("Main Type"); case AttrSubType: diff --git a/libcockatrice_filters/libcockatrice/filters/filter_card.h b/libcockatrice_filters/libcockatrice/filters/filter_card.h index dbeffd18f..e5ee9893d 100644 --- a/libcockatrice_filters/libcockatrice/filters/filter_card.h +++ b/libcockatrice_filters/libcockatrice/filters/filter_card.h @@ -45,6 +45,7 @@ public: AttrMainType, AttrSubType, AttrFormat, + AttrTag, AttrEnd, }; diff --git a/libcockatrice_filters/libcockatrice/filters/filter_tree.cpp b/libcockatrice_filters/libcockatrice/filters/filter_tree.cpp index f12f87181..c4006b028 100644 --- a/libcockatrice_filters/libcockatrice/filters/filter_tree.cpp +++ b/libcockatrice_filters/libcockatrice/filters/filter_tree.cpp @@ -2,6 +2,7 @@ #include "filter_card.h" #include "libcockatrice/card/card_localization.h" +#include "libcockatrice/card/game_specific_terms.h" #include "libcockatrice/card/printing/printing_info.h" #include "libcockatrice/card/set/card_set.h" @@ -461,6 +462,16 @@ bool FilterItem::acceptFormat(const CardInfoPtr &info) const return info->getLegalityProp(lowercaseTerm) == "legal"; } +bool FilterItem::acceptTag(const CardInfoPtr &info) const +{ + const QString stored = info->getProperty(Mtg::Tags); + if (stored.isEmpty()) { + return false; + } + // Tags are stored space-separated; match whole slugs, not substrings. + return stored.split(" ", Qt::SkipEmptyParts).contains(term.trimmed(), Qt::CaseInsensitive); +} + bool FilterItem::acceptLoyalty(const CardInfoPtr &info) const { const QString loyaltyString = info->getLoyalty(); @@ -558,6 +569,8 @@ bool FilterItem::acceptCardAttr(const CardInfoPtr &info, return acceptLoyalty(info); case CardFilter::AttrFormat: return acceptFormat(info); + case CardFilter::AttrTag: + return acceptTag(info); case CardFilter::AttrMainType: return acceptMainType(info); case CardFilter::AttrSubType: diff --git a/libcockatrice_filters/libcockatrice/filters/filter_tree.h b/libcockatrice_filters/libcockatrice/filters/filter_tree.h index 85a57304d..f97b8bc32 100644 --- a/libcockatrice_filters/libcockatrice/filters/filter_tree.h +++ b/libcockatrice_filters/libcockatrice/filters/filter_tree.h @@ -254,6 +254,7 @@ public: [[nodiscard]] bool acceptCardAttr(const CardInfoPtr &info, CardFilter::Attr attr, const CardSearchLanguage &searchLanguage) const; [[nodiscard]] bool acceptFormat(const CardInfoPtr &info) const; + [[nodiscard]] bool acceptTag(const CardInfoPtr &info) const; [[nodiscard]] bool relationCheck(int cardInfo) const; }; diff --git a/tests/carddatabase/filter_string_test.cpp b/tests/carddatabase/filter_string_test.cpp index 37f0a1605..595d56c5f 100644 --- a/tests/carddatabase/filter_string_test.cpp +++ b/tests/carddatabase/filter_string_test.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -220,6 +221,23 @@ TEST_F(CardQuery, TagQueryTreatsCommasAsPartOfTheSlug) ASSERT_FALSE(FilterString("tags:ramp,removal").check(tagged)); } +TEST_F(CardQuery, FilterTreeTagAttribute) +{ + const CardData tagged = taggedCard(); + + FilterTree matching; + matching.termNode(CardFilter::AttrTag, CardFilter::TypeAnd, "ramp"); + ASSERT_TRUE(matching.acceptsCard(tagged, CardSearchLanguage{})); + + FilterTree partial; + partial.termNode(CardFilter::AttrTag, CardFilter::TypeAnd, "ram"); + ASSERT_FALSE(partial.acceptsCard(tagged, CardSearchLanguage{})); + + FilterTree missing; + missing.termNode(CardFilter::AttrTag, CardFilter::TypeAnd, "squirrel"); + ASSERT_FALSE(missing.acceptsCard(tagged, CardSearchLanguage{})); +} + TEST_F(CardQuery, TagQueryFalseWhenCardHasNoTags) { ASSERT_FALSE(FilterString("tags:ramp").check(cat));