mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-11-05 09:33:25 -08:00
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / vcpkg baseline (push) Waiting to run
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 sanitize (push) Blocked by required conditions
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 cover (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 27 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / Prune caches (push) Waiting to run
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
* [Client] Add exclude and color identity filters to the visual database display * [Filters] Extract the color filter mode derivation into libcockatrice_filters The mode table and the 'which mode produced this filter set' derivation are filter semantics, not widget code, so they belong next to CardFilter rather than in an anonymous namespace in the client. Moving them also makes them testable: filter_mode_test pins every mode, the Custom fallback and the 'keep the live mode when it still fits' rule. * [Tests] Cover the negated color identity query ci! read the camelCase colorIdentity property instead of the lowercase coloridentity key, so it matched the empty string and rejected every card. The new case uses a card whose colors and color identity differ, so it fails on the old lookup and passes on the fixed one. --------- Co-authored-by: Lukas Brübach <44814898+BruebachL@users.noreply.github.com>
342 lines
11 KiB
C++
342 lines
11 KiB
C++
#include "card_info.h"
|
|
|
|
#include "game_specific_terms.h"
|
|
#include "libcockatrice/card/lazy_properties_hash.h"
|
|
#include "printing/printing_info.h"
|
|
#include "relation/card_relation.h"
|
|
#include "set/card_set.h"
|
|
|
|
#include <QDebug>
|
|
#include <QHashIterator>
|
|
#include <QRegularExpression>
|
|
#include <QSharedPointer>
|
|
#include <QString>
|
|
#include <utility>
|
|
|
|
using CardInfoPtr = QSharedPointer<CardInfo>;
|
|
|
|
const QHash<QString, QString> &CardInfo::getPropertiesHash() const
|
|
{
|
|
return properties.getProperties();
|
|
}
|
|
|
|
void CardInfo::notifyChanged()
|
|
{
|
|
const CardInfoPtr self = sharedFromThis();
|
|
Q_ASSERT_X(self, "CardInfo::notifyChanged", "every CardInfo is owned by a QSharedPointer");
|
|
if (!self) {
|
|
qCWarning(CardInfoLog) << "notifyChanged() on an unowned CardInfo" << name << "- observers stay stale";
|
|
return;
|
|
}
|
|
|
|
emit cardInfoChanged(self);
|
|
}
|
|
|
|
void CardInfo::setProperty(const QString &_name, const QString &_value)
|
|
{
|
|
bool changed = properties.insert(_name, _value);
|
|
if (!changed) {
|
|
return;
|
|
}
|
|
|
|
notifyChanged();
|
|
}
|
|
|
|
CardInfo::CardInfo(const QString &_name,
|
|
const QString &_text,
|
|
bool _isToken,
|
|
const QHash<QString, QString> &_properties,
|
|
const QList<CardRelation *> &_relatedCards,
|
|
const QList<CardRelation *> &_reverseRelatedCards,
|
|
SetToPrintingsMap _sets,
|
|
const UiAttributes _uiAttributes,
|
|
QMap<QString, QString> _localizedNames,
|
|
QMap<QString, QString> _localizedTexts)
|
|
: name(_name), text(_text), isToken(_isToken), localizedNames(std::move(_localizedNames)),
|
|
localizedTexts(std::move(_localizedTexts)), properties(LazyPropertiesHash(_properties)),
|
|
relatedCards(_relatedCards), reverseRelatedCards(_reverseRelatedCards), setsToPrintings(std::move(_sets)),
|
|
uiAttributes(_uiAttributes)
|
|
{
|
|
simpleName = CardInfo::simplifyName(name);
|
|
|
|
refreshCachedSets();
|
|
}
|
|
|
|
CardInfo::CardInfo(const QString &_name,
|
|
const QString &_text,
|
|
bool _isToken,
|
|
const QByteArray &_propertiesBlob,
|
|
const QList<CardRelation *> &_relatedCards,
|
|
const QList<CardRelation *> &_reverseRelatedCards,
|
|
SetToPrintingsMap _sets,
|
|
const UiAttributes _uiAttributes,
|
|
QString _simpleName,
|
|
QSet<QString> _altNames,
|
|
QMap<QString, QString> _localizedNames,
|
|
QMap<QString, QString> _localizedTexts)
|
|
: name(_name), simpleName(std::move(_simpleName)), text(_text), isToken(_isToken),
|
|
localizedNames(std::move(_localizedNames)), localizedTexts(std::move(_localizedTexts)),
|
|
properties(LazyPropertiesHash(_propertiesBlob)), relatedCards(_relatedCards),
|
|
reverseRelatedCards(_reverseRelatedCards), setsToPrintings(std::move(_sets)), uiAttributes(_uiAttributes),
|
|
altNames(std::move(_altNames))
|
|
{
|
|
// propertiesBlob is materialized lazily on first query. The binary cache supplies simpleName
|
|
// and altNames to skip the Unicode normalization and the printing scan; the XML parsers leave
|
|
// them empty and get the same derived state the property-hash constructor computes.
|
|
if (simpleName.isEmpty()) {
|
|
simpleName = simplifyName(name);
|
|
}
|
|
refreshCachedSetNames();
|
|
if (altNames.isEmpty()) {
|
|
refreshCachedAltNames();
|
|
}
|
|
}
|
|
|
|
CardInfoPtr CardInfo::newInstance(const QString &_name)
|
|
{
|
|
return newInstance(_name, "", false, {}, {}, {}, {}, {});
|
|
}
|
|
|
|
CardInfoPtr CardInfo::newInstance(const QString &_name,
|
|
const QString &_text,
|
|
bool _isToken,
|
|
const QHash<QString, QString> &_properties,
|
|
const QList<CardRelation *> &_relatedCards,
|
|
const QList<CardRelation *> &_reverseRelatedCards,
|
|
SetToPrintingsMap _sets,
|
|
const UiAttributes _uiAttributes,
|
|
QMap<QString, QString> _localizedNames,
|
|
QMap<QString, QString> _localizedTexts)
|
|
{
|
|
CardInfoPtr ptr(new CardInfo(_name, _text, _isToken, _properties, _relatedCards, _reverseRelatedCards, _sets,
|
|
_uiAttributes, std::move(_localizedNames), std::move(_localizedTexts)));
|
|
|
|
for (const auto &printings : _sets) {
|
|
for (const PrintingInfo &printing : printings) {
|
|
printing.getSet()->append(ptr);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return ptr;
|
|
}
|
|
|
|
CardInfoPtr CardInfo::newInstance(const QString &_name,
|
|
const QString &_text,
|
|
bool _isToken,
|
|
QByteArray _propertiesBlob,
|
|
const QList<CardRelation *> &_relatedCards,
|
|
const QList<CardRelation *> &_reverseRelatedCards,
|
|
SetToPrintingsMap _sets,
|
|
const UiAttributes _uiAttributes,
|
|
QString _simpleName,
|
|
QSet<QString> _altNames,
|
|
bool _appendToSets,
|
|
QMap<QString, QString> _localizedNames,
|
|
QMap<QString, QString> _localizedTexts)
|
|
{
|
|
CardInfoPtr ptr(new CardInfo(_name, _text, _isToken, std::move(_propertiesBlob), _relatedCards,
|
|
_reverseRelatedCards, _sets, _uiAttributes, std::move(_simpleName),
|
|
std::move(_altNames), std::move(_localizedNames), std::move(_localizedTexts)));
|
|
|
|
if (_appendToSets) {
|
|
for (const auto &printings : _sets) {
|
|
for (const PrintingInfo &printing : printings) {
|
|
printing.getSet()->append(ptr);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return ptr;
|
|
}
|
|
|
|
QString CardInfo::getCorrectedName() const
|
|
{
|
|
// remove all the characters reserved in windows file paths,
|
|
// other oses only disallow a subset of these so it covers all
|
|
static const QRegularExpression rmrx(R"(( // |[*<>:"\\?\x00-\x08\x10-\x1f]))");
|
|
static const QRegularExpression spacerx(R"([/\x09-\x0f])");
|
|
static const QString space(' ');
|
|
QString result = name;
|
|
// Fire // Ice, Circle of Protection: Red, "Ach! Hans, Run!", Who/What/When/Where/Why, Question Elemental?
|
|
return result.remove(rmrx).replace(spacerx, space);
|
|
}
|
|
|
|
QString CardInfo::getLegalityProp(const QString &format) const
|
|
{
|
|
return getProperty("format-" + format);
|
|
}
|
|
|
|
bool CardInfo::isLegalInFormat(const QString &format) const
|
|
{
|
|
if (format.isEmpty()) {
|
|
return true;
|
|
}
|
|
|
|
QString formatLegality = getLegalityProp(format);
|
|
return formatLegality == "legal" || formatLegality == "restricted";
|
|
}
|
|
|
|
void CardInfo::addToSet(const CardSetPtr &_set, const PrintingInfo &_info)
|
|
{
|
|
const CardInfoPtr self = sharedFromThis();
|
|
Q_ASSERT_X(self, "CardInfo::addToSet", "every CardInfo is owned by a QSharedPointer");
|
|
if (!self) {
|
|
qCWarning(CardInfoLog) << "addToSet() on an unowned CardInfo" << name << "- the set will not list it";
|
|
return;
|
|
}
|
|
|
|
if (!_set->contains(self)) {
|
|
_set->append(self);
|
|
}
|
|
if (!setsToPrintings[_set->getShortName()].contains(_info)) {
|
|
setsToPrintings[_set->getShortName()].append(_info);
|
|
}
|
|
|
|
refreshCachedSets();
|
|
}
|
|
|
|
void CardInfo::combineLegalities(const QHash<QString, QString> &props)
|
|
{
|
|
QHashIterator it(props);
|
|
while (it.hasNext()) {
|
|
it.next();
|
|
if (it.key().startsWith("format-")) {
|
|
properties.insert(it.key(), it.value());
|
|
}
|
|
}
|
|
notifyChanged();
|
|
}
|
|
|
|
void CardInfo::refreshCachedSets()
|
|
{
|
|
refreshCachedSetNames();
|
|
refreshCachedAltNames();
|
|
}
|
|
|
|
void CardInfo::refreshCachedSetNames()
|
|
{
|
|
QStringList setList;
|
|
// update the cached list of set names
|
|
for (const auto &printings : setsToPrintings) {
|
|
for (const auto &printing : printings) {
|
|
if (printing.getSet()->getEnabled()) {
|
|
setList << printing.getSet()->getShortName();
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
setsNames = setList.join(", ");
|
|
}
|
|
|
|
void CardInfo::refreshCachedAltNames()
|
|
{
|
|
altNames.clear();
|
|
|
|
// update the altNames with the flavorNames
|
|
for (const auto &printings : setsToPrintings) {
|
|
for (const auto &printing : printings) {
|
|
QString flavorName = printing.getFlavorName();
|
|
if (!flavorName.isEmpty()) {
|
|
altNames.insert(flavorName);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
QString CardInfo::simplifyName(const QString &name)
|
|
{
|
|
static const QRegularExpression spaceOrSplit("(\\s+|\\/\\/.*)");
|
|
static const QRegularExpression nonAlnum("[^a-z0-9]");
|
|
|
|
QString simpleName = name.toLower();
|
|
|
|
// remove spaces and right halves of split cards
|
|
simpleName.remove(spaceOrSplit);
|
|
|
|
// So Aetherling would work, but not Ætherling since 'Æ' would get replaced
|
|
// with nothing.
|
|
simpleName.replace("æ", "ae");
|
|
|
|
// Replace Jötun Grunt with Jotun Grunt.
|
|
simpleName = simpleName.normalized(QString::NormalizationForm_KD);
|
|
|
|
// remove all non alphanumeric characters from the name
|
|
simpleName.remove(nonAlnum);
|
|
return simpleName;
|
|
}
|
|
|
|
QChar CardInfo::getColorChar() const
|
|
{
|
|
const QString colors = getColors();
|
|
switch (colors.size()) {
|
|
case 0:
|
|
return QChar();
|
|
case 1:
|
|
return colors.at(0);
|
|
default:
|
|
return QChar('m');
|
|
}
|
|
}
|
|
|
|
void CardInfo::resetReverseRelatedCards2Me()
|
|
{
|
|
for (CardRelation *cardRelation : this->getReverseRelatedCards2Me()) {
|
|
cardRelation->deleteLater();
|
|
}
|
|
reverseRelatedCardsToMe = QList<CardRelation *>();
|
|
}
|
|
|
|
void CardInfo::clearDirectRelations()
|
|
{
|
|
relatedCards = QList<CardRelation *>();
|
|
reverseRelatedCards = QList<CardRelation *>();
|
|
reverseRelatedCardsToMe = QList<CardRelation *>();
|
|
}
|
|
|
|
// Back-compatibility methods. Remove ASAP
|
|
QString CardInfo::getCardType() const
|
|
{
|
|
return getProperty(Mtg::CardType);
|
|
}
|
|
void CardInfo::setCardType(const QString &value)
|
|
{
|
|
setProperty(Mtg::CardType, value);
|
|
}
|
|
QString CardInfo::getCmc() const
|
|
{
|
|
return getProperty(Mtg::ConvertedManaCost);
|
|
}
|
|
QString CardInfo::getColors() const
|
|
{
|
|
return getProperty(Mtg::Colors);
|
|
}
|
|
void CardInfo::setColors(const QString &value)
|
|
{
|
|
setProperty(Mtg::Colors, value);
|
|
}
|
|
QString CardInfo::getColorIdentity() const
|
|
{
|
|
return getProperty(Mtg::ColorIdentity);
|
|
}
|
|
QString CardInfo::getLoyalty() const
|
|
{
|
|
return getProperty(Mtg::Loyalty);
|
|
}
|
|
QString CardInfo::getMainCardType() const
|
|
{
|
|
return getProperty(Mtg::MainCardType);
|
|
}
|
|
QString CardInfo::getManaCost() const
|
|
{
|
|
return getProperty(Mtg::ManaCost);
|
|
}
|
|
QString CardInfo::getPowTough() const
|
|
{
|
|
return getProperty(Mtg::PowTough);
|
|
}
|
|
void CardInfo::setPowTough(const QString &value)
|
|
{
|
|
setProperty(Mtg::PowTough, value);
|
|
}
|