mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-11-06 01:53:25 -08:00
Honor finite deck copy limits across printings and deck zones [skip ci]
This commit is contained in:
parent
e08402ac94
commit
142341bc3c
9 changed files with 438 additions and 48 deletions
|
|
@ -295,6 +295,7 @@ CardInfoPtr OracleImporter::addCard(QString name,
|
|||
qDebug() << "warning: an empty card was added to set" << printingInfo.getSet()->getShortName();
|
||||
}
|
||||
cards.insert(name, newCard);
|
||||
magicFormatsDirty = true;
|
||||
|
||||
return newCard;
|
||||
}
|
||||
|
|
@ -756,8 +757,38 @@ static FormatRulesNameMap buildDefaultMagicFormats()
|
|||
|
||||
const FormatRulesNameMap &OracleImporter::createDefaultMagicFormats()
|
||||
{
|
||||
static const FormatRulesNameMap cached = buildDefaultMagicFormats();
|
||||
return cached;
|
||||
static const FormatRulesNameMap defaults = buildDefaultMagicFormats();
|
||||
if (!magicFormatsDirty) {
|
||||
return magicFormats;
|
||||
}
|
||||
|
||||
// Sort by name so saved rules are independent of QHash iteration order.
|
||||
QMap<QString, int> finiteLimits;
|
||||
for (const auto &card : cards) {
|
||||
if (const auto limit = parseDeckCopyLimit(card->getName(), card->getText())) {
|
||||
finiteLimits.insert(card->getName(), *limit);
|
||||
}
|
||||
}
|
||||
magicFormats = defaults;
|
||||
if (!finiteLimits.isEmpty()) {
|
||||
for (auto it = magicFormats.begin(); it != magicFormats.end(); ++it) {
|
||||
// Copy the value before appending: defaults and other importer
|
||||
// instances retain their existing shared FormatRules objects.
|
||||
FormatRulesPtr format(new FormatRules(*it.value()));
|
||||
for (auto limit = finiteLimits.cbegin(); limit != finiteLimits.cend(); ++limit) {
|
||||
ExceptionRule exception;
|
||||
exception.conditions.append({"name", "equals", limit.key()});
|
||||
// A card's copy allowance does not make it legal in a format
|
||||
// where it is banned, restricted or outside the card pool.
|
||||
exception.conditions.append({"format-" + it.key(), "equals", "legal"});
|
||||
exception.maxCopies = limit.value();
|
||||
format->exceptions.append(exception);
|
||||
}
|
||||
it.value() = format;
|
||||
}
|
||||
}
|
||||
magicFormatsDirty = false;
|
||||
return magicFormats;
|
||||
}
|
||||
|
||||
int OracleImporter::startImport()
|
||||
|
|
@ -863,6 +894,8 @@ void OracleImporter::clear()
|
|||
{
|
||||
sets.clear();
|
||||
cards.clear();
|
||||
magicFormats.clear();
|
||||
magicFormatsDirty = true;
|
||||
allSets.clear();
|
||||
rawSetsData.clear();
|
||||
localizedEntries.clear();
|
||||
|
|
|
|||
|
|
@ -170,6 +170,10 @@ private:
|
|||
*/
|
||||
CardNameMap cards;
|
||||
|
||||
// Shared defaults are immutable; card-specific copy limits belong to this import.
|
||||
FormatRulesNameMap magicFormats;
|
||||
bool magicFormatsDirty = true;
|
||||
|
||||
/**
|
||||
* The sets, indexed by short name.
|
||||
*/
|
||||
|
|
@ -299,7 +303,8 @@ public:
|
|||
bool saveToFile(const QString &fileName, const QString &sourceUrl, const QString &sourceVersion);
|
||||
int importCardsFromSet(const CardSetPtr ¤tSet, const QJsonArray &cardsList);
|
||||
/**
|
||||
* @brief Returns the default format rules. The result is memoized on first use and must be treated as immutable.
|
||||
* @brief Returns format rules including finite copy allowances from imported cards.
|
||||
* The result is cached until cards are added or cleared and must be treated as immutable.
|
||||
*/
|
||||
const FormatRulesNameMap &createDefaultMagicFormats();
|
||||
const CardNameMap &getCardList() const
|
||||
|
|
|
|||
|
|
@ -71,3 +71,52 @@ bool parseCipt(const QString &name, const QString &text)
|
|||
|
||||
return ciptPattern.match(text).hasMatch();
|
||||
}
|
||||
|
||||
std::optional<int> parseDeckCopyLimit(const QString &name, const QString &text)
|
||||
{
|
||||
// Require a complete, standalone allowance. Other effects that mention a
|
||||
// number of cards or a different card name must not change deck construction.
|
||||
static const QRegularExpression rule(
|
||||
R"(^A deck can have up to ([a-z]+(?:[ -][a-z]+)*|[0-9]+) cards? named ([^\r\n]+)\.\r?$)",
|
||||
QRegularExpression::MultilineOption);
|
||||
static const QStringList smallNumbers = {
|
||||
"zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine",
|
||||
"ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen"};
|
||||
static const QStringList tens = {"twenty", "thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninety"};
|
||||
auto normalizedName = [](QString cardName) {
|
||||
return cardName.replace("Æ", "AE").replace("’", "'").normalized(QString::NormalizationForm_C);
|
||||
};
|
||||
const QString expectedName = normalizedName(name);
|
||||
if (expectedName.isEmpty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
std::optional<int> limit;
|
||||
auto matches = rule.globalMatch(text);
|
||||
while (matches.hasNext()) {
|
||||
const auto match = matches.next();
|
||||
if (normalizedName(match.captured(2)) != expectedName) {
|
||||
continue;
|
||||
}
|
||||
const QString count = match.captured(1);
|
||||
bool numeric = false;
|
||||
int value = count.toInt(&numeric);
|
||||
if (!numeric) {
|
||||
// English words through ninety-nine; larger counts remain supported
|
||||
// as decimal numerals. Unknown wording is deliberately not guessed.
|
||||
const QStringList words = QString(count).replace('-', ' ').split(' ');
|
||||
value = words.size() == 1 ? smallNumbers.indexOf(words.first()) : -1;
|
||||
const int ten = tens.indexOf(words.first());
|
||||
if (ten >= 0 && words.size() <= 2) {
|
||||
const int unit = words.size() == 1 ? 0 : smallNumbers.indexOf(words.last());
|
||||
if (unit >= 0 && unit < 10 && (words.size() == 1 || unit > 0)) {
|
||||
value = (ten + 2) * 10 + unit;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (value <= 0 || (limit && *limit != value)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
limit = value;
|
||||
}
|
||||
return limit;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,10 @@
|
|||
#define PARSEHELPERS_H
|
||||
|
||||
#include <QString>
|
||||
#include <optional>
|
||||
|
||||
/** @brief Read a finite deck-copy allowance naming this card in canonical English rules text. */
|
||||
std::optional<int> parseDeckCopyLimit(const QString &name, const QString &text);
|
||||
|
||||
bool parseCipt(const QString &name, const QString &text);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue