diff --git a/cockatrice/src/game/cards/card_info.cpp b/cockatrice/src/game/cards/card_info.cpp new file mode 100644 index 000000000..a5d93f185 --- /dev/null +++ b/cockatrice/src/game/cards/card_info.cpp @@ -0,0 +1,947 @@ +#include "card_info.h" + +#include "../../client/network/spoiler_background_updater.h" +#include "../../client/ui/picture_loader/picture_loader.h" +#include "../../settings/cache_settings.h" +#include "../../utility/card_set_comparator.h" +#include "../game_specific_terms.h" +#include "./card_database_parser/cockatrice_xml_3.h" +#include "./card_database_parser/cockatrice_xml_4.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +const char *CardDatabase::TOKENS_SETNAME = "TK"; + +CardSet::CardSet(const QString &_shortName, + const QString &_longName, + const QString &_setType, + const QDate &_releaseDate, + const CardSet::Priority _priority) + : shortName(_shortName), longName(_longName), releaseDate(_releaseDate), setType(_setType), priority(_priority) +{ + loadSetOptions(); +} + +CardSetPtr CardSet::newInstance(const QString &_shortName, + const QString &_longName, + const QString &_setType, + const QDate &_releaseDate, + const Priority _priority) +{ + CardSetPtr ptr(new CardSet(_shortName, _longName, _setType, _releaseDate, _priority)); + // ptr->setSmartPointer(ptr); + return ptr; +} + +QString CardSet::getCorrectedShortName() const +{ + // For Windows machines. + QSet invalidFileNames; + invalidFileNames << "CON" + << "PRN" + << "AUX" + << "NUL" + << "COM1" + << "COM2" + << "COM3" + << "COM4" + << "COM5" + << "COM6" + << "COM7" + << "COM8" + << "COM9" + << "LPT1" + << "LPT2" + << "LPT3" + << "LPT4" + << "LPT5" + << "LPT6" + << "LPT7" + << "LPT8" + << "LPT9"; + + return invalidFileNames.contains(shortName) ? shortName + "_" : shortName; +} + +void CardSet::loadSetOptions() +{ + sortKey = SettingsCache::instance().cardDatabase().getSortKey(shortName); + enabled = SettingsCache::instance().cardDatabase().isEnabled(shortName); + isknown = SettingsCache::instance().cardDatabase().isKnown(shortName); +} + +void CardSet::setSortKey(unsigned int _sortKey) +{ + sortKey = _sortKey; + SettingsCache::instance().cardDatabase().setSortKey(shortName, _sortKey); +} + +void CardSet::setEnabled(bool _enabled) +{ + enabled = _enabled; + SettingsCache::instance().cardDatabase().setEnabled(shortName, _enabled); +} + +void CardSet::setIsKnown(bool _isknown) +{ + isknown = _isknown; + SettingsCache::instance().cardDatabase().setIsKnown(shortName, _isknown); +} + +class SetList::KeyCompareFunctor +{ +public: + inline bool operator()(const CardSetPtr &a, const CardSetPtr &b) const + { + if (a.isNull() || b.isNull()) { + qCDebug(CardDatabaseLog) << "SetList::KeyCompareFunctor a or b is null"; + return false; + } + + return a->getSortKey() < b->getSortKey(); + } +}; + +void SetList::sortByKey() +{ + std::sort(begin(), end(), KeyCompareFunctor()); +} + +int SetList::getEnabledSetsNum() +{ + int num = 0; + for (int i = 0; i < size(); ++i) { + CardSetPtr set = at(i); + if (set && set->getEnabled()) { + ++num; + } + } + return num; +} + +int SetList::getUnknownSetsNum() +{ + int num = 0; + for (int i = 0; i < size(); ++i) { + CardSetPtr set = at(i); + if (set && !set->getIsKnown() && !set->getIsKnownIgnored()) { + ++num; + } + } + return num; +} + +QStringList SetList::getUnknownSetsNames() +{ + QStringList sets = QStringList(); + for (int i = 0; i < size(); ++i) { + CardSetPtr set = at(i); + if (set && !set->getIsKnown() && !set->getIsKnownIgnored()) { + sets << set->getShortName(); + } + } + return sets; +} + +void SetList::enableAllUnknown() +{ + for (int i = 0; i < size(); ++i) { + CardSetPtr set = at(i); + if (set && !set->getIsKnown() && !set->getIsKnownIgnored()) { + set->setIsKnown(true); + set->setEnabled(true); + } else if (set && set->getIsKnownIgnored() && !set->getEnabled()) { + set->setEnabled(true); + } + } +} + +void SetList::enableAll() +{ + for (int i = 0; i < size(); ++i) { + CardSetPtr set = at(i); + + if (set == nullptr) { + qCDebug(CardDatabaseLog) << "enabledAll has null"; + continue; + } + + if (!set->getIsKnownIgnored()) { + set->setIsKnown(true); + } + + set->setEnabled(true); + } +} + +void SetList::markAllAsKnown() +{ + for (int i = 0; i < size(); ++i) { + CardSetPtr set = at(i); + if (set && !set->getIsKnown() && !set->getIsKnownIgnored()) { + set->setIsKnown(true); + set->setEnabled(false); + } else if (set && set->getIsKnownIgnored() && !set->getEnabled()) { + set->setEnabled(true); + } + } +} + +void SetList::guessSortKeys() +{ + defaultSort(); + for (int i = 0; i < size(); ++i) { + CardSetPtr set = at(i); + if (set.isNull()) { + qCDebug(CardDatabaseLog) << "guessSortKeys set is null"; + continue; + } + set->setSortKey(i); + } +} + +void SetList::defaultSort() +{ + std::sort(begin(), end(), [](const CardSetPtr &a, const CardSetPtr &b) { + // Sort by priority, then by release date, then by short name + if (a->getPriority() != b->getPriority()) { + return a->getPriority() < b->getPriority(); // lowest first + } else if (a->getReleaseDate() != b->getReleaseDate()) { + return a->getReleaseDate() > b->getReleaseDate(); // most recent first + } else { + return a->getShortName() < b->getShortName(); // alphabetically + } + }); +} + +CardInfoPerSet::CardInfoPerSet(const CardSetPtr &_set) : set(_set) +{ +} + +CardInfo::CardInfo(const QString &_name, + const QString &_text, + bool _isToken, + QVariantHash _properties, + const QList &_relatedCards, + const QList &_reverseRelatedCards, + CardInfoPerSetMap _sets, + bool _cipt, + bool _landscapeOrientation, + int _tableRow, + bool _upsideDownArt) + : name(_name), text(_text), isToken(_isToken), properties(std::move(_properties)), relatedCards(_relatedCards), + reverseRelatedCards(_reverseRelatedCards), sets(std::move(_sets)), cipt(_cipt), + landscapeOrientation(_landscapeOrientation), tableRow(_tableRow), upsideDownArt(_upsideDownArt) +{ + pixmapCacheKey = QLatin1String("card_") + name; + simpleName = CardInfo::simplifyName(name); + + refreshCachedSetNames(); +} + +CardInfo::~CardInfo() +{ + PictureLoader::clearPixmapCache(smartThis); +} + +CardInfoPtr CardInfo::newInstance(const QString &_name) +{ + return newInstance(_name, QString(), false, QVariantHash(), QList(), QList(), + CardInfoPerSetMap(), false, false, 0, false); +} + +CardInfoPtr CardInfo::newInstance(const QString &_name, + const QString &_text, + bool _isToken, + QVariantHash _properties, + const QList &_relatedCards, + const QList &_reverseRelatedCards, + CardInfoPerSetMap _sets, + bool _cipt, + bool _landscapeOrientation, + int _tableRow, + bool _upsideDownArt) +{ + CardInfoPtr ptr(new CardInfo(_name, _text, _isToken, std::move(_properties), _relatedCards, _reverseRelatedCards, + _sets, _cipt, _landscapeOrientation, _tableRow, _upsideDownArt)); + ptr->setSmartPointer(ptr); + + for (const auto &cardInfoPerSetList : _sets) { + for (const CardInfoPerSet &set : cardInfoPerSetList) { + set.getPtr()->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); +} + +void CardInfo::addToSet(const CardSetPtr &_set, const CardInfoPerSet _info) +{ + _set->append(smartThis); + sets[_set->getShortName()].append(_info); + + refreshCachedSetNames(); +} + +void CardInfo::combineLegalities(const QVariantHash &props) +{ + QHashIterator it(props); + while (it.hasNext()) { + it.next(); + if (it.key().startsWith("format-")) { + smartThis->setProperty(it.key(), it.value().toString()); + } + } +} + +void CardInfo::refreshCachedSetNames() +{ + QStringList setList; + // update the cached list of set names + for (const auto &cardInfoPerSetList : sets) { + for (const auto &set : cardInfoPerSetList) { + if (set.getPtr()->getEnabled()) { + setList << set.getPtr()->getShortName(); + } + break; + } + } + setsNames = setList.join(", "); +} + +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; +} + +const QChar CardInfo::getColorChar() const +{ + QString colors = getColors(); + switch (colors.size()) { + case 0: + return QChar(); + case 1: + return colors.at(0); + default: + return QChar('m'); + } +} + +CardDatabase::CardDatabase(QObject *parent) : QObject(parent), loadStatus(NotLoaded) +{ + qRegisterMetaType("CardInfoPtr"); + qRegisterMetaType("CardSetPtr"); + + // add new parsers here + availableParsers << new CockatriceXml4Parser; + availableParsers << new CockatriceXml3Parser; + + for (auto &parser : availableParsers) { + connect(parser, &ICardDatabaseParser::addCard, this, &CardDatabase::addCard, Qt::DirectConnection); + connect(parser, &ICardDatabaseParser::addSet, this, &CardDatabase::addSet, Qt::DirectConnection); + } + + connect(&SettingsCache::instance(), &SettingsCache::cardDatabasePathChanged, this, + &CardDatabase::loadCardDatabases); +} + +CardDatabase::~CardDatabase() +{ + clear(); + qDeleteAll(availableParsers); +} + +void CardDatabase::clear() +{ + clearDatabaseMutex->lock(); + + QHashIterator i(cards); + while (i.hasNext()) { + i.next(); + if (i.value()) { + removeCard(i.value()); + } + } + + cards.clear(); + simpleNameCards.clear(); + + sets.clear(); + ICardDatabaseParser::clearSetlist(); + + loadStatus = NotLoaded; + + clearDatabaseMutex->unlock(); +} + +void CardDatabase::addCard(CardInfoPtr card) +{ + if (card == nullptr) { + qCDebug(CardDatabaseLog) << "CardDatabase::addCard(nullptr)"; + return; + } + + // if card already exists just add the new set property + if (cards.contains(card->getName())) { + CardInfoPtr sameCard = cards[card->getName()]; + for (const auto &cardInfoPerSetList : card->getSets()) { + for (const CardInfoPerSet &set : cardInfoPerSetList) { + sameCard->addToSet(set.getPtr(), set); + } + } + return; + } + + addCardMutex->lock(); + cards.insert(card->getName(), card); + simpleNameCards.insert(card->getSimpleName(), card); + addCardMutex->unlock(); + emit cardAdded(card); +} + +void CardDatabase::removeCard(CardInfoPtr card) +{ + if (card.isNull()) { + qCDebug(CardDatabaseLog) << "CardDatabase::removeCard(nullptr)"; + return; + } + + for (auto *cardRelation : card->getRelatedCards()) + cardRelation->deleteLater(); + + for (auto *cardRelation : card->getReverseRelatedCards()) + cardRelation->deleteLater(); + + for (auto *cardRelation : card->getReverseRelatedCards2Me()) + cardRelation->deleteLater(); + + removeCardMutex->lock(); + cards.remove(card->getName()); + simpleNameCards.remove(card->getSimpleName()); + removeCardMutex->unlock(); + emit cardRemoved(card); +} + +CardInfoPtr CardDatabase::getCard(const QString &cardName) const +{ + return getCardFromMap(cards, cardName); +} + +QList CardDatabase::getCards(const QStringList &cardNames) const +{ + QList cardInfos; + for (const QString &cardName : cardNames) { + CardInfoPtr ptr = getCardFromMap(cards, cardName); + if (ptr) + cardInfos.append(ptr); + } + + return cardInfos; +} + +QList CardDatabase::getCardsByNameAndProviderId(const QMap &cardNames) const +{ + QList cardInfos; + for (const QString &cardName : cardNames) { + CardInfoPtr ptr = getCardByNameAndProviderId(cardName, cardNames[cardName]); + if (ptr) + cardInfos.append(ptr); + } + + return cardInfos; +} + +CardInfoPtr CardDatabase::getCardByNameAndProviderId(const QString &cardName, const QString &providerId) const +{ + auto info = getCard(cardName); + if (providerId.isNull() || providerId.isEmpty() || info.isNull()) { + return info; + } + + for (const auto &cardInfoPerSetList : info->getSets()) { + for (const auto &set : cardInfoPerSetList) { + if (set.getProperty("uuid") == providerId) { + CardInfoPtr cardFromSpecificSet = info->clone(); + cardFromSpecificSet->setPixmapCacheKey(QLatin1String("card_") + QString(info->getName()) + + QString("_") + QString(set.getProperty("uuid"))); + return cardFromSpecificSet; + } + } + } + return {}; +} + +CardInfoPtr CardDatabase::getCardBySimpleName(const QString &cardName) const +{ + return getCardFromMap(simpleNameCards, CardInfo::simplifyName(cardName)); +} + +CardInfoPtr CardDatabase::guessCard(const QString &cardName, const QString &providerId) const +{ + CardInfoPtr temp = providerId.isEmpty() ? getCard(cardName) : getCardByNameAndProviderId(cardName, providerId); + + if (temp == nullptr) { // get card by simple name instead + temp = getCardBySimpleName(cardName); + if (temp == nullptr) { // still could not find the card, so simplify the cardName too + const auto &simpleCardName = CardInfo::simplifyName(cardName); + temp = getCardBySimpleName(simpleCardName); + } + } + return temp; // returns nullptr if not found +} + +CardSetPtr CardDatabase::getSet(const QString &setName) +{ + if (sets.contains(setName)) { + return sets.value(setName); + } else { + CardSetPtr newSet = CardSet::newInstance(setName); + sets.insert(setName, newSet); + return newSet; + } +} + +void CardDatabase::addSet(CardSetPtr set) +{ + sets.insert(set->getShortName(), set); +} + +SetList CardDatabase::getSetList() const +{ + SetList result; + QHashIterator i(sets); + while (i.hasNext()) { + i.next(); + result << i.value(); + } + return result; +} + +CardInfoPtr CardDatabase::getCardFromMap(const CardNameMap &cardMap, const QString &cardName) const +{ + if (cardMap.contains(cardName)) + return cardMap.value(cardName); + + return {}; +} + +LoadStatus CardDatabase::loadFromFile(const QString &fileName) +{ + QFile file(fileName); + file.open(QIODevice::ReadOnly); + if (!file.isOpen()) { + return FileError; + } + + for (auto parser : availableParsers) { + file.reset(); + if (parser->getCanParseFile(fileName, file)) { + file.reset(); + parser->parseFile(file); + return Ok; + } + } + + return Invalid; +} + +LoadStatus CardDatabase::loadCardDatabase(const QString &path) +{ + auto startTime = QTime::currentTime(); + LoadStatus tempLoadStatus = NotLoaded; + if (!path.isEmpty()) { + loadFromFileMutex->lock(); + tempLoadStatus = loadFromFile(path); + loadFromFileMutex->unlock(); + } + + int msecs = startTime.msecsTo(QTime::currentTime()); + qCDebug(CardDatabaseLoadingLog) << "Path =" << path << "Status =" << tempLoadStatus << "Cards =" << cards.size() + << "Sets =" << sets.size() << QString("%1ms").arg(msecs); + + return tempLoadStatus; +} + +LoadStatus CardDatabase::loadCardDatabases() +{ + reloadDatabaseMutex->lock(); + + qCDebug(CardDatabaseLoadingLog) << "Started"; + + clear(); // remove old db + + loadStatus = loadCardDatabase(SettingsCache::instance().getCardDatabasePath()); // load main card database + loadCardDatabase(SettingsCache::instance().getTokenDatabasePath()); // load tokens database + loadCardDatabase(SettingsCache::instance().getSpoilerCardDatabasePath()); // load spoilers database + + // find all custom card databases, recursively & following symlinks + // then load them alphabetically + QDirIterator customDatabaseIterator(SettingsCache::instance().getCustomCardDatabasePath(), QStringList() << "*.xml", + QDir::Files, QDirIterator::Subdirectories | QDirIterator::FollowSymlinks); + QStringList databasePaths; + while (customDatabaseIterator.hasNext()) { + customDatabaseIterator.next(); + databasePaths.push_back(customDatabaseIterator.filePath()); + } + databasePaths.sort(); + + for (auto i = 0; i < databasePaths.size(); ++i) { + const auto &databasePath = databasePaths.at(i); + qCDebug(CardDatabaseLoadingLog) << "Loading Custom Set" << i << "(" << databasePath << ")"; + loadCardDatabase(databasePath); + } + + // AFTER all the cards have been loaded + + // Refresh the pixmap cache keys for all cards by setting them to the UUID of the preferred printing + refreshPreferredPrintings(); + // resolve the reverse-related tags + refreshCachedReverseRelatedCards(); + + if (loadStatus == Ok) { + checkUnknownSets(); // update deck editors, etc + qCDebug(CardDatabaseLoadingSuccessOrFailureLog) << "Success"; + emit cardDatabaseLoadingFinished(); + } else { + qCDebug(CardDatabaseLoadingSuccessOrFailureLog) << "Failed"; + emit cardDatabaseLoadingFailed(); // bring up the settings dialog + } + + reloadDatabaseMutex->unlock(); + return loadStatus; +} + +void CardDatabase::refreshPreferredPrintings() +{ + for (const CardInfoPtr &card : cards) { + card->setPixmapCacheKey(QLatin1String("card_") + QString(card->getName()) + QString("_") + + QString(getPreferredPrintingProviderIdForCard(card->getName()))); + } +} + +CardInfoPerSet CardDatabase::getPreferredSetForCard(const QString &cardName) const +{ + CardInfoPtr cardInfo = getCard(cardName); + if (!cardInfo) { + return CardInfoPerSet(nullptr); + } + + CardInfoPerSetMap setMap = cardInfo->getSets(); + if (setMap.empty()) { + return CardInfoPerSet(nullptr); + } + + CardSetPtr preferredSet = nullptr; + CardInfoPerSet preferredCard; + SetPriorityComparator comparator; + + for (const auto &cardInfoPerSetList : setMap) { + for (auto &cardInfoForSet : cardInfoPerSetList) { + CardSetPtr currentSet = cardInfoForSet.getPtr(); + if (!preferredSet || comparator(currentSet, preferredSet)) { + preferredSet = currentSet; + preferredCard = cardInfoForSet; + } + } + } + + if (preferredSet) { + return preferredCard; + } + + return CardInfoPerSet(nullptr); +} + +CardInfoPerSet CardDatabase::getSpecificSetForCard(const QString &cardName, const QString &providerId) const +{ + CardInfoPtr cardInfo = getCard(cardName); + if (!cardInfo) { + return CardInfoPerSet(nullptr); + } + + CardInfoPerSetMap setMap = cardInfo->getSets(); + if (setMap.empty()) { + return CardInfoPerSet(nullptr); + } + + for (const auto &cardInfoPerSetList : setMap) { + for (auto &cardInfoForSet : cardInfoPerSetList) { + if (cardInfoForSet.getProperty("uuid") == providerId) { + return cardInfoForSet; + } + } + } + + if (providerId.isNull()) { + return getPreferredSetForCard(cardName); + } + + return CardInfoPerSet(nullptr); +} + +CardInfoPerSet CardDatabase::getSpecificSetForCard(const QString &cardName, + const QString &setShortName, + const QString &collectorNumber) const +{ + CardInfoPtr cardInfo = getCard(cardName); + if (!cardInfo) { + return CardInfoPerSet(nullptr); + } + + CardInfoPerSetMap setMap = cardInfo->getSets(); + if (setMap.empty()) { + return CardInfoPerSet(nullptr); + } + + for (const auto &cardInfoPerSetList : setMap) { + for (auto &cardInfoForSet : cardInfoPerSetList) { + if (cardInfoForSet.getPtr()->getShortName() == setShortName && + cardInfoForSet.getProperty("num") == collectorNumber) { + return cardInfoForSet; + } + } + } + + return CardInfoPerSet(nullptr); +} + +QString CardDatabase::getPreferredPrintingProviderIdForCard(const QString &cardName) +{ + CardInfoPerSet preferredSetCardInfo = getPreferredSetForCard(cardName); + QString preferredPrintingProviderId = preferredSetCardInfo.getProperty(QString("uuid")); + if (preferredPrintingProviderId.isEmpty()) { + CardInfoPtr defaultCardInfo = getCard(cardName); + if (defaultCardInfo.isNull()) { + return cardName; + } + return defaultCardInfo->getName(); + } + return preferredPrintingProviderId; +} + +bool CardDatabase::isProviderIdForPreferredPrinting(const QString &cardName, const QString &providerId) +{ + if (providerId.startsWith("card_")) { + return providerId == + QLatin1String("card_") + cardName + QString("_") + getPreferredPrintingProviderIdForCard(cardName); + } + return providerId == getPreferredPrintingProviderIdForCard(cardName); +} + +CardInfoPerSet CardDatabase::getSetInfoForCard(const CardInfoPtr &_card) +{ + const CardInfoPerSetMap &setMap = _card->getSets(); + if (setMap.empty()) { + return CardInfoPerSet(nullptr); + } + + for (const auto &cardInfoPerSetList : setMap) { + for (const auto &cardInfoForSet : cardInfoPerSetList) { + if (QLatin1String("card_") + _card->getName() + QString("_") + cardInfoForSet.getProperty("uuid") == + _card->getPixmapCacheKey()) { + return cardInfoForSet; + } + } + } + + return CardInfoPerSet(nullptr); +} + +void CardDatabase::refreshCachedReverseRelatedCards() +{ + for (const CardInfoPtr &card : cards) + card->resetReverseRelatedCards2Me(); + + for (const CardInfoPtr &card : cards) { + if (card->getReverseRelatedCards().isEmpty()) { + continue; + } + + for (CardRelation *cardRelation : card->getReverseRelatedCards()) { + const QString &targetCard = cardRelation->getName(); + if (!cards.contains(targetCard)) { + continue; + } + + auto *newCardRelation = new CardRelation( + card->getName(), cardRelation->getAttachType(), cardRelation->getIsCreateAllExclusion(), + cardRelation->getIsVariable(), cardRelation->getDefaultCount(), cardRelation->getIsPersistent()); + cards.value(targetCard)->addReverseRelatedCards2Me(newCardRelation); + } + } +} + +QStringList CardDatabase::getAllMainCardTypes() const +{ + QSet types; + QHashIterator cardIterator(cards); + while (cardIterator.hasNext()) { + types.insert(cardIterator.next().value()->getMainCardType()); + } + return types.values(); +} + +void CardDatabase::checkUnknownSets() +{ + auto _sets = getSetList(); + + if (_sets.getEnabledSetsNum()) { + // if some sets are first found on this run, ask the user + int numUnknownSets = _sets.getUnknownSetsNum(); + QStringList unknownSetNames = _sets.getUnknownSetsNames(); + if (numUnknownSets > 0) { + emit cardDatabaseNewSetsFound(numUnknownSets, unknownSetNames); + } else { + _sets.markAllAsKnown(); + } + } else { + // No set enabled. Probably this is the first time running trice + _sets.guessSortKeys(); + _sets.sortByKey(); + _sets.enableAll(); + notifyEnabledSetsChanged(); + + emit cardDatabaseAllNewSetsEnabled(); + } +} + +void CardDatabase::enableAllUnknownSets() +{ + auto _sets = getSetList(); + _sets.enableAllUnknown(); +} + +void CardDatabase::markAllSetsAsKnown() +{ + auto _sets = getSetList(); + _sets.markAllAsKnown(); +} + +void CardDatabase::notifyEnabledSetsChanged() +{ + // refresh the list of cached set names + for (const CardInfoPtr &card : cards) + card->refreshCachedSetNames(); + + // inform the carddatabasemodels that they need to re-check their list of cards + emit cardDatabaseEnabledSetsChanged(); +} + +bool CardDatabase::saveCustomTokensToFile() +{ + QString fileName = + SettingsCache::instance().getCustomCardDatabasePath() + "/" + CardDatabase::TOKENS_SETNAME + ".xml"; + + SetNameMap tmpSets; + CardSetPtr customTokensSet = getSet(CardDatabase::TOKENS_SETNAME); + tmpSets.insert(CardDatabase::TOKENS_SETNAME, customTokensSet); + + CardNameMap tmpCards; + for (const CardInfoPtr &card : cards) { + if (card->getSets().contains(CardDatabase::TOKENS_SETNAME)) { + tmpCards.insert(card->getName(), card); + } + } + + availableParsers.first()->saveToFile(tmpSets, tmpCards, fileName); + return true; +} + +CardRelation::CardRelation(const QString &_name, + AttachType _attachType, + bool _isCreateAllExclusion, + bool _isVariableCount, + int _defaultCount, + bool _isPersistent) + : name(_name), attachType(_attachType), isCreateAllExclusion(_isCreateAllExclusion), + isVariableCount(_isVariableCount), defaultCount(_defaultCount), isPersistent(_isPersistent) +{ +} + +void CardInfo::resetReverseRelatedCards2Me() +{ + for (CardRelation *cardRelation : this->getReverseRelatedCards2Me()) { + cardRelation->deleteLater(); + } + reverseRelatedCardsToMe = QList(); +} + +// Back-compatibility methods. Remove ASAP +const QString CardInfo::getCardType() const +{ + return getProperty(Mtg::CardType); +} +void CardInfo::setCardType(const QString &value) +{ + setProperty(Mtg::CardType, value); +} +const QString CardInfo::getCmc() const +{ + return getProperty(Mtg::ConvertedManaCost); +} +const QString CardInfo::getColors() const +{ + return getProperty(Mtg::Colors); +} +void CardInfo::setColors(const QString &value) +{ + setProperty(Mtg::Colors, value); +} +const QString CardInfo::getLoyalty() const +{ + return getProperty(Mtg::Loyalty); +} +const QString CardInfo::getMainCardType() const +{ + return getProperty(Mtg::MainCardType); +} +const QString CardInfo::getManaCost() const +{ + return getProperty(Mtg::ManaCost); +} +const QString CardInfo::getPowTough() const +{ + return getProperty(Mtg::PowTough); +} +void CardInfo::setPowTough(const QString &value) +{ + setProperty(Mtg::PowTough, value); +} diff --git a/cockatrice/src/game/cards/card_info.h b/cockatrice/src/game/cards/card_info.h new file mode 100644 index 000000000..12b4a06fe --- /dev/null +++ b/cockatrice/src/game/cards/card_info.h @@ -0,0 +1,601 @@ +#ifndef CARDDATABASE_H +#define CARDDATABASE_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +inline Q_LOGGING_CATEGORY(CardDatabaseLog, "card_database"); +inline Q_LOGGING_CATEGORY(CardDatabaseLoadingLog, "card_database.loading"); +inline Q_LOGGING_CATEGORY(CardDatabaseLoadingSuccessOrFailureLog, "card_database.loading.success_or_failure"); + +class CardDatabase; +class CardInfo; +class CardInfoPerSet; +class CardSet; +class CardRelation; +class ICardDatabaseParser; + +typedef QMap QStringMap; +typedef QSharedPointer CardInfoPtr; +typedef QSharedPointer CardSetPtr; +typedef QMap> CardInfoPerSetMap; + +Q_DECLARE_METATYPE(CardInfoPtr) + +class CardSet : public QList +{ +public: + enum Priority + { + PriorityFallback = 0, + PriorityPrimary = 10, + PrioritySecondary = 20, + PriorityReprint = 30, + PriorityOther = 40, + PriorityLowest = 100, + }; + +private: + QString shortName, longName; + unsigned int sortKey; + QDate releaseDate; + QString setType; + Priority priority; + bool enabled, isknown; + +public: + explicit CardSet(const QString &_shortName = QString(), + const QString &_longName = QString(), + const QString &_setType = QString(), + const QDate &_releaseDate = QDate(), + const Priority _priority = PriorityFallback); + static CardSetPtr newInstance(const QString &_shortName = QString(), + const QString &_longName = QString(), + const QString &_setType = QString(), + const QDate &_releaseDate = QDate(), + const Priority _priority = PriorityFallback); + QString getCorrectedShortName() const; + QString getShortName() const + { + return shortName; + } + QString getLongName() const + { + return longName; + } + QString getSetType() const + { + return setType; + } + QDate getReleaseDate() const + { + return releaseDate; + } + Priority getPriority() const + { + return priority; + } + void setLongName(const QString &_longName) + { + longName = _longName; + } + void setSetType(const QString &_setType) + { + setType = _setType; + } + void setReleaseDate(const QDate &_releaseDate) + { + releaseDate = _releaseDate; + } + void setPriority(const Priority _priority) + { + priority = _priority; + } + + void loadSetOptions(); + int getSortKey() const + { + return sortKey; + } + void setSortKey(unsigned int _sortKey); + bool getEnabled() const + { + return enabled; + } + void setEnabled(bool _enabled); + bool getIsKnown() const + { + return isknown; + } + void setIsKnown(bool _isknown); + + // Determine incomplete sets. + bool getIsKnownIgnored() const + { + return longName.length() + setType.length() + releaseDate.toString().length() == 0; + } +}; + +class SetList : public QList +{ +private: + class KeyCompareFunctor; + +public: + void sortByKey(); + void guessSortKeys(); + void enableAllUnknown(); + void enableAll(); + void markAllAsKnown(); + int getEnabledSetsNum(); + int getUnknownSetsNum(); + QStringList getUnknownSetsNames(); + void defaultSort(); +}; + +class CardInfoPerSet +{ +public: + explicit CardInfoPerSet(const CardSetPtr &_set = QSharedPointer(nullptr)); + ~CardInfoPerSet() = default; + + bool operator==(const CardInfoPerSet &other) const + { + return this->set == other.set && this->properties == other.properties; + } + +private: + CardSetPtr set; + // per-set card properties; + QVariantHash properties; + +public: + const CardSetPtr getPtr() const + { + return set; + } + const QStringList getProperties() const + { + return properties.keys(); + } + const QString getProperty(const QString &propertyName) const + { + return properties.value(propertyName).toString(); + } + void setProperty(const QString &_name, const QString &_value) + { + properties.insert(_name, _value); + } +}; + +class CardInfo : public QObject +{ + Q_OBJECT +private: + CardInfoPtr smartThis; + // The card name + QString name; + // The name without punctuation or capitalization, for better card name recognition. + QString simpleName; + // The key used to identify this card in the cache + QString pixmapCacheKey; + // card text + QString text; + // whether this is not a "real" card but a token + bool isToken; + // basic card properties; common for all the sets + QVariantHash properties; + // the cards i'm related to + QList relatedCards; + // the card i'm reverse-related to + QList reverseRelatedCards; + // the cards thare are reverse-related to me + QList reverseRelatedCardsToMe; + // card sets + CardInfoPerSetMap sets; + // cached set names + QString setsNames; + // positioning properties; used by UI + bool cipt; + bool landscapeOrientation; + int tableRow; + bool upsideDownArt; + +public: + explicit CardInfo(const QString &_name, + const QString &_text, + bool _isToken, + QVariantHash _properties, + const QList &_relatedCards, + const QList &_reverseRelatedCards, + CardInfoPerSetMap _sets, + bool _cipt, + bool _landscapeOrientation, + int _tableRow, + bool _upsideDownArt); + CardInfo(const CardInfo &other) + : QObject(other.parent()), name(other.name), simpleName(other.simpleName), pixmapCacheKey(other.pixmapCacheKey), + text(other.text), isToken(other.isToken), properties(other.properties), relatedCards(other.relatedCards), + reverseRelatedCards(other.reverseRelatedCards), reverseRelatedCardsToMe(other.reverseRelatedCardsToMe), + sets(other.sets), setsNames(other.setsNames), cipt(other.cipt), + landscapeOrientation(other.landscapeOrientation), tableRow(other.tableRow), upsideDownArt(other.upsideDownArt) + { + } + ~CardInfo() override; + + static CardInfoPtr newInstance(const QString &_name); + + static CardInfoPtr newInstance(const QString &_name, + const QString &_text, + bool _isToken, + QVariantHash _properties, + const QList &_relatedCards, + const QList &_reverseRelatedCards, + CardInfoPerSetMap _sets, + bool _cipt, + bool _landscapeOrientation, + int _tableRow, + bool _upsideDownArt); + + CardInfoPtr clone() const + { + // Use the copy constructor to create a new instance + CardInfoPtr newCardInfo = CardInfoPtr(new CardInfo(*this)); + newCardInfo->setSmartPointer(newCardInfo); // Set the smart pointer for the new instance + return newCardInfo; + } + + void setSmartPointer(CardInfoPtr _ptr) + { + smartThis = std::move(_ptr); + } + + // basic properties + inline const QString &getName() const + { + return name; + } + const QString &getSimpleName() const + { + return simpleName; + } + void setPixmapCacheKey(QString _pixmapCacheKey) + { + pixmapCacheKey = _pixmapCacheKey; + } + const QString &getPixmapCacheKey() const + { + return pixmapCacheKey; + } + + const QString &getText() const + { + return text; + } + void setText(const QString &_text) + { + text = _text; + emit cardInfoChanged(smartThis); + } + + bool getIsToken() const + { + return isToken; + } + const QStringList getProperties() const + { + return properties.keys(); + } + const QString getProperty(const QString &propertyName) const + { + return properties.value(propertyName).toString(); + } + void setProperty(const QString &_name, const QString &_value) + { + properties.insert(_name, _value); + emit cardInfoChanged(smartThis); + } + bool hasProperty(const QString &propertyName) const + { + return properties.contains(propertyName); + } + const CardInfoPerSetMap &getSets() const + { + return sets; + } + const QString &getSetsNames() const + { + return setsNames; + } + const QString getSetProperty(const QString &setName, const QString &propertyName) const + { + if (!sets.contains(setName)) + return ""; + + for (const auto &set : sets[setName]) { + if (QLatin1String("card_") + this->getName() + QString("_") + QString(set.getProperty("uuid")) == + this->getPixmapCacheKey()) { + return set.getProperty(propertyName); + } + } + + return sets[setName][0].getProperty(propertyName); + } + + // related cards + const QList &getRelatedCards() const + { + return relatedCards; + } + const QList &getReverseRelatedCards() const + { + return reverseRelatedCards; + } + const QList &getReverseRelatedCards2Me() const + { + return reverseRelatedCardsToMe; + } + const QList getAllRelatedCards() const + { + QList result; + result.append(getRelatedCards()); + result.append(getReverseRelatedCards2Me()); + return result; + } + void resetReverseRelatedCards2Me(); + void addReverseRelatedCards2Me(CardRelation *cardRelation) + { + reverseRelatedCardsToMe.append(cardRelation); + } + + // positioning + bool getCipt() const + { + return cipt; + } + bool getLandscapeOrientation() const + { + return landscapeOrientation; + } + int getTableRow() const + { + return tableRow; + } + void setTableRow(int _tableRow) + { + tableRow = _tableRow; + } + bool getUpsideDownArt() const + { + return upsideDownArt; + } + const QChar getColorChar() const; + + // Back-compatibility methods. Remove ASAP + const QString getCardType() const; + void setCardType(const QString &value); + const QString getCmc() const; + const QString getColors() const; + void setColors(const QString &value); + const QString getLoyalty() const; + const QString getMainCardType() const; + const QString getManaCost() const; + const QString getPowTough() const; + void setPowTough(const QString &value); + + // methods using per-set properties + QString getCustomPicURL(const QString &set) const + { + return getSetProperty(set, "picurl"); + } + QString getCorrectedName() const; + void addToSet(const CardSetPtr &_set, CardInfoPerSet _info = CardInfoPerSet()); + void combineLegalities(const QVariantHash &props); + void emitPixmapUpdated() + { + emit pixmapUpdated(); + } + void refreshCachedSetNames(); + + /** + * Simplify a name to have no punctuation and lowercase all letters, for + * less strict name-matching. + */ + static QString simplifyName(const QString &name); + +signals: + void pixmapUpdated(); + void cardInfoChanged(CardInfoPtr card); +}; + +enum LoadStatus +{ + Ok, + VersionTooOld, + Invalid, + NotLoaded, + FileError, + NoCards +}; + +typedef QHash CardNameMap; +typedef QHash SetNameMap; + +class CardDatabase : public QObject +{ + Q_OBJECT +protected: + /* + * The cards, indexed by name. + */ + CardNameMap cards; + + /** + * The cards, indexed by their simple name. + */ + CardNameMap simpleNameCards; + + /* + * The sets, indexed by short name. + */ + SetNameMap sets; + + LoadStatus loadStatus; + + QVector availableParsers; + +private: + CardInfoPtr getCardFromMap(const CardNameMap &cardMap, const QString &cardName) const; + void checkUnknownSets(); + void refreshCachedReverseRelatedCards(); + + QBasicMutex *reloadDatabaseMutex = new QBasicMutex(), *clearDatabaseMutex = new QBasicMutex(), + *loadFromFileMutex = new QBasicMutex(), *addCardMutex = new QBasicMutex(), + *removeCardMutex = new QBasicMutex(); + +public: + static const char *TOKENS_SETNAME; + + explicit CardDatabase(QObject *parent = nullptr); + ~CardDatabase() override; + void clear(); + void removeCard(CardInfoPtr card); + [[nodiscard]] CardInfoPtr getCard(const QString &cardName) const; + [[nodiscard]] QList getCards(const QStringList &cardNames) const; + QList getCardsByNameAndProviderId(const QMap &cardNames) const; + [[nodiscard]] CardInfoPtr getCardByNameAndProviderId(const QString &cardName, const QString &providerId) const; + [[nodiscard]] CardInfoPerSet getPreferredSetForCard(const QString &cardName) const; + [[nodiscard]] CardInfoPerSet getSpecificSetForCard(const QString &cardName, const QString &providerId) const; + CardInfoPerSet + getSpecificSetForCard(const QString &cardName, const QString &setShortName, const QString &collectorNumber) const; + QString getPreferredPrintingProviderIdForCard(const QString &cardName); + [[nodiscard]] CardInfoPtr guessCard(const QString &cardName, const QString &providerId = QString()) const; + + /* + * Get a card by its simple name. The name will be simplified in this + * function, so you don't need to simplify it beforehand. + */ + [[nodiscard]] CardInfoPtr getCardBySimpleName(const QString &cardName) const; + + CardSetPtr getSet(const QString &setName); + bool isProviderIdForPreferredPrinting(const QString &cardName, const QString &providerId); + static CardInfoPerSet getSetInfoForCard(const CardInfoPtr &_card); + const CardNameMap &getCardList() const + { + return cards; + } + SetList getSetList() const; + LoadStatus loadFromFile(const QString &fileName); + bool saveCustomTokensToFile(); + QStringList getAllMainCardTypes() const; + LoadStatus getLoadStatus() const + { + return loadStatus; + } + void enableAllUnknownSets(); + void markAllSetsAsKnown(); + void notifyEnabledSetsChanged(); + +public slots: + LoadStatus loadCardDatabases(); + void refreshPreferredPrintings(); + void addCard(CardInfoPtr card); + void addSet(CardSetPtr set); +protected slots: + LoadStatus loadCardDatabase(const QString &path); +signals: + void cardDatabaseLoadingFinished(); + void cardDatabaseLoadingFailed(); + void cardDatabaseNewSetsFound(int numUnknownSets, QStringList unknownSetsNames); + void cardDatabaseAllNewSetsEnabled(); + void cardDatabaseEnabledSetsChanged(); + void cardAdded(CardInfoPtr card); + void cardRemoved(CardInfoPtr card); +}; + +class CardRelation : public QObject +{ + Q_OBJECT +public: + enum AttachType + { + DoesNotAttach = 0, + AttachTo = 1, + TransformInto = 2, + }; + +private: + QString name; + AttachType attachType; + bool isCreateAllExclusion; + bool isVariableCount; + int defaultCount; + bool isPersistent; + +public: + explicit CardRelation(const QString &_name = QString(), + AttachType _attachType = DoesNotAttach, + bool _isCreateAllExclusion = false, + bool _isVariableCount = false, + int _defaultCount = 1, + bool _isPersistent = false); + + inline const QString &getName() const + { + return name; + } + AttachType getAttachType() const + { + return attachType; + } + bool getDoesAttach() const + { + return attachType != DoesNotAttach; + } + bool getDoesTransform() const + { + return attachType == TransformInto; + } + QString getAttachTypeAsString() const + { + switch (attachType) { + case AttachTo: + return "attach"; + case TransformInto: + return "transform"; + default: + return ""; + } + } + bool getCanCreateAnother() const + { + return !getDoesAttach(); + } + bool getIsCreateAllExclusion() const + { + return isCreateAllExclusion; + } + bool getIsVariable() const + { + return isVariableCount; + } + int getDefaultCount() const + { + return defaultCount; + } + bool getIsPersistent() const + { + return isPersistent; + } +}; +#endif