[Filters] Precompute term data instead of redoing it per card (#7603)

* [Filters] Precompute term data instead of redoing it per card

Card filters are evaluated once per card in the database, and several
attributes derived constant values from the filter term on every single
evaluation:

- acceptColor() ran up to twelve case-insensitive string replacements and
  a space strip for each card, then called getColors() again for every
  character of the term.
- acceptRarity() ran the rarity abbreviation expansion loop per card.
- acceptManaCost() uppercased and sorted the term per card, before
  sorting the card's own cost.
- acceptLoyalty() and acceptFormat() trimmed/lowercased the term per card.
- relationCheck() re-parsed the numeric operator and operand per card,
  for every cmc/power/toughness/loyalty comparison.

The term of a FilterItem never changes, so these values are now computed
once in the constructor and only the card-dependent part remains in the
accept path. For colors this also removes the repeat getColors() call,
which materializes the printing properties hash.

Additionally:
- CardInfoPtr is passed by const reference through FilterItemList,
  FilterItem and FilterTree instead of by value, removing an atomic
  refcount pair per card per filter node.
- Main/sub type filtering no longer allocates a QStringList via split();
  it looks the " - " separator up directly and compares QStringViews.
- acceptSet() and acceptPowerToughness() fetch the printing/set or the
  power/toughness string once instead of once per branch/accessor.

No filtering behavior is intended to change. Invalid numeric terms (for
example a bare "<") now return false instead of indexing trimmedTerm[1]
past the end.

Verified with filter_string_test (42 tests) and a full client build.

* [Filters] Address review comments

- Replace the main/sub type separator accessor function with a plain
  namespace-scope constant.
- Take card type, colors, cmc, loyalty, power/toughness and the printing's
  set by value; those getters already return by value.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
This commit is contained in:
BruebachL
2026-10-23 11:23:17 +02:00
committed by GitHub
parent f4b4365464
commit b85436461e
2 changed files with 250 additions and 174 deletions

View File

@@ -9,9 +9,16 @@
#include <QList>
#include <QSharedPointer>
#include <QStringList>
#include <QStringView>
#include <algorithm>
#include <qnamespace.h>
namespace
{
/** Separator between the main and sub types of a card type ("Creature — Elf"). */
const QString MAIN_SUB_TYPE_SEPARATOR = QStringLiteral(" — ");
} // namespace
template <class T> FilterTreeNode *FilterTreeBranch<T>::nodeAt(int i) const
{
return (childNodes.size() > i) ? childNodes.at(i) : nullptr;
@@ -108,7 +115,7 @@ FilterTreeNode *FilterItemList::termNode(const QString &term)
return childNodes.at(i);
}
bool FilterItemList::testTypeAnd(const CardInfoPtr info,
bool FilterItemList::testTypeAnd(const CardInfoPtr &info,
CardFilter::Attr attr,
const CardSearchLanguage &searchLanguage) const
{
@@ -125,7 +132,7 @@ bool FilterItemList::testTypeAnd(const CardInfoPtr info,
return true;
}
bool FilterItemList::testTypeAndNot(const CardInfoPtr info,
bool FilterItemList::testTypeAndNot(const CardInfoPtr &info,
CardFilter::Attr attr,
const CardSearchLanguage &searchLanguage) const
{
@@ -133,7 +140,7 @@ bool FilterItemList::testTypeAndNot(const CardInfoPtr info,
return !testTypeOr(info, attr, searchLanguage);
}
bool FilterItemList::testTypeOr(const CardInfoPtr info,
bool FilterItemList::testTypeOr(const CardInfoPtr &info,
CardFilter::Attr attr,
const CardSearchLanguage &searchLanguage) const
{
@@ -156,7 +163,7 @@ bool FilterItemList::testTypeOr(const CardInfoPtr info,
return noChildEnabledChild;
}
bool FilterItemList::testTypeOrNot(const CardInfoPtr info,
bool FilterItemList::testTypeOrNot(const CardInfoPtr &info,
CardFilter::Attr attr,
const CardSearchLanguage &searchLanguage) const
{
@@ -164,7 +171,118 @@ bool FilterItemList::testTypeOrNot(const CardInfoPtr info,
return !testTypeAnd(info, attr, searchLanguage);
}
bool FilterItem::acceptName(const CardInfoPtr info, const CardSearchLanguage &searchLanguage) const
FilterItem::FilterItem(QString trm, FilterItemList *parent) : p(parent), term(std::move(trm))
{
lowercaseTerm = term.toLower();
switch (attr()) {
case CardFilter::AttrColor: {
QString converted = term.trimmed();
converted.replace("green", "g", Qt::CaseInsensitive);
converted.replace("grn", "g", Qt::CaseInsensitive);
converted.replace("blue", "u", Qt::CaseInsensitive);
converted.replace("blu", "u", Qt::CaseInsensitive);
converted.replace("black", "b", Qt::CaseInsensitive);
converted.replace("blk", "b", Qt::CaseInsensitive);
converted.replace("red", "r", Qt::CaseInsensitive);
converted.replace("white", "w", Qt::CaseInsensitive);
converted.replace("wht", "w", Qt::CaseInsensitive);
converted.replace("colorless", "c", Qt::CaseInsensitive);
converted.replace("colourless", "c", Qt::CaseInsensitive);
converted.replace("none", "c", Qt::CaseInsensitive);
converted.replace(QString(" "), QString(""), Qt::CaseInsensitive);
convertedColorTerm = converted;
break;
}
case CardFilter::AttrRarity: {
QString converted = term.trimmed();
/*
* The purpose of this loop is to only apply one of the replacement
* policies and then escape. If we attempt to layer them on top of
* each other, we will get awkward results (i.e. comythic rare mythic rareon)
* Conditional statement will exit once a case is successful in
* replacement OR we go through all possible cases.
* Will also need to replace just "mythic"
*/
for (int i = 0; converted.length() <= 3 && i <= 6; i++) {
switch (i) {
case 0:
converted.replace("mr", "mythic", Qt::CaseInsensitive);
break;
case 1:
converted.replace("m r", "mythic", Qt::CaseInsensitive);
break;
case 2:
converted.replace("m", "mythic", Qt::CaseInsensitive);
break;
case 3:
converted.replace("c", "common", Qt::CaseInsensitive);
break;
case 4:
converted.replace("u", "uncommon", Qt::CaseInsensitive);
break;
case 5:
converted.replace("r", "rare", Qt::CaseInsensitive);
break;
case 6:
converted.replace("s", "special", Qt::CaseInsensitive);
break;
default:
break;
}
}
convertedRarityTerm = converted;
break;
}
case CardFilter::AttrManaCost:
sortedManaCostTerm = term.toUpper();
// Sort the mana cost so it will be easy to find
std::sort(sortedManaCostTerm.begin(), sortedManaCostTerm.end());
break;
case CardFilter::AttrLoyalty:
uppercaseLoyaltyTerm = term.trimmed().toUpper();
break;
default:
break;
}
// Parse the numeric relation used by cmc, power, toughness and loyalty once.
// If the term is not numeric, it probably starts with an operator.
const QString trimmedTerm = term.trimmed();
bool converted = false;
const int value = trimmedTerm.toInt(&converted);
if (converted) {
numericOperator = NumericOperator::Equal;
numericOperand = value;
} else if (trimmedTerm.size() > 1) {
if (trimmedTerm.at(1) == '=') {
numericOperand = trimmedTerm.mid(2).toInt();
if (trimmedTerm.startsWith('<')) {
numericOperator = NumericOperator::LessEqual;
} else if (trimmedTerm.startsWith('>')) {
numericOperator = NumericOperator::GreaterEqual;
} else {
numericOperator = NumericOperator::Equal;
}
} else {
numericOperand = trimmedTerm.mid(1).toInt();
if (trimmedTerm.startsWith('<')) {
numericOperator = NumericOperator::Less;
} else if (trimmedTerm.startsWith('>')) {
numericOperator = NumericOperator::Greater;
} else if (trimmedTerm.startsWith('=')) {
numericOperator = NumericOperator::Equal;
}
}
}
}
bool FilterItem::acceptName(const CardInfoPtr &info, const CardSearchLanguage &searchLanguage) const
{
const QString &englishName = info->getName();
const QString &localizedName = info->getLocalizedName(searchLanguage.language);
@@ -187,7 +305,7 @@ bool FilterItem::acceptName(const CardInfoPtr info, const CardSearchLanguage &se
return false;
}
bool FilterItem::acceptNameExact(const CardInfoPtr info, const CardSearchLanguage &searchLanguage) const
bool FilterItem::acceptNameExact(const CardInfoPtr &info, const CardSearchLanguage &searchLanguage) const
{
const QString &englishName = info->getName();
const QString &localizedName = info->getLocalizedName(searchLanguage.language);
@@ -210,44 +328,42 @@ bool FilterItem::acceptNameExact(const CardInfoPtr info, const CardSearchLanguag
return false;
}
bool FilterItem::acceptType(const CardInfoPtr info) const
bool FilterItem::acceptType(const CardInfoPtr &info) const
{
return info->getCardType().contains(term, Qt::CaseInsensitive);
}
bool FilterItem::acceptMainType(const CardInfoPtr info) const
bool FilterItem::acceptMainType(const CardInfoPtr &info) const
{
const QStringList typeParts = info->getCardType().split(" — ");
return !typeParts.isEmpty() && typeParts[0].contains(term, Qt::CaseInsensitive);
const QString type = info->getCardType();
const qsizetype separator = type.indexOf(MAIN_SUB_TYPE_SEPARATOR);
if (separator >= 0) {
return QStringView(type).left(separator).contains(term, Qt::CaseInsensitive);
}
return type.contains(term, Qt::CaseInsensitive);
}
bool FilterItem::acceptSubType(const CardInfoPtr info) const
bool FilterItem::acceptSubType(const CardInfoPtr &info) const
{
const QStringList typeParts = info->getCardType().split(" — ");
return typeParts.size() > 1 && typeParts[1].contains(term, Qt::CaseInsensitive);
const QString type = info->getCardType();
const qsizetype separator = type.indexOf(MAIN_SUB_TYPE_SEPARATOR);
if (separator < 0) {
return false;
}
const qsizetype subTypeStart = separator + MAIN_SUB_TYPE_SEPARATOR.size();
const qsizetype nextSeparator = type.indexOf(MAIN_SUB_TYPE_SEPARATOR, subTypeStart);
const QStringView subType = nextSeparator < 0 ? QStringView(type).mid(subTypeStart)
: QStringView(type).mid(subTypeStart, nextSeparator - subTypeStart);
return subType.contains(term, Qt::CaseInsensitive);
}
bool FilterItem::acceptColor(const CardInfoPtr info) const
bool FilterItem::acceptColor(const CardInfoPtr &info) const
{
QString converted_term = term.trimmed();
converted_term.replace("green", "g", Qt::CaseInsensitive);
converted_term.replace("grn", "g", Qt::CaseInsensitive);
converted_term.replace("blue", "u", Qt::CaseInsensitive);
converted_term.replace("blu", "u", Qt::CaseInsensitive);
converted_term.replace("black", "b", Qt::CaseInsensitive);
converted_term.replace("blk", "b", Qt::CaseInsensitive);
converted_term.replace("red", "r", Qt::CaseInsensitive);
converted_term.replace("white", "w", Qt::CaseInsensitive);
converted_term.replace("wht", "w", Qt::CaseInsensitive);
converted_term.replace("colorless", "c", Qt::CaseInsensitive);
converted_term.replace("colourless", "c", Qt::CaseInsensitive);
converted_term.replace("none", "c", Qt::CaseInsensitive);
converted_term.replace(QString(" "), QString(""), Qt::CaseInsensitive);
const QString colors = info->getColors();
// Colorless card filter
if (converted_term.toLower() == "c" && info->getColors().length() < 1) {
if (convertedColorTerm.toLower() == "c" && colors.length() < 1) {
return true;
}
@@ -256,16 +372,16 @@ bool FilterItem::acceptColor(const CardInfoPtr info) const
* then we should match all of them to the card's colors
*/
int match_count = 0;
for (auto &it : converted_term) {
if (info->getColors().contains(it, Qt::CaseInsensitive)) {
for (const QChar &color : convertedColorTerm) {
if (colors.contains(color, Qt::CaseInsensitive)) {
match_count++;
}
}
return match_count == converted_term.length();
return match_count == convertedColorTerm.length();
}
bool FilterItem::acceptText(const CardInfoPtr info, const CardSearchLanguage &searchLanguage) const
bool FilterItem::acceptText(const CardInfoPtr &info, const CardSearchLanguage &searchLanguage) const
{
const QString &englishText = info->getText();
const QString &localizedText = info->getLocalizedText(searchLanguage.language);
@@ -288,51 +404,47 @@ bool FilterItem::acceptText(const CardInfoPtr info, const CardSearchLanguage &se
return false;
}
bool FilterItem::acceptSet(const CardInfoPtr info) const
bool FilterItem::acceptSet(const CardInfoPtr &info) const
{
bool status = false;
for (const auto &printings : info->getSets()) {
for (const auto &set : printings) {
if (set.getSet()->getShortName().compare(term, Qt::CaseInsensitive) == 0 ||
set.getSet()->getLongName().compare(term, Qt::CaseInsensitive) == 0) {
status = true;
break;
for (const auto &printing : printings) {
const CardSetPtr set = printing.getSet();
if (set && (set->getShortName().compare(term, Qt::CaseInsensitive) == 0 ||
set->getLongName().compare(term, Qt::CaseInsensitive) == 0)) {
return true;
}
}
}
return status;
return false;
}
bool FilterItem::acceptManaCost(const CardInfoPtr info) const
bool FilterItem::acceptManaCost(const CardInfoPtr &info) const
{
QString partialCost = term.toUpper();
// Sort the mana cost so it will be easy to find
std::sort(partialCost.begin(), partialCost.end());
// Try to seperate the mana cost in case it's a split card
// if it's not a split card the loop will run only once
for (QString fullManaCost : info->getManaCost().split("//")) {
for (const QString &manaCostPart : info->getManaCost().split("//")) {
QString fullManaCost = manaCostPart;
std::sort(fullManaCost.begin(), fullManaCost.end());
// If the partial is found in the full, return true
if (fullManaCost.contains(partialCost)) {
if (fullManaCost.contains(sortedManaCostTerm)) {
return true;
}
}
return false;
}
bool FilterItem::acceptCmc(const CardInfoPtr info) const
bool FilterItem::acceptCmc(const CardInfoPtr &info) const
{
const QString cmc = info->getCmc();
bool convertSuccess;
int cmcInt = info->getCmc().toInt(&convertSuccess);
int cmcInt = cmc.toInt(&convertSuccess);
// if conversion failed, check for the "//" separator used in split cards
if (!convertSuccess) {
int cmcSum = 0;
for (const QString &cmc : info->getCmc().split("//")) {
cmcInt = cmc.toInt();
for (const QString &cmcPart : cmc.split("//")) {
cmcInt = cmcPart.toInt();
cmcSum += cmcInt;
if (relationCheck(cmcInt)) {
return true;
@@ -344,39 +456,36 @@ bool FilterItem::acceptCmc(const CardInfoPtr info) const
}
}
bool FilterItem::acceptFormat(const CardInfoPtr info) const
bool FilterItem::acceptFormat(const CardInfoPtr &info) const
{
return info->getLegalityProp(term.toLower()) == "legal";
return info->getLegalityProp(lowercaseTerm) == "legal";
}
bool FilterItem::acceptLoyalty(const CardInfoPtr info) const
bool FilterItem::acceptLoyalty(const CardInfoPtr &info) const
{
if (info->getLoyalty().isEmpty()) {
const QString loyaltyString = info->getLoyalty();
if (loyaltyString.isEmpty()) {
return false;
} else {
bool success;
// if loyalty can't be converted to "int" it must be "X"
int loyalty = info->getLoyalty().toInt(&success);
if (success) {
return relationCheck(loyalty);
} else {
return term.trimmed().toUpper() == info->getLoyalty();
}
}
bool success;
// if loyalty can't be converted to "int" it must be "X"
int loyalty = loyaltyString.toInt(&success);
if (success) {
return relationCheck(loyalty);
}
return uppercaseLoyaltyTerm == loyaltyString;
}
bool FilterItem::acceptPowerToughness(const CardInfoPtr info, CardFilter::Attr attr) const
bool FilterItem::acceptPowerToughness(const CardInfoPtr &info, CardFilter::Attr attr) const
{
int slash = info->getPowTough().indexOf("/");
const QString powTough = info->getPowTough();
const qsizetype slash = powTough.indexOf("/");
if (slash == -1) {
return false;
}
QString valueString;
if (attr == CardFilter::AttrPow) {
valueString = info->getPowTough().mid(0, slash);
} else {
valueString = info->getPowTough().mid(slash + 1);
}
QString valueString = attr == CardFilter::AttrPow ? powTough.mid(0, slash) : powTough.mid(slash + 1);
if (term == valueString) {
return true;
}
@@ -386,49 +495,11 @@ bool FilterItem::acceptPowerToughness(const CardInfoPtr info, CardFilter::Attr a
return conversion ? relationCheck(value) : false;
}
bool FilterItem::acceptRarity(const CardInfoPtr info) const
bool FilterItem::acceptRarity(const CardInfoPtr &info) const
{
QString converted_term = term.trimmed();
/*
* The purpose of this loop is to only apply one of the replacement
* policies and then escape. If we attempt to layer them on top of
* each other, we will get awkward results (i.e. comythic rare mythic rareon)
* Conditional statement will exit once a case is successful in
* replacement OR we go through all possible cases.
* Will also need to replace just "mythic"
*/
for (int i = 0; converted_term.length() <= 3 && i <= 6; i++) {
switch (i) {
case 0:
converted_term.replace("mr", "mythic", Qt::CaseInsensitive);
break;
case 1:
converted_term.replace("m r", "mythic", Qt::CaseInsensitive);
break;
case 2:
converted_term.replace("m", "mythic", Qt::CaseInsensitive);
break;
case 3:
converted_term.replace("c", "common", Qt::CaseInsensitive);
break;
case 4:
converted_term.replace("u", "uncommon", Qt::CaseInsensitive);
break;
case 5:
converted_term.replace("r", "rare", Qt::CaseInsensitive);
break;
case 6:
converted_term.replace("s", "special", Qt::CaseInsensitive);
break;
default:
break;
}
}
for (const auto &printings : info->getSets()) {
for (const auto &printing : printings) {
if (printing.getProperty("rarity").compare(converted_term, Qt::CaseInsensitive) == 0) {
if (printing.getProperty("rarity").compare(convertedRarityTerm, Qt::CaseInsensitive) == 0) {
return true;
}
}
@@ -438,41 +509,25 @@ bool FilterItem::acceptRarity(const CardInfoPtr info) const
bool FilterItem::relationCheck(int cardInfo) const
{
bool result, conversion;
// if int conversion fails, there's probably an operator at the start
result = (cardInfo == term.toInt(&conversion));
if (!conversion) {
// leading whitespaces could cause indexing to fail
QString trimmedTerm = term.trimmed();
// check whether it's a 2 char operator (<=, >=, or ==)
if (trimmedTerm[1] == '=') {
int termInt = trimmedTerm.mid(2).toInt();
if (trimmedTerm.startsWith('<')) {
result = (cardInfo <= termInt);
} else if (trimmedTerm.startsWith('>')) {
result = (cardInfo >= termInt);
} else {
result = (cardInfo == termInt);
}
} else {
int termInt = trimmedTerm.mid(1).toInt();
if (trimmedTerm.startsWith('<')) {
result = (cardInfo < termInt);
} else if (trimmedTerm.startsWith('>')) {
result = (cardInfo > termInt);
} else if (trimmedTerm.startsWith("=")) {
result = (cardInfo == termInt);
} else {
// the int conversion hasn't failed due to an operator at the start
result = false;
}
}
switch (numericOperator) {
case NumericOperator::Equal:
return cardInfo == numericOperand;
case NumericOperator::Less:
return cardInfo < numericOperand;
case NumericOperator::LessEqual:
return cardInfo <= numericOperand;
case NumericOperator::Greater:
return cardInfo > numericOperand;
case NumericOperator::GreaterEqual:
return cardInfo >= numericOperand;
case NumericOperator::Invalid:
return false;
}
return result;
return false;
}
bool FilterItem::acceptCardAttr(const CardInfoPtr info,
bool FilterItem::acceptCardAttr(const CardInfoPtr &info,
CardFilter::Attr attr,
const CardSearchLanguage &searchLanguage) const
{
@@ -556,7 +611,7 @@ FilterTreeNode *FilterTree::termNode(const CardFilter *f)
return termNode(f->attr(), f->type(), f->term());
}
bool FilterTree::testAttr(const CardInfoPtr info, const LogicMap *lm, const CardSearchLanguage &searchLanguage) const
bool FilterTree::testAttr(const CardInfoPtr &info, const LogicMap *lm, const CardSearchLanguage &searchLanguage) const
{
const FilterItemList *fil;
bool status = true;
@@ -589,7 +644,7 @@ bool FilterTree::testAttr(const CardInfoPtr info, const LogicMap *lm, const Card
return status;
}
bool FilterTree::acceptsCard(const CardInfoPtr info, const CardSearchLanguage &searchLanguage) const
bool FilterTree::acceptsCard(const CardInfoPtr &info, const CardSearchLanguage &searchLanguage) const
{
for (auto i = childNodes.constBegin(); i != childNodes.constEnd(); i++) {
if ((*i)->isEnabled() && !testAttr(info, *i, searchLanguage)) {

View File

@@ -174,13 +174,13 @@ public:
}
[[nodiscard]] bool
testTypeAnd(CardInfoPtr info, CardFilter::Attr attr, const CardSearchLanguage &searchLanguage) const;
testTypeAnd(const CardInfoPtr &info, CardFilter::Attr attr, const CardSearchLanguage &searchLanguage) const;
[[nodiscard]] bool
testTypeAndNot(CardInfoPtr info, CardFilter::Attr attr, const CardSearchLanguage &searchLanguage) const;
testTypeAndNot(const CardInfoPtr &info, CardFilter::Attr attr, const CardSearchLanguage &searchLanguage) const;
[[nodiscard]] bool
testTypeOr(CardInfoPtr info, CardFilter::Attr attr, const CardSearchLanguage &searchLanguage) const;
testTypeOr(const CardInfoPtr &info, CardFilter::Attr attr, const CardSearchLanguage &searchLanguage) const;
[[nodiscard]] bool
testTypeOrNot(CardInfoPtr info, CardFilter::Attr attr, const CardSearchLanguage &searchLanguage) const;
testTypeOrNot(const CardInfoPtr &info, CardFilter::Attr attr, const CardSearchLanguage &searchLanguage) const;
};
class FilterItem : public FilterTreeNode
@@ -188,12 +188,33 @@ class FilterItem : public FilterTreeNode
private:
FilterItemList *const p;
/**
* Values derived from the filter term that never change while this node
* exists. Computing them in the constructor keeps them out of the per-card
* evaluation path.
*/
QString convertedColorTerm;
QString convertedRarityTerm;
QString sortedManaCostTerm;
QString uppercaseLoyaltyTerm;
QString lowercaseTerm;
enum class NumericOperator
{
Invalid,
Equal,
Less,
LessEqual,
Greater,
GreaterEqual,
};
NumericOperator numericOperator = NumericOperator::Invalid;
int numericOperand = 0;
public:
const QString term;
FilterItem(QString trm, FilterItemList *parent) : p(parent), term(std::move(trm))
{
}
FilterItem(QString trm, FilterItemList *parent);
virtual ~FilterItem() = default;
[[nodiscard]] CardFilter::Attr attr() const
@@ -217,22 +238,22 @@ public:
return true;
}
[[nodiscard]] bool acceptName(CardInfoPtr info, const CardSearchLanguage &searchLanguage) const;
[[nodiscard]] bool acceptNameExact(CardInfoPtr info, const CardSearchLanguage &searchLanguage) const;
[[nodiscard]] bool acceptType(CardInfoPtr info) const;
[[nodiscard]] bool acceptMainType(CardInfoPtr info) const;
[[nodiscard]] bool acceptSubType(CardInfoPtr info) const;
[[nodiscard]] bool acceptColor(CardInfoPtr info) const;
[[nodiscard]] bool acceptText(CardInfoPtr info, const CardSearchLanguage &searchLanguage) const;
[[nodiscard]] bool acceptSet(CardInfoPtr info) const;
[[nodiscard]] bool acceptManaCost(CardInfoPtr info) const;
[[nodiscard]] bool acceptCmc(CardInfoPtr info) const;
[[nodiscard]] bool acceptPowerToughness(CardInfoPtr info, CardFilter::Attr attr) const;
[[nodiscard]] bool acceptLoyalty(CardInfoPtr info) const;
[[nodiscard]] bool acceptRarity(CardInfoPtr info) const;
[[nodiscard]] bool acceptName(const CardInfoPtr &info, const CardSearchLanguage &searchLanguage) const;
[[nodiscard]] bool acceptNameExact(const CardInfoPtr &info, const CardSearchLanguage &searchLanguage) const;
[[nodiscard]] bool acceptType(const CardInfoPtr &info) const;
[[nodiscard]] bool acceptMainType(const CardInfoPtr &info) const;
[[nodiscard]] bool acceptSubType(const CardInfoPtr &info) const;
[[nodiscard]] bool acceptColor(const CardInfoPtr &info) const;
[[nodiscard]] bool acceptText(const CardInfoPtr &info, const CardSearchLanguage &searchLanguage) const;
[[nodiscard]] bool acceptSet(const CardInfoPtr &info) const;
[[nodiscard]] bool acceptManaCost(const CardInfoPtr &info) const;
[[nodiscard]] bool acceptCmc(const CardInfoPtr &info) const;
[[nodiscard]] bool acceptPowerToughness(const CardInfoPtr &info, CardFilter::Attr attr) const;
[[nodiscard]] bool acceptLoyalty(const CardInfoPtr &info) const;
[[nodiscard]] bool acceptRarity(const CardInfoPtr &info) const;
[[nodiscard]] bool
acceptCardAttr(CardInfoPtr info, CardFilter::Attr attr, const CardSearchLanguage &searchLanguage) const;
[[nodiscard]] bool acceptFormat(CardInfoPtr info) const;
acceptCardAttr(const CardInfoPtr &info, CardFilter::Attr attr, const CardSearchLanguage &searchLanguage) const;
[[nodiscard]] bool acceptFormat(const CardInfoPtr &info) const;
[[nodiscard]] bool relationCheck(int cardInfo) const;
};
@@ -251,7 +272,7 @@ private:
LogicMap *attrLogicMap(CardFilter::Attr attr);
FilterItemList *attrTypeList(CardFilter::Attr attr, CardFilter::Type type);
bool testAttr(CardInfoPtr info, const LogicMap *lm, const CardSearchLanguage &searchLanguage) const;
bool testAttr(const CardInfoPtr &info, const LogicMap *lm, const CardSearchLanguage &searchLanguage) const;
void nodeChanged() const override
{
@@ -290,7 +311,7 @@ public:
return 0;
}
[[nodiscard]] bool acceptsCard(CardInfoPtr info, const CardSearchLanguage &searchLanguage) const;
[[nodiscard]] bool acceptsCard(const CardInfoPtr &info, const CardSearchLanguage &searchLanguage) const;
void removeFiltersByAttr(CardFilter::Attr filterType);
void removeFilter(const CardFilter *toRemove);
void clear();