Store allowedCounts by format

Took 15 minutes

Took 6 seconds
This commit is contained in:
Lukas Brübach 2025-12-13 13:56:09 +01:00
parent 0d33a79387
commit b1f0026b6c
4 changed files with 94 additions and 43 deletions

View file

@ -109,10 +109,26 @@ static QSharedPointer<FormatRules> parseFormat(QXmlStreamReader &xml)
rulesPtr->maxDeckSize = text.toInt();
} else if (xmlName == "maxSideboardSize") {
rulesPtr->maxSideboardSize = xml.readElementText().toInt();
} else if (xmlName == "maxCopies") {
rulesPtr->maxCopies = xml.readElementText().toInt();
} else if (xmlName == "maxRestrictedCopies") {
rulesPtr->maxRestrictedCopies = xml.readElementText().toInt();
} else if (xmlName == "allowedCounts") {
while (!xml.atEnd()) {
token = xml.readNext();
if (token == QXmlStreamReader::EndElement && xml.name().toString() == "allowedCounts") {
break;
}
if (token == QXmlStreamReader::StartElement && xml.name().toString() == "count") {
AllowedCount c;
QString maxAttr = xml.attributes().value("max").toString();
c.max = (maxAttr == "unlimited") ? -1 : maxAttr.toInt();
c.label = xml.readElementText().trimmed();
rulesPtr->allowedCounts.append(c);
}
}
} else if (xmlName == "exceptions") {
while (!xml.atEnd()) {
token = xml.readNext();
@ -387,8 +403,18 @@ static QXmlStreamWriter &operator<<(QXmlStreamWriter &xml, const QSharedPointer<
xml.writeTextElement("minDeckSize", QString::number(rules.minDeckSize));
xml.writeTextElement("maxDeckSize", rules.maxDeckSize >= 0 ? QString::number(rules.maxDeckSize) : "0");
xml.writeTextElement("maxSideboardSize", QString::number(rules.maxSideboardSize));
xml.writeTextElement("maxCopies", QString::number(rules.maxCopies));
xml.writeTextElement("maxRestrictedCopies", QString::number(rules.maxRestrictedCopies));
if (!rules.allowedCounts.isEmpty()) {
xml.writeStartElement("allowedCounts");
for (const AllowedCount &c : rules.allowedCounts) {
xml.writeStartElement("count");
xml.writeAttribute("max", c.max == -1 ? "unlimited" : QString::number(c.max));
xml.writeCharacters(c.label);
xml.writeEndElement(); // count
}
xml.writeEndElement(); // allowedCounts
}
if (!rules.exceptions.isEmpty()) {
xml.writeStartElement("exceptions");

View file

@ -15,6 +15,12 @@ struct CardCondition
QString value; // e.g. "Basic Land"
};
struct AllowedCount
{
int max = 0; // 4, 1, 0, or -1 for unlimited
QString label; // "legal", "restricted", "banned"
};
struct ExceptionRule
{
QList<CardCondition> conditions; // All must match
@ -28,8 +34,7 @@ struct FormatRules
int maxDeckSize = -1; // -1 = unlimited
int maxSideboardSize = 15;
int maxCopies = 4; // default constructed rule
int maxRestrictedCopies = 1; // Used for "restricted" legality tags
QList<AllowedCount> allowedCounts;
QList<ExceptionRule> exceptions; // Cards allowed to break maxCopies
};

View file

@ -622,6 +622,17 @@ bool DeckListModel::isCardLegalForCurrentFormat(const CardInfoPtr cardInfo)
return true;
}
int maxAllowedForLegality(const FormatRules &format, const QString &legality)
{
for (const AllowedCount &c : format.allowedCounts) {
if (c.label == legality) {
return c.max;
}
}
return -1; // unknown legality → treat as illegal
}
bool DeckListModel::isCardQuantityLegalForCurrentFormat(const CardInfoPtr cardInfo, int quantity)
{
auto formatRules = CardDatabaseManager::query()->getFormat(deckList->getGameFormat());
@ -630,24 +641,29 @@ bool DeckListModel::isCardQuantityLegalForCurrentFormat(const CardInfoPtr cardIn
return true;
}
if (cardHasAnyException(*cardInfo.data(), *formatRules.data())) {
// Exceptions always win
if (cardHasAnyException(*cardInfo, *formatRules)) {
return true;
}
if (cardInfo->getProperties().contains("format-" + deckList->getGameFormat())) {
QString formatLegality = cardInfo->getProperty("format-" + deckList->getGameFormat());
if (formatLegality == "legal") {
if (quantity <= formatRules->maxCopies) {
return true;
}
} else if (formatLegality == "restricted") {
if (quantity <= formatRules->maxRestrictedCopies) {
return true;
}
}
const QString legalityProp = "format-" + deckList->getGameFormat();
if (!cardInfo->getProperties().contains(legalityProp)) {
return false;
}
return false;
const QString legality = cardInfo->getProperty(legalityProp);
int maxAllowed = maxAllowedForLegality(*formatRules, legality);
if (maxAllowed == -1) {
return false;
}
if (maxAllowed < 0) { // unlimited
return true;
}
return quantity <= maxAllowed;
}
void DeckListModel::refreshCardFormatLegalities()

View file

@ -13,6 +13,10 @@
#include <libcockatrice/card/database/parser/cockatrice_xml_4.h>
#include <libcockatrice/card/relation/card_relation.h>
static const QList<AllowedCount> kConstructedCounts = {{4, "legal"}, {1, "restricted"}, {0, "banned"}};
static const QList<AllowedCount> kSingletonCounts = {{1, "legal"}, {1, "restricted"}, {0, "banned"}};
SplitCardPart::SplitCardPart(const QString &_name,
const QString &_text,
const QVariantHash &_properties,
@ -486,11 +490,11 @@ FormatRulesNameMap OracleImporter::createDefaultMagicFormats()
FormatRulesNameMap defaultFormatRulesNameMap;
// ----------------- Helper lambda to create format -----------------
auto makeFormat = [&](const QString &name, int maxCopies = 4, int minDeck = 60, int maxDeck = -1,
int maxSideboardSize = 15) -> FormatRulesPtr {
auto makeFormat = [&](const QString &name, int minDeck = 60, int maxDeck = -1, int maxSideboardSize = 15,
const QList<AllowedCount> &allowedCounts = kConstructedCounts) -> FormatRulesPtr {
FormatRulesPtr f(new FormatRules);
f->formatName = name;
f->maxCopies = maxCopies;
f->allowedCounts = allowedCounts;
f->minDeckSize = minDeck;
f->maxDeckSize = maxDeck;
f->maxSideboardSize = maxSideboardSize;
@ -501,27 +505,27 @@ FormatRulesNameMap OracleImporter::createDefaultMagicFormats()
};
// ----------------- Standard formats -----------------
makeFormat("Alchemy", 4, 60);
makeFormat("Brawl", 1, 60, 60);
makeFormat("Commander", 1, 100, 100);
makeFormat("Duel", 1, 100, 100);
makeFormat("Future");
makeFormat("Gladiator", 4, 60, 60, 0);
makeFormat("Historic");
makeFormat("Legacy");
makeFormat("Modern");
makeFormat("Oathbreaker", 1, 60, 60);
makeFormat("OldSchool");
makeFormat("Pauper");
makeFormat("PauperCommander", 1, 100, 100);
makeFormat("Penny");
makeFormat("Pioneer");
makeFormat("Predh", 1, 100, 100);
makeFormat("Premodern");
makeFormat("Standard");
makeFormat("StandardBrawl", 1, 60, 60);
makeFormat("Timeless");
makeFormat("Modern");
makeFormat("Legacy");
makeFormat("Vintage");
makeFormat("Pioneer");
makeFormat("Historic");
makeFormat("Timeless");
makeFormat("Future");
makeFormat("OldSchool");
makeFormat("Premodern");
makeFormat("Pauper");
makeFormat("Penny");
// ----------------- Singleton formats -----------------
makeFormat("Commander", 100, 100, 15, kSingletonCounts);
makeFormat("Duel", 100, 100, 15, kSingletonCounts);
makeFormat("Brawl", 60, 60, 15, kSingletonCounts);
makeFormat("StandardBrawl", 60, 60, 15, kSingletonCounts);
makeFormat("Oathbreaker", 60, 60, 15, kSingletonCounts);
makeFormat("PauperCommander", 100, 100, 15, kSingletonCounts);
makeFormat("Predh", 100, 100, 15, kSingletonCounts);
return defaultFormatRulesNameMap;
}