Cockatrice/libcockatrice_card/libcockatrice/card/card_info.h
2026-11-01 23:16:39 +01:00

580 lines
22 KiB
C++

#ifndef CARD_INFO_H
#define CARD_INFO_H
#include "format/format_legality_rules.h"
#include "lazy_properties_hash.h"
#include "printing/printing_info.h"
#include <QByteArray>
#include <QChar>
#include <QEnableSharedFromThis>
#include <QHash>
#include <QList>
#include <QLoggingCategory>
#include <QMap>
#include <QMetaType>
#include <QObject>
#include <QSet>
#include <QSharedPointer>
#include <QString>
#include <QStringList>
#include <qtmetamacros.h>
#include <utility>
inline Q_LOGGING_CATEGORY(CardInfoLog, "card_info");
class CardInfo;
class CardSet;
class CardRelation;
typedef QSharedPointer<CardInfo> CardInfoPtr;
typedef QSharedPointer<CardSet> CardSetPtr;
typedef QSharedPointer<FormatRules> FormatRulesPtr;
typedef QMap<QString, QList<PrintingInfo>> SetToPrintingsMap;
typedef QHash<QString, CardInfoPtr> CardNameMap;
typedef QHash<QString, CardSetPtr> SetNameMap;
typedef QHash<QString, FormatRulesPtr> FormatRulesNameMap;
Q_DECLARE_METATYPE(CardInfoPtr)
/**
* @class CardInfo
* @ingroup Cards
*
* @brief Represents a card and its associated metadata, properties, and relationships.
*
* CardInfo holds both static information (name, text, flags) and dynamic data
* (properties, set memberships, relationships). It also integrates with
* signals/slots, allowing observers to react to property or visual updates.
*
* Each CardInfo may belong to multiple sets through its printings, and can
* be related to other cards through defined relationships.
*/
class CardInfo : public QObject, public QEnableSharedFromThis<CardInfo>
{
Q_OBJECT
public:
/**
* @class CardInfo::UiAttributes
* @ingroup Cards
*
* @brief Attributes of the card that affect display and game logic.
*/
struct UiAttributes
{
bool cipt = false; ///< Positioning flag used by UI.
bool landscapeOrientation = false; ///< Orientation flag for rendering.
int tableRow = 0; ///< Row index in a table or visual representation.
bool upsideDownArt = false; ///< Whether artwork is flipped for visual purposes.
};
private:
/** @name Private Card Properties
* @anchor PrivateCardProperties
*/
///@{
QString name; ///< Full name of the card.
QString simpleName; ///< Simplified name for fuzzy matching.
QString text; ///< Text description or rules text of the card.
bool isToken; ///< Whether this card is a token or not.
QMap<QString, QString> localizedNames; ///< Localized card names, keyed by language code.
QMap<QString, QString> localizedTexts; ///< Localized rules text, keyed by language code.
LazyPropertiesHash properties; ///< Key-value store of dynamic card properties.
QList<CardRelation *> relatedCards; ///< Forward references to related cards.
QList<CardRelation *> reverseRelatedCards; ///< Cards that refer back to this card.
QList<CardRelation *> reverseRelatedCardsToMe; ///< Cards that consider this card as related.
SetToPrintingsMap setsToPrintings; ///< Mapping from set names to printing variations.
UiAttributes uiAttributes; ///< Attributes that affect display and game logic
QString setsNames; ///< Cached, human-readable list of set names.
QSet<QString> altNames; ///< Cached set of alternate names, used when searching
///@}
public:
/**
* @brief Constructs a CardInfo with full initialization.
*
* @param _name The name of the card.
* @param _text Rules text or description of the card.
* @param _isToken Flag indicating whether the card is a token.
* @param _properties Arbitrary key-value properties.
* @param _relatedCards Forward references to related cards.
* @param _reverseRelatedCards Backward references to related cards.
* @param _sets Map of set names to printing information.
* @param _uiAttributes Attributes that affect display and game logic
* @param _localizedNames Localized card names, keyed by language code.
* @param _localizedTexts Localized rules text, keyed by language code.
*/
explicit CardInfo(const QString &_name,
const QString &_text,
bool _isToken,
const QHash<QString, QString> &_properties,
const QList<CardRelation *> &_relatedCards,
const QList<CardRelation *> &_reverseRelatedCards,
SetToPrintingsMap _sets,
UiAttributes _uiAttributes,
QMap<QString, QString> _localizedNames = {},
QMap<QString, QString> _localizedTexts = {});
/**
* @brief Constructs a CardInfo from a serialized properties blob.
*
* Properties stay in @p _propertiesBlob so the QHash<QString, QString> is not
* built at load time (it is materialized on first query). @p _simpleName and
* @p _altNames are filled in when empty; the binary cache supplies them to
* skip the Unicode normalization and the printing scan.
*
* @param _name The card name.
* @param _text Rules text or description of the card.
* @param _isToken Token flag.
* @param _propertiesBlob Pre-serialized properties blob (as written by the cache).
* @param _relatedCards Forward relationships.
* @param _reverseRelatedCards Reverse relationships.
* @param _sets Printing information per set.
* @param _uiAttributes Attributes that affect display and game logic.
* @param _simpleName Precomputed simplified name; derived from @p _name when empty.
* @param _altNames Precomputed alternate names; derived from the printings when empty.
* @param _localizedNames Localized card names, keyed by language code.
* @param _localizedTexts Localized rules text, keyed by language code.
*/
explicit CardInfo(const QString &_name,
const QString &_text,
bool _isToken,
const QByteArray &_propertiesBlob,
const QList<CardRelation *> &_relatedCards,
const QList<CardRelation *> &_reverseRelatedCards,
SetToPrintingsMap _sets,
UiAttributes _uiAttributes,
QString _simpleName,
QSet<QString> _altNames,
QMap<QString, QString> _localizedNames = {},
QMap<QString, QString> _localizedTexts = {});
/**
* @brief Copy constructor for CardInfo.
*
* Performs a deep copy of properties, sets, and related card lists.
*
* @param other Another CardInfo to copy.
*/
CardInfo(const CardInfo &other)
: QObject(other.parent()), QEnableSharedFromThis<CardInfo>(), name(other.name), simpleName(other.simpleName),
text(other.text), isToken(other.isToken), localizedNames(other.localizedNames),
localizedTexts(other.localizedTexts), properties(other.properties), relatedCards(other.relatedCards),
reverseRelatedCards(other.reverseRelatedCards), reverseRelatedCardsToMe(other.reverseRelatedCardsToMe),
setsToPrintings(other.setsToPrintings), uiAttributes(other.uiAttributes), setsNames(other.setsNames),
altNames(other.altNames)
{
// The copy deliberately starts unowned: the QSharedPointer that wraps it registers the self reference.
}
/**
* @brief Creates a new instance with only the card name.
*
* All other fields are set to defaults.
*
* @param _name The card name.
* @return Shared pointer to the new CardInfo instance.
*/
static CardInfoPtr newInstance(const QString &_name);
/**
* @brief Creates a new instance with full initialization.
*
* @param _name Name of the card.
* @param _text Rules text or description.
* @param _isToken Token flag.
* @param _properties Arbitrary properties.
* @param _relatedCards Forward relationships.
* @param _reverseRelatedCards Reverse relationships.
* @param _sets Printing information per set.
* @param _uiAttributes Attributes that affect display and game logic
* @param _localizedNames Localized card names, keyed by language code.
* @param _localizedTexts Localized rules text, keyed by language code.
* @return Shared pointer to the new CardInfo instance.
*/
static CardInfoPtr newInstance(const QString &_name,
const QString &_text,
bool _isToken,
const QHash<QString, QString> &_properties,
const QList<CardRelation *> &_relatedCards,
const QList<CardRelation *> &_reverseRelatedCards,
SetToPrintingsMap _sets,
UiAttributes _uiAttributes,
QMap<QString, QString> _localizedNames = {},
QMap<QString, QString> _localizedTexts = {});
/**
* @brief Creates a new instance from a serialized properties blob.
*
* The properties stay in blob form, so the QHash<QString, QString> is not
* built at load time (it is materialized on first query). The derived state
* is required so it is never recomputed here, and the overload cannot be a
* candidate for a shorter call that passes a property hash instead of a blob.
*
* @param _name Name of the card.
* @param _text Rules text or description.
* @param _isToken Token flag.
* @param _propertiesBlob Serialized properties blob.
* @param _relatedCards Forward relationships.
* @param _reverseRelatedCards Reverse relationships.
* @param _sets Printing information per set.
* @param _uiAttributes Attributes that affect display and game logic.
* @param _simpleName Precomputed simplified name; pass {} to derive it from
* @p _name.
* @param _altNames Precomputed alternate names; pass {} to derive them from
* the printings.
* @param _appendToSets When true, the card is appended to each of
* its CardSets. Pass false when building cards in parallel so the
* (non-thread-safe) set membership is populated in a later
* single-threaded pass.
* @param _localizedNames Localized card names, keyed by language code.
* @param _localizedTexts Localized rules text, keyed by language code.
* @return Shared pointer to the new CardInfo instance.
*/
static CardInfoPtr newInstance(const QString &_name,
const QString &_text,
bool _isToken,
QByteArray _propertiesBlob,
const QList<CardRelation *> &_relatedCards,
const QList<CardRelation *> &_reverseRelatedCards,
SetToPrintingsMap _sets,
UiAttributes _uiAttributes,
QString _simpleName,
QSet<QString> _altNames,
bool _appendToSets,
QMap<QString, QString> _localizedNames = {},
QMap<QString, QString> _localizedTexts = {});
/**
* @brief Clones the current CardInfo instance.
*
* Uses the copy constructor; the copy is managed by its own shared pointer.
*
* @return Shared pointer to the cloned CardInfo.
*/
[[nodiscard]] CardInfoPtr clone() const
{
return CardInfoPtr(new CardInfo(*this));
}
/** @name Basic Properties Accessors */ //@{
[[nodiscard]] inline const QString &getName() const
{
return name;
}
[[nodiscard]] const QString &getSimpleName() const
{
return simpleName;
}
const QSet<QString> &getAltNames() const
{
return altNames;
}
[[nodiscard]] const QString &getText() const
{
return text;
}
void setText(const QString &_text)
{
text = _text;
notifyChanged();
}
/**
* @brief Returns the card name in the given language, falling back to the
* English name when no localization is available.
*
* @param lang Language code (e.g. "de", "ja", "zhs").
* @return The localized name, or the English name as fallback.
*/
[[nodiscard]] const QString &getLocalizedName(const QString &lang) const
{
const auto it = localizedNames.constFind(lang);
return it != localizedNames.constEnd() ? it.value() : name;
}
/**
* @brief Returns the rules text in the given language, falling back to the
* English text when no localization is available.
*
* @param lang Language code (e.g. "de", "ja", "zhs").
* @return The localized text, or the English text as fallback.
*/
[[nodiscard]] const QString &getLocalizedText(const QString &lang) const
{
const auto it = localizedTexts.constFind(lang);
return it != localizedTexts.constEnd() ? it.value() : text;
}
/**
* @brief Returns the localized card names keyed by language code.
*
* Only languages that have an entry are present; there is no English
* fallback in this map.
*/
[[nodiscard]] const QMap<QString, QString> &getLocalizedNames() const
{
return localizedNames;
}
/**
* @brief Returns the localized rules text keyed by language code.
*
* Only languages that have an entry are present; there is no English
* fallback in this map.
*/
[[nodiscard]] const QMap<QString, QString> &getLocalizedTexts() const
{
return localizedTexts;
}
/**
* @brief Sets the card name for the given language.
*
* @param lang Language code.
* @param _localizedName The localized card name.
*/
void setLocalizedName(const QString &lang, const QString &_localizedName)
{
if (localizedNames.value(lang) == _localizedName) {
return;
}
localizedNames.insert(lang, _localizedName);
notifyChanged();
}
/**
* @brief Sets the rules text for the given language.
*
* @param lang Language code.
* @param _localizedText The localized rules text.
*/
void setLocalizedText(const QString &lang, const QString &_localizedText)
{
if (localizedTexts.value(lang) == _localizedText) {
return;
}
localizedTexts.insert(lang, _localizedText);
notifyChanged();
}
/**
* @brief Returns the language codes for which this card has a localized
* name or rules text.
*/
[[nodiscard]] QStringList localizationLanguages() const
{
QStringList languages = localizedNames.keys();
languages.append(localizedTexts.keys());
languages.removeDuplicates();
return languages;
}
[[nodiscard]] bool getIsToken() const
{
return isToken;
}
[[nodiscard]] QStringList getProperties() const
{
return properties.keys();
}
/**
* @brief Returns the properties in their serialized blob form.
*
* Cheap while the properties were never materialized (the cache and the XML parsers pass the blob through).
*/
[[nodiscard]] QByteArray getPropertiesBlob() const
{
return properties.serialized();
}
/**
* @brief Returns the materialized property hash, forcing deserialization of the blob.
*
* Prefer getProperty(), hasProperty() or getPropertiesBlob(), which avoid building the hash.
*/
[[nodiscard]] const QHash<QString, QString> &getPropertiesHash() const;
[[nodiscard]] QString getProperty(const QString &propertyName) const
{
return properties.value(propertyName);
}
void setProperty(const QString &_name, const QString &_value);
[[nodiscard]] bool hasProperty(const QString &propertyName) const
{
return properties.contains(propertyName);
}
[[nodiscard]] const SetToPrintingsMap &getSets() const
{
return setsToPrintings;
}
[[nodiscard]] const QString &getSetsNames() const
{
return setsNames;
}
//@}
/** @name Related Cards Accessors */ //@{
[[nodiscard]] const QList<CardRelation *> &getRelatedCards() const
{
return relatedCards;
}
[[nodiscard]] const QList<CardRelation *> &getReverseRelatedCards() const
{
return reverseRelatedCards;
}
[[nodiscard]] const QList<CardRelation *> &getReverseRelatedCards2Me() const
{
return reverseRelatedCardsToMe;
}
[[nodiscard]] QList<CardRelation *> getAllRelatedCards() const
{
QList<CardRelation *> result;
result.append(getRelatedCards());
result.append(getReverseRelatedCards2Me());
return result;
}
void resetReverseRelatedCards2Me();
/**
* @brief Clears the raw relation lists without deleting the CardRelation objects.
*
* Callers that schedule the relations for deletion (e.g. CardDatabase::removeCard())
* must use this to drop the now-dangling pointers from this CardInfo, which may
* outlive them through external shared pointers.
*/
void clearDirectRelations();
void addReverseRelatedCards2Me(CardRelation *cardRelation)
{
reverseRelatedCardsToMe.append(cardRelation);
}
//@}
/** @name UI Positioning */ //@{
[[nodiscard]] const UiAttributes &getUiAttributes() const
{
return uiAttributes;
}
//@}
[[nodiscard]] QChar getColorChar() const;
/** @name Legacy/Convenience Property Accessors */ //@{
[[nodiscard]] QString getCardType() const;
void setCardType(const QString &value);
[[nodiscard]] QString getCmc() const;
[[nodiscard]] QString getColors() const;
void setColors(const QString &value);
[[nodiscard]] QString getColorIdentity() const;
[[nodiscard]] QString getLoyalty() const;
[[nodiscard]] QString getMainCardType() const;
[[nodiscard]] QString getManaCost() const;
[[nodiscard]] QString getPowTough() const;
void setPowTough(const QString &value);
//@}
/**
* @brief Returns a version of the card name safe for file storage or fuzzy matching.
*
* Removes invalid characters, replaces spacing markers, and normalizes diacritics.
*
* @return Corrected card name as a QString.
*/
[[nodiscard]] QString getCorrectedName() const;
/**
* @brief Gets the card's legality value for the given format.
* The legality prop for a format is stored in the property map under the key "format-<name>"
* @param format The format's name.
* @return The card's legality value for the format. Empty if not found.
*/
[[nodiscard]] QString getLegalityProp(const QString &format) const;
/**
* @brief Checks if the card is legal in the given format.
* A card is considered legal in a format if its properties map contains an entry for "format-<name>", with value
* "legal" or "restricted".
* @param format The format's name. If empty, will always return true.
* @return Whether the card is legal in the given format.
*/
[[nodiscard]] bool isLegalInFormat(const QString &format) const;
/**
* @brief Adds a printing to a specific set.
*
* Updates the mapping and refreshes the cached list of set names.
*
* @param _set The set to which the card should be added.
* @param _info Optional printing information.
*/
void addToSet(const CardSetPtr &_set, const PrintingInfo &_info = PrintingInfo());
/**
* @brief Combines legality properties from a provided map.
*
* Useful for merging format legality flags from multiple sources.
*
* @param props Key-value mapping of format legalities.
*/
void combineLegalities(const QHash<QString, QString> &props);
/**
* @brief Refreshes all cached fields that are calculated from the contained sets and printings.
*
* Typically called after adding or modifying set memberships or printings.
*/
void refreshCachedSets();
/**
* @brief Simplifies a name for fuzzy matching.
*
* Converts to lowercase, removes punctuation/spacing.
*
* @param name Original name string.
* @return Simplified name string.
*/
static QString simplifyName(const QString &name);
private:
/**
* @brief Emits cardInfoChanged() with a strong reference to this card.
*
* A CardInfo is always owned by a QSharedPointer, so the self reference is always available; a released
* build that somehow finds it missing warns rather than emitting a null card.
*/
void notifyChanged();
/**
* @brief Refreshes the cached, human-readable list of set names.
*
* Typically called after adding or modifying set memberships.
*/
void refreshCachedSetNames();
/**
* @brief Refreshes the cached list of alt names for the card.
*
* Typically called after adding or modifying the contained printings.
*/
void refreshCachedAltNames();
signals:
/**
* @brief Emitted when a pixmap for this card has been updated or finished loading.
*
* @param printing Specific printing for which the pixmap has updated.
*/
void pixmapUpdated(const PrintingInfo &printing);
/**
* @brief Emitted when card properties or state have changed.
*
* @param card Shared pointer to the CardInfo instance that changed.
*/
void cardInfoChanged(CardInfoPtr card);
};
#endif