#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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include inline Q_LOGGING_CATEGORY(CardInfoLog, "card_info"); class CardInfo; class CardSet; class CardRelation; typedef QSharedPointer CardInfoPtr; typedef QSharedPointer CardSetPtr; typedef QSharedPointer FormatRulesPtr; typedef QMap> SetToPrintingsMap; typedef QHash CardNameMap; typedef QHash SetNameMap; typedef QHash 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 { 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 localizedNames; ///< Localized card names, keyed by language code. QMap localizedTexts; ///< Localized rules text, keyed by language code. LazyPropertiesHash properties; ///< Key-value store of dynamic card properties. QList relatedCards; ///< Forward references to related cards. QList reverseRelatedCards; ///< Cards that refer back to this card. QList 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 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 &_properties, const QList &_relatedCards, const QList &_reverseRelatedCards, SetToPrintingsMap _sets, UiAttributes _uiAttributes, QMap _localizedNames = {}, QMap _localizedTexts = {}); /** * @brief Constructs a CardInfo from a serialized properties blob. * * Properties stay in @p _propertiesBlob so the QHash 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 &_relatedCards, const QList &_reverseRelatedCards, SetToPrintingsMap _sets, UiAttributes _uiAttributes, QString _simpleName, QSet _altNames, QMap _localizedNames = {}, QMap _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(), 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 &_properties, const QList &_relatedCards, const QList &_reverseRelatedCards, SetToPrintingsMap _sets, UiAttributes _uiAttributes, QMap _localizedNames = {}, QMap _localizedTexts = {}); /** * @brief Creates a new instance from a serialized properties blob. * * The properties stay in blob form, so the QHash 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 &_relatedCards, const QList &_reverseRelatedCards, SetToPrintingsMap _sets, UiAttributes _uiAttributes, QString _simpleName, QSet _altNames, bool _appendToSets, QMap _localizedNames = {}, QMap _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 &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 &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 &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 &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 &getRelatedCards() const { return relatedCards; } [[nodiscard]] const QList &getReverseRelatedCards() const { return reverseRelatedCards; } [[nodiscard]] const QList &getReverseRelatedCards2Me() const { return reverseRelatedCardsToMe; } [[nodiscard]] QList getAllRelatedCards() const { QList 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 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-" * @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-", 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 &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