mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-07-14 14:32:15 -07:00
Turn things in common into separate libs.
Took 2 hours 27 minutes
This commit is contained in:
parent
53d80efab8
commit
01378b8314
389 changed files with 336 additions and 233 deletions
67
libs/card/CMakeLists.txt
Normal file
67
libs/card/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
set(CMAKE_AUTOMOC ON)
|
||||
set(CMAKE_AUTOUIC ON)
|
||||
set(CMAKE_AUTORCC ON)
|
||||
|
||||
set(HEADERS
|
||||
include/card/card_info.h
|
||||
include/card/card_info_comparator.h
|
||||
include/card/card_database/card_database.h
|
||||
include/card/card_database/card_database_loader.h
|
||||
include/card/card_database/card_database_manager.h
|
||||
include/card/card_database/card_database_querier.h
|
||||
include/card/card_database/model/card_database_model.h
|
||||
include/card/card_database/model/card_database_display_model.h
|
||||
include/card/card_database/model/card/card_completer_proxy_model.h
|
||||
include/card/card_database/model/card/card_search_model.h
|
||||
include/card/card_database/model/token/token_display_model.h
|
||||
include/card/card_database/model/token/token_edit_model.h
|
||||
include/card/card_database/parser/card_database_parser.h
|
||||
include/card/card_database/parser/cockatrice_xml_3.h
|
||||
include/card/card_database/parser/cockatrice_xml_4.h
|
||||
include/card/card_printing/exact_card.h
|
||||
include/card/card_printing/printing_info.h
|
||||
include/card/card_set/card_set.h
|
||||
include/card/card_relation/card_relation.h
|
||||
)
|
||||
|
||||
if (Qt6_FOUND)
|
||||
qt6_wrap_cpp(MOC_SOURCES ${HEADERS})
|
||||
elseif (Qt5_FOUND)
|
||||
qt5_wrap_cpp(MOC_SOURCES ${HEADERS})
|
||||
endif ()
|
||||
|
||||
qt_add_library(cardlib STATIC
|
||||
src/card_info.cpp
|
||||
src/card_info_comparator.cpp
|
||||
src/card_database/card_database.cpp
|
||||
src/card_database/card_database_loader.cpp
|
||||
src/card_database/card_database_manager.cpp
|
||||
src/card_database/card_database_querier.cpp
|
||||
src/card_database/model/card_database_model.cpp
|
||||
src/card_database/model/card_database_display_model.cpp
|
||||
src/card_database/model/card/card_completer_proxy_model.cpp
|
||||
src/card_database/model/card/card_search_model.cpp
|
||||
src/card_database/model/token/token_display_model.cpp
|
||||
src/card_database/model/token/token_edit_model.cpp
|
||||
src/card_database/parser/card_database_parser.cpp
|
||||
src/card_database/parser/cockatrice_xml_3.cpp
|
||||
src/card_database/parser/cockatrice_xml_4.cpp
|
||||
src/card_printing/exact_card.cpp
|
||||
src/card_printing/printing_info.cpp
|
||||
src/card_set/card_set.cpp
|
||||
src/card_set/card_set_list.cpp
|
||||
src/card_relation/card_relation.cpp
|
||||
${MOC_SOURCES}
|
||||
)
|
||||
|
||||
target_include_directories(cardlib
|
||||
PUBLIC include
|
||||
PUBLIC ${CMAKE_SOURCE_DIR}/common
|
||||
PUBLIC ${CMAKE_SOURCE_DIR}/libs/settings/include
|
||||
PUBLIC ${CMAKE_SOURCE_DIR}/cockatrice/src/filters
|
||||
)
|
||||
|
||||
target_link_libraries(cardlib
|
||||
PUBLIC settingslib
|
||||
PUBLIC ${COCKATRICE_QT_MODULES}
|
||||
)
|
||||
102
libs/card/include/card/card_database/card_database.h
Normal file
102
libs/card/include/card/card_database/card_database.h
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
/**
|
||||
* @file card_database.h
|
||||
* @ingroup CardDatabase
|
||||
* @brief The CardDatabase is responsible for holding the card and set maps.
|
||||
*/
|
||||
|
||||
#ifndef CARDDATABASE_H
|
||||
#define CARDDATABASE_H
|
||||
|
||||
#include "card/card_database/card_database_loader.h"
|
||||
#include "card/card_set/card_set_list.h"
|
||||
#include "card_database_querier.h"
|
||||
#include "utility/card_ref.h"
|
||||
|
||||
#include <QBasicMutex>
|
||||
#include <QDate>
|
||||
#include <QHash>
|
||||
#include <QList>
|
||||
#include <QLoggingCategory>
|
||||
#include <QStringList>
|
||||
#include <QVector>
|
||||
#include <utility>
|
||||
|
||||
inline Q_LOGGING_CATEGORY(CardDatabaseLog, "card_database");
|
||||
|
||||
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;
|
||||
|
||||
// loader responsible for file discovery & parsing
|
||||
CardDatabaseLoader *loader;
|
||||
|
||||
LoadStatus loadStatus;
|
||||
|
||||
CardDatabaseQuerier *querier;
|
||||
|
||||
private:
|
||||
void checkUnknownSets();
|
||||
void refreshCachedReverseRelatedCards();
|
||||
|
||||
QBasicMutex *clearDatabaseMutex = new QBasicMutex(), *addCardMutex = new QBasicMutex(),
|
||||
*removeCardMutex = new QBasicMutex();
|
||||
|
||||
public:
|
||||
explicit CardDatabase(QObject *parent = nullptr);
|
||||
~CardDatabase() override;
|
||||
|
||||
void removeCard(CardInfoPtr card);
|
||||
void clear();
|
||||
|
||||
const CardNameMap &getCardList() const
|
||||
{
|
||||
return cards;
|
||||
}
|
||||
CardSetPtr getSet(const QString &setName);
|
||||
CardSetList getSetList() const;
|
||||
LoadStatus getLoadStatus() const
|
||||
{
|
||||
return loadStatus;
|
||||
}
|
||||
CardDatabaseQuerier *query() const
|
||||
{
|
||||
return querier;
|
||||
}
|
||||
void enableAllUnknownSets();
|
||||
void markAllSetsAsKnown();
|
||||
void notifyEnabledSetsChanged();
|
||||
|
||||
public slots:
|
||||
void addCard(CardInfoPtr card);
|
||||
void addSet(CardSetPtr set);
|
||||
void loadCardDatabases();
|
||||
bool saveCustomTokensToFile();
|
||||
signals:
|
||||
void cardDatabaseLoadingFinished();
|
||||
void cardDatabaseLoadingFailed();
|
||||
void cardDatabaseNewSetsFound(int numUnknownSets, QStringList unknownSetsNames);
|
||||
void cardDatabaseAllNewSetsEnabled();
|
||||
void cardDatabaseEnabledSetsChanged();
|
||||
void cardAdded(CardInfoPtr card);
|
||||
void cardRemoved(CardInfoPtr card);
|
||||
|
||||
friend class CardDatabaseLoader;
|
||||
friend class CardDatabaseQuerier;
|
||||
};
|
||||
|
||||
#endif
|
||||
63
libs/card/include/card/card_database/card_database_loader.h
Normal file
63
libs/card/include/card/card_database/card_database_loader.h
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
/**
|
||||
* @file card_database_loader.h
|
||||
* @ingroup CardDatabase
|
||||
* @brief The CardDatabaseLoader is responsible for populating the card database from files on disk.
|
||||
*/
|
||||
|
||||
#ifndef COCKATRICE_CARD_DATABASE_LOADER_H
|
||||
#define COCKATRICE_CARD_DATABASE_LOADER_H
|
||||
|
||||
#include <QBasicMutex>
|
||||
#include <QList>
|
||||
#include <QLoggingCategory>
|
||||
#include <QObject>
|
||||
|
||||
inline Q_LOGGING_CATEGORY(CardDatabaseLoadingLog, "card_database.loading");
|
||||
inline Q_LOGGING_CATEGORY(CardDatabaseLoadingSuccessOrFailureLog, "card_database.loading.success_or_failure");
|
||||
|
||||
class CardDatabase;
|
||||
class ICardDatabaseParser;
|
||||
|
||||
enum LoadStatus
|
||||
{
|
||||
Ok,
|
||||
VersionTooOld,
|
||||
Invalid,
|
||||
NotLoaded,
|
||||
FileError,
|
||||
NoCards
|
||||
};
|
||||
|
||||
class CardDatabaseLoader : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit CardDatabaseLoader(QObject *parent, CardDatabase *db);
|
||||
~CardDatabaseLoader() override;
|
||||
|
||||
public slots:
|
||||
LoadStatus loadCardDatabases(); // discover & load the configured databases
|
||||
LoadStatus loadCardDatabase(const QString &path); // load a single file
|
||||
bool saveCustomTokensToFile(); // write tokens to custom DB path
|
||||
|
||||
signals:
|
||||
void loadingStarted();
|
||||
void loadingFinished();
|
||||
void loadingFailed();
|
||||
void newSetsFound(int numSets, const QStringList &setNames);
|
||||
void allNewSetsEnabled();
|
||||
|
||||
private:
|
||||
LoadStatus loadFromFile(const QString &fileName); // internal helper
|
||||
QStringList collectCustomDatabasePaths() const;
|
||||
|
||||
CardDatabase *database; // non-owning pointer to the container
|
||||
|
||||
// parsers
|
||||
QList<ICardDatabaseParser *> availableParsers;
|
||||
|
||||
QBasicMutex *loadFromFileMutex = new QBasicMutex();
|
||||
QBasicMutex *reloadDatabaseMutex = new QBasicMutex();
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_CARD_DATABASE_LOADER_H
|
||||
29
libs/card/include/card/card_database/card_database_manager.h
Normal file
29
libs/card/include/card/card_database/card_database_manager.h
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
/**
|
||||
* @file card_database_manager.h
|
||||
* @ingroup CardDatabase
|
||||
* @brief The CardDatabaseManager is responsible for managing the global database singleton.
|
||||
*/
|
||||
|
||||
#ifndef CARD_DATABASE_ACCESSOR_H
|
||||
#define CARD_DATABASE_ACCESSOR_H
|
||||
|
||||
#pragma once
|
||||
#include "card_database.h"
|
||||
|
||||
class CardDatabaseManager
|
||||
{
|
||||
public:
|
||||
// Delete copy constructor and assignment operator to enforce singleton
|
||||
CardDatabaseManager(const CardDatabaseManager &) = delete;
|
||||
CardDatabaseManager &operator=(const CardDatabaseManager &) = delete;
|
||||
|
||||
// Static method to access the singleton instance
|
||||
static CardDatabase *getInstance();
|
||||
static CardDatabaseQuerier *query();
|
||||
|
||||
private:
|
||||
CardDatabaseManager() = default; // Private constructor
|
||||
~CardDatabaseManager() = default;
|
||||
};
|
||||
|
||||
#endif // CARD_DATABASE_ACCESSOR_H
|
||||
61
libs/card/include/card/card_database/card_database_querier.h
Normal file
61
libs/card/include/card/card_database/card_database_querier.h
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
/**
|
||||
* @file card_database_querier.h
|
||||
* @ingroup CardDatabase
|
||||
* @brief The CardDatabaseQuerier is responsible for querying the database and returning data.
|
||||
*/
|
||||
|
||||
#ifndef COCKATRICE_CARD_DATABASE_QUERIER_H
|
||||
#define COCKATRICE_CARD_DATABASE_QUERIER_H
|
||||
|
||||
#include "card/card_info.h"
|
||||
#include "card/card_printing/exact_card.h"
|
||||
#include "utility/card_ref.h"
|
||||
|
||||
#include <QObject>
|
||||
|
||||
class CardDatabase;
|
||||
class CardDatabaseQuerier : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit CardDatabaseQuerier(QObject *parent, const CardDatabase *db);
|
||||
|
||||
[[nodiscard]] CardInfoPtr getCardInfo(const QString &cardName) const;
|
||||
[[nodiscard]] QList<CardInfoPtr> getCardInfos(const QStringList &cardNames) 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;
|
||||
|
||||
[[nodiscard]] ExactCard guessCard(const CardRef &cardRef) const;
|
||||
[[nodiscard]] ExactCard getCard(const CardRef &cardRef) const;
|
||||
[[nodiscard]] QList<ExactCard> getCards(const QList<CardRef> &cardRefs) const;
|
||||
|
||||
[[nodiscard]] ExactCard getRandomCard() const;
|
||||
[[nodiscard]] ExactCard getCardFromSameSet(const QString &cardName, const PrintingInfo &otherPrinting) const;
|
||||
|
||||
[[nodiscard]] ExactCard getPreferredCard(const CardInfoPtr &card) const;
|
||||
[[nodiscard]] bool isPreferredPrinting(const CardRef &cardRef) const;
|
||||
[[nodiscard]] PrintingInfo getPreferredPrinting(const CardInfoPtr &card) const;
|
||||
[[nodiscard]] PrintingInfo getPreferredPrinting(const QString &cardName) const;
|
||||
[[nodiscard]] QString getPreferredPrintingProviderId(const QString &cardName) const;
|
||||
|
||||
[[nodiscard]] PrintingInfo getSpecificPrinting(const CardRef &cardRef) const;
|
||||
[[nodiscard]] PrintingInfo
|
||||
getSpecificPrinting(const QString &cardName, const QString &setCode, const QString &collectorNumber) const;
|
||||
[[nodiscard]] PrintingInfo findPrintingWithId(const CardInfoPtr &card, const QString &providerId) const;
|
||||
|
||||
[[nodiscard]] QStringList getAllMainCardTypes() const;
|
||||
[[nodiscard]] QMap<QString, int> getAllMainCardTypesWithCount() const;
|
||||
[[nodiscard]] QMap<QString, int> getAllSubCardTypesWithCount() const;
|
||||
|
||||
private:
|
||||
const CardDatabase *db;
|
||||
|
||||
CardInfoPtr lookupCardByName(const QString &name) const;
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_CARD_DATABASE_QUERIER_H
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
/**
|
||||
* @file card_completer_proxy_model.h
|
||||
* @ingroup CardDatabaseModels
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
|
||||
#ifndef CARD_COMPLETER_PROXY_MODEL_H
|
||||
#define CARD_COMPLETER_PROXY_MODEL_H
|
||||
|
||||
#include <QSortFilterProxyModel>
|
||||
|
||||
class CardCompleterProxyModel : public QSortFilterProxyModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit CardCompleterProxyModel(QObject *parent = nullptr);
|
||||
|
||||
protected:
|
||||
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
|
||||
};
|
||||
|
||||
#endif // CARD_COMPLETER_PROXY_MODEL_H
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
/**
|
||||
* @file card_search_model.h
|
||||
* @ingroup CardDatabaseModels
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
|
||||
#ifndef CARD_SEARCH_MODEL_H
|
||||
#define CARD_SEARCH_MODEL_H
|
||||
|
||||
#include "card/card_database/model/card_database_display_model.h"
|
||||
|
||||
#include <QAbstractListModel>
|
||||
|
||||
class CardSearchModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit CardSearchModel(CardDatabaseDisplayModel *sourceModel, QObject *parent = nullptr);
|
||||
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
|
||||
void updateSearchResults(const QString &query); // Update results based on input
|
||||
|
||||
private:
|
||||
struct SearchResult
|
||||
{
|
||||
CardInfoPtr card;
|
||||
int distance;
|
||||
};
|
||||
|
||||
CardDatabaseDisplayModel *sourceModel;
|
||||
QList<SearchResult> searchResults;
|
||||
};
|
||||
|
||||
#endif // CARD_SEARCH_MODEL_H
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
/**
|
||||
* @file card_database_display_model.h
|
||||
* @ingroup CardDatabaseModels
|
||||
* @brief The CardDatabaseDisplayModel is a QSortFilterProxyModel that allows applying filters and sorting to a
|
||||
* CardDatabaseModel.
|
||||
*/
|
||||
|
||||
#ifndef COCKATRICE_CARD_DATABASE_DISPLAY_MODEL_H
|
||||
#define COCKATRICE_CARD_DATABASE_DISPLAY_MODEL_H
|
||||
|
||||
#include "filter_string.h"
|
||||
|
||||
#include <QList>
|
||||
#include <QSet>
|
||||
#include <QSortFilterProxyModel>
|
||||
#include <QTimer>
|
||||
|
||||
class FilterTree;
|
||||
class CardDatabaseDisplayModel : public QSortFilterProxyModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum FilterBool
|
||||
{
|
||||
ShowTrue,
|
||||
ShowFalse,
|
||||
ShowAll
|
||||
};
|
||||
|
||||
private:
|
||||
FilterBool isToken;
|
||||
QString cardName, cardText;
|
||||
QSet<QString> cardNameSet, cardTypes, cardColors;
|
||||
FilterTree *filterTree;
|
||||
FilterString *filterString;
|
||||
int loadedRowCount;
|
||||
QTimer dirtyTimer;
|
||||
|
||||
/** The translation table that will be used for sanitizeCardName. */
|
||||
static QMap<wchar_t, wchar_t> characterTranslation;
|
||||
|
||||
public:
|
||||
explicit CardDatabaseDisplayModel(QObject *parent = nullptr);
|
||||
void setFilterTree(FilterTree *_filterTree);
|
||||
void setIsToken(FilterBool _isToken)
|
||||
{
|
||||
isToken = _isToken;
|
||||
emit modelDirty();
|
||||
dirty();
|
||||
}
|
||||
|
||||
void setCardName(const QString &_cardName)
|
||||
{
|
||||
if (filterString != nullptr) {
|
||||
delete filterString;
|
||||
filterString = nullptr;
|
||||
}
|
||||
cardName = sanitizeCardName(_cardName, characterTranslation);
|
||||
emit modelDirty();
|
||||
dirty();
|
||||
}
|
||||
void setStringFilter(const QString &_src)
|
||||
{
|
||||
delete filterString;
|
||||
filterString = new FilterString(_src);
|
||||
emit modelDirty();
|
||||
dirty();
|
||||
}
|
||||
void setCardNameSet(const QSet<QString> &_cardNameSet)
|
||||
{
|
||||
cardNameSet = _cardNameSet;
|
||||
emit modelDirty();
|
||||
dirty();
|
||||
}
|
||||
|
||||
void dirty()
|
||||
{
|
||||
dirtyTimer.start(20);
|
||||
}
|
||||
void clearFilterAll();
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
bool canFetchMore(const QModelIndex &parent) const override;
|
||||
void fetchMore(const QModelIndex &parent) override;
|
||||
signals:
|
||||
void modelDirty();
|
||||
|
||||
protected:
|
||||
bool lessThan(const QModelIndex &left, const QModelIndex &right) const override;
|
||||
static int lessThanNumerically(const QString &left, const QString &right);
|
||||
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
|
||||
bool rowMatchesCardName(CardInfoPtr info) const;
|
||||
|
||||
private slots:
|
||||
void filterTreeChanged();
|
||||
/** Will translate all undesirable characters in DIRTYNAME according to the TABLE. */
|
||||
const QString sanitizeCardName(const QString &dirtyName, const QMap<wchar_t, wchar_t> &table);
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_CARD_DATABASE_DISPLAY_MODEL_H
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
/**
|
||||
* @file card_database_model.h
|
||||
* @ingroup CardDatabaseModels
|
||||
* @brief The CardDatabaseModel maps the cardList contained in the CardDatabase as a QAbstractListModel.
|
||||
*/
|
||||
|
||||
#ifndef CARDDATABASEMODEL_H
|
||||
#define CARDDATABASEMODEL_H
|
||||
|
||||
#include "card/card_database/card_database.h"
|
||||
|
||||
#include <QAbstractListModel>
|
||||
#include <QList>
|
||||
#include <QSet>
|
||||
|
||||
class CardDatabaseModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum Columns
|
||||
{
|
||||
NameColumn,
|
||||
SetListColumn,
|
||||
ManaCostColumn,
|
||||
PTColumn,
|
||||
CardTypeColumn,
|
||||
ColorColumn
|
||||
};
|
||||
enum Role
|
||||
{
|
||||
SortRole = Qt::UserRole
|
||||
};
|
||||
CardDatabaseModel(CardDatabase *_db, bool _showOnlyCardsFromEnabledSets, QObject *parent = nullptr);
|
||||
~CardDatabaseModel() override;
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
|
||||
CardDatabase *getDatabase() const
|
||||
{
|
||||
return db;
|
||||
}
|
||||
CardInfoPtr getCard(int index) const
|
||||
{
|
||||
return cardList[index];
|
||||
}
|
||||
|
||||
private:
|
||||
QList<CardInfoPtr> cardList;
|
||||
QSet<CardInfoPtr> cardListSet; // Supports faster lookups in cardDatabaseEnabledSetsChanged()
|
||||
CardDatabase *db;
|
||||
bool showOnlyCardsFromEnabledSets;
|
||||
|
||||
inline bool checkCardHasAtLeastOneEnabledSet(CardInfoPtr card);
|
||||
private slots:
|
||||
void cardAdded(CardInfoPtr card);
|
||||
void cardRemoved(CardInfoPtr card);
|
||||
void cardInfoChanged(CardInfoPtr card);
|
||||
void cardDatabaseEnabledSetsChanged();
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
/**
|
||||
* @file token_display_model.h
|
||||
* @ingroup CardDatabaseModels
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
|
||||
#ifndef COCKATRICE_TOKEN_DISPLAY_MODEL_H
|
||||
#define COCKATRICE_TOKEN_DISPLAY_MODEL_H
|
||||
|
||||
#include "card/card_database/model/card_database_display_model.h"
|
||||
|
||||
class TokenDisplayModel : public CardDatabaseDisplayModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit TokenDisplayModel(QObject *parent = nullptr);
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
|
||||
protected:
|
||||
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_TOKEN_DISPLAY_MODEL_H
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
/**
|
||||
* @file token_edit_model.h
|
||||
* @ingroup CardDatabaseModels
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
|
||||
#ifndef COCKATRICE_TOKEN_EDIT_MODEL_H
|
||||
#define COCKATRICE_TOKEN_EDIT_MODEL_H
|
||||
|
||||
#include "card/card_database/model/card_database_display_model.h"
|
||||
|
||||
class TokenEditModel : public CardDatabaseDisplayModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit TokenEditModel(QObject *parent = nullptr);
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
|
||||
protected:
|
||||
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_TOKEN_EDIT_MODEL_H
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
/**
|
||||
* @file card_database_parser.h
|
||||
* @ingroup CardDatabaseParsers
|
||||
* @brief The ICardDatabaseParser defines the base interface for parser sub-classes.
|
||||
*/
|
||||
|
||||
#ifndef CARDDATABASE_PARSER_H
|
||||
#define CARDDATABASE_PARSER_H
|
||||
|
||||
#include "card/card_info.h"
|
||||
|
||||
#include <QIODevice>
|
||||
#include <QString>
|
||||
|
||||
#define COCKATRICE_XML_XSI_NAMESPACE "http://www.w3.org/2001/XMLSchema-instance"
|
||||
|
||||
class ICardDatabaseParser : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
~ICardDatabaseParser() override = default;
|
||||
|
||||
virtual bool getCanParseFile(const QString &name, QIODevice &device) = 0;
|
||||
virtual void parseFile(QIODevice &device) = 0;
|
||||
virtual bool saveToFile(SetNameMap sets,
|
||||
CardNameMap cards,
|
||||
const QString &fileName,
|
||||
const QString &sourceUrl = "unknown",
|
||||
const QString &sourceVersion = "unknown") = 0;
|
||||
static void clearSetlist();
|
||||
|
||||
protected:
|
||||
/*
|
||||
* A cached list of the available sets, needed to cross-reference sets from cards.
|
||||
* Shared between all parsers
|
||||
*/
|
||||
static SetNameMap sets;
|
||||
|
||||
CardSetPtr internalAddSet(const QString &setName,
|
||||
const QString &longName = "",
|
||||
const QString &setType = "",
|
||||
const QDate &releaseDate = QDate(),
|
||||
const CardSet::Priority priority = CardSet::PriorityFallback);
|
||||
signals:
|
||||
void addCard(CardInfoPtr card);
|
||||
void addSet(CardSetPtr set);
|
||||
};
|
||||
|
||||
Q_DECLARE_INTERFACE(ICardDatabaseParser, "ICardDatabaseParser")
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
/**
|
||||
* @file cockatrice_xml_3.h
|
||||
* @ingroup CardDatabaseParsers
|
||||
* @brief The CockatriceXml3Parser is capable of parsing version 3 of the Cockatrice XML Schema.
|
||||
*/
|
||||
|
||||
#ifndef COCKATRICE_XML3_H
|
||||
#define COCKATRICE_XML3_H
|
||||
|
||||
#include "card/card_database/parser/card_database_parser.h"
|
||||
|
||||
#include <QLoggingCategory>
|
||||
#include <QXmlStreamReader>
|
||||
|
||||
inline Q_LOGGING_CATEGORY(CockatriceXml3Log, "cockatrice_xml.xml_3_parser");
|
||||
|
||||
class CockatriceXml3Parser : public ICardDatabaseParser
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
CockatriceXml3Parser() = default;
|
||||
~CockatriceXml3Parser() override = default;
|
||||
bool getCanParseFile(const QString &name, QIODevice &device) override;
|
||||
void parseFile(QIODevice &device) override;
|
||||
bool saveToFile(SetNameMap _sets,
|
||||
CardNameMap cards,
|
||||
const QString &fileName,
|
||||
const QString &sourceUrl = "unknown",
|
||||
const QString &sourceVersion = "unknown") override;
|
||||
|
||||
private:
|
||||
void loadCardsFromXml(QXmlStreamReader &xml);
|
||||
void loadSetsFromXml(QXmlStreamReader &xml);
|
||||
QString getMainCardType(QString &type);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
/**
|
||||
* @file cockatrice_xml_4.h
|
||||
* @ingroup CardDatabaseParsers
|
||||
* @brief The CockatriceXml4Parser is capable of parsing version 4 of the Cockatrice XML Schema.
|
||||
*/
|
||||
|
||||
#ifndef COCKATRICE_XML4_H
|
||||
#define COCKATRICE_XML4_H
|
||||
|
||||
#include "card/card_database/parser/card_database_parser.h"
|
||||
|
||||
#include <QLoggingCategory>
|
||||
#include <QXmlStreamReader>
|
||||
|
||||
inline Q_LOGGING_CATEGORY(CockatriceXml4Log, "cockatrice_xml.xml_4_parser");
|
||||
|
||||
class CockatriceXml4Parser : public ICardDatabaseParser
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
CockatriceXml4Parser() = default;
|
||||
~CockatriceXml4Parser() override = default;
|
||||
bool getCanParseFile(const QString &name, QIODevice &device) override;
|
||||
void parseFile(QIODevice &device) override;
|
||||
bool saveToFile(SetNameMap _sets,
|
||||
CardNameMap cards,
|
||||
const QString &fileName,
|
||||
const QString &sourceUrl = "unknown",
|
||||
const QString &sourceVersion = "unknown") override;
|
||||
|
||||
private:
|
||||
QVariantHash loadCardPropertiesFromXml(QXmlStreamReader &xml);
|
||||
void loadCardsFromXml(QXmlStreamReader &xml);
|
||||
void loadSetsFromXml(QXmlStreamReader &xml);
|
||||
};
|
||||
|
||||
#endif
|
||||
354
libs/card/include/card/card_info.h
Normal file
354
libs/card/include/card/card_info.h
Normal file
|
|
@ -0,0 +1,354 @@
|
|||
/**
|
||||
* @file card_info.h
|
||||
* @ingroup Cards
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
|
||||
#ifndef CARD_INFO_H
|
||||
#define CARD_INFO_H
|
||||
|
||||
#include "card_printing/printing_info.h"
|
||||
|
||||
#include <QDate>
|
||||
#include <QHash>
|
||||
#include <QList>
|
||||
#include <QLoggingCategory>
|
||||
#include <QMap>
|
||||
#include <QMetaType>
|
||||
#include <QSharedPointer>
|
||||
#include <QStringList>
|
||||
#include <QVariant>
|
||||
#include <utility>
|
||||
|
||||
inline Q_LOGGING_CATEGORY(CardInfoLog, "card_info");
|
||||
|
||||
class CardInfo;
|
||||
class CardSet;
|
||||
class CardRelation;
|
||||
class ICardDatabaseParser;
|
||||
|
||||
typedef QSharedPointer<CardInfo> CardInfoPtr;
|
||||
typedef QSharedPointer<CardSet> CardSetPtr;
|
||||
typedef QMap<QString, QList<PrintingInfo>> SetToPrintingsMap;
|
||||
|
||||
typedef QHash<QString, CardInfoPtr> CardNameMap;
|
||||
typedef QHash<QString, CardSetPtr> SetNameMap;
|
||||
|
||||
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
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
CardInfoPtr smartThis; ///< Smart pointer to self for safe cross-references.
|
||||
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.
|
||||
QVariantHash 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.
|
||||
QString setsNames; ///< Cached, human-readable list of set names.
|
||||
bool cipt; ///< Positioning flag used by UI.
|
||||
bool landscapeOrientation; ///< Orientation flag for rendering.
|
||||
int tableRow; ///< Row index in a table or visual representation.
|
||||
bool upsideDownArt; ///< Whether artwork is flipped for visual purposes.
|
||||
|
||||
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 _cipt UI positioning flag.
|
||||
* @param _landscapeOrientation UI rendering orientation.
|
||||
* @param _tableRow Row index for table placement.
|
||||
* @param _upsideDownArt Whether the artwork should be displayed upside down.
|
||||
*/
|
||||
explicit CardInfo(const QString &_name,
|
||||
const QString &_text,
|
||||
bool _isToken,
|
||||
QVariantHash _properties,
|
||||
const QList<CardRelation *> &_relatedCards,
|
||||
const QList<CardRelation *> &_reverseRelatedCards,
|
||||
SetToPrintingsMap _sets,
|
||||
bool _cipt,
|
||||
bool _landscapeOrientation,
|
||||
int _tableRow,
|
||||
bool _upsideDownArt);
|
||||
|
||||
/**
|
||||
* @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()), name(other.name), simpleName(other.simpleName), text(other.text),
|
||||
isToken(other.isToken), properties(other.properties), relatedCards(other.relatedCards),
|
||||
reverseRelatedCards(other.reverseRelatedCards), reverseRelatedCardsToMe(other.reverseRelatedCardsToMe),
|
||||
setsToPrintings(other.setsToPrintings), setsNames(other.setsNames), cipt(other.cipt),
|
||||
landscapeOrientation(other.landscapeOrientation), tableRow(other.tableRow), upsideDownArt(other.upsideDownArt)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 _cipt UI positioning flag.
|
||||
* @param _landscapeOrientation UI rendering orientation.
|
||||
* @param _tableRow Row index for table placement.
|
||||
* @param _upsideDownArt Artwork orientation flag.
|
||||
* @return Shared pointer to the new CardInfo instance.
|
||||
*/
|
||||
static CardInfoPtr newInstance(const QString &_name,
|
||||
const QString &_text,
|
||||
bool _isToken,
|
||||
QVariantHash _properties,
|
||||
const QList<CardRelation *> &_relatedCards,
|
||||
const QList<CardRelation *> &_reverseRelatedCards,
|
||||
SetToPrintingsMap _sets,
|
||||
bool _cipt,
|
||||
bool _landscapeOrientation,
|
||||
int _tableRow,
|
||||
bool _upsideDownArt);
|
||||
|
||||
/**
|
||||
* @brief Clones the current CardInfo instance.
|
||||
*
|
||||
* Uses the copy constructor and ensures the smart pointer is properly set.
|
||||
*
|
||||
* @return Shared pointer to the cloned CardInfo.
|
||||
*/
|
||||
CardInfoPtr clone() const
|
||||
{
|
||||
CardInfoPtr newCardInfo = CardInfoPtr(new CardInfo(*this));
|
||||
newCardInfo->setSmartPointer(newCardInfo); // Set the smart pointer for the new instance
|
||||
return newCardInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets the internal smart pointer to self.
|
||||
*
|
||||
* Used internally to allow safe cross-references among CardInfo and CardSet.
|
||||
*
|
||||
* @param _ptr Shared pointer pointing to this instance.
|
||||
*/
|
||||
void setSmartPointer(CardInfoPtr _ptr)
|
||||
{
|
||||
smartThis = std::move(_ptr);
|
||||
}
|
||||
|
||||
/** @name Basic Properties Accessors */ //@{
|
||||
inline const QString &getName() const
|
||||
{
|
||||
return name;
|
||||
}
|
||||
const QString &getSimpleName() const
|
||||
{
|
||||
return simpleName;
|
||||
}
|
||||
const QString &getText() const
|
||||
{
|
||||
return text;
|
||||
}
|
||||
void setText(const QString &_text)
|
||||
{
|
||||
text = _text;
|
||||
emit cardInfoChanged(smartThis);
|
||||
}
|
||||
bool getIsToken() const
|
||||
{
|
||||
return isToken;
|
||||
}
|
||||
QStringList getProperties() const
|
||||
{
|
||||
return properties.keys();
|
||||
}
|
||||
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 SetToPrintingsMap &getSets() const
|
||||
{
|
||||
return setsToPrintings;
|
||||
}
|
||||
const QString &getSetsNames() const
|
||||
{
|
||||
return setsNames;
|
||||
}
|
||||
//@}
|
||||
|
||||
/** @name Related Cards Accessors */ //@{
|
||||
const QList<CardRelation *> &getRelatedCards() const
|
||||
{
|
||||
return relatedCards;
|
||||
}
|
||||
const QList<CardRelation *> &getReverseRelatedCards() const
|
||||
{
|
||||
return reverseRelatedCards;
|
||||
}
|
||||
const QList<CardRelation *> &getReverseRelatedCards2Me() const
|
||||
{
|
||||
return reverseRelatedCardsToMe;
|
||||
}
|
||||
QList<CardRelation *> getAllRelatedCards() const
|
||||
{
|
||||
QList<CardRelation *> result;
|
||||
result.append(getRelatedCards());
|
||||
result.append(getReverseRelatedCards2Me());
|
||||
return result;
|
||||
}
|
||||
void resetReverseRelatedCards2Me();
|
||||
void addReverseRelatedCards2Me(CardRelation *cardRelation)
|
||||
{
|
||||
reverseRelatedCardsToMe.append(cardRelation);
|
||||
}
|
||||
//@}
|
||||
|
||||
/** @name UI 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;
|
||||
//@}
|
||||
|
||||
/** @name Legacy/Convenience Property Accessors */ //@{
|
||||
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);
|
||||
//@}
|
||||
|
||||
/**
|
||||
* @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.
|
||||
*/
|
||||
QString getCorrectedName() 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, 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 QVariantHash &props);
|
||||
|
||||
/**
|
||||
* @brief Refreshes the cached, human-readable list of set names.
|
||||
*
|
||||
* Typically called after adding or modifying set memberships.
|
||||
*/
|
||||
void refreshCachedSetNames();
|
||||
|
||||
/**
|
||||
* @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);
|
||||
|
||||
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
|
||||
30
libs/card/include/card/card_info_comparator.h
Normal file
30
libs/card/include/card/card_info_comparator.h
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
/**
|
||||
* @file card_info_comparator.h
|
||||
* @ingroup Cards
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
|
||||
#ifndef CARD_INFO_COMPARATOR_H
|
||||
#define CARD_INFO_COMPARATOR_H
|
||||
|
||||
#include "card_info.h"
|
||||
|
||||
#include <QStringList>
|
||||
#include <QVariant>
|
||||
#include <Qt>
|
||||
|
||||
class CardInfoComparator
|
||||
{
|
||||
public:
|
||||
explicit CardInfoComparator(const QStringList &properties, Qt::SortOrder order = Qt::AscendingOrder);
|
||||
bool operator()(const CardInfoPtr &a, const CardInfoPtr &b) const;
|
||||
|
||||
private:
|
||||
QStringList m_properties; // List of properties to sort by
|
||||
Qt::SortOrder m_order;
|
||||
|
||||
QVariant getProperty(const CardInfoPtr &card, const QString &property) const;
|
||||
bool compareVariants(const QVariant &a, const QVariant &b) const;
|
||||
};
|
||||
|
||||
#endif // CARD_INFO_COMPARATOR_H
|
||||
48
libs/card/include/card/card_printing/exact_card.h
Normal file
48
libs/card/include/card/card_printing/exact_card.h
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
#ifndef EXACT_CARD_H
|
||||
#define EXACT_CARD_H
|
||||
|
||||
#include "card/card_info.h"
|
||||
|
||||
/**
|
||||
* @class ExactCard
|
||||
* @ingroup Cards
|
||||
* @brief Identifies the card by its CardInfoPtr along with its exact printing by its PrintingInfo.
|
||||
*/
|
||||
class ExactCard
|
||||
{
|
||||
CardInfoPtr card;
|
||||
PrintingInfo printing;
|
||||
|
||||
public:
|
||||
ExactCard();
|
||||
explicit ExactCard(const CardInfoPtr &_card, const PrintingInfo &_printing = PrintingInfo());
|
||||
|
||||
/**
|
||||
* Gets the CardInfoPtr. Can be null.
|
||||
*/
|
||||
CardInfoPtr getCardPtr() const
|
||||
{
|
||||
return card;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the PrintingInfo. Can be empty.
|
||||
*/
|
||||
PrintingInfo getPrinting() const
|
||||
{
|
||||
return printing;
|
||||
}
|
||||
|
||||
bool operator==(const ExactCard &other) const;
|
||||
|
||||
QString getName() const;
|
||||
const CardInfo &getInfo() const;
|
||||
QString getPixmapCacheKey() const;
|
||||
|
||||
bool isEmpty() const;
|
||||
explicit operator bool() const;
|
||||
|
||||
void emitPixmapUpdated() const;
|
||||
};
|
||||
|
||||
#endif // EXACT_CARD_H
|
||||
56
libs/card/include/card/card_printing/printing_info.h
Normal file
56
libs/card/include/card/card_printing/printing_info.h
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
#ifndef COCKATRICE_PRINTING_INFO_H
|
||||
#define COCKATRICE_PRINTING_INFO_H
|
||||
|
||||
#include "card/card_set/card_set.h"
|
||||
|
||||
#include <QList>
|
||||
#include <QMap>
|
||||
#include <QStringList>
|
||||
#include <QVariant>
|
||||
|
||||
class PrintingInfo;
|
||||
|
||||
using SetToPrintingsMap = QMap<QString, QList<PrintingInfo>>;
|
||||
|
||||
/**
|
||||
* Info relating to a specific printing for a card.
|
||||
*/
|
||||
class PrintingInfo
|
||||
{
|
||||
public:
|
||||
explicit PrintingInfo(const CardSetPtr &_set = nullptr);
|
||||
~PrintingInfo() = default;
|
||||
|
||||
bool operator==(const PrintingInfo &other) const
|
||||
{
|
||||
return this->set == other.set && this->properties == other.properties;
|
||||
}
|
||||
|
||||
private:
|
||||
CardSetPtr set;
|
||||
// per-printing card properties;
|
||||
QVariantHash properties;
|
||||
|
||||
public:
|
||||
CardSetPtr getSet() const
|
||||
{
|
||||
return set;
|
||||
}
|
||||
|
||||
QStringList getProperties() const
|
||||
{
|
||||
return properties.keys();
|
||||
}
|
||||
QString getProperty(const QString &propertyName) const
|
||||
{
|
||||
return properties.value(propertyName).toString();
|
||||
}
|
||||
void setProperty(const QString &_name, const QString &_value)
|
||||
{
|
||||
properties.insert(_name, _value);
|
||||
}
|
||||
|
||||
QString getUuid() const;
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_PRINTING_INFO_H
|
||||
73
libs/card/include/card/card_relation/card_relation.h
Normal file
73
libs/card/include/card/card_relation/card_relation.h
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
#ifndef COCKATRICE_CARD_RELATION_H
|
||||
#define COCKATRICE_CARD_RELATION_H
|
||||
|
||||
#include "card/card_relation/card_relation_type.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
|
||||
class CardRelation : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
QString name;
|
||||
CardRelationType attachType;
|
||||
bool isCreateAllExclusion;
|
||||
bool isVariableCount;
|
||||
int defaultCount;
|
||||
bool isPersistent;
|
||||
|
||||
public:
|
||||
explicit CardRelation(const QString &_name = QString(),
|
||||
CardRelationType _attachType = CardRelationType::DoesNotAttach,
|
||||
bool _isCreateAllExclusion = false,
|
||||
bool _isVariableCount = false,
|
||||
int _defaultCount = 1,
|
||||
bool _isPersistent = false);
|
||||
|
||||
const QString &getName() const
|
||||
{
|
||||
return name;
|
||||
}
|
||||
CardRelationType getAttachType() const
|
||||
{
|
||||
return attachType;
|
||||
}
|
||||
bool getDoesAttach() const
|
||||
{
|
||||
return attachType != CardRelationType::DoesNotAttach;
|
||||
}
|
||||
bool getDoesTransform() const
|
||||
{
|
||||
return attachType == CardRelationType::TransformInto;
|
||||
}
|
||||
|
||||
QString getAttachTypeAsString() const
|
||||
{
|
||||
return cardAttachTypeToString(attachType);
|
||||
}
|
||||
|
||||
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 // COCKATRICE_CARD_RELATION_H
|
||||
29
libs/card/include/card/card_relation/card_relation_type.h
Normal file
29
libs/card/include/card/card_relation/card_relation_type.h
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
#ifndef COCKATRICE_CARD_RELATION_TYPE_H
|
||||
#define COCKATRICE_CARD_RELATION_TYPE_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
/**
|
||||
* Represents how a card relates to another (attach, transform, etc.).
|
||||
*/
|
||||
enum class CardRelationType
|
||||
{
|
||||
DoesNotAttach = 0,
|
||||
AttachTo = 1,
|
||||
TransformInto = 2,
|
||||
};
|
||||
|
||||
// Optional helper
|
||||
inline QString cardAttachTypeToString(CardRelationType type)
|
||||
{
|
||||
switch (type) {
|
||||
case CardRelationType::AttachTo:
|
||||
return "attach";
|
||||
case CardRelationType::TransformInto:
|
||||
return "transform";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
#endif // COCKATRICE_CARD_RELATION_TYPE_H
|
||||
116
libs/card/include/card/card_set/card_set.h
Normal file
116
libs/card/include/card/card_set/card_set.h
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
#ifndef COCKATRICE_CARD_SET_H
|
||||
#define COCKATRICE_CARD_SET_H
|
||||
|
||||
#include <QDate>
|
||||
#include <QList>
|
||||
#include <QSharedPointer>
|
||||
#include <QString>
|
||||
|
||||
class CardInfo;
|
||||
using CardInfoPtr = QSharedPointer<CardInfo>;
|
||||
|
||||
class CardSet;
|
||||
using CardSetPtr = QSharedPointer<CardSet>;
|
||||
|
||||
class CardSet : public QList<CardInfoPtr>
|
||||
{
|
||||
public:
|
||||
enum Priority
|
||||
{
|
||||
PriorityFallback = 0,
|
||||
PriorityPrimary = 10,
|
||||
PrioritySecondary = 20,
|
||||
PriorityReprint = 30,
|
||||
PriorityOther = 40,
|
||||
PriorityLowest = 100,
|
||||
};
|
||||
|
||||
static const char *TOKENS_SETNAME;
|
||||
|
||||
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);
|
||||
|
||||
bool getIsKnownIgnored() const
|
||||
{
|
||||
return longName.length() + setType.length() + releaseDate.toString().length() == 0;
|
||||
}
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_CARD_SET_H
|
||||
66
libs/card/include/card/card_set/card_set_comparator.h
Normal file
66
libs/card/include/card/card_set/card_set_comparator.h
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
/**
|
||||
* @file card_set_comparator.h
|
||||
* @ingroup Cards
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
|
||||
#ifndef SET_PRIORITY_COMPARATOR_H
|
||||
#define SET_PRIORITY_COMPARATOR_H
|
||||
|
||||
#include "card/card_info.h"
|
||||
|
||||
class SetPriorityComparator
|
||||
{
|
||||
public:
|
||||
/*
|
||||
* Returns true if a has higher download priority than b
|
||||
* Enabled sets have priority over disabled sets
|
||||
* Both groups follow the user-defined order
|
||||
*/
|
||||
inline bool operator()(const CardSetPtr &a, const CardSetPtr &b) const
|
||||
{
|
||||
if (a->getEnabled()) {
|
||||
return !b->getEnabled() || a->getSortKey() < b->getSortKey();
|
||||
} else {
|
||||
return !b->getEnabled() && a->getSortKey() < b->getSortKey();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class SetReleaseDateComparator
|
||||
{
|
||||
public:
|
||||
/*
|
||||
* Returns true if a has higher download priority than b
|
||||
* Enabled sets have priority over disabled sets
|
||||
* Both groups follow the user-defined order
|
||||
*/
|
||||
inline bool operator()(const CardSetPtr &a, const CardSetPtr &b) const
|
||||
{
|
||||
if (a->getEnabled()) {
|
||||
return !b->getEnabled() || a->getReleaseDate() < b->getReleaseDate();
|
||||
} else {
|
||||
return !b->getEnabled() && a->getReleaseDate() < b->getReleaseDate();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class CardSetPriorityComparator
|
||||
{
|
||||
public:
|
||||
/*
|
||||
* Returns true if a has higher download priority than b
|
||||
* Enabled sets have priority over disabled sets
|
||||
* Both groups follow the user-defined order
|
||||
*/
|
||||
inline bool operator()(const PrintingInfo &a, const PrintingInfo &b) const
|
||||
{
|
||||
if (a.getSet()->getEnabled()) {
|
||||
return !b.getSet()->getEnabled() || a.getSet()->getSortKey() < b.getSet()->getSortKey();
|
||||
} else {
|
||||
return !b.getSet()->getEnabled() && a.getSet()->getSortKey() < b.getSet()->getSortKey();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#endif // SET_PRIORITY_COMPARATOR_H
|
||||
26
libs/card/include/card/card_set/card_set_list.h
Normal file
26
libs/card/include/card/card_set/card_set_list.h
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
#ifndef COCKATRICE_CARD_SET_LIST_H
|
||||
#define COCKATRICE_CARD_SET_LIST_H
|
||||
|
||||
#include "card_set.h"
|
||||
|
||||
#include <QList>
|
||||
#include <QStringList>
|
||||
|
||||
class CardSetList : public QList<CardSetPtr>
|
||||
{
|
||||
private:
|
||||
class KeyCompareFunctor;
|
||||
|
||||
public:
|
||||
void sortByKey();
|
||||
void guessSortKeys();
|
||||
void enableAllUnknown();
|
||||
void enableAll();
|
||||
void markAllAsKnown();
|
||||
int getEnabledSetsNum();
|
||||
int getUnknownSetsNum();
|
||||
QStringList getUnknownSetsNames();
|
||||
void defaultSort();
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_CARD_SET_LIST_H
|
||||
58
libs/card/include/card/game_specific_terms.h
Normal file
58
libs/card/include/card/game_specific_terms.h
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
/**
|
||||
* @file game_specific_terms.h
|
||||
* @ingroup Cards
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
|
||||
#ifndef GAME_SPECIFIC_TERMS_H
|
||||
#define GAME_SPECIFIC_TERMS_H
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QString>
|
||||
|
||||
/*
|
||||
* Collection of traslatable property names used in games,
|
||||
* so we can use Game::Property instead of hardcoding strings.
|
||||
* Note: Mtg = "Maybe that game"
|
||||
*/
|
||||
|
||||
namespace Mtg
|
||||
{
|
||||
QString const CardType("type");
|
||||
QString const ConvertedManaCost("cmc");
|
||||
QString const Colors("colors");
|
||||
QString const Loyalty("loyalty");
|
||||
QString const MainCardType("maintype");
|
||||
QString const ManaCost("manacost");
|
||||
QString const PowTough("pt");
|
||||
QString const Side("side");
|
||||
QString const Layout("layout");
|
||||
QString const ColorIdentity("coloridentity");
|
||||
|
||||
inline static const QString getNicePropertyName(QString key)
|
||||
{
|
||||
if (key == CardType)
|
||||
return QCoreApplication::translate("Mtg", "Card Type");
|
||||
if (key == ConvertedManaCost)
|
||||
return QCoreApplication::translate("Mtg", "Mana Value");
|
||||
if (key == Colors)
|
||||
return QCoreApplication::translate("Mtg", "Color(s)");
|
||||
if (key == Loyalty)
|
||||
return QCoreApplication::translate("Mtg", "Loyalty");
|
||||
if (key == MainCardType)
|
||||
return QCoreApplication::translate("Mtg", "Main Card Type");
|
||||
if (key == ManaCost)
|
||||
return QCoreApplication::translate("Mtg", "Mana Cost");
|
||||
if (key == PowTough)
|
||||
return QCoreApplication::translate("Mtg", "P/T");
|
||||
if (key == Side)
|
||||
return QCoreApplication::translate("Mtg", "Side");
|
||||
if (key == Layout)
|
||||
return QCoreApplication::translate("Mtg", "Layout");
|
||||
if (key == ColorIdentity)
|
||||
return QCoreApplication::translate("Mtg", "Color Identity");
|
||||
return key;
|
||||
}
|
||||
}; // namespace Mtg
|
||||
|
||||
#endif
|
||||
200
libs/card/src/card_database/card_database.cpp
Normal file
200
libs/card/src/card_database/card_database.cpp
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
#include "card/card_database/card_database.h"
|
||||
|
||||
#include "card/card_database/parser/cockatrice_xml_3.h"
|
||||
#include "card/card_database/parser/cockatrice_xml_4.h"
|
||||
#include "card/card_relation/card_relation.h"
|
||||
#include "settings/cache_settings.h"
|
||||
|
||||
#include <QCryptographicHash>
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QDirIterator>
|
||||
#include <QFile>
|
||||
#include <QMessageBox>
|
||||
#include <QRegularExpression>
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
|
||||
CardDatabase::CardDatabase(QObject *parent) : QObject(parent), loadStatus(NotLoaded)
|
||||
{
|
||||
qRegisterMetaType<CardInfoPtr>("CardInfoPtr");
|
||||
qRegisterMetaType<CardInfoPtr>("CardSetPtr");
|
||||
|
||||
// create loader and wire it up
|
||||
loader = new CardDatabaseLoader(this, this);
|
||||
// re-emit loader signals (so other code doesn't need to know about internals)
|
||||
connect(loader, &CardDatabaseLoader::loadingFinished, this, &CardDatabase::cardDatabaseLoadingFinished);
|
||||
connect(loader, &CardDatabaseLoader::loadingFailed, this, &CardDatabase::cardDatabaseLoadingFailed);
|
||||
connect(loader, &CardDatabaseLoader::newSetsFound, this, &CardDatabase::cardDatabaseNewSetsFound);
|
||||
connect(loader, &CardDatabaseLoader::allNewSetsEnabled, this, &CardDatabase::cardDatabaseAllNewSetsEnabled);
|
||||
|
||||
querier = new CardDatabaseQuerier(this, this);
|
||||
}
|
||||
|
||||
CardDatabase::~CardDatabase()
|
||||
{
|
||||
clear();
|
||||
}
|
||||
|
||||
void CardDatabase::clear()
|
||||
{
|
||||
QMutexLocker locker(clearDatabaseMutex);
|
||||
|
||||
for (const auto &card : cards.values()) {
|
||||
if (card) {
|
||||
removeCard(card);
|
||||
}
|
||||
}
|
||||
|
||||
cards.clear();
|
||||
simpleNameCards.clear();
|
||||
|
||||
sets.clear();
|
||||
ICardDatabaseParser::clearSetlist();
|
||||
|
||||
loadStatus = NotLoaded;
|
||||
}
|
||||
|
||||
void CardDatabase::loadCardDatabases()
|
||||
{
|
||||
loadStatus = loader->loadCardDatabases();
|
||||
}
|
||||
|
||||
bool CardDatabase::saveCustomTokensToFile()
|
||||
{
|
||||
return loader->saveCustomTokensToFile();
|
||||
}
|
||||
|
||||
void CardDatabase::refreshCachedReverseRelatedCards()
|
||||
{
|
||||
for (const auto &card : cards) {
|
||||
card->resetReverseRelatedCards2Me();
|
||||
}
|
||||
|
||||
for (const auto &card : cards) {
|
||||
for (auto *rel : card->getReverseRelatedCards()) {
|
||||
if (auto target = cards.value(rel->getName())) {
|
||||
auto *newRel = new CardRelation(card->getName(), rel->getAttachType(), rel->getIsCreateAllExclusion(),
|
||||
rel->getIsVariable(), rel->getDefaultCount(), rel->getIsPersistent());
|
||||
target->addReverseRelatedCards2Me(newRel);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CardDatabase::addCard(CardInfoPtr card)
|
||||
{
|
||||
if (card == nullptr) {
|
||||
qCWarning(CardDatabaseLog) << "CardDatabase::addCard(nullptr)";
|
||||
return;
|
||||
}
|
||||
|
||||
auto name = card->getName();
|
||||
|
||||
// If a card already exists, just add the new set property.
|
||||
if (auto existing = cards.value(name)) {
|
||||
for (const auto &printings : card->getSets())
|
||||
for (const auto &printing : printings)
|
||||
existing->addToSet(printing.getSet(), printing);
|
||||
return;
|
||||
}
|
||||
|
||||
QMutexLocker locker(addCardMutex);
|
||||
cards.insert(name, card);
|
||||
simpleNameCards.insert(card->getSimpleName(), card);
|
||||
|
||||
emit cardAdded(card);
|
||||
}
|
||||
|
||||
void CardDatabase::removeCard(CardInfoPtr card)
|
||||
{
|
||||
if (card.isNull()) {
|
||||
qCWarning(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();
|
||||
|
||||
QMutexLocker locker(removeCardMutex);
|
||||
cards.remove(card->getName());
|
||||
simpleNameCards.remove(card->getSimpleName());
|
||||
emit cardRemoved(card);
|
||||
}
|
||||
|
||||
void CardDatabase::addSet(CardSetPtr set)
|
||||
{
|
||||
sets.insert(set->getShortName(), set);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
CardSetList CardDatabase::getSetList() const
|
||||
{
|
||||
CardSetList result;
|
||||
for (auto set : sets.values()) {
|
||||
result << set;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
153
libs/card/src/card_database/card_database_loader.cpp
Normal file
153
libs/card/src/card_database/card_database_loader.cpp
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
#include "card/card_database/card_database_loader.h"
|
||||
|
||||
#include "card/card_database/card_database.h"
|
||||
#include "card/card_database/parser/cockatrice_xml_3.h"
|
||||
#include "card/card_database/parser/cockatrice_xml_4.h"
|
||||
#include "settings/cache_settings.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QDirIterator>
|
||||
#include <QFile>
|
||||
#include <QTime>
|
||||
|
||||
CardDatabaseLoader::CardDatabaseLoader(QObject *parent, CardDatabase *db) : QObject(parent), database(db)
|
||||
{
|
||||
// instantiate available parsers here and connect them to the database
|
||||
availableParsers << new CockatriceXml4Parser;
|
||||
availableParsers << new CockatriceXml3Parser;
|
||||
|
||||
for (auto *p : availableParsers) {
|
||||
// connect parser outputs to the database adders
|
||||
connect(p, &ICardDatabaseParser::addCard, database, &CardDatabase::addCard, Qt::DirectConnection);
|
||||
connect(p, &ICardDatabaseParser::addSet, database, &CardDatabase::addSet, Qt::DirectConnection);
|
||||
}
|
||||
|
||||
// when SettingsCache's path changes, trigger reloads
|
||||
connect(&SettingsCache::instance(), &SettingsCache::cardDatabasePathChanged, this,
|
||||
&CardDatabaseLoader::loadCardDatabases);
|
||||
}
|
||||
|
||||
CardDatabaseLoader::~CardDatabaseLoader()
|
||||
{
|
||||
qDeleteAll(availableParsers);
|
||||
availableParsers.clear();
|
||||
}
|
||||
|
||||
LoadStatus CardDatabaseLoader::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 CardDatabaseLoader::loadCardDatabase(const QString &path)
|
||||
{
|
||||
auto startTime = QTime::currentTime();
|
||||
LoadStatus tempLoadStatus = NotLoaded;
|
||||
if (!path.isEmpty()) {
|
||||
QMutexLocker locker(loadFromFileMutex);
|
||||
tempLoadStatus = loadFromFile(path);
|
||||
}
|
||||
|
||||
int msecs = startTime.msecsTo(QTime::currentTime());
|
||||
qCInfo(CardDatabaseLoadingLog) << "Loaded card database: Path =" << path << "Status =" << tempLoadStatus
|
||||
<< "Cards =" << (database ? database->cards.size() : 0)
|
||||
<< "Sets =" << (database ? database->sets.size() : 0) << QString("%1ms").arg(msecs);
|
||||
|
||||
return tempLoadStatus;
|
||||
}
|
||||
|
||||
LoadStatus CardDatabaseLoader::loadCardDatabases()
|
||||
{
|
||||
QMutexLocker locker(reloadDatabaseMutex);
|
||||
|
||||
if (!database) {
|
||||
qCWarning(CardDatabaseLoadingLog) << "Loader has no database pointer";
|
||||
emit loadingFailed();
|
||||
return FileError;
|
||||
}
|
||||
emit loadingStarted();
|
||||
qCInfo(CardDatabaseLoadingLog) << "Card Database Loading Started";
|
||||
|
||||
database->clear(); // remove old db
|
||||
|
||||
LoadStatus 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
|
||||
const QStringList customPaths = collectCustomDatabasePaths();
|
||||
for (int i = 0; i < customPaths.size(); ++i) {
|
||||
const auto &p = customPaths.at(i);
|
||||
qCInfo(CardDatabaseLoadingLog) << "Loading Custom Set" << i << "(" << p << ")";
|
||||
loadCardDatabase(p);
|
||||
}
|
||||
|
||||
// AFTER all the cards have been loaded
|
||||
|
||||
// resolve the reverse-related tags
|
||||
|
||||
database->refreshCachedReverseRelatedCards();
|
||||
|
||||
if (loadStatus == Ok) {
|
||||
database->checkUnknownSets(); // update deck editors, etc
|
||||
qCInfo(CardDatabaseLoadingSuccessOrFailureLog) << "Card Database Loading Success";
|
||||
emit loadingFinished();
|
||||
} else {
|
||||
qCInfo(CardDatabaseLoadingSuccessOrFailureLog) << "Card Database Loading Failed";
|
||||
emit loadingFailed(); // bring up the settings dialog
|
||||
}
|
||||
|
||||
return loadStatus;
|
||||
}
|
||||
|
||||
QStringList CardDatabaseLoader::collectCustomDatabasePaths() const
|
||||
{
|
||||
QDirIterator it(SettingsCache::instance().getCustomCardDatabasePath(), {"*.xml"}, QDir::Files,
|
||||
QDirIterator::Subdirectories | QDirIterator::FollowSymlinks);
|
||||
|
||||
QStringList paths;
|
||||
while (it.hasNext())
|
||||
paths << it.next();
|
||||
paths.sort();
|
||||
return paths;
|
||||
}
|
||||
|
||||
bool CardDatabaseLoader::saveCustomTokensToFile()
|
||||
{
|
||||
if (!database) {
|
||||
qCWarning(CardDatabaseLog) << "saveCustomTokensToFile: database pointer missing";
|
||||
return false;
|
||||
}
|
||||
|
||||
QString fileName = SettingsCache::instance().getCustomCardDatabasePath() + "/" + CardSet::TOKENS_SETNAME + ".xml";
|
||||
|
||||
SetNameMap tmpSets;
|
||||
CardSetPtr customTokensSet = database->getSet(CardSet::TOKENS_SETNAME);
|
||||
tmpSets.insert(CardSet::TOKENS_SETNAME, customTokensSet);
|
||||
|
||||
CardNameMap tmpCards;
|
||||
for (const CardInfoPtr &card : database->cards) {
|
||||
if (card->getSets().contains(CardSet::TOKENS_SETNAME)) {
|
||||
tmpCards.insert(card->getName(), card);
|
||||
}
|
||||
}
|
||||
|
||||
availableParsers.first()->saveToFile(tmpSets, tmpCards, fileName);
|
||||
return true;
|
||||
}
|
||||
12
libs/card/src/card_database/card_database_manager.cpp
Normal file
12
libs/card/src/card_database/card_database_manager.cpp
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
#include "card/card_database/card_database_manager.h"
|
||||
|
||||
CardDatabase *CardDatabaseManager::getInstance()
|
||||
{
|
||||
static CardDatabase instance; // Created only once, on first access
|
||||
return &instance;
|
||||
}
|
||||
|
||||
CardDatabaseQuerier *CardDatabaseManager::query()
|
||||
{
|
||||
return getInstance()->query();
|
||||
}
|
||||
324
libs/card/src/card_database/card_database_querier.cpp
Normal file
324
libs/card/src/card_database/card_database_querier.cpp
Normal file
|
|
@ -0,0 +1,324 @@
|
|||
#include "card/card_database/card_database_querier.h"
|
||||
|
||||
#include "card/card_database/card_database.h"
|
||||
#include "card/card_info.h"
|
||||
#include "card/card_printing/exact_card.h"
|
||||
#include "card/card_set/card_set_comparator.h"
|
||||
|
||||
#include <qrandom.h>
|
||||
|
||||
CardDatabaseQuerier::CardDatabaseQuerier(QObject *_parent, const CardDatabase *_db) : QObject(_parent), db(_db)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up the cardInfo corresponding to the cardName.
|
||||
*
|
||||
* @param cardName The card name to look up
|
||||
* @return A CardInfoPtr, or null if not corresponding CardInfo is found.
|
||||
*/
|
||||
CardInfoPtr CardDatabaseQuerier::getCardInfo(const QString &cardName) const
|
||||
{
|
||||
return db->cards.value(cardName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up the cardInfos for a list of card names.
|
||||
*
|
||||
* @param cardNames The card names to look up
|
||||
* @return A List of CardInfoPtr. Any failed lookups will be ignored and dropped from the resulting list
|
||||
*/
|
||||
QList<CardInfoPtr> CardDatabaseQuerier::getCardInfos(const QStringList &cardNames) const
|
||||
{
|
||||
QList<CardInfoPtr> cardInfos;
|
||||
for (const QString &cardName : cardNames) {
|
||||
CardInfoPtr ptr = db->cards.value(cardName);
|
||||
if (ptr)
|
||||
cardInfos.append(ptr);
|
||||
}
|
||||
|
||||
return cardInfos;
|
||||
}
|
||||
|
||||
CardInfoPtr CardDatabaseQuerier::getCardBySimpleName(const QString &cardName) const
|
||||
{
|
||||
return db->simpleNameCards.value(CardInfo::simplifyName(cardName));
|
||||
}
|
||||
|
||||
CardInfoPtr CardDatabaseQuerier::lookupCardByName(const QString &name) const
|
||||
{
|
||||
if (auto info = getCardInfo(name))
|
||||
return info;
|
||||
if (auto info = getCardBySimpleName(name))
|
||||
return info;
|
||||
return getCardBySimpleName(CardInfo::simplifyName(name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up the cards corresponding to the CardRefs.
|
||||
* If the providerId is empty, will default to the preferred printing.
|
||||
* If providerId is given but not found, the PrintingInfo will be empty.
|
||||
*
|
||||
* @param cardRefs The cards to look up. If providerId is empty for an entry, will default to the preferred printing for
|
||||
* that entry. If providerId is given but not found, the PrintingInfo will be empty for that entry.
|
||||
* @return A list of cards. Any failed lookups will be ignored and dropped from the resulting list.
|
||||
*/
|
||||
QList<ExactCard> CardDatabaseQuerier::getCards(const QList<CardRef> &cardRefs) const
|
||||
{
|
||||
QList<ExactCard> cards;
|
||||
for (const auto &cardRef : cardRefs) {
|
||||
ExactCard card = getCard(cardRef);
|
||||
if (card)
|
||||
cards.append(card);
|
||||
}
|
||||
|
||||
return cards;
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up the card corresponding to the CardRef.
|
||||
* If the providerId is empty, will default to the preferred printing.
|
||||
* If providerId is given but not found, the PrintingInfo will be empty.
|
||||
*
|
||||
* @param cardRef The card to look up.
|
||||
* @return A specific printing of a card, or empty if not found.
|
||||
*/
|
||||
ExactCard CardDatabaseQuerier::getCard(const CardRef &cardRef) const
|
||||
{
|
||||
auto info = getCardInfo(cardRef.name);
|
||||
if (info.isNull()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (cardRef.providerId.isEmpty() || cardRef.providerId.isNull()) {
|
||||
return ExactCard(info, getPreferredPrinting(info));
|
||||
}
|
||||
|
||||
return ExactCard(info, findPrintingWithId(info, cardRef.providerId));
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up the card by CardRef, simplifying the name if required.
|
||||
* If the providerId is empty, will default to the preferred printing.
|
||||
* If providerId is given but not found, the PrintingInfo will be empty.
|
||||
*
|
||||
* @param cardRef The card to look up.
|
||||
* @return A specific printing of a card, or empty if not found.
|
||||
*/
|
||||
ExactCard CardDatabaseQuerier::guessCard(const CardRef &cardRef) const
|
||||
{
|
||||
auto card = lookupCardByName(cardRef.name);
|
||||
auto printing =
|
||||
cardRef.providerId.isEmpty() ? getPreferredPrinting(card) : findPrintingWithId(card, cardRef.providerId);
|
||||
|
||||
return ExactCard(card, printing);
|
||||
}
|
||||
|
||||
ExactCard CardDatabaseQuerier::getRandomCard() const
|
||||
{
|
||||
if (db->cards.isEmpty())
|
||||
return {};
|
||||
|
||||
const auto keys = db->cards.keys();
|
||||
int randomIndex = QRandomGenerator::global()->bounded(keys.size());
|
||||
const QString &randomKey = keys.at(randomIndex);
|
||||
CardInfoPtr randomCard = getCardInfo(randomKey);
|
||||
|
||||
return ExactCard{randomCard, getPreferredPrinting(randomCard)};
|
||||
}
|
||||
|
||||
ExactCard CardDatabaseQuerier::getCardFromSameSet(const QString &cardName, const PrintingInfo &otherPrinting) const
|
||||
{
|
||||
// The source card does not have a printing defined, which means we can't get a card from the same set.
|
||||
if (otherPrinting == PrintingInfo()) {
|
||||
return getCard({cardName});
|
||||
}
|
||||
|
||||
// The source card does have a printing defined, which means we can attempt to get a card from the same set.
|
||||
PrintingInfo relatedPrinting = getSpecificPrinting(cardName, otherPrinting.getSet()->getCorrectedShortName(), "");
|
||||
ExactCard relatedCard(guessCard({cardName}).getCardPtr(), relatedPrinting);
|
||||
|
||||
// If we didn't find a card from the same set, just try to find any card with the same name.
|
||||
return relatedCard ? relatedCard : getCard({cardName});
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the PrintingInfo in the cardInfo that has the given uuid field.
|
||||
*
|
||||
* @param cardInfo The CardInfo to search
|
||||
* @param providerId The uuid to look for
|
||||
* @return The PrintingInfo, or a default-constructed PrintingInfo if not found.
|
||||
*/
|
||||
PrintingInfo CardDatabaseQuerier::findPrintingWithId(const CardInfoPtr &cardInfo, const QString &providerId) const
|
||||
{
|
||||
for (const auto &printings : cardInfo->getSets()) {
|
||||
for (const auto &printing : printings) {
|
||||
if (printing.getUuid() == providerId) {
|
||||
return printing;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return PrintingInfo();
|
||||
}
|
||||
|
||||
PrintingInfo CardDatabaseQuerier::getSpecificPrinting(const CardRef &cardRef) const
|
||||
{
|
||||
CardInfoPtr cardInfo = getCardInfo(cardRef.name);
|
||||
if (!cardInfo) {
|
||||
return PrintingInfo(nullptr);
|
||||
}
|
||||
|
||||
return findPrintingWithId(cardInfo, cardRef.providerId);
|
||||
}
|
||||
|
||||
PrintingInfo CardDatabaseQuerier::getSpecificPrinting(const QString &cardName,
|
||||
const QString &setShortName,
|
||||
const QString &collectorNumber) const
|
||||
{
|
||||
CardInfoPtr cardInfo = getCardInfo(cardName);
|
||||
if (!cardInfo) {
|
||||
return PrintingInfo(nullptr);
|
||||
}
|
||||
|
||||
SetToPrintingsMap setMap = cardInfo->getSets();
|
||||
if (setMap.empty()) {
|
||||
return PrintingInfo(nullptr);
|
||||
}
|
||||
|
||||
for (const auto &printings : setMap) {
|
||||
for (auto &cardInfoForSet : printings) {
|
||||
if (!collectorNumber.isEmpty()) {
|
||||
if (cardInfoForSet.getSet()->getShortName() == setShortName &&
|
||||
cardInfoForSet.getProperty("num") == collectorNumber) {
|
||||
return cardInfoForSet;
|
||||
}
|
||||
} else {
|
||||
if (cardInfoForSet.getSet()->getShortName() == setShortName) {
|
||||
return cardInfoForSet;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return PrintingInfo(nullptr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the card representing the preferred printing of the cardInfo
|
||||
*
|
||||
* @param cardInfo The cardInfo to find the preferred printing for
|
||||
* @return A specific printing of a card
|
||||
*/
|
||||
ExactCard CardDatabaseQuerier::getPreferredCard(const CardInfoPtr &cardInfo) const
|
||||
{
|
||||
return ExactCard(cardInfo, getPreferredPrinting(cardInfo));
|
||||
}
|
||||
|
||||
bool CardDatabaseQuerier::isPreferredPrinting(const CardRef &cardRef) const
|
||||
{
|
||||
if (cardRef.providerId.startsWith("card_")) {
|
||||
return cardRef.providerId ==
|
||||
QLatin1String("card_") + cardRef.name + QString("_") + getPreferredPrintingProviderId(cardRef.name);
|
||||
}
|
||||
return cardRef.providerId == getPreferredPrintingProviderId(cardRef.name);
|
||||
}
|
||||
|
||||
PrintingInfo CardDatabaseQuerier::getPreferredPrinting(const QString &cardName) const
|
||||
{
|
||||
CardInfoPtr cardInfo = getCardInfo(cardName);
|
||||
return getPreferredPrinting(cardInfo);
|
||||
}
|
||||
|
||||
PrintingInfo CardDatabaseQuerier::getPreferredPrinting(const CardInfoPtr &cardInfo) const
|
||||
{
|
||||
if (!cardInfo) {
|
||||
return PrintingInfo(nullptr);
|
||||
}
|
||||
|
||||
SetToPrintingsMap setMap = cardInfo->getSets();
|
||||
if (setMap.empty()) {
|
||||
return PrintingInfo(nullptr);
|
||||
}
|
||||
|
||||
CardSetPtr preferredSet = nullptr;
|
||||
PrintingInfo preferredPrinting;
|
||||
SetPriorityComparator comparator;
|
||||
|
||||
for (const auto &printings : setMap) {
|
||||
for (auto &printing : printings) {
|
||||
CardSetPtr currentSet = printing.getSet();
|
||||
if (!preferredSet || comparator(currentSet, preferredSet)) {
|
||||
preferredSet = currentSet;
|
||||
preferredPrinting = printing;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (preferredSet) {
|
||||
return preferredPrinting;
|
||||
}
|
||||
|
||||
return PrintingInfo(nullptr);
|
||||
}
|
||||
|
||||
QString CardDatabaseQuerier::getPreferredPrintingProviderId(const QString &cardName) const
|
||||
{
|
||||
PrintingInfo preferredPrinting = getPreferredPrinting(cardName);
|
||||
QString uuid = preferredPrinting.getUuid();
|
||||
if (!uuid.isEmpty()) {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
CardInfoPtr defaultCardInfo = getCardInfo(cardName);
|
||||
if (defaultCardInfo.isNull()) {
|
||||
return cardName;
|
||||
}
|
||||
return defaultCardInfo->getName();
|
||||
}
|
||||
|
||||
QStringList CardDatabaseQuerier::getAllMainCardTypes() const
|
||||
{
|
||||
QSet<QString> types;
|
||||
for (const auto &card : db->cards.values()) {
|
||||
types.insert(card->getMainCardType());
|
||||
}
|
||||
return types.values();
|
||||
}
|
||||
|
||||
QMap<QString, int> CardDatabaseQuerier::getAllMainCardTypesWithCount() const
|
||||
{
|
||||
QMap<QString, int> typeCounts;
|
||||
|
||||
for (const auto &card : db->cards.values()) {
|
||||
QString type = card->getMainCardType();
|
||||
typeCounts[type]++;
|
||||
}
|
||||
|
||||
return typeCounts;
|
||||
}
|
||||
|
||||
QMap<QString, int> CardDatabaseQuerier::getAllSubCardTypesWithCount() const
|
||||
{
|
||||
QMap<QString, int> typeCounts;
|
||||
|
||||
for (const auto &card : db->cards.values()) {
|
||||
QString type = card->getCardType();
|
||||
|
||||
QStringList parts = type.split(" — ");
|
||||
|
||||
if (parts.size() > 1) { // Ensure there are subtypes
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
|
||||
QStringList subtypes = parts[1].split(" ", Qt::SkipEmptyParts);
|
||||
#else
|
||||
QStringList subtypes = parts[1].split(" ", QString::SkipEmptyParts);
|
||||
#endif
|
||||
|
||||
for (const QString &subtype : subtypes) {
|
||||
typeCounts[subtype]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return typeCounts;
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
#include "card/card_database/model/card/card_completer_proxy_model.h"
|
||||
|
||||
CardCompleterProxyModel::CardCompleterProxyModel(QObject *parent) : QSortFilterProxyModel(parent)
|
||||
{
|
||||
}
|
||||
|
||||
bool CardCompleterProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
|
||||
{
|
||||
if (filterRegularExpression().pattern().isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
|
||||
QString data = index.data(Qt::DisplayRole).toString();
|
||||
|
||||
// Ensure substring matching
|
||||
return data.contains(filterRegularExpression());
|
||||
}
|
||||
73
libs/card/src/card_database/model/card/card_search_model.cpp
Normal file
73
libs/card/src/card_database/model/card/card_search_model.cpp
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
#include "card/card_database/model/card/card_search_model.h"
|
||||
|
||||
#include "card/card_database/model/card_database_display_model.h"
|
||||
#include "card/card_database/model/card_database_model.h"
|
||||
#include "utility/levenshtein.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
CardSearchModel::CardSearchModel(CardDatabaseDisplayModel *sourceModel, QObject *parent)
|
||||
: QAbstractListModel(parent), sourceModel(sourceModel)
|
||||
{
|
||||
}
|
||||
|
||||
int CardSearchModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
Q_UNUSED(parent);
|
||||
return searchResults.size();
|
||||
}
|
||||
|
||||
QVariant CardSearchModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!index.isValid() || index.row() >= searchResults.size())
|
||||
return QVariant();
|
||||
|
||||
if (role == Qt::DisplayRole) {
|
||||
return searchResults.at(index.row()).card->getName();
|
||||
}
|
||||
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
void CardSearchModel::updateSearchResults(const QString &query)
|
||||
{
|
||||
beginResetModel();
|
||||
searchResults.clear();
|
||||
|
||||
if (query.isEmpty() || !sourceModel)
|
||||
return;
|
||||
|
||||
// Set the filter for the display model
|
||||
sourceModel->setCardName(query);
|
||||
|
||||
// Collect matching cards and compute Levenshtein distance
|
||||
for (int i = 0; i < sourceModel->rowCount(); ++i) {
|
||||
QModelIndex modelIndex = sourceModel->index(i, 0);
|
||||
QModelIndex sourceIndex = sourceModel->mapToSource(modelIndex);
|
||||
CardDatabaseModel *sourceDbModel = qobject_cast<CardDatabaseModel *>(sourceModel->sourceModel());
|
||||
|
||||
if (!sourceDbModel || !sourceIndex.isValid())
|
||||
return;
|
||||
|
||||
CardInfoPtr card = sourceDbModel->getCard(sourceIndex.row());
|
||||
|
||||
if (!card)
|
||||
continue;
|
||||
|
||||
int distance = levenshteinDistance(query.toLower(), card->getName().toLower());
|
||||
searchResults.append({card, distance});
|
||||
}
|
||||
|
||||
// Sort by Levenshtein distance (lower distance = better match)
|
||||
std::sort(searchResults.begin(), searchResults.end(),
|
||||
[](const SearchResult &a, const SearchResult &b) { return a.distance < b.distance; });
|
||||
|
||||
// Keep only the top 5 results
|
||||
if (searchResults.size() > 10)
|
||||
searchResults = searchResults.mid(0, 10);
|
||||
|
||||
emit dataChanged(index(0, 0), index(rowCount() - 1, 0));
|
||||
emit layoutChanged();
|
||||
|
||||
endResetModel();
|
||||
}
|
||||
|
|
@ -0,0 +1,220 @@
|
|||
#include "card/card_database/model/card_database_display_model.h"
|
||||
|
||||
#include "card/card_database/model/card_database_model.h"
|
||||
|
||||
CardDatabaseDisplayModel::CardDatabaseDisplayModel(QObject *parent)
|
||||
: QSortFilterProxyModel(parent), isToken(ShowAll), filterString(nullptr)
|
||||
{
|
||||
filterTree = nullptr;
|
||||
setFilterCaseSensitivity(Qt::CaseInsensitive);
|
||||
setSortCaseSensitivity(Qt::CaseInsensitive);
|
||||
|
||||
dirtyTimer.setSingleShot(true);
|
||||
connect(&dirtyTimer, &QTimer::timeout, this, &CardDatabaseDisplayModel::invalidate);
|
||||
|
||||
loadedRowCount = 0;
|
||||
}
|
||||
|
||||
QMap<wchar_t, wchar_t> CardDatabaseDisplayModel::characterTranslation = {{L'“', L'\"'},
|
||||
{L'”', L'\"'},
|
||||
{L'‘', L'\''},
|
||||
{L'’', L'\''}};
|
||||
|
||||
bool CardDatabaseDisplayModel::canFetchMore(const QModelIndex &index) const
|
||||
{
|
||||
return loadedRowCount < sourceModel()->rowCount(index);
|
||||
}
|
||||
|
||||
void CardDatabaseDisplayModel::fetchMore(const QModelIndex &index)
|
||||
{
|
||||
int remainder = sourceModel()->rowCount(index) - loadedRowCount;
|
||||
int itemsToFetch = qMin(100, remainder);
|
||||
|
||||
if (itemsToFetch == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto startIndex = qMin(rowCount(QModelIndex()), loadedRowCount);
|
||||
beginInsertRows(QModelIndex(), startIndex, startIndex + itemsToFetch - 1);
|
||||
|
||||
loadedRowCount += itemsToFetch;
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
int CardDatabaseDisplayModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
return QSortFilterProxyModel::rowCount(parent);
|
||||
}
|
||||
|
||||
bool CardDatabaseDisplayModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
|
||||
{
|
||||
|
||||
QString leftString = sourceModel()->data(left, CardDatabaseModel::SortRole).toString();
|
||||
QString rightString = sourceModel()->data(right, CardDatabaseModel::SortRole).toString();
|
||||
|
||||
if (!cardName.isEmpty() && left.column() == CardDatabaseModel::NameColumn) {
|
||||
bool isLeftType = leftString.startsWith(cardName, Qt::CaseInsensitive);
|
||||
bool isRightType = rightString.startsWith(cardName, Qt::CaseInsensitive);
|
||||
|
||||
// test for an exact match: isLeftType && leftString.size() == cardName.size()
|
||||
// or an exclusive start match: isLeftType && !isRightType
|
||||
if (isLeftType && (!isRightType || leftString.size() == cardName.size()))
|
||||
return true;
|
||||
|
||||
// same checks for the right string
|
||||
if (isRightType && (!isLeftType || rightString.size() == cardName.size()))
|
||||
return false;
|
||||
} else if (right.column() == CardDatabaseModel::PTColumn && left.column() == CardDatabaseModel::PTColumn) {
|
||||
QStringList leftList = leftString.split("/");
|
||||
QStringList rightList = rightString.split("/");
|
||||
|
||||
if (leftList.size() == 2 && rightList.size() == 2) {
|
||||
|
||||
// cool, have both P/T in list now
|
||||
int lessThanNum = lessThanNumerically(leftList.at(0), rightList.at(0));
|
||||
if (lessThanNum != 0) {
|
||||
return lessThanNum < 0;
|
||||
} else {
|
||||
// power equal, check toughness
|
||||
return lessThanNumerically(leftList.at(1), rightList.at(1)) < 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
return QString::localeAwareCompare(leftString, rightString) < 0;
|
||||
}
|
||||
|
||||
int CardDatabaseDisplayModel::lessThanNumerically(const QString &left, const QString &right)
|
||||
{
|
||||
if (left == right) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool okLeft, okRight;
|
||||
float leftNum = left.toFloat(&okLeft);
|
||||
float rightNum = right.toFloat(&okRight);
|
||||
|
||||
if (okLeft && okRight) {
|
||||
if (leftNum < rightNum) {
|
||||
return -1;
|
||||
} else if (leftNum > rightNum) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
// try and parsing again, for weird ones like "1+*"
|
||||
QString leftAfterNum = "";
|
||||
QString rightAfterNum = "";
|
||||
if (!okLeft) {
|
||||
int leftNumIndex = 0;
|
||||
for (; leftNumIndex < left.length(); leftNumIndex++) {
|
||||
if (!left.at(leftNumIndex).isDigit()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (leftNumIndex != 0) {
|
||||
leftNum = left.left(leftNumIndex).toFloat(&okLeft);
|
||||
leftAfterNum = left.right(leftNumIndex);
|
||||
}
|
||||
}
|
||||
if (!okRight) {
|
||||
int rightNumIndex = 0;
|
||||
for (; rightNumIndex < right.length(); rightNumIndex++) {
|
||||
if (!right.at(rightNumIndex).isDigit()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (rightNumIndex != 0) {
|
||||
rightNum = right.left(rightNumIndex).toFloat(&okRight);
|
||||
rightAfterNum = right.right(rightNumIndex);
|
||||
}
|
||||
}
|
||||
if (okLeft && okRight) {
|
||||
|
||||
if (leftNum != rightNum) {
|
||||
// both parsed as numbers, but different number
|
||||
if (leftNum < rightNum) {
|
||||
return -1;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
// both parsed, same number, but at least one has something else
|
||||
// so compare the part after the number - prefer nothing
|
||||
return QString::localeAwareCompare(leftAfterNum, rightAfterNum);
|
||||
}
|
||||
} else if (okLeft) {
|
||||
return -1;
|
||||
} else if (okRight) {
|
||||
return 1;
|
||||
}
|
||||
// couldn't parse it, just return String comparison
|
||||
return QString::localeAwareCompare(left, right);
|
||||
}
|
||||
bool CardDatabaseDisplayModel::filterAcceptsRow(int sourceRow, const QModelIndex & /*sourceParent*/) const
|
||||
{
|
||||
CardInfoPtr info = static_cast<CardDatabaseModel *>(sourceModel())->getCard(sourceRow);
|
||||
|
||||
if (((isToken == ShowTrue) && !info->getIsToken()) || ((isToken == ShowFalse) && info->getIsToken()))
|
||||
return false;
|
||||
|
||||
if (filterString != nullptr) {
|
||||
if (filterTree != nullptr && !filterTree->acceptsCard(info)) {
|
||||
return false;
|
||||
}
|
||||
return filterString->check(info);
|
||||
}
|
||||
|
||||
return rowMatchesCardName(info);
|
||||
}
|
||||
|
||||
bool CardDatabaseDisplayModel::rowMatchesCardName(CardInfoPtr info) const
|
||||
{
|
||||
if (!cardName.isEmpty() && !info->getName().contains(cardName, Qt::CaseInsensitive))
|
||||
return false;
|
||||
|
||||
if (!cardNameSet.isEmpty() && !cardNameSet.contains(info->getName()))
|
||||
return false;
|
||||
|
||||
if (filterTree != nullptr)
|
||||
return filterTree->acceptsCard(info);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CardDatabaseDisplayModel::clearFilterAll()
|
||||
{
|
||||
cardName.clear();
|
||||
cardText.clear();
|
||||
cardTypes.clear();
|
||||
cardColors.clear();
|
||||
if (filterTree != nullptr)
|
||||
filterTree->clear();
|
||||
invalidateFilter();
|
||||
}
|
||||
|
||||
void CardDatabaseDisplayModel::setFilterTree(FilterTree *_filterTree)
|
||||
{
|
||||
if (this->filterTree != nullptr)
|
||||
disconnect(this->filterTree, nullptr, this, nullptr);
|
||||
|
||||
this->filterTree = _filterTree;
|
||||
connect(this->filterTree, &FilterTree::changed, this, &CardDatabaseDisplayModel::filterTreeChanged);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
void CardDatabaseDisplayModel::filterTreeChanged()
|
||||
{
|
||||
invalidate();
|
||||
}
|
||||
|
||||
const QString CardDatabaseDisplayModel::sanitizeCardName(const QString &dirtyName, const QMap<wchar_t, wchar_t> &table)
|
||||
{
|
||||
std::wstring toReturn = dirtyName.toStdWString();
|
||||
for (wchar_t &ch : toReturn) {
|
||||
if (table.contains(ch)) {
|
||||
ch = table.value(ch);
|
||||
}
|
||||
}
|
||||
return QString::fromStdWString(toReturn);
|
||||
}
|
||||
148
libs/card/src/card_database/model/card_database_model.cpp
Normal file
148
libs/card/src/card_database/model/card_database_model.cpp
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
#include "card/card_database/model/card_database_model.h"
|
||||
|
||||
#include "card/card_database/card_database.h"
|
||||
|
||||
#include <QMap>
|
||||
|
||||
#define CARDDBMODEL_COLUMNS 6
|
||||
|
||||
CardDatabaseModel::CardDatabaseModel(CardDatabase *_db, bool _showOnlyCardsFromEnabledSets, QObject *parent)
|
||||
: QAbstractListModel(parent), db(_db), showOnlyCardsFromEnabledSets(_showOnlyCardsFromEnabledSets)
|
||||
{
|
||||
connect(db, &CardDatabase::cardAdded, this, &CardDatabaseModel::cardAdded);
|
||||
connect(db, &CardDatabase::cardRemoved, this, &CardDatabaseModel::cardRemoved);
|
||||
connect(db, &CardDatabase::cardDatabaseEnabledSetsChanged, this,
|
||||
&CardDatabaseModel::cardDatabaseEnabledSetsChanged);
|
||||
|
||||
cardDatabaseEnabledSetsChanged();
|
||||
}
|
||||
|
||||
CardDatabaseModel::~CardDatabaseModel() = default;
|
||||
|
||||
int CardDatabaseModel::rowCount(const QModelIndex & /*parent*/) const
|
||||
{
|
||||
return cardList.size();
|
||||
}
|
||||
|
||||
int CardDatabaseModel::columnCount(const QModelIndex & /*parent*/) const
|
||||
{
|
||||
return CARDDBMODEL_COLUMNS;
|
||||
}
|
||||
|
||||
QVariant CardDatabaseModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!index.isValid() || index.row() >= cardList.size() || index.column() >= CARDDBMODEL_COLUMNS ||
|
||||
(role != Qt::DisplayRole && role != SortRole))
|
||||
return QVariant();
|
||||
|
||||
CardInfoPtr card = cardList.at(index.row());
|
||||
switch (index.column()) {
|
||||
case NameColumn:
|
||||
return card->getName();
|
||||
case SetListColumn:
|
||||
return card->getSetsNames();
|
||||
case ManaCostColumn:
|
||||
return role == SortRole ? QString("%1%2").arg(card->getCmc(), 4, QChar('0')).arg(card->getManaCost())
|
||||
: card->getManaCost();
|
||||
case CardTypeColumn:
|
||||
return card->getCardType();
|
||||
case PTColumn:
|
||||
return card->getPowTough();
|
||||
case ColorColumn:
|
||||
return card->getColors();
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
QVariant CardDatabaseModel::headerData(int section, Qt::Orientation orientation, int role) const
|
||||
{
|
||||
if (role != Qt::DisplayRole)
|
||||
return QVariant();
|
||||
if (orientation != Qt::Horizontal)
|
||||
return QVariant();
|
||||
switch (section) {
|
||||
case NameColumn:
|
||||
return QString(tr("Name"));
|
||||
case SetListColumn:
|
||||
return QString(tr("Sets"));
|
||||
case ManaCostColumn:
|
||||
return QString(tr("Mana cost"));
|
||||
case CardTypeColumn:
|
||||
return QString(tr("Card type"));
|
||||
case PTColumn:
|
||||
return QString(tr("P/T"));
|
||||
case ColorColumn:
|
||||
return QString(tr("Color(s)"));
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
void CardDatabaseModel::cardInfoChanged(CardInfoPtr card)
|
||||
{
|
||||
const int row = cardList.indexOf(card);
|
||||
if (row == -1)
|
||||
return;
|
||||
|
||||
emit dataChanged(index(row, 0), index(row, CARDDBMODEL_COLUMNS - 1));
|
||||
}
|
||||
|
||||
bool CardDatabaseModel::checkCardHasAtLeastOneEnabledSet(CardInfoPtr card)
|
||||
{
|
||||
if (!showOnlyCardsFromEnabledSets)
|
||||
return true;
|
||||
|
||||
for (const auto &printings : card->getSets()) {
|
||||
for (const auto &printing : printings) {
|
||||
if (printing.getSet()->getEnabled())
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void CardDatabaseModel::cardDatabaseEnabledSetsChanged()
|
||||
{
|
||||
// remove all the cards no more present in at least one enabled set
|
||||
for (const CardInfoPtr &card : cardList) {
|
||||
if (!checkCardHasAtLeastOneEnabledSet(card)) {
|
||||
cardRemoved(card);
|
||||
}
|
||||
}
|
||||
|
||||
// re-check all the card currently not shown, maybe their part of a newly-enabled set
|
||||
for (const CardInfoPtr &card : db->getCardList()) {
|
||||
if (!cardListSet.contains(card)) {
|
||||
cardAdded(card);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CardDatabaseModel::cardAdded(CardInfoPtr card)
|
||||
{
|
||||
if (checkCardHasAtLeastOneEnabledSet(card)) {
|
||||
// add the card if it's present in at least one enabled set
|
||||
beginInsertRows(QModelIndex(), cardList.size(), cardList.size());
|
||||
cardList.append(card);
|
||||
cardListSet.insert(card);
|
||||
connect(card.data(), &CardInfo::cardInfoChanged, this, &CardDatabaseModel::cardInfoChanged);
|
||||
endInsertRows();
|
||||
}
|
||||
}
|
||||
|
||||
void CardDatabaseModel::cardRemoved(CardInfoPtr card)
|
||||
{
|
||||
const int row = cardList.indexOf(card);
|
||||
if (row == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
beginRemoveRows(QModelIndex(), row, row);
|
||||
disconnect(card.data(), nullptr, this, nullptr);
|
||||
cardListSet.remove(card);
|
||||
card.clear();
|
||||
cardList.removeAt(row);
|
||||
endRemoveRows();
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
#include "card/card_database/model/token/token_display_model.h"
|
||||
|
||||
#include "card/card_database/model/card_database_model.h"
|
||||
|
||||
TokenDisplayModel::TokenDisplayModel(QObject *parent) : CardDatabaseDisplayModel(parent)
|
||||
{
|
||||
}
|
||||
|
||||
bool TokenDisplayModel::filterAcceptsRow(int sourceRow, const QModelIndex & /*sourceParent*/) const
|
||||
{
|
||||
CardInfoPtr info = static_cast<CardDatabaseModel *>(sourceModel())->getCard(sourceRow);
|
||||
return info->getIsToken() && rowMatchesCardName(info);
|
||||
}
|
||||
|
||||
int TokenDisplayModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
// always load all tokens at start
|
||||
return QSortFilterProxyModel::rowCount(parent);
|
||||
}
|
||||
21
libs/card/src/card_database/model/token/token_edit_model.cpp
Normal file
21
libs/card/src/card_database/model/token/token_edit_model.cpp
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
#include "card/card_database/model/token/token_edit_model.h"
|
||||
|
||||
#include "card/card_database/model/card_database_display_model.h"
|
||||
#include "card/card_database/model/card_database_model.h"
|
||||
#include "card/card_info.h"
|
||||
|
||||
TokenEditModel::TokenEditModel(QObject *parent) : CardDatabaseDisplayModel(parent)
|
||||
{
|
||||
}
|
||||
|
||||
bool TokenEditModel::filterAcceptsRow(int sourceRow, const QModelIndex & /*sourceParent*/) const
|
||||
{
|
||||
CardInfoPtr info = static_cast<CardDatabaseModel *>(sourceModel())->getCard(sourceRow);
|
||||
return info->getIsToken() && info->getSets().contains(CardSet::TOKENS_SETNAME) && rowMatchesCardName(info);
|
||||
}
|
||||
|
||||
int TokenEditModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
// always load all tokens at start
|
||||
return QSortFilterProxyModel::rowCount(parent);
|
||||
}
|
||||
29
libs/card/src/card_database/parser/card_database_parser.cpp
Normal file
29
libs/card/src/card_database/parser/card_database_parser.cpp
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
#include "card/card_database/parser/card_database_parser.h"
|
||||
|
||||
SetNameMap ICardDatabaseParser::sets;
|
||||
|
||||
void ICardDatabaseParser::clearSetlist()
|
||||
{
|
||||
sets.clear();
|
||||
}
|
||||
|
||||
CardSetPtr ICardDatabaseParser::internalAddSet(const QString &setName,
|
||||
const QString &longName,
|
||||
const QString &setType,
|
||||
const QDate &releaseDate,
|
||||
const CardSet::Priority priority)
|
||||
{
|
||||
if (sets.contains(setName)) {
|
||||
return sets.value(setName);
|
||||
}
|
||||
|
||||
CardSetPtr newSet = CardSet::newInstance(setName);
|
||||
newSet->setLongName(longName);
|
||||
newSet->setSetType(setType);
|
||||
newSet->setReleaseDate(releaseDate);
|
||||
newSet->setPriority(priority);
|
||||
|
||||
sets.insert(setName, newSet);
|
||||
emit addSet(newSet);
|
||||
return newSet;
|
||||
}
|
||||
483
libs/card/src/card_database/parser/cockatrice_xml_3.cpp
Normal file
483
libs/card/src/card_database/parser/cockatrice_xml_3.cpp
Normal file
|
|
@ -0,0 +1,483 @@
|
|||
#include "card/card_database/parser/cockatrice_xml_3.h"
|
||||
|
||||
#include "card/card_relation/card_relation.h"
|
||||
#include "card/card_relation/card_relation_type.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
#include <QFile>
|
||||
#include <QXmlStreamReader>
|
||||
#include <version_string.h>
|
||||
|
||||
#define COCKATRICE_XML3_TAGNAME "cockatrice_carddatabase"
|
||||
#define COCKATRICE_XML3_TAGVER 3
|
||||
#define COCKATRICE_XML3_SCHEMALOCATION \
|
||||
"https://raw.githubusercontent.com/Cockatrice/Cockatrice/master/doc/carddatabase_v3/cards.xsd"
|
||||
|
||||
bool CockatriceXml3Parser::getCanParseFile(const QString &fileName, QIODevice &device)
|
||||
{
|
||||
qCInfo(CockatriceXml3Log) << "Trying to parse: " << fileName;
|
||||
|
||||
if (!fileName.endsWith(".xml", Qt::CaseInsensitive)) {
|
||||
qCInfo(CockatriceXml3Log) << "Parsing failed: wrong extension";
|
||||
return false;
|
||||
}
|
||||
|
||||
QXmlStreamReader xml(&device);
|
||||
while (!xml.atEnd()) {
|
||||
if (xml.readNext() == QXmlStreamReader::StartElement) {
|
||||
if (xml.name().toString() == COCKATRICE_XML3_TAGNAME) {
|
||||
int version = xml.attributes().value("version").toString().toInt();
|
||||
if (version == COCKATRICE_XML3_TAGVER) {
|
||||
return true;
|
||||
} else {
|
||||
qCInfo(CockatriceXml3Log) << "Parsing failed: wrong version" << version;
|
||||
return false;
|
||||
}
|
||||
|
||||
} else {
|
||||
qCInfo(CockatriceXml3Log) << "Parsing failed: wrong element tag" << xml.name();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CockatriceXml3Parser::parseFile(QIODevice &device)
|
||||
{
|
||||
QXmlStreamReader xml(&device);
|
||||
while (!xml.atEnd()) {
|
||||
if (xml.readNext() == QXmlStreamReader::StartElement) {
|
||||
while (!xml.atEnd()) {
|
||||
if (xml.readNext() == QXmlStreamReader::EndElement) {
|
||||
break;
|
||||
}
|
||||
|
||||
auto name = xml.name().toString();
|
||||
if (name == "sets") {
|
||||
loadSetsFromXml(xml);
|
||||
} else if (name == "cards") {
|
||||
loadCardsFromXml(xml);
|
||||
} else if (!name.isEmpty()) {
|
||||
qCInfo(CockatriceXml3Log) << "Unknown item" << name << ", trying to continue anyway";
|
||||
xml.skipCurrentElement();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (xml.hasError()) {
|
||||
QString preamble = tr("Parse error at line %1 col %2:").arg(xml.lineNumber()).arg(xml.columnNumber());
|
||||
qCWarning(CockatriceXml3Log).noquote() << preamble << xml.errorString();
|
||||
}
|
||||
}
|
||||
|
||||
void CockatriceXml3Parser::loadSetsFromXml(QXmlStreamReader &xml)
|
||||
{
|
||||
while (!xml.atEnd()) {
|
||||
if (xml.readNext() == QXmlStreamReader::EndElement) {
|
||||
break;
|
||||
}
|
||||
|
||||
auto name = xml.name().toString();
|
||||
if (name == "set") {
|
||||
QString shortName, longName, setType;
|
||||
QDate releaseDate;
|
||||
while (!xml.atEnd()) {
|
||||
if (xml.readNext() == QXmlStreamReader::EndElement) {
|
||||
break;
|
||||
}
|
||||
name = xml.name().toString();
|
||||
|
||||
if (name == "name") {
|
||||
shortName = xml.readElementText(QXmlStreamReader::IncludeChildElements);
|
||||
} else if (name == "longname") {
|
||||
longName = xml.readElementText(QXmlStreamReader::IncludeChildElements);
|
||||
} else if (name == "settype") {
|
||||
setType = xml.readElementText(QXmlStreamReader::IncludeChildElements);
|
||||
} else if (name == "releasedate") {
|
||||
releaseDate =
|
||||
QDate::fromString(xml.readElementText(QXmlStreamReader::IncludeChildElements), Qt::ISODate);
|
||||
} else if (!name.isEmpty()) {
|
||||
qCInfo(CockatriceXml3Log) << "Unknown set property" << name << ", trying to continue anyway";
|
||||
xml.skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
internalAddSet(shortName, longName, setType, releaseDate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QString CockatriceXml3Parser::getMainCardType(QString &type)
|
||||
{
|
||||
QString result = type;
|
||||
/*
|
||||
Legendary Artifact Creature - Golem
|
||||
Instant // Instant
|
||||
*/
|
||||
|
||||
int pos;
|
||||
if ((pos = result.indexOf('-')) != -1) {
|
||||
result.remove(pos, result.length());
|
||||
}
|
||||
|
||||
if ((pos = result.indexOf("—")) != -1) {
|
||||
result.remove(pos, result.length());
|
||||
}
|
||||
|
||||
if ((pos = result.indexOf("//")) != -1) {
|
||||
result.remove(pos, result.length());
|
||||
}
|
||||
|
||||
result = result.simplified();
|
||||
/*
|
||||
Legendary Artifact Creature
|
||||
Instant
|
||||
*/
|
||||
|
||||
if ((pos = result.lastIndexOf(' ')) != -1) {
|
||||
result = result.mid(pos + 1);
|
||||
}
|
||||
/*
|
||||
Creature
|
||||
Instant
|
||||
*/
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void CockatriceXml3Parser::loadCardsFromXml(QXmlStreamReader &xml)
|
||||
{
|
||||
while (!xml.atEnd()) {
|
||||
if (xml.readNext() == QXmlStreamReader::EndElement) {
|
||||
break;
|
||||
}
|
||||
|
||||
auto xmlName = xml.name().toString();
|
||||
if (xmlName == "card") {
|
||||
QString name = QString("");
|
||||
QString text = QString("");
|
||||
QVariantHash properties = QVariantHash();
|
||||
QString colors = QString("");
|
||||
QList<CardRelation *> relatedCards, reverseRelatedCards;
|
||||
auto _sets = SetToPrintingsMap();
|
||||
int tableRow = 0;
|
||||
bool cipt = false;
|
||||
bool landscapeOrientation = false;
|
||||
bool isToken = false;
|
||||
bool upsideDown = false;
|
||||
|
||||
while (!xml.atEnd()) {
|
||||
if (xml.readNext() == QXmlStreamReader::EndElement) {
|
||||
break;
|
||||
}
|
||||
xmlName = xml.name().toString();
|
||||
|
||||
// variable - assigned properties
|
||||
if (xmlName == "name") {
|
||||
name = xml.readElementText(QXmlStreamReader::IncludeChildElements);
|
||||
} else if (xmlName == "text") {
|
||||
text = xml.readElementText(QXmlStreamReader::IncludeChildElements);
|
||||
} else if (xmlName == "color" || xmlName == "colors") {
|
||||
colors.append(xml.readElementText(QXmlStreamReader::IncludeChildElements));
|
||||
} else if (xmlName == "token") {
|
||||
isToken = static_cast<bool>(xml.readElementText(QXmlStreamReader::IncludeChildElements).toInt());
|
||||
// generic properties
|
||||
} else if (xmlName == "manacost") {
|
||||
properties.insert("manacost", xml.readElementText(QXmlStreamReader::IncludeChildElements));
|
||||
} else if (xmlName == "cmc") {
|
||||
properties.insert("cmc", xml.readElementText(QXmlStreamReader::IncludeChildElements));
|
||||
} else if (xmlName == "type") {
|
||||
QString type = xml.readElementText(QXmlStreamReader::IncludeChildElements);
|
||||
properties.insert("type", type);
|
||||
properties.insert("maintype", getMainCardType(type));
|
||||
} else if (xmlName == "pt") {
|
||||
properties.insert("pt", xml.readElementText(QXmlStreamReader::IncludeChildElements));
|
||||
} else if (xmlName == "loyalty") {
|
||||
properties.insert("loyalty", xml.readElementText(QXmlStreamReader::IncludeChildElements));
|
||||
// positioning info
|
||||
} else if (xmlName == "tablerow") {
|
||||
tableRow = xml.readElementText(QXmlStreamReader::IncludeChildElements).toInt();
|
||||
} else if (xmlName == "cipt") {
|
||||
cipt = (xml.readElementText(QXmlStreamReader::IncludeChildElements) == "1");
|
||||
} else if (xmlName == "landscapeOrientation") {
|
||||
landscapeOrientation = (xml.readElementText(QXmlStreamReader::IncludeChildElements) == "1");
|
||||
} else if (xmlName == "upsidedown") {
|
||||
upsideDown = (xml.readElementText(QXmlStreamReader::IncludeChildElements) == "1");
|
||||
// sets
|
||||
} else if (xmlName == "set") {
|
||||
// NOTE: attributes must be read before readElementText()
|
||||
QXmlStreamAttributes attrs = xml.attributes();
|
||||
QString setName = xml.readElementText(QXmlStreamReader::IncludeChildElements);
|
||||
PrintingInfo setInfo(internalAddSet(setName));
|
||||
if (attrs.hasAttribute("muId")) {
|
||||
setInfo.setProperty("muid", attrs.value("muId").toString());
|
||||
}
|
||||
|
||||
if (attrs.hasAttribute("muId")) {
|
||||
setInfo.setProperty("uuid", attrs.value("uuId").toString());
|
||||
}
|
||||
|
||||
if (attrs.hasAttribute("picURL")) {
|
||||
setInfo.setProperty("picurl", attrs.value("picURL").toString());
|
||||
}
|
||||
|
||||
if (attrs.hasAttribute("num")) {
|
||||
setInfo.setProperty("num", attrs.value("num").toString());
|
||||
}
|
||||
|
||||
if (attrs.hasAttribute("rarity")) {
|
||||
setInfo.setProperty("rarity", attrs.value("rarity").toString());
|
||||
}
|
||||
_sets[setName].append(setInfo);
|
||||
// related cards
|
||||
} else if (xmlName == "related" || xmlName == "reverse-related") {
|
||||
CardRelationType attach = CardRelationType::DoesNotAttach;
|
||||
bool exclude = false;
|
||||
bool variable = false;
|
||||
int count = 1;
|
||||
QXmlStreamAttributes attrs = xml.attributes();
|
||||
QString cardName = xml.readElementText(QXmlStreamReader::IncludeChildElements);
|
||||
if (attrs.hasAttribute("count")) {
|
||||
if (attrs.value("count").toString().indexOf("x=") == 0) {
|
||||
variable = true;
|
||||
count = attrs.value("count").toString().remove(0, 2).toInt();
|
||||
} else if (attrs.value("count").toString().indexOf("x") == 0) {
|
||||
variable = true;
|
||||
} else {
|
||||
count = attrs.value("count").toString().toInt();
|
||||
}
|
||||
|
||||
if (count < 1) {
|
||||
count = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (attrs.hasAttribute("attach")) {
|
||||
attach = CardRelationType::AttachTo;
|
||||
}
|
||||
|
||||
if (attrs.hasAttribute("exclude")) {
|
||||
exclude = true;
|
||||
}
|
||||
|
||||
auto *relation = new CardRelation(cardName, attach, exclude, variable, count);
|
||||
if (xmlName == "reverse-related") {
|
||||
reverseRelatedCards << relation;
|
||||
} else {
|
||||
relatedCards << relation;
|
||||
}
|
||||
} else if (!xmlName.isEmpty()) {
|
||||
qCInfo(CockatriceXml3Log) << "Unknown card property" << xmlName << ", trying to continue anyway";
|
||||
xml.skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
if (name.isEmpty()) {
|
||||
qCWarning(CockatriceXml3Log) << "Encountered card with empty name; skipping";
|
||||
continue;
|
||||
}
|
||||
|
||||
properties.insert("colors", colors);
|
||||
CardInfoPtr newCard =
|
||||
CardInfo::newInstance(name, text, isToken, properties, relatedCards, reverseRelatedCards, _sets, cipt,
|
||||
landscapeOrientation, tableRow, upsideDown);
|
||||
emit addCard(newCard);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static QXmlStreamWriter &operator<<(QXmlStreamWriter &xml, const CardSetPtr &set)
|
||||
{
|
||||
if (set.isNull()) {
|
||||
qCWarning(CockatriceXml3Log) << "&operator<< set is nullptr";
|
||||
return xml;
|
||||
}
|
||||
|
||||
xml.writeStartElement("set");
|
||||
xml.writeTextElement("name", set->getShortName());
|
||||
xml.writeTextElement("longname", set->getLongName());
|
||||
xml.writeTextElement("settype", set->getSetType());
|
||||
xml.writeTextElement("releasedate", set->getReleaseDate().toString(Qt::ISODate));
|
||||
xml.writeEndElement();
|
||||
|
||||
return xml;
|
||||
}
|
||||
|
||||
static QXmlStreamWriter &operator<<(QXmlStreamWriter &xml, const CardInfoPtr &info)
|
||||
{
|
||||
if (info.isNull()) {
|
||||
qCWarning(CockatriceXml3Log) << "operator<< info is nullptr";
|
||||
return xml;
|
||||
}
|
||||
|
||||
QString tmpString;
|
||||
|
||||
xml.writeStartElement("card");
|
||||
|
||||
// variable - assigned properties
|
||||
xml.writeTextElement("name", info->getName());
|
||||
xml.writeTextElement("text", info->getText());
|
||||
if (info->getIsToken()) {
|
||||
xml.writeTextElement("token", "1");
|
||||
}
|
||||
|
||||
// generic properties
|
||||
xml.writeTextElement("manacost", info->getProperty("manacost"));
|
||||
xml.writeTextElement("cmc", info->getProperty("cmc"));
|
||||
xml.writeTextElement("type", info->getProperty("type"));
|
||||
|
||||
int colorSize = info->getColors().size();
|
||||
for (int i = 0; i < colorSize; ++i) {
|
||||
xml.writeTextElement("color", info->getColors().at(i));
|
||||
}
|
||||
|
||||
tmpString = info->getProperty("pt");
|
||||
if (!tmpString.isEmpty()) {
|
||||
xml.writeTextElement("pt", tmpString);
|
||||
}
|
||||
|
||||
tmpString = info->getProperty("loyalty");
|
||||
if (!tmpString.isEmpty()) {
|
||||
xml.writeTextElement("loyalty", tmpString);
|
||||
}
|
||||
|
||||
// sets
|
||||
const SetToPrintingsMap setMap = info->getSets();
|
||||
for (const auto &printings : setMap) {
|
||||
for (const PrintingInfo &set : printings) {
|
||||
xml.writeStartElement("set");
|
||||
xml.writeAttribute("rarity", set.getProperty("rarity"));
|
||||
xml.writeAttribute("muId", set.getProperty("muid"));
|
||||
xml.writeAttribute("uuId", set.getProperty("uuid"));
|
||||
|
||||
tmpString = set.getProperty("num");
|
||||
if (!tmpString.isEmpty()) {
|
||||
xml.writeAttribute("num", tmpString);
|
||||
}
|
||||
|
||||
tmpString = set.getProperty("picurl");
|
||||
if (!tmpString.isEmpty()) {
|
||||
xml.writeAttribute("picURL", tmpString);
|
||||
}
|
||||
|
||||
xml.writeCharacters(set.getSet()->getShortName());
|
||||
xml.writeEndElement();
|
||||
}
|
||||
}
|
||||
|
||||
// related cards
|
||||
const QList<CardRelation *> related = info->getRelatedCards();
|
||||
for (auto i : related) {
|
||||
xml.writeStartElement("related");
|
||||
if (i->getDoesAttach()) {
|
||||
xml.writeAttribute("attach", "attach");
|
||||
}
|
||||
if (i->getIsCreateAllExclusion()) {
|
||||
xml.writeAttribute("exclude", "exclude");
|
||||
}
|
||||
|
||||
if (i->getIsVariable()) {
|
||||
if (1 == i->getDefaultCount()) {
|
||||
xml.writeAttribute("count", "x");
|
||||
} else {
|
||||
xml.writeAttribute("count", "x=" + QString::number(i->getDefaultCount()));
|
||||
}
|
||||
} else if (1 != i->getDefaultCount()) {
|
||||
xml.writeAttribute("count", QString::number(i->getDefaultCount()));
|
||||
}
|
||||
xml.writeCharacters(i->getName());
|
||||
xml.writeEndElement();
|
||||
}
|
||||
const QList<CardRelation *> reverseRelated = info->getReverseRelatedCards();
|
||||
for (auto i : reverseRelated) {
|
||||
xml.writeStartElement("reverse-related");
|
||||
if (i->getDoesAttach()) {
|
||||
xml.writeAttribute("attach", "attach");
|
||||
}
|
||||
|
||||
if (i->getIsCreateAllExclusion()) {
|
||||
xml.writeAttribute("exclude", "exclude");
|
||||
}
|
||||
|
||||
if (i->getIsVariable()) {
|
||||
if (1 == i->getDefaultCount()) {
|
||||
xml.writeAttribute("count", "x");
|
||||
} else {
|
||||
xml.writeAttribute("count", "x=" + QString::number(i->getDefaultCount()));
|
||||
}
|
||||
} else if (1 != i->getDefaultCount()) {
|
||||
xml.writeAttribute("count", QString::number(i->getDefaultCount()));
|
||||
}
|
||||
xml.writeCharacters(i->getName());
|
||||
xml.writeEndElement();
|
||||
}
|
||||
|
||||
// positioning
|
||||
xml.writeTextElement("tablerow", QString::number(info->getTableRow()));
|
||||
if (info->getCipt()) {
|
||||
xml.writeTextElement("cipt", "1");
|
||||
}
|
||||
if (info->getLandscapeOrientation()) {
|
||||
xml.writeTextElement("landscapeOrientation", "1");
|
||||
}
|
||||
if (info->getUpsideDownArt()) {
|
||||
xml.writeTextElement("upsidedown", "1");
|
||||
}
|
||||
|
||||
xml.writeEndElement(); // card
|
||||
|
||||
return xml;
|
||||
}
|
||||
|
||||
bool CockatriceXml3Parser::saveToFile(SetNameMap _sets,
|
||||
CardNameMap cards,
|
||||
const QString &fileName,
|
||||
const QString &sourceUrl,
|
||||
const QString &sourceVersion)
|
||||
{
|
||||
QFile file(fileName);
|
||||
if (!file.open(QIODevice::WriteOnly)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QXmlStreamWriter xml(&file);
|
||||
|
||||
xml.setAutoFormatting(true);
|
||||
xml.writeStartDocument();
|
||||
xml.writeStartElement(COCKATRICE_XML3_TAGNAME);
|
||||
xml.writeAttribute("version", QString::number(COCKATRICE_XML3_TAGVER));
|
||||
xml.writeAttribute("xmlns:xsi", COCKATRICE_XML_XSI_NAMESPACE);
|
||||
xml.writeAttribute("xsi:schemaLocation", COCKATRICE_XML3_SCHEMALOCATION);
|
||||
|
||||
xml.writeStartElement("info");
|
||||
xml.writeTextElement("author", QCoreApplication::applicationName() + QString(" %1").arg(VERSION_STRING));
|
||||
xml.writeTextElement("createdAt", QDateTime::currentDateTimeUtc().toString(Qt::ISODate));
|
||||
xml.writeTextElement("sourceUrl", sourceUrl);
|
||||
xml.writeTextElement("sourceVersion", sourceVersion);
|
||||
xml.writeEndElement();
|
||||
|
||||
if (_sets.count() > 0) {
|
||||
xml.writeStartElement("sets");
|
||||
for (CardSetPtr set : _sets) {
|
||||
xml << set;
|
||||
}
|
||||
xml.writeEndElement();
|
||||
}
|
||||
|
||||
if (cards.count() > 0) {
|
||||
xml.writeStartElement("cards");
|
||||
for (CardInfoPtr card : cards) {
|
||||
xml << card;
|
||||
}
|
||||
xml.writeEndElement();
|
||||
}
|
||||
|
||||
xml.writeEndElement(); // cockatrice_carddatabase
|
||||
xml.writeEndDocument();
|
||||
|
||||
return true;
|
||||
}
|
||||
441
libs/card/src/card_database/parser/cockatrice_xml_4.cpp
Normal file
441
libs/card/src/card_database/parser/cockatrice_xml_4.cpp
Normal file
|
|
@ -0,0 +1,441 @@
|
|||
#include "card/card_database/parser/cockatrice_xml_4.h"
|
||||
|
||||
#include "card/card_relation/card_relation.h"
|
||||
#include "settings/cache_settings.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
#include <QFile>
|
||||
#include <QXmlStreamReader>
|
||||
#include <version_string.h>
|
||||
|
||||
#define COCKATRICE_XML4_TAGNAME "cockatrice_carddatabase"
|
||||
#define COCKATRICE_XML4_TAGVER 4
|
||||
#define COCKATRICE_XML4_SCHEMALOCATION \
|
||||
"https://raw.githubusercontent.com/Cockatrice/Cockatrice/master/doc/carddatabase_v4/cards.xsd"
|
||||
|
||||
bool CockatriceXml4Parser::getCanParseFile(const QString &fileName, QIODevice &device)
|
||||
{
|
||||
qCInfo(CockatriceXml4Log) << "Trying to parse: " << fileName;
|
||||
|
||||
if (!fileName.endsWith(".xml", Qt::CaseInsensitive)) {
|
||||
qCInfo(CockatriceXml4Log) << "Parsing failed: wrong extension";
|
||||
return false;
|
||||
}
|
||||
|
||||
QXmlStreamReader xml(&device);
|
||||
while (!xml.atEnd()) {
|
||||
if (xml.readNext() == QXmlStreamReader::StartElement) {
|
||||
if (xml.name().toString() == COCKATRICE_XML4_TAGNAME) {
|
||||
int version = xml.attributes().value("version").toString().toInt();
|
||||
if (version == COCKATRICE_XML4_TAGVER) {
|
||||
return true;
|
||||
} else {
|
||||
qCInfo(CockatriceXml4Log) << "Parsing failed: wrong version" << version;
|
||||
return false;
|
||||
}
|
||||
|
||||
} else {
|
||||
qCInfo(CockatriceXml4Log) << "Parsing failed: wrong element tag" << xml.name();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CockatriceXml4Parser::parseFile(QIODevice &device)
|
||||
{
|
||||
QXmlStreamReader xml(&device);
|
||||
while (!xml.atEnd()) {
|
||||
if (xml.readNext() == QXmlStreamReader::StartElement) {
|
||||
while (!xml.atEnd()) {
|
||||
if (xml.readNext() == QXmlStreamReader::EndElement) {
|
||||
break;
|
||||
}
|
||||
|
||||
auto xmlName = xml.name().toString();
|
||||
if (xmlName == "sets") {
|
||||
loadSetsFromXml(xml);
|
||||
} else if (xmlName == "cards") {
|
||||
loadCardsFromXml(xml);
|
||||
} else if (!xmlName.isEmpty()) {
|
||||
qCInfo(CockatriceXml4Log) << "Unknown item" << xmlName << ", trying to continue anyway";
|
||||
xml.skipCurrentElement();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (xml.hasError()) {
|
||||
QString preamble = tr("Parse error at line %1 col %2:").arg(xml.lineNumber()).arg(xml.columnNumber());
|
||||
qCWarning(CockatriceXml4Log).noquote() << preamble << xml.errorString();
|
||||
}
|
||||
}
|
||||
|
||||
void CockatriceXml4Parser::loadSetsFromXml(QXmlStreamReader &xml)
|
||||
{
|
||||
while (!xml.atEnd()) {
|
||||
if (xml.readNext() == QXmlStreamReader::EndElement) {
|
||||
break;
|
||||
}
|
||||
|
||||
auto xmlName = xml.name().toString();
|
||||
if (xmlName == "set") {
|
||||
QString shortName, longName, setType;
|
||||
QDate releaseDate;
|
||||
short priority;
|
||||
while (!xml.atEnd()) {
|
||||
if (xml.readNext() == QXmlStreamReader::EndElement) {
|
||||
break;
|
||||
}
|
||||
xmlName = xml.name().toString();
|
||||
|
||||
if (xmlName == "name") {
|
||||
shortName = xml.readElementText(QXmlStreamReader::IncludeChildElements);
|
||||
} else if (xmlName == "longname") {
|
||||
longName = xml.readElementText(QXmlStreamReader::IncludeChildElements);
|
||||
} else if (xmlName == "settype") {
|
||||
setType = xml.readElementText(QXmlStreamReader::IncludeChildElements);
|
||||
} else if (xmlName == "releasedate") {
|
||||
releaseDate =
|
||||
QDate::fromString(xml.readElementText(QXmlStreamReader::IncludeChildElements), Qt::ISODate);
|
||||
} else if (xmlName == "priority") {
|
||||
priority = xml.readElementText(QXmlStreamReader::IncludeChildElements).toShort();
|
||||
} else if (!xmlName.isEmpty()) {
|
||||
qCInfo(CockatriceXml4Log) << "Unknown set property" << xmlName << ", trying to continue anyway";
|
||||
xml.skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
internalAddSet(shortName, longName, setType, releaseDate, static_cast<CardSet::Priority>(priority));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QVariantHash CockatriceXml4Parser::loadCardPropertiesFromXml(QXmlStreamReader &xml)
|
||||
{
|
||||
QVariantHash properties = QVariantHash();
|
||||
while (!xml.atEnd()) {
|
||||
if (xml.readNext() == QXmlStreamReader::EndElement) {
|
||||
break;
|
||||
}
|
||||
|
||||
auto xmlName = xml.name().toString();
|
||||
if (!xmlName.isEmpty()) {
|
||||
properties.insert(xmlName, xml.readElementText(QXmlStreamReader::IncludeChildElements));
|
||||
}
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
|
||||
void CockatriceXml4Parser::loadCardsFromXml(QXmlStreamReader &xml)
|
||||
{
|
||||
bool includeRebalancedCards = SettingsCache::instance().getIncludeRebalancedCards();
|
||||
while (!xml.atEnd()) {
|
||||
if (xml.readNext() == QXmlStreamReader::EndElement) {
|
||||
break;
|
||||
}
|
||||
|
||||
auto xmlName = xml.name().toString();
|
||||
|
||||
if (xmlName == "card") {
|
||||
QString name = QString("");
|
||||
QString text = QString("");
|
||||
QVariantHash properties = QVariantHash();
|
||||
QList<CardRelation *> relatedCards, reverseRelatedCards;
|
||||
auto _sets = SetToPrintingsMap();
|
||||
int tableRow = 0;
|
||||
bool cipt = false;
|
||||
bool landscapeOrientation = false;
|
||||
bool isToken = false;
|
||||
bool upsideDown = false;
|
||||
|
||||
while (!xml.atEnd()) {
|
||||
if (xml.readNext() == QXmlStreamReader::EndElement) {
|
||||
break;
|
||||
}
|
||||
|
||||
xmlName = xml.name().toString();
|
||||
|
||||
// variable - assigned properties
|
||||
if (xmlName == "name") {
|
||||
name = xml.readElementText(QXmlStreamReader::IncludeChildElements);
|
||||
} else if (xmlName == "text") {
|
||||
text = xml.readElementText(QXmlStreamReader::IncludeChildElements);
|
||||
} else if (xmlName == "token") {
|
||||
isToken = static_cast<bool>(xml.readElementText(QXmlStreamReader::IncludeChildElements).toInt());
|
||||
// generic properties
|
||||
} else if (xmlName == "prop") {
|
||||
properties = loadCardPropertiesFromXml(xml);
|
||||
// positioning info
|
||||
} else if (xmlName == "tablerow") {
|
||||
tableRow = xml.readElementText(QXmlStreamReader::IncludeChildElements).toInt();
|
||||
} else if (xmlName == "cipt") {
|
||||
cipt = (xml.readElementText(QXmlStreamReader::IncludeChildElements) == "1");
|
||||
} else if (xmlName == "landscapeOrientation") {
|
||||
landscapeOrientation = (xml.readElementText(QXmlStreamReader::IncludeChildElements) == "1");
|
||||
} else if (xmlName == "upsidedown") {
|
||||
upsideDown = (xml.readElementText(QXmlStreamReader::IncludeChildElements) == "1");
|
||||
// sets
|
||||
} else if (xmlName == "set") {
|
||||
// NOTE: attributes but be read before readElementText()
|
||||
QXmlStreamAttributes attrs = xml.attributes();
|
||||
QString setName = xml.readElementText(QXmlStreamReader::IncludeChildElements);
|
||||
auto set = internalAddSet(setName);
|
||||
if (set->getEnabled()) {
|
||||
PrintingInfo printingInfo(set);
|
||||
for (QXmlStreamAttribute attr : attrs) {
|
||||
QString attrName = attr.name().toString();
|
||||
if (attrName == "picURL")
|
||||
attrName = "picurl";
|
||||
printingInfo.setProperty(attrName, attr.value().toString());
|
||||
}
|
||||
|
||||
// This is very much a hack and not the right place to
|
||||
// put this check, as it requires a reload of Cockatrice
|
||||
// to be apply.
|
||||
//
|
||||
// However, this is also true of the `set->getEnabled()`
|
||||
// check above (which is currently bugged as well), so
|
||||
// we'll fix both at the same time.
|
||||
if (includeRebalancedCards || printingInfo.getProperty("isRebalanced") != "true") {
|
||||
_sets[setName].append(printingInfo);
|
||||
}
|
||||
}
|
||||
// related cards
|
||||
} else if (xmlName == "related" || xmlName == "reverse-related") {
|
||||
CardRelationType attachType = CardRelationType::DoesNotAttach;
|
||||
bool exclude = false;
|
||||
bool variable = false;
|
||||
bool persistent = false;
|
||||
int count = 1;
|
||||
QXmlStreamAttributes attrs = xml.attributes();
|
||||
QString cardName = xml.readElementText(QXmlStreamReader::IncludeChildElements);
|
||||
if (attrs.hasAttribute("count")) {
|
||||
if (attrs.value("count").toString().indexOf("x=") == 0) {
|
||||
variable = true;
|
||||
count = attrs.value("count").toString().remove(0, 2).toInt();
|
||||
} else if (attrs.value("count").toString().indexOf("x") == 0) {
|
||||
variable = true;
|
||||
} else {
|
||||
count = attrs.value("count").toString().toInt();
|
||||
}
|
||||
|
||||
if (count < 1) {
|
||||
count = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (attrs.hasAttribute("attach")) {
|
||||
attachType = attrs.value("attach").toString() == "transform" ? CardRelationType::TransformInto
|
||||
: CardRelationType::AttachTo;
|
||||
}
|
||||
|
||||
if (attrs.hasAttribute("exclude")) {
|
||||
exclude = true;
|
||||
}
|
||||
|
||||
if (attrs.hasAttribute("persistent")) {
|
||||
persistent = true;
|
||||
}
|
||||
|
||||
auto *relation = new CardRelation(cardName, attachType, exclude, variable, count, persistent);
|
||||
if (xmlName == "reverse-related") {
|
||||
reverseRelatedCards << relation;
|
||||
} else {
|
||||
relatedCards << relation;
|
||||
}
|
||||
} else if (!xmlName.isEmpty()) {
|
||||
qCInfo(CockatriceXml4Log) << "Unknown card property" << xmlName << ", trying to continue anyway";
|
||||
xml.skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
if (name.isEmpty()) {
|
||||
qCWarning(CockatriceXml4Log) << "Encountered card with empty name; skipping";
|
||||
continue;
|
||||
}
|
||||
|
||||
CardInfoPtr newCard =
|
||||
CardInfo::newInstance(name, text, isToken, properties, relatedCards, reverseRelatedCards, _sets, cipt,
|
||||
landscapeOrientation, tableRow, upsideDown);
|
||||
emit addCard(newCard);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static QXmlStreamWriter &operator<<(QXmlStreamWriter &xml, const CardSetPtr &set)
|
||||
{
|
||||
if (set.isNull()) {
|
||||
qCWarning(CockatriceXml4Log) << "&operator<< set is nullptr";
|
||||
return xml;
|
||||
}
|
||||
|
||||
xml.writeStartElement("set");
|
||||
xml.writeTextElement("name", set->getShortName());
|
||||
xml.writeTextElement("longname", set->getLongName());
|
||||
xml.writeTextElement("settype", set->getSetType());
|
||||
xml.writeTextElement("releasedate", set->getReleaseDate().toString(Qt::ISODate));
|
||||
xml.writeTextElement("priority", QString::number(set->getPriority()));
|
||||
xml.writeEndElement();
|
||||
|
||||
return xml;
|
||||
}
|
||||
|
||||
static QXmlStreamWriter &operator<<(QXmlStreamWriter &xml, const CardInfoPtr &info)
|
||||
{
|
||||
if (info.isNull()) {
|
||||
qCWarning(CockatriceXml4Log) << "operator<< info is nullptr";
|
||||
return xml;
|
||||
}
|
||||
|
||||
QString tmpString;
|
||||
|
||||
xml.writeStartElement("card");
|
||||
|
||||
// variable - assigned properties
|
||||
xml.writeTextElement("name", info->getName());
|
||||
xml.writeTextElement("text", info->getText());
|
||||
if (info->getIsToken()) {
|
||||
xml.writeTextElement("token", "1");
|
||||
}
|
||||
|
||||
// generic properties
|
||||
xml.writeStartElement("prop");
|
||||
for (QString propName : info->getProperties()) {
|
||||
xml.writeTextElement(propName, info->getProperty(propName));
|
||||
}
|
||||
xml.writeEndElement();
|
||||
|
||||
// sets
|
||||
for (const auto &printings : info->getSets()) {
|
||||
for (const PrintingInfo &set : printings) {
|
||||
xml.writeStartElement("set");
|
||||
for (const QString &propName : set.getProperties()) {
|
||||
xml.writeAttribute(propName, set.getProperty(propName));
|
||||
}
|
||||
|
||||
xml.writeCharacters(set.getSet()->getShortName());
|
||||
xml.writeEndElement();
|
||||
}
|
||||
}
|
||||
|
||||
// related cards
|
||||
const QList<CardRelation *> related = info->getRelatedCards();
|
||||
for (auto i : related) {
|
||||
xml.writeStartElement("related");
|
||||
if (i->getDoesAttach()) {
|
||||
xml.writeAttribute("attach", i->getAttachTypeAsString());
|
||||
}
|
||||
if (i->getIsCreateAllExclusion()) {
|
||||
xml.writeAttribute("exclude", "exclude");
|
||||
}
|
||||
if (i->getIsPersistent()) {
|
||||
xml.writeAttribute("persistent", "persistent");
|
||||
}
|
||||
if (i->getIsVariable()) {
|
||||
if (1 == i->getDefaultCount()) {
|
||||
xml.writeAttribute("count", "x");
|
||||
} else {
|
||||
xml.writeAttribute("count", "x=" + QString::number(i->getDefaultCount()));
|
||||
}
|
||||
} else if (1 != i->getDefaultCount()) {
|
||||
xml.writeAttribute("count", QString::number(i->getDefaultCount()));
|
||||
}
|
||||
xml.writeCharacters(i->getName());
|
||||
xml.writeEndElement();
|
||||
}
|
||||
const QList<CardRelation *> reverseRelated = info->getReverseRelatedCards();
|
||||
for (auto i : reverseRelated) {
|
||||
xml.writeStartElement("reverse-related");
|
||||
if (i->getDoesAttach()) {
|
||||
xml.writeAttribute("attach", i->getAttachTypeAsString());
|
||||
}
|
||||
|
||||
if (i->getIsCreateAllExclusion()) {
|
||||
xml.writeAttribute("exclude", "exclude");
|
||||
}
|
||||
|
||||
if (i->getIsPersistent()) {
|
||||
xml.writeAttribute("persistent", "persistent");
|
||||
}
|
||||
if (i->getIsVariable()) {
|
||||
if (1 == i->getDefaultCount()) {
|
||||
xml.writeAttribute("count", "x");
|
||||
} else {
|
||||
xml.writeAttribute("count", "x=" + QString::number(i->getDefaultCount()));
|
||||
}
|
||||
} else if (1 != i->getDefaultCount()) {
|
||||
xml.writeAttribute("count", QString::number(i->getDefaultCount()));
|
||||
}
|
||||
xml.writeCharacters(i->getName());
|
||||
xml.writeEndElement();
|
||||
}
|
||||
|
||||
// positioning
|
||||
xml.writeTextElement("tablerow", QString::number(info->getTableRow()));
|
||||
if (info->getCipt()) {
|
||||
xml.writeTextElement("cipt", "1");
|
||||
}
|
||||
if (info->getLandscapeOrientation()) {
|
||||
xml.writeTextElement("landscapeOrientation", "1");
|
||||
}
|
||||
if (info->getUpsideDownArt()) {
|
||||
xml.writeTextElement("upsidedown", "1");
|
||||
}
|
||||
|
||||
xml.writeEndElement(); // card
|
||||
|
||||
return xml;
|
||||
}
|
||||
|
||||
bool CockatriceXml4Parser::saveToFile(SetNameMap _sets,
|
||||
CardNameMap cards,
|
||||
const QString &fileName,
|
||||
const QString &sourceUrl,
|
||||
const QString &sourceVersion)
|
||||
{
|
||||
QFile file(fileName);
|
||||
if (!file.open(QIODevice::WriteOnly)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QXmlStreamWriter xml(&file);
|
||||
|
||||
xml.setAutoFormatting(true);
|
||||
xml.writeStartDocument();
|
||||
xml.writeStartElement(COCKATRICE_XML4_TAGNAME);
|
||||
xml.writeAttribute("version", QString::number(COCKATRICE_XML4_TAGVER));
|
||||
xml.writeAttribute("xmlns:xsi", COCKATRICE_XML_XSI_NAMESPACE);
|
||||
xml.writeAttribute("xsi:schemaLocation", COCKATRICE_XML4_SCHEMALOCATION);
|
||||
|
||||
xml.writeStartElement("info");
|
||||
xml.writeTextElement("author", QCoreApplication::applicationName() + QString(" %1").arg(VERSION_STRING));
|
||||
xml.writeTextElement("createdAt", QDateTime::currentDateTimeUtc().toString(Qt::ISODate));
|
||||
xml.writeTextElement("sourceUrl", sourceUrl);
|
||||
xml.writeTextElement("sourceVersion", sourceVersion);
|
||||
xml.writeEndElement();
|
||||
|
||||
if (_sets.count() > 0) {
|
||||
xml.writeStartElement("sets");
|
||||
for (CardSetPtr set : _sets) {
|
||||
xml << set;
|
||||
}
|
||||
xml.writeEndElement();
|
||||
}
|
||||
|
||||
if (cards.count() > 0) {
|
||||
xml.writeStartElement("cards");
|
||||
for (CardInfoPtr card : cards) {
|
||||
xml << card;
|
||||
}
|
||||
xml.writeEndElement();
|
||||
}
|
||||
|
||||
xml.writeEndElement(); // cockatrice_carddatabase
|
||||
xml.writeEndDocument();
|
||||
|
||||
return true;
|
||||
}
|
||||
208
libs/card/src/card_info.cpp
Normal file
208
libs/card/src/card_info.cpp
Normal file
|
|
@ -0,0 +1,208 @@
|
|||
#include "card/card_info.h"
|
||||
|
||||
#include "card/card_printing/printing_info.h"
|
||||
#include "card/card_relation/card_relation.h"
|
||||
#include "card/card_set/card_set.h"
|
||||
#include "card/game_specific_terms.h"
|
||||
#include "settings/cache_settings.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QRegularExpression>
|
||||
#include <QSharedPointer>
|
||||
#include <QString>
|
||||
#include <QVariant>
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
|
||||
class CardRelation;
|
||||
class CardSet;
|
||||
class CardInfo;
|
||||
|
||||
using CardInfoPtr = QSharedPointer<CardInfo>;
|
||||
|
||||
CardInfo::CardInfo(const QString &_name,
|
||||
const QString &_text,
|
||||
bool _isToken,
|
||||
QVariantHash _properties,
|
||||
const QList<CardRelation *> &_relatedCards,
|
||||
const QList<CardRelation *> &_reverseRelatedCards,
|
||||
SetToPrintingsMap _sets,
|
||||
bool _cipt,
|
||||
bool _landscapeOrientation,
|
||||
int _tableRow,
|
||||
bool _upsideDownArt)
|
||||
: name(_name), text(_text), isToken(_isToken), properties(std::move(_properties)), relatedCards(_relatedCards),
|
||||
reverseRelatedCards(_reverseRelatedCards), setsToPrintings(std::move(_sets)), cipt(_cipt),
|
||||
landscapeOrientation(_landscapeOrientation), tableRow(_tableRow), upsideDownArt(_upsideDownArt)
|
||||
{
|
||||
simpleName = CardInfo::simplifyName(name);
|
||||
|
||||
refreshCachedSetNames();
|
||||
}
|
||||
|
||||
CardInfoPtr CardInfo::newInstance(const QString &_name)
|
||||
{
|
||||
return newInstance(_name, QString(), false, QVariantHash(), QList<CardRelation *>(), QList<CardRelation *>(),
|
||||
SetToPrintingsMap(), false, false, 0, false);
|
||||
}
|
||||
|
||||
CardInfoPtr CardInfo::newInstance(const QString &_name,
|
||||
const QString &_text,
|
||||
bool _isToken,
|
||||
QVariantHash _properties,
|
||||
const QList<CardRelation *> &_relatedCards,
|
||||
const QList<CardRelation *> &_reverseRelatedCards,
|
||||
SetToPrintingsMap _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 &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);
|
||||
}
|
||||
|
||||
void CardInfo::addToSet(const CardSetPtr &_set, const PrintingInfo _info)
|
||||
{
|
||||
if (!_set->contains(smartThis)) {
|
||||
_set->append(smartThis);
|
||||
}
|
||||
if (!setsToPrintings[_set->getShortName()].contains(_info)) {
|
||||
setsToPrintings[_set->getShortName()].append(_info);
|
||||
}
|
||||
|
||||
refreshCachedSetNames();
|
||||
}
|
||||
|
||||
void CardInfo::combineLegalities(const QVariantHash &props)
|
||||
{
|
||||
QHashIterator<QString, QVariant> 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 &printings : setsToPrintings) {
|
||||
for (const auto &printing : printings) {
|
||||
if (printing.getSet()->getEnabled()) {
|
||||
setList << printing.getSet()->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');
|
||||
}
|
||||
}
|
||||
|
||||
void CardInfo::resetReverseRelatedCards2Me()
|
||||
{
|
||||
for (CardRelation *cardRelation : this->getReverseRelatedCards2Me()) {
|
||||
cardRelation->deleteLater();
|
||||
}
|
||||
reverseRelatedCardsToMe = QList<CardRelation *>();
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
75
libs/card/src/card_info_comparator.cpp
Normal file
75
libs/card/src/card_info_comparator.cpp
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
#include "card/card_info_comparator.h"
|
||||
|
||||
CardInfoComparator::CardInfoComparator(const QStringList &properties, Qt::SortOrder order)
|
||||
: m_properties(properties), m_order(order)
|
||||
{
|
||||
}
|
||||
|
||||
bool CardInfoComparator::operator()(const CardInfoPtr &a, const CardInfoPtr &b) const
|
||||
{
|
||||
// Iterate over each property in the list
|
||||
for (const QString &property : m_properties) {
|
||||
QVariant valueA = getProperty(a, property);
|
||||
QVariant valueB = getProperty(b, property);
|
||||
|
||||
// Compare the current property
|
||||
if (valueA != valueB) {
|
||||
// If values differ, perform comparison
|
||||
return compareVariants(valueA, valueB) ? (m_order == Qt::AscendingOrder) : (m_order == Qt::DescendingOrder);
|
||||
}
|
||||
}
|
||||
|
||||
// If all properties are equal, return false (indicating they are considered equal for sorting purposes)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CardInfoComparator::compareVariants(const QVariant &a, const QVariant &b) const
|
||||
{
|
||||
// Determine the type of QVariant based on Qt version
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
if (a.typeId() != b.typeId()) {
|
||||
#else
|
||||
if (a.type() != b.type()) {
|
||||
#endif
|
||||
// If they are not the same type, compare as strings
|
||||
return a.toString() < b.toString();
|
||||
}
|
||||
|
||||
// Perform type-specific comparison
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
switch (static_cast<int>(a.typeId())) {
|
||||
#else
|
||||
switch (static_cast<int>(a.type())) {
|
||||
#endif
|
||||
case static_cast<int>(QMetaType::Int):
|
||||
return a.toInt() < b.toInt();
|
||||
case static_cast<int>(QMetaType::Double):
|
||||
return a.toDouble() < b.toDouble();
|
||||
case static_cast<int>(QMetaType::QString):
|
||||
return a.toString() < b.toString();
|
||||
case static_cast<int>(QMetaType::Bool):
|
||||
return a.toBool() < b.toBool();
|
||||
default:
|
||||
// Default to comparing as strings
|
||||
return a.toString() < b.toString();
|
||||
}
|
||||
}
|
||||
|
||||
QVariant CardInfoComparator::getProperty(const CardInfoPtr &card, const QString &property) const
|
||||
{
|
||||
// Check if the property exists in the main fields of the class
|
||||
if (property == "name") {
|
||||
return card->getName();
|
||||
} else if (property == "text") {
|
||||
return card->getText();
|
||||
} else if (property == "isToken") {
|
||||
return card->getIsToken();
|
||||
}
|
||||
|
||||
// Otherwise, check if it's a custom property in the QVariantHash
|
||||
if (card->hasProperty(property)) {
|
||||
return card->getProperty(property);
|
||||
}
|
||||
|
||||
return QVariant(); // Return an invalid variant if the property does not exist
|
||||
}
|
||||
82
libs/card/src/card_printing/exact_card.cpp
Normal file
82
libs/card/src/card_printing/exact_card.cpp
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
#include "card/card_printing/exact_card.h"
|
||||
|
||||
#include "card/card_info.h"
|
||||
#include "card/card_printing/printing_info.h"
|
||||
|
||||
/**
|
||||
* Default constructor.
|
||||
* This will set the CardInfoPtr to null.
|
||||
* The printing will be the default-constructed PrintingInfo.
|
||||
*/
|
||||
ExactCard::ExactCard()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @param _card The card. Can be null.
|
||||
* @param _printing The printing. Can be empty.
|
||||
*/
|
||||
ExactCard::ExactCard(const CardInfoPtr &_card, const PrintingInfo &_printing) : card(_card), printing(_printing)
|
||||
{
|
||||
}
|
||||
|
||||
bool ExactCard::operator==(const ExactCard &other) const
|
||||
{
|
||||
return this->card == other.card && this->printing == other.printing;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method to safely get the card's name.
|
||||
* @return The name in the CardInfo, or an empty string if card is null
|
||||
*/
|
||||
QString ExactCard::getName() const
|
||||
{
|
||||
return card.isNull() ? "" : card->getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a view of the underlying cardInfoPtr.
|
||||
* @return A const reference to the CardInfo, or an empty CardInfo if card is null
|
||||
*/
|
||||
const CardInfo &ExactCard::getInfo() const
|
||||
{
|
||||
if (card.isNull()) {
|
||||
static CardInfoPtr emptyCard = CardInfo::newInstance("");
|
||||
return *emptyCard;
|
||||
}
|
||||
return *card;
|
||||
}
|
||||
|
||||
/**
|
||||
* The key used to identify this exact printing in the cache
|
||||
*/
|
||||
QString ExactCard::getPixmapCacheKey() const
|
||||
{
|
||||
QString uuid = printing.getUuid();
|
||||
QString suffix = uuid.isEmpty() ? "" : "_" + uuid;
|
||||
return QLatin1String("card_") + card->getName() + suffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the card is null or empty.
|
||||
*/
|
||||
bool ExactCard::isEmpty() const
|
||||
{
|
||||
return card.isNull() || card->getName().isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if isEmpty() is false
|
||||
*/
|
||||
ExactCard::operator bool() const
|
||||
{
|
||||
return !isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the CardInfo to emit the pixmapUpdated signal
|
||||
*/
|
||||
void ExactCard::emitPixmapUpdated() const
|
||||
{
|
||||
emit card->pixmapUpdated(printing);
|
||||
}
|
||||
15
libs/card/src/card_printing/printing_info.cpp
Normal file
15
libs/card/src/card_printing/printing_info.cpp
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
#include "card/card_printing/printing_info.h"
|
||||
|
||||
#include "card/card_set/card_set.h"
|
||||
|
||||
PrintingInfo::PrintingInfo(const CardSetPtr &_set) : set(_set)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the uuid property of the printing, or an empty string if the property isn't present
|
||||
*/
|
||||
QString PrintingInfo::getUuid() const
|
||||
{
|
||||
return properties.value("uuid").toString();
|
||||
}
|
||||
14
libs/card/src/card_relation/card_relation.cpp
Normal file
14
libs/card/src/card_relation/card_relation.cpp
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
#include "card/card_relation/card_relation.h"
|
||||
|
||||
#include "card/card_relation/card_relation_type.h"
|
||||
|
||||
CardRelation::CardRelation(const QString &_name,
|
||||
CardRelationType _attachType,
|
||||
bool _isCreateAllExclusion,
|
||||
bool _isVariableCount,
|
||||
int _defaultCount,
|
||||
bool _isPersistent)
|
||||
: name(_name), attachType(_attachType), isCreateAllExclusion(_isCreateAllExclusion),
|
||||
isVariableCount(_isVariableCount), defaultCount(_defaultCount), isPersistent(_isPersistent)
|
||||
{
|
||||
}
|
||||
81
libs/card/src/card_set/card_set.cpp
Normal file
81
libs/card/src/card_set/card_set.cpp
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
#include "card/card_set/card_set.h"
|
||||
|
||||
#include "settings/cache_settings.h"
|
||||
|
||||
const char *CardSet::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<QString> 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);
|
||||
}
|
||||
127
libs/card/src/card_set/card_set_list.cpp
Normal file
127
libs/card/src/card_set/card_set_list.cpp
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
#include "card/card_set/card_set_list.h"
|
||||
|
||||
class CardSetList::KeyCompareFunctor
|
||||
{
|
||||
public:
|
||||
inline bool operator()(const CardSetPtr &a, const CardSetPtr &b) const
|
||||
{
|
||||
if (a.isNull() || b.isNull()) {
|
||||
// qCWarning(CardInfoLog) << "SetList::KeyCompareFunctor a or b is null";
|
||||
return false;
|
||||
}
|
||||
|
||||
return a->getSortKey() < b->getSortKey();
|
||||
}
|
||||
};
|
||||
|
||||
void CardSetList::sortByKey()
|
||||
{
|
||||
std::sort(begin(), end(), KeyCompareFunctor());
|
||||
}
|
||||
|
||||
int CardSetList::getEnabledSetsNum()
|
||||
{
|
||||
int num = 0;
|
||||
for (int i = 0; i < size(); ++i) {
|
||||
CardSetPtr set = at(i);
|
||||
if (set && set->getEnabled()) {
|
||||
++num;
|
||||
}
|
||||
}
|
||||
return num;
|
||||
}
|
||||
|
||||
int CardSetList::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 CardSetList::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 CardSetList::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 CardSetList::enableAll()
|
||||
{
|
||||
for (int i = 0; i < size(); ++i) {
|
||||
CardSetPtr set = at(i);
|
||||
|
||||
if (set == nullptr) {
|
||||
// qCWarning(CardInfoLog) << "enabledAll has null";
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!set->getIsKnownIgnored()) {
|
||||
set->setIsKnown(true);
|
||||
}
|
||||
|
||||
set->setEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
void CardSetList::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 CardSetList::guessSortKeys()
|
||||
{
|
||||
defaultSort();
|
||||
for (int i = 0; i < size(); ++i) {
|
||||
CardSetPtr set = at(i);
|
||||
if (set.isNull()) {
|
||||
// qCWarning(CardInfoLog) << "guessSortKeys set is null";
|
||||
continue;
|
||||
}
|
||||
set->setSortKey(i);
|
||||
}
|
||||
}
|
||||
|
||||
void CardSetList::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
|
||||
}
|
||||
});
|
||||
}
|
||||
36
libs/deck_list/CMakeLists.txt
Normal file
36
libs/deck_list/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
set(CMAKE_AUTOMOC ON)
|
||||
set(CMAKE_AUTOUIC ON)
|
||||
set(CMAKE_AUTORCC ON)
|
||||
|
||||
set(HEADERS
|
||||
include/deck_list/abstract_deck_list_card_node.h
|
||||
include/deck_list/abstract_deck_list_node.h
|
||||
include/deck_list/deck_list.h
|
||||
include/deck_list/deck_list_card_node.h
|
||||
include/deck_list/inner_deck_list_node.h
|
||||
)
|
||||
|
||||
if (Qt6_FOUND)
|
||||
qt6_wrap_cpp(MOC_SOURCES ${HEADERS})
|
||||
elseif (Qt5_FOUND)
|
||||
qt5_wrap_cpp(MOC_SOURCES ${HEADERS})
|
||||
endif ()
|
||||
|
||||
qt_add_library(cardlib STATIC
|
||||
src/abstract_deck_list_card_node.cpp
|
||||
src/abstract_deck_list_node.cpp
|
||||
src/deck_list.cpp
|
||||
src/deck_list_card_node.cpp
|
||||
src/inner_deck_list_node.cpp
|
||||
${MOC_SOURCES}
|
||||
)
|
||||
|
||||
target_include_directories(deck_list
|
||||
PUBLIC include
|
||||
PUBLIC ${CMAKE_SOURCE_DIR}/libs/utility/include
|
||||
)
|
||||
|
||||
target_link_libraries(deck_list
|
||||
utility
|
||||
PUBLIC ${COCKATRICE_QT_MODULES}
|
||||
)
|
||||
149
libs/deck_list/include/deck_list/abstract_deck_list_card_node.h
Normal file
149
libs/deck_list/include/deck_list/abstract_deck_list_card_node.h
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
/**
|
||||
* @file abstract_deck_list_card_node.h
|
||||
* @brief Defines the AbstractDecklistCardNode base class, which adds
|
||||
* card-specific behavior on top of AbstractDecklistNode.
|
||||
*
|
||||
* This class is the intermediate abstract base between the generic
|
||||
* AbstractDecklistNode and concrete card entries such as DecklistCardNode
|
||||
* or DecklistModelCardNode.
|
||||
*/
|
||||
|
||||
#ifndef COCKATRICE_ABSTRACT_DECK_LIST_CARD_NODE_H
|
||||
#define COCKATRICE_ABSTRACT_DECK_LIST_CARD_NODE_H
|
||||
|
||||
#include "abstract_deck_list_node.h"
|
||||
|
||||
/**
|
||||
* @class AbstractDecklistCardNode
|
||||
* @ingroup DeckModels
|
||||
* @brief Abstract base class for all deck list nodes that represent
|
||||
* actual card entries.
|
||||
*
|
||||
* While AbstractDecklistNode provides the general interface for all
|
||||
* nodes in the deck tree (zones, groups, cards), this subclass refines
|
||||
* the interface to cover properties specific to *cards*:
|
||||
* - Quantity (number of copies).
|
||||
* - Name.
|
||||
* - Set code and collector number.
|
||||
* - Provider ID.
|
||||
*
|
||||
* ### Role in the hierarchy:
|
||||
* - Leaf-oriented abstract class; no children of its own.
|
||||
* - Serves as the base for concrete implementations:
|
||||
* - @c DecklistCardNode: Stores real card data in the deck tree.
|
||||
* - @c DecklistModelCardNode: Wraps a DecklistCardNode for use
|
||||
* in the Qt model layer.
|
||||
*
|
||||
* ### Responsibilities:
|
||||
* - Defines getters/setters for all card-identifying attributes.
|
||||
* - Provides comparison logic for sorting by name or number.
|
||||
* - Implements XML serialization for saving/loading deck files.
|
||||
*
|
||||
* ### Ownership:
|
||||
* - As with all nodes, owned by its parent InnerDecklistNode.
|
||||
*/
|
||||
class AbstractDecklistCardNode : public AbstractDecklistNode
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a new AbstractDecklistCardNode.
|
||||
*
|
||||
* @param _parent Optional parent node. If provided, this node
|
||||
* will be inserted into the parent’s children list.
|
||||
* @param position Index at which to insert into parent’s children.
|
||||
* If -1, the node is appended to the end.
|
||||
*/
|
||||
explicit AbstractDecklistCardNode(InnerDecklistNode *_parent = nullptr, int position = -1)
|
||||
: AbstractDecklistNode(_parent, position)
|
||||
{
|
||||
}
|
||||
|
||||
/// @return The number of copies of this card in the deck.
|
||||
virtual int getNumber() const = 0;
|
||||
|
||||
/// @param _number Set the number of copies of this card.
|
||||
virtual void setNumber(int _number) = 0;
|
||||
|
||||
/// @return The display name of this card.
|
||||
QString getName() const override = 0;
|
||||
|
||||
/// @param _name Set the display name of this card.
|
||||
virtual void setName(const QString &_name) = 0;
|
||||
|
||||
/// @return The provider identifier for this card (e.g., UUID).
|
||||
virtual QString getCardProviderId() const override = 0;
|
||||
|
||||
/// @param _cardProviderId Set the provider identifier for this card.
|
||||
virtual void setCardProviderId(const QString &_cardProviderId) = 0;
|
||||
|
||||
/// @return The abbreviated set code (e.g., "NEO").
|
||||
virtual QString getCardSetShortName() const override = 0;
|
||||
|
||||
/// @param _cardSetShortName Set the abbreviated set code.
|
||||
virtual void setCardSetShortName(const QString &_cardSetShortName) = 0;
|
||||
|
||||
/// @return The collector number of the card within its set.
|
||||
virtual QString getCardCollectorNumber() const override = 0;
|
||||
|
||||
/// @param _cardSetNumber Set the collector number.
|
||||
virtual void setCardCollectorNumber(const QString &_cardSetNumber) = 0;
|
||||
|
||||
/**
|
||||
* @brief Get the height of this node in the tree.
|
||||
*
|
||||
* For card nodes, height is always 0 because they are leaf nodes
|
||||
* and do not contain children.
|
||||
*
|
||||
* @return 0
|
||||
*/
|
||||
int height() const override
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Compare this card node against another for sorting.
|
||||
*
|
||||
* Uses the node’s current @c sortMethod to determine how to compare:
|
||||
* - ByName: Alphabetical comparison.
|
||||
* - ByNumber: Numerical comparison.
|
||||
* - Default: Falls back to implementation-defined behavior.
|
||||
*
|
||||
* @param other Another node to compare against.
|
||||
* @return true if this node should sort before @p other.
|
||||
*/
|
||||
bool compare(AbstractDecklistNode *other) const override;
|
||||
|
||||
/**
|
||||
* @brief Compare this card node to another by quantity.
|
||||
* @param other Node to compare against.
|
||||
* @return true if this node’s number < other’s number.
|
||||
*/
|
||||
bool compareNumber(AbstractDecklistNode *other) const;
|
||||
|
||||
/**
|
||||
* @brief Compare this card node to another by name.
|
||||
* @param other Node to compare against.
|
||||
* @return true if this node’s name comes before other’s name.
|
||||
*/
|
||||
bool compareName(AbstractDecklistNode *other) const;
|
||||
|
||||
/**
|
||||
* @brief Deserialize this node’s properties from XML.
|
||||
* @param xml QXmlStreamReader positioned at the element.
|
||||
* @return true if parsing succeeded.
|
||||
*
|
||||
* This supports loading deck files from Cockatrice’s XML format.
|
||||
*/
|
||||
bool readElement(QXmlStreamReader *xml) override;
|
||||
|
||||
/**
|
||||
* @brief Serialize this node’s properties to XML.
|
||||
* @param xml Writer to append this node’s XML element.
|
||||
*
|
||||
* This supports saving deck files to Cockatrice’s XML format.
|
||||
*/
|
||||
void writeElement(QXmlStreamWriter *xml) override;
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_ABSTRACT_DECK_LIST_CARD_NODE_H
|
||||
186
libs/deck_list/include/deck_list/abstract_deck_list_node.h
Normal file
186
libs/deck_list/include/deck_list/abstract_deck_list_node.h
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
/**
|
||||
* @file abstract_deck_list_node.h
|
||||
* @brief Defines the AbstractDecklistNode base class used as the foundation
|
||||
* for all nodes in the deck list tree (zones, groups, and cards).
|
||||
*
|
||||
* The deck list is modeled as a tree:
|
||||
* - The invisible root node is managed by DeckListModel.
|
||||
* - Top-level children are zones (e.g. Mainboard, Sideboard).
|
||||
* - Zones contain grouping nodes (e.g. by type, color, or mana cost).
|
||||
* - Grouping nodes contain card nodes.
|
||||
*
|
||||
* This abstract base class provides the interface and shared functionality
|
||||
* for all node types. Concrete subclasses (InnerDecklistNode,
|
||||
* DecklistCardNode, DecklistModelCardNode, etc.) implement the specifics.
|
||||
*/
|
||||
|
||||
#ifndef COCKATRICE_ABSTRACT_DECK_LIST_NODE_H
|
||||
#define COCKATRICE_ABSTRACT_DECK_LIST_NODE_H
|
||||
|
||||
#include <QtCore/QXmlStreamReader>
|
||||
#include <QtCore/QXmlStreamWriter>
|
||||
|
||||
/**
|
||||
* @enum DeckSortMethod
|
||||
* @ingroup DeckModels
|
||||
* @brief Defines the different sort strategies a node may use
|
||||
* to order its children.
|
||||
*
|
||||
* Sorting behavior is typically set by the DeckListModel when the user
|
||||
* requests sorting in the UI.
|
||||
*
|
||||
* - ByNumber: Sort numerically (often by collector number).
|
||||
* - ByName: Sort alphabetically by card name.
|
||||
* - Default: No explicit sorting; insertion order is preserved.
|
||||
*/
|
||||
enum DeckSortMethod
|
||||
{
|
||||
ByNumber, ///< Sort by numeric properties (e.g. collector number).
|
||||
ByName, ///< Sort by card name (locale-aware comparison).
|
||||
Default ///< Leave in insertion order.
|
||||
};
|
||||
|
||||
class InnerDecklistNode;
|
||||
|
||||
/**
|
||||
* @class AbstractDecklistNode
|
||||
* @ingroup DeckModels
|
||||
* @brief Base class for all nodes in the deck list tree.
|
||||
*
|
||||
* This class defines the common interface for every node in the
|
||||
* deck representation: zones, groupings, and cards.
|
||||
*
|
||||
* Responsibilities:
|
||||
* - Maintain a pointer to its parent (if any).
|
||||
* - Track the sorting method to be used for child nodes.
|
||||
* - Provide a consistent interface for retrieving basic identifying
|
||||
* properties (name, set, collector number, provider ID).
|
||||
* - Define abstract methods for XML serialization, used when saving
|
||||
* or loading deck files.
|
||||
*
|
||||
* Lifetime / Ownership:
|
||||
* - Nodes are arranged hierarchically under @c InnerDecklistNode parents.
|
||||
* - The parent takes ownership of its children; destruction cascades.
|
||||
* - The DeckListModel holds the invisible root node, which in turn
|
||||
* owns the entire hierarchy.
|
||||
*
|
||||
* Extension:
|
||||
* - @c InnerDecklistNode is the concrete subclass representing
|
||||
* "folders" in the tree (zones, groups).
|
||||
* - @c DecklistCardNode and @c DecklistModelCardNode represent
|
||||
* actual card entries.
|
||||
*/
|
||||
class AbstractDecklistNode
|
||||
{
|
||||
protected:
|
||||
/**
|
||||
* @brief Pointer to the parent node, or nullptr if this is the root.
|
||||
*
|
||||
* Ownership note: The parent is responsible for destroying this node
|
||||
* when it is removed from the tree.
|
||||
*/
|
||||
InnerDecklistNode *parent;
|
||||
|
||||
/**
|
||||
* @brief Current sorting strategy for this node's children.
|
||||
*
|
||||
* Sorting is applied recursively by the DeckListModel when
|
||||
* the view requests it.
|
||||
*/
|
||||
DeckSortMethod sortMethod;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a new AbstractDecklistNode and insert it into its parent.
|
||||
*
|
||||
* @param _parent Parent node. May be nullptr if this is the root.
|
||||
* @param position Optional index at which to insert into the parent's
|
||||
* children. If -1, the node is appended to the end.
|
||||
*
|
||||
* If a parent is provided, the constructor automatically appends
|
||||
* or inserts this node into the parent’s child list.
|
||||
*/
|
||||
explicit AbstractDecklistNode(InnerDecklistNode *_parent = nullptr, int position = -1);
|
||||
|
||||
/// Virtual destructor. Child classes must clean up their resources.
|
||||
virtual ~AbstractDecklistNode() = default;
|
||||
|
||||
/**
|
||||
* @brief Set the sort method for this node’s children.
|
||||
* @param method The sorting strategy to use.
|
||||
*
|
||||
* Subclasses may override if they need to apply additional logic.
|
||||
*/
|
||||
virtual void setSortMethod(DeckSortMethod method)
|
||||
{
|
||||
sortMethod = method;
|
||||
}
|
||||
|
||||
/**
|
||||
* @name Core identification properties
|
||||
*
|
||||
* These methods provide a standard way for the model to retrieve
|
||||
* identifying information about a node, regardless of type.
|
||||
* @{
|
||||
*/
|
||||
virtual QString getName() const = 0;
|
||||
virtual QString getCardProviderId() const = 0;
|
||||
virtual QString getCardSetShortName() const = 0;
|
||||
virtual QString getCardCollectorNumber() const = 0;
|
||||
/// @}
|
||||
|
||||
/**
|
||||
* @brief Whether this node is the "deck header" (deck metadata).
|
||||
*
|
||||
* This distinguishes special nodes that represent deck-level
|
||||
* information rather than cards or groupings.
|
||||
*/
|
||||
[[nodiscard]] virtual bool isDeckHeader() const = 0;
|
||||
|
||||
/// @return The parent node, or nullptr if this is the root.
|
||||
InnerDecklistNode *getParent() const
|
||||
{
|
||||
return parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Compute the depth of this node in the tree.
|
||||
* @return Distance from the root (root = 0, children = 1, etc.).
|
||||
*/
|
||||
int depth() const;
|
||||
|
||||
/**
|
||||
* @brief Compute the "height" of this node.
|
||||
*
|
||||
* Height is defined by subclasses; it usually represents how
|
||||
* many levels of descendants this node spans.
|
||||
*
|
||||
* For example:
|
||||
* - A card node has height 1.
|
||||
* - A group node containing cards has height 2.
|
||||
*/
|
||||
virtual int height() const = 0;
|
||||
|
||||
/**
|
||||
* @brief Compare this node against another for sorting.
|
||||
*
|
||||
* The semantics of comparison depend on the node type and the
|
||||
* current @c sortMethod.
|
||||
*
|
||||
* @param other The node to compare against.
|
||||
* @return true if this node should come before @p other.
|
||||
*/
|
||||
virtual bool compare(AbstractDecklistNode *other) const = 0;
|
||||
|
||||
/**
|
||||
* @name XML serialization
|
||||
* These methods support reading and writing decks from/to
|
||||
* Cockatrice deck XML format.
|
||||
* @{
|
||||
*/
|
||||
virtual bool readElement(QXmlStreamReader *xml) = 0;
|
||||
virtual void writeElement(QXmlStreamWriter *xml) = 0;
|
||||
/// @}
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_ABSTRACT_DECK_LIST_NODE_H
|
||||
320
libs/deck_list/include/deck_list/deck_list.h
Normal file
320
libs/deck_list/include/deck_list/deck_list.h
Normal file
|
|
@ -0,0 +1,320 @@
|
|||
/**
|
||||
* @file decklist.h
|
||||
* @brief Defines the DeckList class and supporting types for managing a full
|
||||
* deck structure including cards, zones, sideboard plans, and
|
||||
* serialization to/from multiple formats. This is a logic class which
|
||||
* does not care about Qt or user facing views.
|
||||
* See @c DeckListModel for the actual Qt Model to be used for views
|
||||
*/
|
||||
|
||||
#ifndef DECKLIST_H
|
||||
#define DECKLIST_H
|
||||
|
||||
#include "inner_deck_list_node.h"
|
||||
#include "utility/card_ref.h"
|
||||
|
||||
#include <QMap>
|
||||
#include <QVector>
|
||||
#include <QtCore/QXmlStreamReader>
|
||||
#include <QtCore/QXmlStreamWriter>
|
||||
#include <common/pb/move_card_to_zone.pb.h>
|
||||
|
||||
class AbstractDecklistNode;
|
||||
class DecklistCardNode;
|
||||
class CardDatabase;
|
||||
class QIODevice;
|
||||
class QTextStream;
|
||||
class InnerDecklistNode;
|
||||
|
||||
/**
|
||||
* @class SideboardPlan
|
||||
* @ingroup Decks
|
||||
* @brief Represents a predefined sideboarding strategy for a deck.
|
||||
*
|
||||
* Sideboard plans store a named list of card movements that should be applied
|
||||
* between the mainboard and sideboard for a specific matchup. Each movement
|
||||
* is expressed using a `MoveCard_ToZone` protobuf message.
|
||||
*
|
||||
* ### Responsibilities:
|
||||
* - Store the plan name and list of moves.
|
||||
* - Support XML serialization/deserialization.
|
||||
*
|
||||
* ### Typical usage:
|
||||
* A deck can contain multiple sideboard plans (e.g., "vs Aggro", "vs Control"),
|
||||
* each describing how to transform the main deck into its intended configuration.
|
||||
*/
|
||||
class SideboardPlan
|
||||
{
|
||||
private:
|
||||
QString name; ///< Human-readable name of this plan.
|
||||
QList<MoveCard_ToZone> moveList; ///< List of move instructions for this plan.
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a new SideboardPlan.
|
||||
* @param _name The plan name.
|
||||
* @param _moveList Initial list of card move instructions.
|
||||
*/
|
||||
explicit SideboardPlan(const QString &_name = QString(),
|
||||
const QList<MoveCard_ToZone> &_moveList = QList<MoveCard_ToZone>());
|
||||
|
||||
/**
|
||||
* @brief Read a SideboardPlan from an XML stream.
|
||||
* @param xml XML reader positioned at the plan element.
|
||||
* @return true if parsing succeeded.
|
||||
*/
|
||||
bool readElement(QXmlStreamReader *xml);
|
||||
|
||||
/**
|
||||
* @brief Write this SideboardPlan to XML.
|
||||
* @param xml Stream to append the serialized element to.
|
||||
*/
|
||||
void write(QXmlStreamWriter *xml);
|
||||
|
||||
/// @return The plan name.
|
||||
QString getName() const
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
/// @return Const reference to the move list.
|
||||
const QList<MoveCard_ToZone> &getMoveList() const
|
||||
{
|
||||
return moveList;
|
||||
}
|
||||
|
||||
/// @brief Replace the move list with a new one.
|
||||
void setMoveList(const QList<MoveCard_ToZone> &_moveList);
|
||||
};
|
||||
|
||||
/**
|
||||
* @class DeckList
|
||||
* @ingroup Decks
|
||||
* @brief Represents a complete deck, including metadata, zones, cards,
|
||||
* and sideboard plans.
|
||||
*
|
||||
* A DeckList is a QObject wrapper around an `InnerDecklistNode` tree,
|
||||
* enriched with metadata like deck name, comments, tags, banner card,
|
||||
* and multiple sideboard plans.
|
||||
*
|
||||
* ### Core responsibilities:
|
||||
* - Store and manage the root node tree (zones → groups → cards).
|
||||
* - Provide deck-level metadata (name, comments, tags, banner).
|
||||
* - Support multiple sideboard plans (meta-game strategies).
|
||||
* - Provide import/export in multiple formats:
|
||||
* - Cockatrice native XML format.
|
||||
* - Plain-text list format.
|
||||
* - Provide hashing for deck identity (deck hash).
|
||||
*
|
||||
* ### Ownership:
|
||||
* - Owns the root `InnerDecklistNode` tree.
|
||||
* - Owns `SideboardPlan` instances stored in `sideboardPlans`.
|
||||
*
|
||||
* ### Signals:
|
||||
* - @c deckHashChanged() — emitted when the deck contents change.
|
||||
* - @c deckTagsChanged() — emitted when tags are added/removed.
|
||||
*
|
||||
* ### Example workflow:
|
||||
* ```
|
||||
* DeckList deck;
|
||||
* deck.setName("Mono Red Aggro");
|
||||
* deck.addCard("Lightning Bolt", "main", -1);
|
||||
* deck.addTag("Aggro");
|
||||
* deck.saveToFile_Native(device);
|
||||
* ```
|
||||
*/
|
||||
class DeckList : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
private:
|
||||
QString name; ///< User-defined deck name.
|
||||
QString comments; ///< Free-form comments or notes.
|
||||
CardRef bannerCard; ///< Optional representative card for the deck.
|
||||
QString lastLoadedTimestamp; ///< Timestamp string of last load.
|
||||
QStringList tags; ///< User-defined tags for deck classification.
|
||||
QMap<QString, SideboardPlan *> sideboardPlans; ///< Named sideboard plans.
|
||||
InnerDecklistNode *root; ///< Root of the deck tree (zones + cards).
|
||||
|
||||
/**
|
||||
* @brief Cached deck hash, recalculated lazily.
|
||||
* An empty string indicates the cache is invalid.
|
||||
*/
|
||||
mutable QString cachedDeckHash;
|
||||
|
||||
// Helpers for traversing the tree
|
||||
static void getCardListHelper(InnerDecklistNode *node, QSet<QString> &result);
|
||||
static void getCardRefListHelper(InnerDecklistNode *item, QList<CardRef> &result);
|
||||
InnerDecklistNode *getZoneObjFromName(const QString &zoneName);
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief Map a card name to its zone.
|
||||
* Override in subclasses for format-specific logic.
|
||||
* @param cardName Card being placed.
|
||||
* @param currentZoneName Zone candidate.
|
||||
* @return Zone name to use.
|
||||
*/
|
||||
virtual QString getCardZoneFromName(const QString /*cardName*/, QString currentZoneName)
|
||||
{
|
||||
return currentZoneName;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Produce the complete display name of a card.
|
||||
* Override in subclasses to add set suffixes or annotations.
|
||||
* @param cardName Base name.
|
||||
* @return Full display name.
|
||||
*/
|
||||
virtual QString getCompleteCardName(const QString &cardName) const
|
||||
{
|
||||
return cardName;
|
||||
};
|
||||
|
||||
signals:
|
||||
/// Emitted when the deck hash changes.
|
||||
void deckHashChanged();
|
||||
/// Emitted when the deck tags are modified.
|
||||
void deckTagsChanged();
|
||||
|
||||
public slots:
|
||||
/// @name Metadata setters
|
||||
///@{
|
||||
void setName(const QString &_name = QString())
|
||||
{
|
||||
name = _name;
|
||||
}
|
||||
void setComments(const QString &_comments = QString())
|
||||
{
|
||||
comments = _comments;
|
||||
}
|
||||
void setTags(const QStringList &_tags = QStringList())
|
||||
{
|
||||
tags = _tags;
|
||||
emit deckTagsChanged();
|
||||
}
|
||||
void addTag(const QString &_tag)
|
||||
{
|
||||
tags.append(_tag);
|
||||
emit deckTagsChanged();
|
||||
}
|
||||
void clearTags()
|
||||
{
|
||||
tags.clear();
|
||||
emit deckTagsChanged();
|
||||
}
|
||||
void setBannerCard(const CardRef &_bannerCard = {})
|
||||
{
|
||||
bannerCard = _bannerCard;
|
||||
}
|
||||
void setLastLoadedTimestamp(const QString &_lastLoadedTimestamp = QString())
|
||||
{
|
||||
lastLoadedTimestamp = _lastLoadedTimestamp;
|
||||
}
|
||||
///@}
|
||||
|
||||
public:
|
||||
/// @brief Construct an empty deck.
|
||||
explicit DeckList();
|
||||
/// @brief Deep-copy constructor.
|
||||
DeckList(const DeckList &other);
|
||||
/// @brief Construct from a serialized native-format string.
|
||||
explicit DeckList(const QString &nativeString);
|
||||
~DeckList() override;
|
||||
|
||||
/// @name Metadata getters
|
||||
///@{
|
||||
QString getName() const
|
||||
{
|
||||
return name;
|
||||
}
|
||||
QString getComments() const
|
||||
{
|
||||
return comments;
|
||||
}
|
||||
QStringList getTags() const
|
||||
{
|
||||
return tags;
|
||||
}
|
||||
CardRef getBannerCard() const
|
||||
{
|
||||
return bannerCard;
|
||||
}
|
||||
QString getLastLoadedTimestamp() const
|
||||
{
|
||||
return lastLoadedTimestamp;
|
||||
}
|
||||
///@}
|
||||
|
||||
bool isBlankDeck() const
|
||||
{
|
||||
return name.isEmpty() && comments.isEmpty() && getCardList().isEmpty();
|
||||
}
|
||||
|
||||
/// @name Sideboard plans
|
||||
///@{
|
||||
QList<MoveCard_ToZone> getCurrentSideboardPlan();
|
||||
void setCurrentSideboardPlan(const QList<MoveCard_ToZone> &plan);
|
||||
const QMap<QString, SideboardPlan *> &getSideboardPlans() const
|
||||
{
|
||||
return sideboardPlans;
|
||||
}
|
||||
///@}
|
||||
|
||||
/// @name Serialization (XML)
|
||||
///@{
|
||||
bool readElement(QXmlStreamReader *xml);
|
||||
void write(QXmlStreamWriter *xml) const;
|
||||
bool loadFromXml(QXmlStreamReader *xml);
|
||||
bool loadFromString_Native(const QString &nativeString);
|
||||
QString writeToString_Native() const;
|
||||
bool loadFromFile_Native(QIODevice *device);
|
||||
bool saveToFile_Native(QIODevice *device);
|
||||
///@}
|
||||
|
||||
/// @name Serialization (Plain text)
|
||||
///@{
|
||||
bool loadFromStream_Plain(QTextStream &stream, bool preserveMetadata);
|
||||
bool loadFromFile_Plain(QIODevice *device);
|
||||
bool saveToStream_Plain(QTextStream &stream, bool prefixSideboardCards, bool slashTappedOutSplitCards);
|
||||
bool saveToFile_Plain(QIODevice *device, bool prefixSideboardCards = true, bool slashTappedOutSplitCards = false);
|
||||
QString writeToString_Plain(bool prefixSideboardCards = true, bool slashTappedOutSplitCards = false);
|
||||
///@}
|
||||
|
||||
/// @name Deck manipulation
|
||||
///@{
|
||||
void cleanList(bool preserveMetadata = false);
|
||||
bool isEmpty() const
|
||||
{
|
||||
return root->isEmpty() && name.isEmpty() && comments.isEmpty() && sideboardPlans.isEmpty();
|
||||
}
|
||||
QStringList getCardList() const;
|
||||
QList<CardRef> getCardRefList() const;
|
||||
int getSideboardSize() const;
|
||||
InnerDecklistNode *getRoot() const
|
||||
{
|
||||
return root;
|
||||
}
|
||||
DecklistCardNode *addCard(const QString &cardName,
|
||||
const QString &zoneName,
|
||||
int position,
|
||||
const QString &cardSetName = QString(),
|
||||
const QString &cardSetCollectorNumber = QString(),
|
||||
const QString &cardProviderId = QString());
|
||||
bool deleteNode(AbstractDecklistNode *node, InnerDecklistNode *rootNode = nullptr);
|
||||
///@}
|
||||
|
||||
/// @name Deck identity
|
||||
///@{
|
||||
QString getDeckHash() const;
|
||||
void refreshDeckHash();
|
||||
///@}
|
||||
|
||||
/**
|
||||
* @brief Apply a function to every card in the deck tree.
|
||||
*
|
||||
* @param func Function taking (zone node, card node).
|
||||
*/
|
||||
void forEachCard(const std::function<void(InnerDecklistNode *, DecklistCardNode *)> &func);
|
||||
};
|
||||
|
||||
#endif
|
||||
170
libs/deck_list/include/deck_list/deck_list_card_node.h
Normal file
170
libs/deck_list/include/deck_list/deck_list_card_node.h
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
/**
|
||||
* @file deck_list_card_node.h
|
||||
* @brief Defines the DecklistCardNode class, representing a single card entry
|
||||
* in the deck list tree.
|
||||
*
|
||||
* DecklistCardNode is the concrete data-bearing node that corresponds to
|
||||
* an individual card entry in a deck. It stores the card’s name, quantity,
|
||||
* set information, and provider ID. These nodes live inside an
|
||||
* InnerDecklistNode (e.g., under Mainboard → Group → Card).
|
||||
*/
|
||||
|
||||
#ifndef COCKATRICE_DECK_LIST_CARD_NODE_H
|
||||
#define COCKATRICE_DECK_LIST_CARD_NODE_H
|
||||
|
||||
#include "abstract_deck_list_card_node.h"
|
||||
#include "utility/card_ref.h"
|
||||
|
||||
/**
|
||||
* @class DecklistCardNode
|
||||
* @ingroup DeckModels
|
||||
* @brief Concrete node type representing an actual card entry in the deck.
|
||||
*
|
||||
* This class extends AbstractDecklistCardNode to hold all information
|
||||
* needed to uniquely identify a card printing within the deck.
|
||||
*
|
||||
* ### Role in the hierarchy:
|
||||
* - Child of an InnerDecklistNode (which groups cards by zone or criteria).
|
||||
* - Leaf node in the deck tree; it does not contain further children.
|
||||
*
|
||||
* ### Data stored:
|
||||
* - @c name: Card’s display name.
|
||||
* - @c number: Quantity of this card in the deck.
|
||||
* - @c cardSetShortName: Abbreviation of the set (e.g., "NEO" for Neon Dynasty).
|
||||
* - @c cardSetNumber: Collector number within the set.
|
||||
* - @c cardProviderId: External provider identifier (e.g., UUID or MTGJSON ID).
|
||||
*
|
||||
* ### Usage:
|
||||
* - Constructed directly when building a deck list from user input or file.
|
||||
* - Used by DeckListModel to present cards in Qt views.
|
||||
* - Convertible to @c CardRef for database lookups or cross-references.
|
||||
*
|
||||
* ### Ownership:
|
||||
* - Owned by its parent InnerDecklistNode.
|
||||
* - Destroyed automatically when its parent is destroyed.
|
||||
*/
|
||||
class DecklistCardNode : public AbstractDecklistCardNode
|
||||
{
|
||||
QString name; ///< Display name of the card.
|
||||
int number; ///< Quantity of this card in the deck.
|
||||
QString cardSetShortName; ///< Short set code (e.g., "NEO").
|
||||
QString cardSetNumber; ///< Collector number within the set.
|
||||
QString cardProviderId; ///< External provider identifier (e.g., UUID).
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a new DecklistCardNode.
|
||||
*
|
||||
* @param _name Display name of the card.
|
||||
* @param _number Quantity of this card (default = 1).
|
||||
* @param _parent Parent node in the tree (zone or group). May be nullptr.
|
||||
* @param position Index to insert into parent’s children. -1 = append.
|
||||
* @param _cardSetShortName Short set code (e.g., "NEO").
|
||||
* @param _cardSetNumber Collector number within the set.
|
||||
* @param _cardProviderId External provider ID (e.g., UUID).
|
||||
*
|
||||
* On construction, if a parent is provided, this node is inserted into
|
||||
* the parent’s children list automatically.
|
||||
*/
|
||||
explicit DecklistCardNode(QString _name = QString(),
|
||||
int _number = 1,
|
||||
InnerDecklistNode *_parent = nullptr,
|
||||
int position = -1,
|
||||
QString _cardSetShortName = QString(),
|
||||
QString _cardSetNumber = QString(),
|
||||
QString _cardProviderId = QString())
|
||||
: AbstractDecklistCardNode(_parent, position), name(std::move(_name)), number(_number),
|
||||
cardSetShortName(std::move(_cardSetShortName)), cardSetNumber(std::move(_cardSetNumber)),
|
||||
cardProviderId(std::move(_cardProviderId))
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Copy constructor with new parent assignment.
|
||||
* @param other Existing DecklistCardNode to copy.
|
||||
* @param _parent Parent node for the copy.
|
||||
*
|
||||
* Creates a deep copy of the card node’s properties, but attaches
|
||||
* the new instance to a different parent in the tree.
|
||||
*/
|
||||
explicit DecklistCardNode(DecklistCardNode *other, InnerDecklistNode *_parent);
|
||||
|
||||
/// @return The quantity of this card.
|
||||
int getNumber() const override
|
||||
{
|
||||
return number;
|
||||
}
|
||||
|
||||
/// @param _number Set the quantity of this card.
|
||||
void setNumber(int _number) override
|
||||
{
|
||||
number = _number;
|
||||
}
|
||||
|
||||
/// @return The display name of this card.
|
||||
QString getName() const override
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
/// @param _name Set the display name of this card.
|
||||
void setName(const QString &_name) override
|
||||
{
|
||||
name = _name;
|
||||
}
|
||||
|
||||
/// @return The provider identifier for this card.
|
||||
QString getCardProviderId() const override
|
||||
{
|
||||
return cardProviderId;
|
||||
}
|
||||
|
||||
/// @param _providerId Set the provider identifier for this card.
|
||||
void setCardProviderId(const QString &_providerId) override
|
||||
{
|
||||
cardProviderId = _providerId;
|
||||
}
|
||||
|
||||
/// @return The short set code (e.g., "NEO").
|
||||
QString getCardSetShortName() const override
|
||||
{
|
||||
return cardSetShortName;
|
||||
}
|
||||
|
||||
/// @param _cardSetShortName Set the short set code.
|
||||
void setCardSetShortName(const QString &_cardSetShortName) override
|
||||
{
|
||||
cardSetShortName = _cardSetShortName;
|
||||
}
|
||||
|
||||
/// @return The collector number of this card within its set.
|
||||
QString getCardCollectorNumber() const override
|
||||
{
|
||||
return cardSetNumber;
|
||||
}
|
||||
|
||||
/// @param _cardSetNumber Set the collector number.
|
||||
void setCardCollectorNumber(const QString &_cardSetNumber) override
|
||||
{
|
||||
cardSetNumber = _cardSetNumber;
|
||||
}
|
||||
|
||||
/// @return Always false; card nodes are not deck headers.
|
||||
[[nodiscard]] bool isDeckHeader() const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Convert this node to a CardRef.
|
||||
*
|
||||
* @return A CardRef with the card’s name and provider ID, suitable
|
||||
* for database lookups or comparison with other card sources.
|
||||
*/
|
||||
CardRef toCardRef() const
|
||||
{
|
||||
return {name, cardProviderId};
|
||||
}
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_DECK_LIST_CARD_NODE_H
|
||||
228
libs/deck_list/include/deck_list/inner_deck_list_node.h
Normal file
228
libs/deck_list/include/deck_list/inner_deck_list_node.h
Normal file
|
|
@ -0,0 +1,228 @@
|
|||
/**
|
||||
* @file inner_deck_list_node.h
|
||||
* @brief Defines the InnerDecklistNode class, which represents
|
||||
* structural nodes (zones and groups) in the deck tree.
|
||||
*
|
||||
* The deck tree consists of:
|
||||
* - A root node (invisible).
|
||||
* - Zones (Main, Sideboard, Tokens).
|
||||
* - Optional grouping nodes (e.g., by type, color, or mana cost).
|
||||
* - Card nodes as leaves.
|
||||
*
|
||||
* InnerDecklistNode implements the zone/group nodes and provides
|
||||
* storage and management of child nodes.
|
||||
*/
|
||||
|
||||
#ifndef COCKATRICE_INNER_DECK_LIST_NODE_H
|
||||
#define COCKATRICE_INNER_DECK_LIST_NODE_H
|
||||
|
||||
#include "abstract_deck_list_node.h"
|
||||
|
||||
/// Constant for the "main" deck zone name.
|
||||
#define DECK_ZONE_MAIN "main"
|
||||
/// Constant for the "sideboard" zone name.
|
||||
#define DECK_ZONE_SIDE "side"
|
||||
/// Constant for the "tokens" zone name.
|
||||
#define DECK_ZONE_TOKENS "tokens"
|
||||
|
||||
/**
|
||||
* @class InnerDecklistNode
|
||||
* @brief Represents a container node in the deck list hierarchy
|
||||
* (zones and groupings).
|
||||
*
|
||||
* Unlike DecklistCardNode, which holds leaf card data, this class
|
||||
* manages collections of child nodes, which may themselves be
|
||||
* InnerDecklistNode or DecklistCardNode objects.
|
||||
*
|
||||
* ### Role in the hierarchy:
|
||||
* - Root node (invisible): Holds zones.
|
||||
* - Zone nodes: "main", "side", "tokens".
|
||||
* - Grouping nodes: Created dynamically when grouping by type,
|
||||
* color, or mana cost.
|
||||
* - Card nodes: Always children of an InnerDecklistNode.
|
||||
*
|
||||
* ### Design notes:
|
||||
* - Inherits from AbstractDecklistNode (tree interface) and
|
||||
* QList<AbstractDecklistNode*> (storage of children).
|
||||
* This allows direct QList-style manipulation of children while
|
||||
* still presenting a polymorphic node interface.
|
||||
*
|
||||
* ### Responsibilities:
|
||||
* - Store a display name.
|
||||
* - Own and manage child nodes (insert, clear, find).
|
||||
* - Provide recursive operations such as counting cards or computing height.
|
||||
* - Implement sorting logic for reordering children.
|
||||
* - Implement XML serialization for persistence.
|
||||
*
|
||||
* ### Ownership:
|
||||
* - Owns all child nodes stored in the QList. The destructor
|
||||
* recursively deletes children.
|
||||
*/
|
||||
class InnerDecklistNode : public AbstractDecklistNode, public QList<AbstractDecklistNode *>
|
||||
{
|
||||
QString name; ///< Internal identifier for this node (zone or group name).
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a new InnerDecklistNode.
|
||||
*
|
||||
* @param _name Internal name (e.g., "main", "side", "tokens", or group label).
|
||||
* @param _parent Parent node (may be nullptr for the root).
|
||||
* @param position Optional index for insertion into parent. -1 = append.
|
||||
*/
|
||||
explicit InnerDecklistNode(QString _name = QString(), InnerDecklistNode *_parent = nullptr, int position = -1)
|
||||
: AbstractDecklistNode(_parent, position), name(std::move(_name))
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Copy constructor with parent reassignment.
|
||||
* @param other Node to copy from (deep copy of children).
|
||||
* @param _parent Parent node for the copy.
|
||||
*/
|
||||
explicit InnerDecklistNode(InnerDecklistNode *other, InnerDecklistNode *_parent = nullptr);
|
||||
|
||||
/**
|
||||
* @brief Destructor. Recursively deletes all child nodes.
|
||||
*/
|
||||
~InnerDecklistNode() override;
|
||||
|
||||
/**
|
||||
* @brief Set the sorting method for this node and all children.
|
||||
* @param method Sort method to apply recursively.
|
||||
*/
|
||||
void setSortMethod(DeckSortMethod method) override;
|
||||
|
||||
/// @return The internal name of this node.
|
||||
[[nodiscard]] QString getName() const override
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
/// @param _name Set the internal name of this node.
|
||||
void setName(const QString &_name)
|
||||
{
|
||||
name = _name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Translate an internal name into a user-visible name.
|
||||
*
|
||||
* For example, the internal string "main" is presented as
|
||||
* "Mainboard" in the UI.
|
||||
*
|
||||
* @param _name Internal identifier.
|
||||
* @return Display-friendly string.
|
||||
*/
|
||||
static QString visibleNameFromName(const QString &_name);
|
||||
|
||||
/**
|
||||
* @brief Get this node’s display-friendly name.
|
||||
* @return Human-readable name (zone/group name).
|
||||
*/
|
||||
[[nodiscard]] virtual QString getVisibleName() const;
|
||||
|
||||
/// @return Always empty for container nodes.
|
||||
[[nodiscard]] QString getCardProviderId() const override
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
/// @return Always empty for container nodes.
|
||||
[[nodiscard]] QString getCardSetShortName() const override
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
/// @return Always empty for container nodes.
|
||||
[[nodiscard]] QString getCardCollectorNumber() const override
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
/// @return Always true; InnerDecklistNode represents deck structure.
|
||||
[[nodiscard]] bool isDeckHeader() const override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Delete all children of this node, recursively.
|
||||
*/
|
||||
void clearTree();
|
||||
|
||||
/**
|
||||
* @brief Find a direct child node by name.
|
||||
* @param _name Name to match.
|
||||
* @return Pointer to child node, or nullptr if not found.
|
||||
*/
|
||||
AbstractDecklistNode *findChild(const QString &_name);
|
||||
|
||||
/**
|
||||
* @brief Find a child card node by name, provider ID, and collector number.
|
||||
*
|
||||
* Searches immediate children only.
|
||||
*
|
||||
* @param _name Card name to match.
|
||||
* @param _providerId Optional provider ID to match.
|
||||
* @param _cardNumber Optional collector number to match.
|
||||
* @return Pointer to child node if found, nullptr otherwise.
|
||||
*/
|
||||
AbstractDecklistNode *findCardChildByNameProviderIdAndNumber(const QString &_name,
|
||||
const QString &_providerId = "",
|
||||
const QString &_cardNumber = "");
|
||||
|
||||
/**
|
||||
* @brief Compute the height of this node.
|
||||
* @return Maximum depth of descendants + 1.
|
||||
*/
|
||||
int height() const override;
|
||||
|
||||
/**
|
||||
* @brief Count cards recursively under this node.
|
||||
* @param countTotalCards If true, sums up quantities of cards.
|
||||
* If false, counts unique card nodes.
|
||||
* @return Total count.
|
||||
*/
|
||||
int recursiveCount(bool countTotalCards = false) const;
|
||||
|
||||
/**
|
||||
* @brief Compare this node against another for sorting.
|
||||
*
|
||||
* Uses current @c sortMethod to determine the comparison.
|
||||
*
|
||||
* @param other Node to compare.
|
||||
* @return true if this node should sort before @p other.
|
||||
*/
|
||||
bool compare(AbstractDecklistNode *other) const override;
|
||||
|
||||
/// @copydoc compare(AbstractDecklistNode*) const
|
||||
bool compareNumber(AbstractDecklistNode *other) const;
|
||||
|
||||
/// @copydoc compare(AbstractDecklistNode*) const
|
||||
bool compareName(AbstractDecklistNode *other) const;
|
||||
|
||||
/**
|
||||
* @brief Sort this node’s children recursively.
|
||||
*
|
||||
* @param order Ascending or descending.
|
||||
* @return A QVector of (oldIndex, newIndex) pairs indicating
|
||||
* how children were reordered.
|
||||
*/
|
||||
QVector<QPair<int, int>> sort(Qt::SortOrder order = Qt::AscendingOrder);
|
||||
|
||||
/**
|
||||
* @brief Deserialize this node and its children from XML.
|
||||
* @param xml Reader positioned at this element.
|
||||
* @return true if parsing succeeded.
|
||||
*/
|
||||
bool readElement(QXmlStreamReader *xml) override;
|
||||
|
||||
/**
|
||||
* @brief Serialize this node and its children to XML.
|
||||
* @param xml Writer to append elements to.
|
||||
*/
|
||||
void writeElement(QXmlStreamWriter *xml) override;
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_INNER_DECK_LIST_NODE_H
|
||||
62
libs/deck_list/src/abstract_deck_list_card_node.cpp
Normal file
62
libs/deck_list/src/abstract_deck_list_card_node.cpp
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
#include "../include/deck_list/abstract_deck_list_card_node.h"
|
||||
|
||||
bool AbstractDecklistCardNode::compare(AbstractDecklistNode *other) const
|
||||
{
|
||||
switch (sortMethod) {
|
||||
case ByNumber:
|
||||
return compareNumber(other);
|
||||
case ByName:
|
||||
return compareName(other);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool AbstractDecklistCardNode::compareNumber(AbstractDecklistNode *other) const
|
||||
{
|
||||
auto *other2 = dynamic_cast<AbstractDecklistCardNode *>(other);
|
||||
if (other2) {
|
||||
int n1 = getNumber();
|
||||
int n2 = other2->getNumber();
|
||||
return (n1 != n2) ? (n1 > n2) : compareName(other);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool AbstractDecklistCardNode::compareName(AbstractDecklistNode *other) const
|
||||
{
|
||||
auto *other2 = dynamic_cast<AbstractDecklistCardNode *>(other);
|
||||
if (other2) {
|
||||
return (getName() > other2->getName());
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool AbstractDecklistCardNode::readElement(QXmlStreamReader *xml)
|
||||
{
|
||||
while (!xml->atEnd()) {
|
||||
xml->readNext();
|
||||
if (xml->isEndElement() && xml->name().toString() == "card")
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void AbstractDecklistCardNode::writeElement(QXmlStreamWriter *xml)
|
||||
{
|
||||
xml->writeEmptyElement("card");
|
||||
xml->writeAttribute("number", QString::number(getNumber()));
|
||||
xml->writeAttribute("name", getName());
|
||||
|
||||
if (!getCardSetShortName().isEmpty()) {
|
||||
xml->writeAttribute("setShortName", getCardSetShortName());
|
||||
}
|
||||
if (!getCardCollectorNumber().isEmpty()) {
|
||||
xml->writeAttribute("collectorNumber", getCardCollectorNumber());
|
||||
}
|
||||
if (!getCardProviderId().isEmpty()) {
|
||||
xml->writeAttribute("uuid", getCardProviderId());
|
||||
}
|
||||
}
|
||||
24
libs/deck_list/src/abstract_deck_list_node.cpp
Normal file
24
libs/deck_list/src/abstract_deck_list_node.cpp
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
#include "../include/deck_list/abstract_deck_list_node.h"
|
||||
|
||||
#include "../include/deck_list/inner_deck_list_node.h"
|
||||
|
||||
AbstractDecklistNode::AbstractDecklistNode(InnerDecklistNode *_parent, int position)
|
||||
: parent(_parent), sortMethod(Default)
|
||||
{
|
||||
if (parent) {
|
||||
if (position == -1) {
|
||||
parent->append(this);
|
||||
} else {
|
||||
parent->insert(position, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int AbstractDecklistNode::depth() const
|
||||
{
|
||||
if (parent) {
|
||||
return parent->depth() + 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
713
libs/deck_list/src/deck_list.cpp
Normal file
713
libs/deck_list/src/deck_list.cpp
Normal file
|
|
@ -0,0 +1,713 @@
|
|||
#include "../include/deck_list/deck_list.h"
|
||||
|
||||
#include "../include/deck_list/abstract_deck_list_node.h"
|
||||
#include "../include/deck_list/deck_list_card_node.h"
|
||||
#include "../include/deck_list/inner_deck_list_node.h"
|
||||
|
||||
#include <QCryptographicHash>
|
||||
#include <QDebug>
|
||||
#include <QFile>
|
||||
#include <QRegularExpression>
|
||||
#include <QTextStream>
|
||||
#include <algorithm>
|
||||
|
||||
#if QT_VERSION < 0x050600
|
||||
// qHash on QRegularExpression was added in 5.6, FIX IT
|
||||
uint qHash(const QRegularExpression &key, uint seed) noexcept
|
||||
{
|
||||
return qHash(key.pattern(), seed); // call qHash on pattern QString instead
|
||||
}
|
||||
#endif
|
||||
|
||||
SideboardPlan::SideboardPlan(const QString &_name, const QList<MoveCard_ToZone> &_moveList)
|
||||
: name(_name), moveList(_moveList)
|
||||
{
|
||||
}
|
||||
|
||||
void SideboardPlan::setMoveList(const QList<MoveCard_ToZone> &_moveList)
|
||||
{
|
||||
moveList = _moveList;
|
||||
}
|
||||
|
||||
bool SideboardPlan::readElement(QXmlStreamReader *xml)
|
||||
{
|
||||
while (!xml->atEnd()) {
|
||||
xml->readNext();
|
||||
const QString childName = xml->name().toString();
|
||||
if (xml->isStartElement()) {
|
||||
if (childName == "name")
|
||||
name = xml->readElementText();
|
||||
else if (childName == "move_card_to_zone") {
|
||||
MoveCard_ToZone m;
|
||||
while (!xml->atEnd()) {
|
||||
xml->readNext();
|
||||
const QString childName2 = xml->name().toString();
|
||||
if (xml->isStartElement()) {
|
||||
if (childName2 == "card_name")
|
||||
m.set_card_name(xml->readElementText().toStdString());
|
||||
else if (childName2 == "start_zone")
|
||||
m.set_start_zone(xml->readElementText().toStdString());
|
||||
else if (childName2 == "target_zone")
|
||||
m.set_target_zone(xml->readElementText().toStdString());
|
||||
} else if (xml->isEndElement() && (childName2 == "move_card_to_zone")) {
|
||||
moveList.append(m);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (xml->isEndElement() && (childName == "sideboard_plan"))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void SideboardPlan::write(QXmlStreamWriter *xml)
|
||||
{
|
||||
xml->writeStartElement("sideboard_plan");
|
||||
xml->writeTextElement("name", name);
|
||||
for (auto &i : moveList) {
|
||||
xml->writeStartElement("move_card_to_zone");
|
||||
xml->writeTextElement("card_name", QString::fromStdString(i.card_name()));
|
||||
xml->writeTextElement("start_zone", QString::fromStdString(i.start_zone()));
|
||||
xml->writeTextElement("target_zone", QString::fromStdString(i.target_zone()));
|
||||
xml->writeEndElement();
|
||||
}
|
||||
xml->writeEndElement();
|
||||
}
|
||||
|
||||
DeckList::DeckList()
|
||||
{
|
||||
root = new InnerDecklistNode;
|
||||
}
|
||||
|
||||
// TODO: https://qt-project.org/doc/qt-4.8/qobject.html#no-copy-constructor-or-assignment-operator
|
||||
DeckList::DeckList(const DeckList &other)
|
||||
: QObject(), name(other.name), comments(other.comments), bannerCard(other.bannerCard),
|
||||
lastLoadedTimestamp(other.lastLoadedTimestamp), tags(other.tags), cachedDeckHash(other.cachedDeckHash)
|
||||
{
|
||||
root = new InnerDecklistNode(other.getRoot());
|
||||
|
||||
QMapIterator<QString, SideboardPlan *> spIterator(other.getSideboardPlans());
|
||||
while (spIterator.hasNext()) {
|
||||
spIterator.next();
|
||||
sideboardPlans.insert(spIterator.key(), new SideboardPlan(spIterator.key(), spIterator.value()->getMoveList()));
|
||||
}
|
||||
}
|
||||
|
||||
DeckList::DeckList(const QString &nativeString)
|
||||
{
|
||||
root = new InnerDecklistNode;
|
||||
loadFromString_Native(nativeString);
|
||||
}
|
||||
|
||||
DeckList::~DeckList()
|
||||
{
|
||||
delete root;
|
||||
|
||||
QMapIterator<QString, SideboardPlan *> i(sideboardPlans);
|
||||
while (i.hasNext())
|
||||
delete i.next().value();
|
||||
}
|
||||
|
||||
QList<MoveCard_ToZone> DeckList::getCurrentSideboardPlan()
|
||||
{
|
||||
SideboardPlan *current = sideboardPlans.value(QString(), 0);
|
||||
if (!current)
|
||||
return QList<MoveCard_ToZone>();
|
||||
else
|
||||
return current->getMoveList();
|
||||
}
|
||||
|
||||
void DeckList::setCurrentSideboardPlan(const QList<MoveCard_ToZone> &plan)
|
||||
{
|
||||
SideboardPlan *current = sideboardPlans.value(QString(), 0);
|
||||
if (!current) {
|
||||
current = new SideboardPlan;
|
||||
sideboardPlans.insert(QString(), current);
|
||||
}
|
||||
|
||||
current->setMoveList(plan);
|
||||
}
|
||||
|
||||
bool DeckList::readElement(QXmlStreamReader *xml)
|
||||
{
|
||||
const QString childName = xml->name().toString();
|
||||
if (xml->isStartElement()) {
|
||||
if (childName == "lastLoadedTimestamp") {
|
||||
lastLoadedTimestamp = xml->readElementText();
|
||||
} else if (childName == "deckname") {
|
||||
name = xml->readElementText();
|
||||
} else if (childName == "comments") {
|
||||
comments = xml->readElementText();
|
||||
} else if (childName == "bannerCard") {
|
||||
QString providerId = xml->attributes().value("providerId").toString();
|
||||
QString cardName = xml->readElementText();
|
||||
bannerCard = {cardName, providerId};
|
||||
} else if (childName == "tags") {
|
||||
tags.clear(); // Clear existing tags
|
||||
while (xml->readNextStartElement()) {
|
||||
if (xml->name().toString() == "tag") {
|
||||
tags.append(xml->readElementText());
|
||||
}
|
||||
}
|
||||
} else if (childName == "zone") {
|
||||
InnerDecklistNode *newZone = getZoneObjFromName(xml->attributes().value("name").toString());
|
||||
newZone->readElement(xml);
|
||||
} else if (childName == "sideboard_plan") {
|
||||
SideboardPlan *newSideboardPlan = new SideboardPlan;
|
||||
if (newSideboardPlan->readElement(xml)) {
|
||||
sideboardPlans.insert(newSideboardPlan->getName(), newSideboardPlan);
|
||||
} else {
|
||||
delete newSideboardPlan;
|
||||
}
|
||||
}
|
||||
} else if (xml->isEndElement() && (childName == "cockatrice_deck")) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void DeckList::write(QXmlStreamWriter *xml) const
|
||||
{
|
||||
xml->writeStartElement("cockatrice_deck");
|
||||
xml->writeAttribute("version", "1");
|
||||
xml->writeTextElement("lastLoadedTimestamp", lastLoadedTimestamp);
|
||||
xml->writeTextElement("deckname", name);
|
||||
xml->writeStartElement("bannerCard");
|
||||
xml->writeAttribute("providerId", bannerCard.providerId);
|
||||
xml->writeCharacters(bannerCard.name);
|
||||
xml->writeEndElement();
|
||||
xml->writeTextElement("comments", comments);
|
||||
|
||||
// Write tags
|
||||
xml->writeStartElement("tags");
|
||||
for (const QString &tag : tags) {
|
||||
xml->writeTextElement("tag", tag);
|
||||
}
|
||||
xml->writeEndElement();
|
||||
|
||||
// Write zones
|
||||
for (int i = 0; i < root->size(); i++) {
|
||||
root->at(i)->writeElement(xml);
|
||||
}
|
||||
|
||||
// Write sideboard plans
|
||||
QMapIterator<QString, SideboardPlan *> i(sideboardPlans);
|
||||
while (i.hasNext()) {
|
||||
i.next().value()->write(xml);
|
||||
}
|
||||
|
||||
xml->writeEndElement(); // Close "cockatrice_deck"
|
||||
}
|
||||
|
||||
bool DeckList::loadFromXml(QXmlStreamReader *xml)
|
||||
{
|
||||
if (xml->error()) {
|
||||
qDebug() << "Error loading deck from xml: " << xml->errorString();
|
||||
return false;
|
||||
}
|
||||
|
||||
cleanList();
|
||||
while (!xml->atEnd()) {
|
||||
xml->readNext();
|
||||
if (xml->isStartElement()) {
|
||||
if (xml->name().toString() != "cockatrice_deck")
|
||||
return false;
|
||||
while (!xml->atEnd()) {
|
||||
xml->readNext();
|
||||
if (!readElement(xml))
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
refreshDeckHash();
|
||||
if (xml->error()) {
|
||||
qDebug() << "Error loading deck from xml: " << xml->errorString();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DeckList::loadFromString_Native(const QString &nativeString)
|
||||
{
|
||||
QXmlStreamReader xml(nativeString);
|
||||
return loadFromXml(&xml);
|
||||
}
|
||||
|
||||
QString DeckList::writeToString_Native() const
|
||||
{
|
||||
QString result;
|
||||
QXmlStreamWriter xml(&result);
|
||||
xml.writeStartDocument();
|
||||
write(&xml);
|
||||
xml.writeEndDocument();
|
||||
return result;
|
||||
}
|
||||
|
||||
bool DeckList::loadFromFile_Native(QIODevice *device)
|
||||
{
|
||||
QXmlStreamReader xml(device);
|
||||
return loadFromXml(&xml);
|
||||
}
|
||||
|
||||
bool DeckList::saveToFile_Native(QIODevice *device)
|
||||
{
|
||||
QXmlStreamWriter xml(device);
|
||||
xml.setAutoFormatting(true);
|
||||
xml.writeStartDocument();
|
||||
|
||||
write(&xml);
|
||||
|
||||
xml.writeEndDocument();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the decklist and loads in a new deck from text
|
||||
*
|
||||
* @param in The text to load
|
||||
* @param preserveMetadata If true, don't clear the existing metadata
|
||||
* @return False if the input was empty, true otherwise.
|
||||
*/
|
||||
bool DeckList::loadFromStream_Plain(QTextStream &in, bool preserveMetadata)
|
||||
{
|
||||
const QRegularExpression reCardLine(R"(^\s*[\w\[\(\{].*$)", QRegularExpression::UseUnicodePropertiesOption);
|
||||
const QRegularExpression reEmpty("^\\s*$");
|
||||
const QRegularExpression reComment(R"([\w\[\(\{].*$)", QRegularExpression::UseUnicodePropertiesOption);
|
||||
const QRegularExpression reSBMark("^\\s*sb:\\s*(.+)", QRegularExpression::CaseInsensitiveOption);
|
||||
const QRegularExpression reSBComment("^sideboard\\b.*$", QRegularExpression::CaseInsensitiveOption);
|
||||
const QRegularExpression reDeckComment("^((main)?deck(list)?|mainboard)\\b",
|
||||
QRegularExpression::CaseInsensitiveOption);
|
||||
|
||||
// Regex for advanced card parsing
|
||||
const QRegularExpression reMultiplier(R"(^[xX\(\[]*(\d+)[xX\*\)\]]* ?(.+))");
|
||||
const QRegularExpression reSplitCard(R"( ?\/\/ ?)");
|
||||
const QRegularExpression reBrace(R"( ?[\[\{][^\]\}]*[\]\}] ?)"); // not nested
|
||||
const QRegularExpression reRoundBrace(R"(^\([^\)]*\) ?)"); // () are only matched at start of string
|
||||
const QRegularExpression reDigitBrace(R"( ?\(\d*\) ?)"); // () are matched if containing digits
|
||||
const QRegularExpression reBraceDigit(
|
||||
R"( ?\([\dA-Z]+\) *\d+$)"); // () are matched if containing setcode then a number
|
||||
const QRegularExpression reDoubleFacedMarker(R"( ?\(Transform\) ?)");
|
||||
|
||||
// Regex for extracting set code and collector number with attached symbols
|
||||
const QRegularExpression reHyphenFormat(R"(\((\w{3,})\)\s+(\w{3,})-(\d+[^\w\s]*))");
|
||||
const QRegularExpression reRegularFormat(R"(\((\w{3,})\)\s+(\d+[^\w\s]*))");
|
||||
|
||||
const QHash<QRegularExpression, QString> differences{{QRegularExpression("’"), QString("'")},
|
||||
{QRegularExpression("Æ"), QString("Ae")},
|
||||
{QRegularExpression("æ"), QString("ae")},
|
||||
{QRegularExpression(" ?[|/]+ ?"), QString(" // ")}};
|
||||
|
||||
cleanList(preserveMetadata);
|
||||
|
||||
auto inputs = in.readAll().trimmed().split('\n');
|
||||
auto max_line = inputs.size();
|
||||
|
||||
// Start at the first empty line before the first card line
|
||||
auto deckStart = inputs.indexOf(reCardLine);
|
||||
if (deckStart == -1) {
|
||||
if (inputs.indexOf(reComment) == -1) {
|
||||
return false; // Input is empty
|
||||
}
|
||||
deckStart = max_line;
|
||||
} else {
|
||||
deckStart = inputs.lastIndexOf(reEmpty, deckStart);
|
||||
if (deckStart == -1) {
|
||||
deckStart = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// find sideboard position, if marks are used this won't be needed
|
||||
int sBStart = -1;
|
||||
if (inputs.indexOf(reSBMark, deckStart) == -1) {
|
||||
sBStart = inputs.indexOf(reSBComment, deckStart);
|
||||
if (sBStart == -1) {
|
||||
sBStart = inputs.indexOf(reEmpty, deckStart + 1);
|
||||
if (sBStart == -1) {
|
||||
sBStart = max_line;
|
||||
}
|
||||
auto nextCard = inputs.indexOf(reCardLine, sBStart + 1);
|
||||
if (inputs.indexOf(reEmpty, nextCard + 1) != -1) {
|
||||
sBStart = max_line;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int index = 0;
|
||||
QRegularExpressionMatch match;
|
||||
|
||||
// Parse name and comments
|
||||
while (index < deckStart) {
|
||||
const auto ¤t = inputs.at(index++);
|
||||
if (!current.contains(reEmpty)) {
|
||||
match = reComment.match(current);
|
||||
name = match.captured();
|
||||
break;
|
||||
}
|
||||
}
|
||||
while (index < deckStart) {
|
||||
const auto ¤t = inputs.at(index++);
|
||||
if (!current.contains(reEmpty)) {
|
||||
match = reComment.match(current);
|
||||
comments += match.captured() + '\n';
|
||||
}
|
||||
}
|
||||
comments.chop(1);
|
||||
|
||||
// Discard empty lines
|
||||
while (index < max_line && inputs.at(index).contains(reEmpty)) {
|
||||
++index;
|
||||
}
|
||||
|
||||
// Discard line if it starts with deck or mainboard, all cards until the sideboard starts are in the mainboard
|
||||
if (inputs.at(index).contains(reDeckComment)) {
|
||||
++index;
|
||||
}
|
||||
|
||||
// Parse decklist
|
||||
for (; index < max_line; ++index) {
|
||||
// check if line is a card
|
||||
match = reCardLine.match(inputs.at(index));
|
||||
if (!match.hasMatch())
|
||||
continue;
|
||||
|
||||
QString cardName = match.captured().simplified();
|
||||
bool sideboard = false;
|
||||
|
||||
// Sideboard detection
|
||||
if (sBStart < 0) {
|
||||
match = reSBMark.match(cardName);
|
||||
if (match.hasMatch()) {
|
||||
sideboard = true;
|
||||
cardName = match.captured(1);
|
||||
}
|
||||
} else {
|
||||
if (index == sBStart)
|
||||
continue;
|
||||
sideboard = index > sBStart;
|
||||
}
|
||||
|
||||
// Extract set code, collector number, and foil
|
||||
QString setCode;
|
||||
QString collectorNumber;
|
||||
bool isFoil = false;
|
||||
|
||||
// Check for foil status at the end of the card name
|
||||
if (cardName.endsWith("*F*", Qt::CaseInsensitive)) {
|
||||
isFoil = true;
|
||||
cardName.chop(3); // Remove the "*F*" from the card name
|
||||
}
|
||||
Q_UNUSED(isFoil);
|
||||
|
||||
// Attempt to match the hyphen-separated format (PLST-2094)
|
||||
match = reHyphenFormat.match(cardName);
|
||||
if (match.hasMatch()) {
|
||||
setCode = match.captured(2).toUpper();
|
||||
collectorNumber = match.captured(3);
|
||||
cardName = cardName.left(match.capturedStart()).trimmed();
|
||||
} else {
|
||||
// Attempt to match the regular format (PLST) 2094
|
||||
match = reRegularFormat.match(cardName);
|
||||
if (match.hasMatch()) {
|
||||
setCode = match.captured(1).toUpper();
|
||||
collectorNumber = match.captured(2);
|
||||
cardName = cardName.left(match.capturedStart()).trimmed();
|
||||
}
|
||||
}
|
||||
|
||||
// check if a specific amount is mentioned
|
||||
int amount = 1;
|
||||
match = reMultiplier.match(cardName);
|
||||
if (match.hasMatch()) {
|
||||
amount = match.captured(1).toInt();
|
||||
cardName = match.captured(2);
|
||||
}
|
||||
|
||||
// Handle advanced card types
|
||||
if (cardName.contains(reSplitCard)) {
|
||||
cardName = cardName.split(reSplitCard).join(" // ");
|
||||
}
|
||||
|
||||
if (cardName.contains(reDoubleFacedMarker)) {
|
||||
QStringList faces = cardName.split(reDoubleFacedMarker);
|
||||
cardName = faces.first().trimmed();
|
||||
}
|
||||
|
||||
// Remove unnecessary characters
|
||||
cardName.remove(reBrace);
|
||||
cardName.remove(reRoundBrace); // I'll be entirely honest here, these are split to accommodate just three cards
|
||||
cardName.remove(reDigitBrace); // from un-sets that have a word in between round braces at the end
|
||||
cardName.remove(reBraceDigit); // very specific format with the set code in () and collectors number after
|
||||
|
||||
// Normalize names
|
||||
for (auto diff = differences.constBegin(); diff != differences.constEnd(); ++diff) {
|
||||
cardName.replace(diff.key(), diff.value());
|
||||
}
|
||||
|
||||
// Resolve complete card name, this function does nothing if the name is not found
|
||||
cardName = getCompleteCardName(cardName);
|
||||
|
||||
// Determine the zone (mainboard/sideboard)
|
||||
QString zoneName = getCardZoneFromName(cardName, sideboard ? DECK_ZONE_SIDE : DECK_ZONE_MAIN);
|
||||
|
||||
// make new entry in decklist
|
||||
new DecklistCardNode(cardName, amount, getZoneObjFromName(zoneName), -1, setCode, collectorNumber);
|
||||
}
|
||||
|
||||
refreshDeckHash();
|
||||
return true;
|
||||
}
|
||||
|
||||
InnerDecklistNode *DeckList::getZoneObjFromName(const QString &zoneName)
|
||||
{
|
||||
for (int i = 0; i < root->size(); i++) {
|
||||
auto *node = dynamic_cast<InnerDecklistNode *>(root->at(i));
|
||||
if (node->getName() == zoneName) {
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
return new InnerDecklistNode(zoneName, root);
|
||||
}
|
||||
|
||||
bool DeckList::loadFromFile_Plain(QIODevice *device)
|
||||
{
|
||||
QTextStream in(device);
|
||||
return loadFromStream_Plain(in, false);
|
||||
}
|
||||
|
||||
bool DeckList::saveToStream_Plain(QTextStream &stream, bool prefixSideboardCards, bool slashTappedOutSplitCards)
|
||||
{
|
||||
auto writeToStream = [&stream, prefixSideboardCards, slashTappedOutSplitCards](const auto node, const auto card) {
|
||||
if (prefixSideboardCards && node->getName() == DECK_ZONE_SIDE) {
|
||||
stream << "SB: ";
|
||||
}
|
||||
if (!slashTappedOutSplitCards) {
|
||||
stream << QString("%1 %2\n").arg(card->getNumber()).arg(card->getName());
|
||||
} else {
|
||||
stream << QString("%1 %2\n").arg(card->getNumber()).arg(card->getName().replace("//", "/"));
|
||||
}
|
||||
};
|
||||
|
||||
forEachCard(writeToStream);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DeckList::saveToFile_Plain(QIODevice *device, bool prefixSideboardCards, bool slashTappedOutSplitCards)
|
||||
{
|
||||
QTextStream out(device);
|
||||
return saveToStream_Plain(out, prefixSideboardCards, slashTappedOutSplitCards);
|
||||
}
|
||||
|
||||
QString DeckList::writeToString_Plain(bool prefixSideboardCards, bool slashTappedOutSplitCards)
|
||||
{
|
||||
QString result;
|
||||
QTextStream out(&result);
|
||||
saveToStream_Plain(out, prefixSideboardCards, slashTappedOutSplitCards);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all cards and other data from the decklist
|
||||
*
|
||||
* @param preserveMetadata If true, only clear the cards
|
||||
*/
|
||||
void DeckList::cleanList(bool preserveMetadata)
|
||||
{
|
||||
root->clearTree();
|
||||
if (!preserveMetadata) {
|
||||
setName();
|
||||
setComments();
|
||||
setTags();
|
||||
}
|
||||
refreshDeckHash();
|
||||
}
|
||||
|
||||
void DeckList::getCardListHelper(InnerDecklistNode *item, QSet<QString> &result)
|
||||
{
|
||||
for (int i = 0; i < item->size(); ++i) {
|
||||
auto *node = dynamic_cast<DecklistCardNode *>(item->at(i));
|
||||
|
||||
if (node) {
|
||||
result.insert(node->getName());
|
||||
} else {
|
||||
getCardListHelper(dynamic_cast<InnerDecklistNode *>(item->at(i)), result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DeckList::getCardRefListHelper(InnerDecklistNode *item, QList<CardRef> &result)
|
||||
{
|
||||
for (int i = 0; i < item->size(); ++i) {
|
||||
auto *node = dynamic_cast<DecklistCardNode *>(item->at(i));
|
||||
|
||||
if (node) {
|
||||
result.append(node->toCardRef());
|
||||
} else {
|
||||
getCardRefListHelper(dynamic_cast<InnerDecklistNode *>(item->at(i)), result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QStringList DeckList::getCardList() const
|
||||
{
|
||||
QSet<QString> result;
|
||||
getCardListHelper(root, result);
|
||||
return result.values();
|
||||
}
|
||||
|
||||
QList<CardRef> DeckList::getCardRefList() const
|
||||
{
|
||||
QList<CardRef> result;
|
||||
getCardRefListHelper(root, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
int DeckList::getSideboardSize() const
|
||||
{
|
||||
int size = 0;
|
||||
for (int i = 0; i < root->size(); ++i) {
|
||||
auto *node = dynamic_cast<InnerDecklistNode *>(root->at(i));
|
||||
if (node->getName() != DECK_ZONE_SIDE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int j = 0; j < node->size(); j++) {
|
||||
auto *card = dynamic_cast<DecklistCardNode *>(node->at(j));
|
||||
size += card->getNumber();
|
||||
}
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
DecklistCardNode *DeckList::addCard(const QString &cardName,
|
||||
const QString &zoneName,
|
||||
const int position,
|
||||
const QString &cardSetName,
|
||||
const QString &cardSetCollectorNumber,
|
||||
const QString &cardProviderId)
|
||||
{
|
||||
auto *zoneNode = dynamic_cast<InnerDecklistNode *>(root->findChild(zoneName));
|
||||
if (zoneNode == nullptr) {
|
||||
zoneNode = new InnerDecklistNode(zoneName, root);
|
||||
}
|
||||
|
||||
auto *node =
|
||||
new DecklistCardNode(cardName, 1, zoneNode, position, cardSetName, cardSetCollectorNumber, cardProviderId);
|
||||
refreshDeckHash();
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
bool DeckList::deleteNode(AbstractDecklistNode *node, InnerDecklistNode *rootNode)
|
||||
{
|
||||
if (node == root) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool updateHash = false;
|
||||
if (rootNode == nullptr) {
|
||||
rootNode = root;
|
||||
updateHash = true;
|
||||
}
|
||||
|
||||
int index = rootNode->indexOf(node);
|
||||
if (index != -1) {
|
||||
delete rootNode->takeAt(index);
|
||||
|
||||
if (rootNode->empty()) {
|
||||
deleteNode(rootNode, rootNode->getParent());
|
||||
}
|
||||
|
||||
if (updateHash) {
|
||||
refreshDeckHash();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
for (int i = 0; i < rootNode->size(); i++) {
|
||||
auto *inner = dynamic_cast<InnerDecklistNode *>(rootNode->at(i));
|
||||
if (inner) {
|
||||
if (deleteNode(node, inner)) {
|
||||
if (updateHash) {
|
||||
refreshDeckHash();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static QString computeDeckHash(const InnerDecklistNode *root)
|
||||
{
|
||||
QStringList cardList;
|
||||
QSet<QString> hashZones, optionalZones;
|
||||
|
||||
hashZones << DECK_ZONE_MAIN << DECK_ZONE_SIDE; // Zones in deck to be included in hashing process
|
||||
optionalZones << DECK_ZONE_TOKENS; // Optional zones in deck not included in hashing process
|
||||
|
||||
for (int i = 0; i < root->size(); i++) {
|
||||
auto *node = dynamic_cast<InnerDecklistNode *>(root->at(i));
|
||||
for (int j = 0; j < node->size(); j++) {
|
||||
if (hashZones.contains(node->getName())) // Mainboard or Sideboard
|
||||
{
|
||||
auto *card = dynamic_cast<DecklistCardNode *>(node->at(j));
|
||||
for (int k = 0; k < card->getNumber(); ++k) {
|
||||
cardList.append((node->getName() == DECK_ZONE_SIDE ? "SB:" : "") + card->getName().toLower());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
cardList.sort();
|
||||
QByteArray deckHashArray = QCryptographicHash::hash(cardList.join(";").toUtf8(), QCryptographicHash::Sha1);
|
||||
quint64 number = (((quint64)(unsigned char)deckHashArray[0]) << 32) +
|
||||
(((quint64)(unsigned char)deckHashArray[1]) << 24) +
|
||||
(((quint64)(unsigned char)deckHashArray[2] << 16)) +
|
||||
(((quint64)(unsigned char)deckHashArray[3]) << 8) + (quint64)(unsigned char)deckHashArray[4];
|
||||
return QString::number(number, 32).rightJustified(8, '0');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the deck hash.
|
||||
* The hash is computed on the first call to this method, and is cached until the decklist is modified.
|
||||
*
|
||||
* @return The deck hash
|
||||
*/
|
||||
QString DeckList::getDeckHash() const
|
||||
{
|
||||
if (!cachedDeckHash.isEmpty()) {
|
||||
return cachedDeckHash;
|
||||
}
|
||||
|
||||
cachedDeckHash = computeDeckHash(root);
|
||||
return cachedDeckHash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidates the cached deckHash and emits the deckHashChanged signal.
|
||||
*/
|
||||
void DeckList::refreshDeckHash()
|
||||
{
|
||||
cachedDeckHash = QString();
|
||||
emit deckHashChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls a given function on each card in the deck.
|
||||
*/
|
||||
void DeckList::forEachCard(const std::function<void(InnerDecklistNode *, DecklistCardNode *)> &func)
|
||||
{
|
||||
// Support for this is only possible if the internal structure
|
||||
// doesn't get more complicated.
|
||||
for (int i = 0; i < root->size(); i++) {
|
||||
InnerDecklistNode *node = dynamic_cast<InnerDecklistNode *>(root->at(i));
|
||||
for (int j = 0; j < node->size(); j++) {
|
||||
DecklistCardNode *card = dynamic_cast<DecklistCardNode *>(node->at(j));
|
||||
func(node, card);
|
||||
}
|
||||
}
|
||||
}
|
||||
8
libs/deck_list/src/deck_list_card_node.cpp
Normal file
8
libs/deck_list/src/deck_list_card_node.cpp
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
#include "../include/deck_list/deck_list_card_node.h"
|
||||
|
||||
DecklistCardNode::DecklistCardNode(DecklistCardNode *other, InnerDecklistNode *_parent)
|
||||
: AbstractDecklistCardNode(_parent), name(other->getName()), number(other->getNumber()),
|
||||
cardSetShortName(other->getCardSetShortName()), cardSetNumber(other->getCardCollectorNumber()),
|
||||
cardProviderId(other->getCardProviderId())
|
||||
{
|
||||
}
|
||||
199
libs/deck_list/src/inner_deck_list_node.cpp
Normal file
199
libs/deck_list/src/inner_deck_list_node.cpp
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
#include "../include/deck_list/inner_deck_list_node.h"
|
||||
|
||||
#include "../include/deck_list/deck_list_card_node.h"
|
||||
|
||||
InnerDecklistNode::InnerDecklistNode(InnerDecklistNode *other, InnerDecklistNode *_parent)
|
||||
: AbstractDecklistNode(_parent), name(other->getName())
|
||||
{
|
||||
for (int i = 0; i < other->size(); ++i) {
|
||||
auto *inner = dynamic_cast<InnerDecklistNode *>(other->at(i));
|
||||
if (inner) {
|
||||
new InnerDecklistNode(inner, this);
|
||||
} else {
|
||||
new DecklistCardNode(dynamic_cast<DecklistCardNode *>(other->at(i)), this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
InnerDecklistNode::~InnerDecklistNode()
|
||||
{
|
||||
clearTree();
|
||||
}
|
||||
|
||||
QString InnerDecklistNode::visibleNameFromName(const QString &_name)
|
||||
{
|
||||
if (_name == DECK_ZONE_MAIN) {
|
||||
return QObject::tr("Maindeck");
|
||||
} else if (_name == DECK_ZONE_SIDE) {
|
||||
return QObject::tr("Sideboard");
|
||||
} else if (_name == DECK_ZONE_TOKENS) {
|
||||
return QObject::tr("Tokens");
|
||||
} else {
|
||||
return _name;
|
||||
}
|
||||
}
|
||||
|
||||
void InnerDecklistNode::setSortMethod(DeckSortMethod method)
|
||||
{
|
||||
sortMethod = method;
|
||||
for (int i = 0; i < size(); i++) {
|
||||
at(i)->setSortMethod(method);
|
||||
}
|
||||
}
|
||||
|
||||
QString InnerDecklistNode::getVisibleName() const
|
||||
{
|
||||
return visibleNameFromName(name);
|
||||
}
|
||||
|
||||
void InnerDecklistNode::clearTree()
|
||||
{
|
||||
for (int i = 0; i < size(); i++)
|
||||
delete at(i);
|
||||
clear();
|
||||
}
|
||||
|
||||
AbstractDecklistNode *InnerDecklistNode::findChild(const QString &_name)
|
||||
{
|
||||
for (int i = 0; i < size(); i++) {
|
||||
if (at(i)->getName() == _name) {
|
||||
return at(i);
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
AbstractDecklistNode *InnerDecklistNode::findCardChildByNameProviderIdAndNumber(const QString &_name,
|
||||
const QString &_providerId,
|
||||
const QString &_cardNumber)
|
||||
{
|
||||
for (const auto &i : *this) {
|
||||
if (!i || i->getName() != _name) {
|
||||
continue;
|
||||
}
|
||||
if (_cardNumber != "" && i->getCardCollectorNumber() != _cardNumber) {
|
||||
continue;
|
||||
}
|
||||
if (_providerId != "" && i->getCardProviderId() != _providerId) {
|
||||
continue;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int InnerDecklistNode::height() const
|
||||
{
|
||||
return at(0)->height() + 1;
|
||||
}
|
||||
|
||||
int InnerDecklistNode::recursiveCount(bool countTotalCards) const
|
||||
{
|
||||
int result = 0;
|
||||
for (int i = 0; i < size(); i++) {
|
||||
auto *node = dynamic_cast<InnerDecklistNode *>(at(i));
|
||||
|
||||
if (node) {
|
||||
result += node->recursiveCount(countTotalCards);
|
||||
} else if (countTotalCards) {
|
||||
result += dynamic_cast<AbstractDecklistCardNode *>(at(i))->getNumber();
|
||||
} else {
|
||||
result++;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool InnerDecklistNode::compare(AbstractDecklistNode *other) const
|
||||
{
|
||||
switch (sortMethod) {
|
||||
case ByNumber:
|
||||
return compareNumber(other);
|
||||
case ByName:
|
||||
return compareName(other);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool InnerDecklistNode::compareNumber(AbstractDecklistNode *other) const
|
||||
{
|
||||
auto *other2 = dynamic_cast<InnerDecklistNode *>(other);
|
||||
if (other2) {
|
||||
int n1 = recursiveCount(true);
|
||||
int n2 = other2->recursiveCount(true);
|
||||
return (n1 != n2) ? (n1 > n2) : compareName(other);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool InnerDecklistNode::compareName(AbstractDecklistNode *other) const
|
||||
{
|
||||
auto *other2 = dynamic_cast<InnerDecklistNode *>(other);
|
||||
if (other2) {
|
||||
return (getName() > other2->getName());
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool InnerDecklistNode::readElement(QXmlStreamReader *xml)
|
||||
{
|
||||
while (!xml->atEnd()) {
|
||||
xml->readNext();
|
||||
const QString childName = xml->name().toString();
|
||||
if (xml->isStartElement()) {
|
||||
if (childName == "zone") {
|
||||
InnerDecklistNode *newZone = new InnerDecklistNode(xml->attributes().value("name").toString(), this);
|
||||
newZone->readElement(xml);
|
||||
} else if (childName == "card") {
|
||||
DecklistCardNode *newCard = new DecklistCardNode(
|
||||
xml->attributes().value("name").toString(), xml->attributes().value("number").toString().toInt(),
|
||||
this, -1, xml->attributes().value("setShortName").toString(),
|
||||
xml->attributes().value("collectorNumber").toString(), xml->attributes().value("uuid").toString());
|
||||
newCard->readElement(xml);
|
||||
}
|
||||
} else if (xml->isEndElement() && (childName == "zone"))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void InnerDecklistNode::writeElement(QXmlStreamWriter *xml)
|
||||
{
|
||||
xml->writeStartElement("zone");
|
||||
xml->writeAttribute("name", name);
|
||||
for (int i = 0; i < size(); i++)
|
||||
at(i)->writeElement(xml);
|
||||
xml->writeEndElement(); // zone
|
||||
}
|
||||
|
||||
QVector<QPair<int, int>> InnerDecklistNode::sort(Qt::SortOrder order)
|
||||
{
|
||||
QVector<QPair<int, int>> result(size());
|
||||
|
||||
// Initialize temporary list with contents of current list
|
||||
QVector<QPair<int, AbstractDecklistNode *>> tempList(size());
|
||||
for (int i = size() - 1; i >= 0; --i) {
|
||||
tempList[i].first = i;
|
||||
tempList[i].second = at(i);
|
||||
}
|
||||
|
||||
// Sort temporary list
|
||||
auto cmp = [order](const auto &a, const auto &b) {
|
||||
return (order == Qt::AscendingOrder) ? (b.second->compare(a.second)) : (a.second->compare(b.second));
|
||||
};
|
||||
|
||||
std::sort(tempList.begin(), tempList.end(), cmp);
|
||||
|
||||
// Map old indexes to new indexes and
|
||||
// copy temporary list to the current one
|
||||
for (int i = size() - 1; i >= 0; --i) {
|
||||
result[i].first = tempList[i].first;
|
||||
result[i].second = i;
|
||||
replace(i, tempList[i].second);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
199
libs/pb/CMakeLists.txt
Normal file
199
libs/pb/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
# CMakeLists for common directory
|
||||
#
|
||||
# provides the protobuf interfaces
|
||||
|
||||
set(PROTO_FILES
|
||||
admin_commands.proto
|
||||
card_attributes.proto
|
||||
color.proto
|
||||
command_attach_card.proto
|
||||
command_change_zone_properties.proto
|
||||
command_concede.proto
|
||||
command_create_arrow.proto
|
||||
command_create_counter.proto
|
||||
command_create_token.proto
|
||||
command_deck_del.proto
|
||||
command_deck_del_dir.proto
|
||||
command_deck_download.proto
|
||||
command_deck_list.proto
|
||||
command_deck_new_dir.proto
|
||||
command_deck_select.proto
|
||||
command_deck_upload.proto
|
||||
command_del_counter.proto
|
||||
command_delete_arrow.proto
|
||||
command_draw_cards.proto
|
||||
command_dump_zone.proto
|
||||
command_flip_card.proto
|
||||
command_game_say.proto
|
||||
command_inc_card_counter.proto
|
||||
command_inc_counter.proto
|
||||
command_kick_from_game.proto
|
||||
command_leave_game.proto
|
||||
command_move_card.proto
|
||||
command_mulligan.proto
|
||||
command_next_turn.proto
|
||||
command_ready_start.proto
|
||||
command_replay_delete_match.proto
|
||||
command_replay_download.proto
|
||||
command_replay_get_code.proto
|
||||
command_replay_list.proto
|
||||
command_replay_modify_match.proto
|
||||
command_replay_submit_code.proto
|
||||
command_reveal_cards.proto
|
||||
command_reverse_turn.proto
|
||||
command_roll_die.proto
|
||||
command_set_active_phase.proto
|
||||
command_set_card_attr.proto
|
||||
command_set_card_counter.proto
|
||||
command_set_counter.proto
|
||||
command_set_sideboard_lock.proto
|
||||
command_set_sideboard_plan.proto
|
||||
command_shuffle.proto
|
||||
command_undo_draw.proto
|
||||
commands.proto
|
||||
context_concede.proto
|
||||
context_connection_state_changed.proto
|
||||
context_deck_select.proto
|
||||
context_move_card.proto
|
||||
context_mulligan.proto
|
||||
context_ping_changed.proto
|
||||
context_ready_start.proto
|
||||
context_set_sideboard_lock.proto
|
||||
context_undo_draw.proto
|
||||
event_add_to_list.proto
|
||||
event_attach_card.proto
|
||||
event_change_zone_properties.proto
|
||||
event_connection_closed.proto
|
||||
event_create_arrow.proto
|
||||
event_create_counter.proto
|
||||
event_create_token.proto
|
||||
event_del_counter.proto
|
||||
event_delete_arrow.proto
|
||||
event_destroy_card.proto
|
||||
event_draw_cards.proto
|
||||
event_dump_zone.proto
|
||||
event_flip_card.proto
|
||||
event_game_closed.proto
|
||||
event_game_host_changed.proto
|
||||
event_game_joined.proto
|
||||
event_game_say.proto
|
||||
event_game_state_changed.proto
|
||||
event_game_state_changed.proto
|
||||
event_join.proto
|
||||
event_join_room.proto
|
||||
event_kicked.proto
|
||||
event_leave.proto
|
||||
event_leave_room.proto
|
||||
event_list_games.proto
|
||||
event_list_rooms.proto
|
||||
event_move_card.proto
|
||||
event_notify_user.proto
|
||||
event_player_properties_changed.proto
|
||||
event_remove_from_list.proto
|
||||
event_remove_messages.proto
|
||||
event_replay_added.proto
|
||||
event_reveal_cards.proto
|
||||
event_reverse_turn.proto
|
||||
event_roll_die.proto
|
||||
event_room_say.proto
|
||||
event_server_complete_list.proto
|
||||
event_server_identification.proto
|
||||
event_server_message.proto
|
||||
event_server_shutdown.proto
|
||||
event_set_active_phase.proto
|
||||
event_set_active_player.proto
|
||||
event_set_card_attr.proto
|
||||
event_set_card_counter.proto
|
||||
event_set_counter.proto
|
||||
event_shuffle.proto
|
||||
event_user_joined.proto
|
||||
event_user_left.proto
|
||||
event_user_message.proto
|
||||
game_commands.proto
|
||||
game_event.proto
|
||||
game_event_container.proto
|
||||
game_event_context.proto
|
||||
game_replay.proto
|
||||
isl_message.proto
|
||||
moderator_commands.proto
|
||||
move_card_to_zone.proto
|
||||
response.proto
|
||||
response_activate.proto
|
||||
response_adjust_mod.proto
|
||||
response_ban_history.proto
|
||||
response_deck_download.proto
|
||||
response_deck_list.proto
|
||||
response_deck_upload.proto
|
||||
response_dump_zone.proto
|
||||
response_forgotpasswordrequest.proto
|
||||
response_get_admin_notes.proto
|
||||
response_get_games_of_user.proto
|
||||
response_get_user_info.proto
|
||||
response_join_room.proto
|
||||
response_list_users.proto
|
||||
response_login.proto
|
||||
response_password_salt.proto
|
||||
response_register.proto
|
||||
response_replay_download.proto
|
||||
response_replay_get_code.proto
|
||||
response_replay_list.proto
|
||||
response_viewlog_history.proto
|
||||
response_warn_history.proto
|
||||
response_warn_list.proto
|
||||
room_commands.proto
|
||||
room_event.proto
|
||||
server_message.proto
|
||||
serverinfo_arrow.proto
|
||||
serverinfo_ban.proto
|
||||
serverinfo_card.proto
|
||||
serverinfo_cardcounter.proto
|
||||
serverinfo_chat_message.proto
|
||||
serverinfo_counter.proto
|
||||
serverinfo_deckstorage.proto
|
||||
serverinfo_game.proto
|
||||
serverinfo_gametype.proto
|
||||
serverinfo_player.proto
|
||||
serverinfo_playerping.proto
|
||||
serverinfo_playerproperties.proto
|
||||
serverinfo_replay.proto
|
||||
serverinfo_replay_match.proto
|
||||
serverinfo_room.proto
|
||||
serverinfo_user.proto
|
||||
serverinfo_warning.proto
|
||||
serverinfo_zone.proto
|
||||
session_commands.proto
|
||||
session_event.proto
|
||||
)
|
||||
|
||||
if(${Protobuf_VERSION} VERSION_LESS "3.21.0.0")
|
||||
message(STATUS "Using Protobuf Legacy Mode")
|
||||
include_directories(${PROTOBUF_INCLUDE_DIRS})
|
||||
include_directories(${CMAKE_CURRENT_BINARY_DIR})
|
||||
protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS ${PROTO_FILES})
|
||||
|
||||
add_library(cockatrice_protocol ${PROTO_SRCS} ${PROTO_HDRS})
|
||||
set(cockatrice_protocol_LIBS ${PROTOBUF_LIBRARIES})
|
||||
if(UNIX)
|
||||
set(cockatrice_protocol_LIBS ${cockatrice_protocol_LIBS} -lpthread)
|
||||
endif(UNIX)
|
||||
target_link_libraries(cockatrice_protocol ${cockatrice_protocol_LIBS})
|
||||
|
||||
# ubuntu uses an outdated package for protobuf, 3.1.0 is required
|
||||
if(${Protobuf_VERSION} VERSION_LESS "3.1.0")
|
||||
# remove unused parameter and misleading indentation warnings when compiling to avoid errors
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Wno-unused-parameter -Wno-misleading-indentation")
|
||||
message(WARNING "Older protobuf version found (${Protobuf_VERSION} < 3.1.0), "
|
||||
"disabled the warnings 'unused-parameter' and 'misleading-indentation' for protobuf generated code "
|
||||
"to avoid compilation errors."
|
||||
)
|
||||
endif()
|
||||
else()
|
||||
add_library(cockatrice_protocol ${PROTO_FILES})
|
||||
target_link_libraries(cockatrice_protocol PUBLIC protobuf::libprotobuf)
|
||||
set(PROTO_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}")
|
||||
target_include_directories(cockatrice_protocol PUBLIC "${PROTOBUF_INCLUDE_DIRS}")
|
||||
|
||||
protobuf_generate(
|
||||
TARGET cockatrice_protocol IMPORT_DIRS "${CMAKE_CURRENT_LIST_DIR}" PROTOC_OUT_DIR "${PROTO_BINARY_DIR}"
|
||||
)
|
||||
endif()
|
||||
39
libs/pb/admin_commands.proto
Normal file
39
libs/pb/admin_commands.proto
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
syntax = "proto2";
|
||||
message AdminCommand {
|
||||
enum AdminCommandType {
|
||||
UPDATE_SERVER_MESSAGE = 1000;
|
||||
SHUTDOWN_SERVER = 1001;
|
||||
RELOAD_CONFIG = 1002;
|
||||
ADJUST_MOD = 1003;
|
||||
}
|
||||
extensions 100 to max;
|
||||
}
|
||||
|
||||
message Command_UpdateServerMessage {
|
||||
extend AdminCommand {
|
||||
optional Command_UpdateServerMessage ext = 1000;
|
||||
}
|
||||
}
|
||||
|
||||
message Command_ShutdownServer {
|
||||
extend AdminCommand {
|
||||
optional Command_ShutdownServer ext = 1001;
|
||||
}
|
||||
optional string reason = 1;
|
||||
optional uint32 minutes = 2;
|
||||
}
|
||||
|
||||
message Command_ReloadConfig {
|
||||
extend AdminCommand {
|
||||
optional Command_ReloadConfig ext = 1002;
|
||||
}
|
||||
}
|
||||
|
||||
message Command_AdjustMod {
|
||||
extend AdminCommand {
|
||||
optional Command_AdjustMod ext = 1003;
|
||||
}
|
||||
required string user_name = 1;
|
||||
optional bool should_be_mod = 2;
|
||||
optional bool should_be_judge = 3;
|
||||
}
|
||||
10
libs/pb/card_attributes.proto
Normal file
10
libs/pb/card_attributes.proto
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
syntax = "proto2";
|
||||
enum CardAttribute {
|
||||
AttrTapped = 1;
|
||||
AttrAttacking = 2;
|
||||
AttrFaceDown = 3;
|
||||
AttrColor = 4;
|
||||
AttrPT = 5;
|
||||
AttrAnnotation = 6;
|
||||
AttrDoesntUntap = 7;
|
||||
}
|
||||
16
libs/pb/color.proto
Normal file
16
libs/pb/color.proto
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
syntax = "proto2";
|
||||
|
||||
// Container for a 4 component color code
|
||||
message color {
|
||||
// the red component of the color, limited to 256 values
|
||||
optional uint32 r = 1;
|
||||
|
||||
// the green component of the color, limited to 256 values
|
||||
optional uint32 g = 2;
|
||||
|
||||
// the blue component of the color, limited to 256 values
|
||||
optional uint32 b = 3;
|
||||
|
||||
// the opacity component of the color, limited to 256 values
|
||||
optional uint32 a = 4;
|
||||
}
|
||||
12
libs/pb/command_attach_card.proto
Normal file
12
libs/pb/command_attach_card.proto
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
syntax = "proto2";
|
||||
import "game_commands.proto";
|
||||
message Command_AttachCard {
|
||||
extend GameCommand {
|
||||
optional Command_AttachCard ext = 1009;
|
||||
}
|
||||
optional string start_zone = 1;
|
||||
optional sint32 card_id = 2 [default = -1];
|
||||
optional sint32 target_player_id = 3 [default = -1];
|
||||
optional string target_zone = 4;
|
||||
optional sint32 target_card_id = 5 [default = -1];
|
||||
}
|
||||
14
libs/pb/command_change_zone_properties.proto
Normal file
14
libs/pb/command_change_zone_properties.proto
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
syntax = "proto2";
|
||||
import "game_commands.proto";
|
||||
|
||||
message Command_ChangeZoneProperties {
|
||||
extend GameCommand {
|
||||
optional Command_ChangeZoneProperties ext = 1031;
|
||||
}
|
||||
optional string zone_name = 1;
|
||||
|
||||
// Reveal top card to all players.
|
||||
optional bool always_reveal_top_card = 10;
|
||||
// reveal top card to the owner.
|
||||
optional bool always_look_at_top_card = 11;
|
||||
}
|
||||
13
libs/pb/command_concede.proto
Normal file
13
libs/pb/command_concede.proto
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
syntax = "proto2";
|
||||
import "game_commands.proto";
|
||||
message Command_Concede {
|
||||
extend GameCommand {
|
||||
optional Command_Concede ext = 1017;
|
||||
}
|
||||
}
|
||||
|
||||
message Command_Unconcede {
|
||||
extend GameCommand {
|
||||
optional Command_Unconcede ext = 1032;
|
||||
}
|
||||
}
|
||||
30
libs/pb/command_create_arrow.proto
Normal file
30
libs/pb/command_create_arrow.proto
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
syntax = "proto2";
|
||||
import "game_commands.proto";
|
||||
import "color.proto";
|
||||
|
||||
// Command to draw an arrow from cards to either other cards or a player
|
||||
message Command_CreateArrow {
|
||||
extend GameCommand {
|
||||
optional Command_CreateArrow ext = 1011;
|
||||
}
|
||||
// the player that has the card the arrow is drawn from
|
||||
optional sint32 start_player_id = 1 [default = -1];
|
||||
|
||||
// the zone that the card the arrow is drawn from is in
|
||||
optional string start_zone = 2;
|
||||
|
||||
// the id of the card that the arrow is drawn from
|
||||
optional sint32 start_card_id = 3 [default = -1];
|
||||
|
||||
// the player that has the card the arrow is drawn to, or that the arrow is drawn to if not a card
|
||||
optional sint32 target_player_id = 4 [default = -1];
|
||||
|
||||
// the zone that the card the arrow is drawn to is in, the player will be targeted if this is absent
|
||||
optional string target_zone = 5;
|
||||
|
||||
// the id of the card that the arrow is drawn to, the player will be targeted if this is absent
|
||||
optional sint32 target_card_id = 6 [default = -1];
|
||||
|
||||
// the color of the arrow
|
||||
optional color arrow_color = 7;
|
||||
}
|
||||
13
libs/pb/command_create_counter.proto
Normal file
13
libs/pb/command_create_counter.proto
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
syntax = "proto2";
|
||||
import "game_commands.proto";
|
||||
import "color.proto";
|
||||
|
||||
message Command_CreateCounter {
|
||||
extend GameCommand {
|
||||
optional Command_CreateCounter ext = 1019;
|
||||
}
|
||||
optional string counter_name = 1;
|
||||
optional color counter_color = 2;
|
||||
optional uint32 radius = 3;
|
||||
optional sint32 value = 4;
|
||||
}
|
||||
31
libs/pb/command_create_token.proto
Normal file
31
libs/pb/command_create_token.proto
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
syntax = "proto2";
|
||||
import "game_commands.proto";
|
||||
|
||||
message Command_CreateToken {
|
||||
enum TargetMode {
|
||||
// Attach the target to the token
|
||||
ATTACH_TO = 0;
|
||||
// Transform the target into the token
|
||||
TRANSFORM_INTO = 1;
|
||||
}
|
||||
|
||||
extend GameCommand {
|
||||
optional Command_CreateToken ext = 1010;
|
||||
}
|
||||
optional string zone = 1;
|
||||
optional string card_name = 2;
|
||||
optional string color = 3;
|
||||
optional string pt = 4;
|
||||
optional string annotation = 5;
|
||||
optional bool destroy_on_zone_change = 6;
|
||||
optional sint32 x = 7;
|
||||
optional sint32 y = 8;
|
||||
optional string target_zone = 9;
|
||||
optional sint32 target_card_id = 10 [default = -1];
|
||||
|
||||
// What to do with the target card. Ignored if there is no target card.
|
||||
optional TargetMode target_mode = 11;
|
||||
|
||||
optional string card_provider_id = 12;
|
||||
optional bool face_down = 13;
|
||||
}
|
||||
9
libs/pb/command_deck_del.proto
Normal file
9
libs/pb/command_deck_del.proto
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
syntax = "proto2";
|
||||
import "session_commands.proto";
|
||||
|
||||
message Command_DeckDel {
|
||||
extend SessionCommand {
|
||||
optional Command_DeckDel ext = 1011;
|
||||
}
|
||||
optional sint32 deck_id = 1 [default = -1];
|
||||
}
|
||||
9
libs/pb/command_deck_del_dir.proto
Normal file
9
libs/pb/command_deck_del_dir.proto
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
syntax = "proto2";
|
||||
import "session_commands.proto";
|
||||
|
||||
message Command_DeckDelDir {
|
||||
extend SessionCommand {
|
||||
optional Command_DeckDelDir ext = 1010;
|
||||
}
|
||||
optional string path = 1;
|
||||
}
|
||||
9
libs/pb/command_deck_download.proto
Normal file
9
libs/pb/command_deck_download.proto
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
syntax = "proto2";
|
||||
import "session_commands.proto";
|
||||
|
||||
message Command_DeckDownload {
|
||||
extend SessionCommand {
|
||||
optional Command_DeckDownload ext = 1012;
|
||||
}
|
||||
optional sint32 deck_id = 1 [default = -1];
|
||||
}
|
||||
8
libs/pb/command_deck_list.proto
Normal file
8
libs/pb/command_deck_list.proto
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
syntax = "proto2";
|
||||
import "session_commands.proto";
|
||||
|
||||
message Command_DeckList {
|
||||
extend SessionCommand {
|
||||
optional Command_DeckList ext = 1008;
|
||||
}
|
||||
}
|
||||
10
libs/pb/command_deck_new_dir.proto
Normal file
10
libs/pb/command_deck_new_dir.proto
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
syntax = "proto2";
|
||||
import "session_commands.proto";
|
||||
|
||||
message Command_DeckNewDir {
|
||||
extend SessionCommand {
|
||||
optional Command_DeckNewDir ext = 1009;
|
||||
}
|
||||
optional string path = 1;
|
||||
optional string dir_name = 2;
|
||||
}
|
||||
9
libs/pb/command_deck_select.proto
Normal file
9
libs/pb/command_deck_select.proto
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
syntax = "proto2";
|
||||
import "game_commands.proto";
|
||||
message Command_DeckSelect {
|
||||
extend GameCommand {
|
||||
optional Command_DeckSelect ext = 1029;
|
||||
}
|
||||
optional string deck = 1;
|
||||
optional sint32 deck_id = 2 [default = -1];
|
||||
}
|
||||
11
libs/pb/command_deck_upload.proto
Normal file
11
libs/pb/command_deck_upload.proto
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
syntax = "proto2";
|
||||
import "session_commands.proto";
|
||||
|
||||
message Command_DeckUpload {
|
||||
extend SessionCommand {
|
||||
optional Command_DeckUpload ext = 1013;
|
||||
}
|
||||
optional string path = 1; // to upload a new deck
|
||||
optional uint32 deck_id = 2; // to replace an existing deck
|
||||
optional string deck_list = 3;
|
||||
}
|
||||
8
libs/pb/command_del_counter.proto
Normal file
8
libs/pb/command_del_counter.proto
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
syntax = "proto2";
|
||||
import "game_commands.proto";
|
||||
message Command_DelCounter {
|
||||
extend GameCommand {
|
||||
optional Command_DelCounter ext = 1021;
|
||||
}
|
||||
optional sint32 counter_id = 1 [default = -1];
|
||||
}
|
||||
8
libs/pb/command_delete_arrow.proto
Normal file
8
libs/pb/command_delete_arrow.proto
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
syntax = "proto2";
|
||||
import "game_commands.proto";
|
||||
message Command_DeleteArrow {
|
||||
extend GameCommand {
|
||||
optional Command_DeleteArrow ext = 1012;
|
||||
}
|
||||
optional sint32 arrow_id = 1 [default = -1];
|
||||
}
|
||||
8
libs/pb/command_draw_cards.proto
Normal file
8
libs/pb/command_draw_cards.proto
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
syntax = "proto2";
|
||||
import "game_commands.proto";
|
||||
message Command_DrawCards {
|
||||
extend GameCommand {
|
||||
optional Command_DrawCards ext = 1006;
|
||||
}
|
||||
optional uint32 number = 1;
|
||||
}
|
||||
11
libs/pb/command_dump_zone.proto
Normal file
11
libs/pb/command_dump_zone.proto
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
syntax = "proto2";
|
||||
import "game_commands.proto";
|
||||
message Command_DumpZone {
|
||||
extend GameCommand {
|
||||
optional Command_DumpZone ext = 1024;
|
||||
}
|
||||
optional sint32 player_id = 1 [default = -1];
|
||||
optional string zone_name = 2;
|
||||
optional sint32 number_cards = 3;
|
||||
optional bool is_reversed = 4 [default = false];
|
||||
}
|
||||
11
libs/pb/command_flip_card.proto
Normal file
11
libs/pb/command_flip_card.proto
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
syntax = "proto2";
|
||||
import "game_commands.proto";
|
||||
message Command_FlipCard {
|
||||
extend GameCommand {
|
||||
optional Command_FlipCard ext = 1008;
|
||||
}
|
||||
optional string zone = 1;
|
||||
optional sint32 card_id = 2 [default = -1];
|
||||
optional bool face_down = 3;
|
||||
optional string pt = 4;
|
||||
}
|
||||
8
libs/pb/command_game_say.proto
Normal file
8
libs/pb/command_game_say.proto
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
syntax = "proto2";
|
||||
import "game_commands.proto";
|
||||
message Command_GameSay {
|
||||
extend GameCommand {
|
||||
optional Command_GameSay ext = 1002;
|
||||
}
|
||||
optional string message = 1;
|
||||
}
|
||||
11
libs/pb/command_inc_card_counter.proto
Normal file
11
libs/pb/command_inc_card_counter.proto
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
syntax = "proto2";
|
||||
import "game_commands.proto";
|
||||
message Command_IncCardCounter {
|
||||
extend GameCommand {
|
||||
optional Command_IncCardCounter ext = 1015;
|
||||
}
|
||||
optional string zone = 1;
|
||||
optional sint32 card_id = 2 [default = -1];
|
||||
optional sint32 counter_id = 3 [default = -1];
|
||||
optional sint32 counter_delta = 4;
|
||||
}
|
||||
9
libs/pb/command_inc_counter.proto
Normal file
9
libs/pb/command_inc_counter.proto
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
syntax = "proto2";
|
||||
import "game_commands.proto";
|
||||
message Command_IncCounter {
|
||||
extend GameCommand {
|
||||
optional Command_IncCounter ext = 1018;
|
||||
}
|
||||
optional sint32 counter_id = 1 [default = -1];
|
||||
optional sint32 delta = 2;
|
||||
}
|
||||
8
libs/pb/command_kick_from_game.proto
Normal file
8
libs/pb/command_kick_from_game.proto
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
syntax = "proto2";
|
||||
import "game_commands.proto";
|
||||
message Command_KickFromGame {
|
||||
extend GameCommand {
|
||||
optional Command_KickFromGame ext = 1000;
|
||||
}
|
||||
optional sint32 player_id = 1 [default = -1];
|
||||
}
|
||||
7
libs/pb/command_leave_game.proto
Normal file
7
libs/pb/command_leave_game.proto
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
syntax = "proto2";
|
||||
import "game_commands.proto";
|
||||
message Command_LeaveGame {
|
||||
extend GameCommand {
|
||||
optional Command_LeaveGame ext = 1001;
|
||||
}
|
||||
}
|
||||
53
libs/pb/command_move_card.proto
Normal file
53
libs/pb/command_move_card.proto
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
syntax = "proto2";
|
||||
import "game_commands.proto";
|
||||
|
||||
// Container describing a single card to move
|
||||
message CardToMove {
|
||||
// Id of the card in its current zone
|
||||
optional sint32 card_id = 1 [default = -1];
|
||||
|
||||
// Places the card face down, hiding its name
|
||||
optional bool face_down = 2;
|
||||
|
||||
// When moving add this value to the power/toughness field of the card
|
||||
optional string pt = 3;
|
||||
|
||||
// When moving sets the card to be tapped
|
||||
optional bool tapped = 4;
|
||||
}
|
||||
|
||||
// Container of multiple cards to move
|
||||
message ListOfCardsToMove {
|
||||
repeated CardToMove card = 1;
|
||||
}
|
||||
|
||||
// Command to move an amount of cards from one zone to another index/coordinate or another zone
|
||||
message Command_MoveCard {
|
||||
extend GameCommand {
|
||||
optional Command_MoveCard ext = 1027;
|
||||
}
|
||||
|
||||
// The player the zone the cards are in belongs to
|
||||
optional sint32 start_player_id = 1 [default = -1];
|
||||
|
||||
// The zone the cards start in
|
||||
optional string start_zone = 2;
|
||||
|
||||
// List of the cards and their new properties
|
||||
optional ListOfCardsToMove cards_to_move = 3;
|
||||
|
||||
// The player the zone the cards will be moved to belongs to
|
||||
optional sint32 target_player_id = 4 [default = -1];
|
||||
|
||||
// The zone the cards will be moved to
|
||||
optional string target_zone = 5;
|
||||
|
||||
// New x coordinate of the first card in the list
|
||||
optional sint32 x = 6 [default = -1];
|
||||
|
||||
// New y coordinate of the first card in the list
|
||||
optional sint32 y = 7 [default = -1];
|
||||
|
||||
// Inverts the x coordinate to apply from the end of the target zone instead of the start
|
||||
optional bool is_reversed = 8 [default = false];
|
||||
}
|
||||
8
libs/pb/command_mulligan.proto
Normal file
8
libs/pb/command_mulligan.proto
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
syntax = "proto2";
|
||||
import "game_commands.proto";
|
||||
message Command_Mulligan {
|
||||
extend GameCommand {
|
||||
optional Command_Mulligan ext = 1004;
|
||||
}
|
||||
optional uint32 number = 7;
|
||||
}
|
||||
7
libs/pb/command_next_turn.proto
Normal file
7
libs/pb/command_next_turn.proto
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
syntax = "proto2";
|
||||
import "game_commands.proto";
|
||||
message Command_NextTurn {
|
||||
extend GameCommand {
|
||||
optional Command_NextTurn ext = 1022;
|
||||
}
|
||||
}
|
||||
9
libs/pb/command_ready_start.proto
Normal file
9
libs/pb/command_ready_start.proto
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
syntax = "proto2";
|
||||
import "game_commands.proto";
|
||||
message Command_ReadyStart {
|
||||
extend GameCommand {
|
||||
optional Command_ReadyStart ext = 1016;
|
||||
}
|
||||
optional bool ready = 1;
|
||||
optional bool force_start = 2;
|
||||
}
|
||||
9
libs/pb/command_replay_delete_match.proto
Normal file
9
libs/pb/command_replay_delete_match.proto
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
syntax = "proto2";
|
||||
import "session_commands.proto";
|
||||
|
||||
message Command_ReplayDeleteMatch {
|
||||
extend SessionCommand {
|
||||
optional Command_ReplayDeleteMatch ext = 1103;
|
||||
}
|
||||
optional sint32 game_id = 1 [default = -1];
|
||||
}
|
||||
9
libs/pb/command_replay_download.proto
Normal file
9
libs/pb/command_replay_download.proto
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
syntax = "proto2";
|
||||
import "session_commands.proto";
|
||||
|
||||
message Command_ReplayDownload {
|
||||
extend SessionCommand {
|
||||
optional Command_ReplayDownload ext = 1101;
|
||||
}
|
||||
optional sint32 replay_id = 1 [default = -1];
|
||||
}
|
||||
9
libs/pb/command_replay_get_code.proto
Normal file
9
libs/pb/command_replay_get_code.proto
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
syntax = "proto2";
|
||||
import "session_commands.proto";
|
||||
|
||||
message Command_ReplayGetCode {
|
||||
extend SessionCommand {
|
||||
optional Command_ReplayGetCode ext = 1104;
|
||||
}
|
||||
optional sint32 game_id = 1 [default = -1];
|
||||
}
|
||||
8
libs/pb/command_replay_list.proto
Normal file
8
libs/pb/command_replay_list.proto
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
syntax = "proto2";
|
||||
import "session_commands.proto";
|
||||
|
||||
message Command_ReplayList {
|
||||
extend SessionCommand {
|
||||
optional Command_ReplayList ext = 1100;
|
||||
}
|
||||
}
|
||||
10
libs/pb/command_replay_modify_match.proto
Normal file
10
libs/pb/command_replay_modify_match.proto
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
syntax = "proto2";
|
||||
import "session_commands.proto";
|
||||
|
||||
message Command_ReplayModifyMatch {
|
||||
extend SessionCommand {
|
||||
optional Command_ReplayModifyMatch ext = 1102;
|
||||
}
|
||||
optional sint32 game_id = 1 [default = -1];
|
||||
optional bool do_not_hide = 2;
|
||||
}
|
||||
9
libs/pb/command_replay_submit_code.proto
Normal file
9
libs/pb/command_replay_submit_code.proto
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
syntax = "proto2";
|
||||
import "session_commands.proto";
|
||||
|
||||
message Command_ReplaySubmitCode {
|
||||
extend SessionCommand {
|
||||
optional Command_ReplaySubmitCode ext = 1105;
|
||||
}
|
||||
optional string replay_code = 1;
|
||||
}
|
||||
12
libs/pb/command_reveal_cards.proto
Normal file
12
libs/pb/command_reveal_cards.proto
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
syntax = "proto2";
|
||||
import "game_commands.proto";
|
||||
message Command_RevealCards {
|
||||
extend GameCommand {
|
||||
optional Command_RevealCards ext = 1026;
|
||||
}
|
||||
optional string zone_name = 1;
|
||||
repeated sint32 card_id = 2 [packed = false];
|
||||
optional sint32 player_id = 3 [default = -1];
|
||||
optional bool grant_write_access = 4;
|
||||
optional sint32 top_cards = 5 [default = -1];
|
||||
}
|
||||
7
libs/pb/command_reverse_turn.proto
Normal file
7
libs/pb/command_reverse_turn.proto
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
syntax = "proto2";
|
||||
import "game_commands.proto";
|
||||
message Command_ReverseTurn {
|
||||
extend GameCommand {
|
||||
optional Command_ReverseTurn ext = 1034;
|
||||
}
|
||||
}
|
||||
9
libs/pb/command_roll_die.proto
Normal file
9
libs/pb/command_roll_die.proto
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
syntax = "proto2";
|
||||
import "game_commands.proto";
|
||||
message Command_RollDie {
|
||||
extend GameCommand {
|
||||
optional Command_RollDie ext = 1005;
|
||||
}
|
||||
optional uint32 sides = 1;
|
||||
optional uint32 count = 2;
|
||||
}
|
||||
8
libs/pb/command_set_active_phase.proto
Normal file
8
libs/pb/command_set_active_phase.proto
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
syntax = "proto2";
|
||||
import "game_commands.proto";
|
||||
message Command_SetActivePhase {
|
||||
extend GameCommand {
|
||||
optional Command_SetActivePhase ext = 1023;
|
||||
}
|
||||
optional uint32 phase = 1;
|
||||
}
|
||||
13
libs/pb/command_set_card_attr.proto
Normal file
13
libs/pb/command_set_card_attr.proto
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
syntax = "proto2";
|
||||
import "game_commands.proto";
|
||||
import "card_attributes.proto";
|
||||
|
||||
message Command_SetCardAttr {
|
||||
extend GameCommand {
|
||||
optional Command_SetCardAttr ext = 1013;
|
||||
}
|
||||
optional string zone = 1;
|
||||
optional sint32 card_id = 2 [default = -1];
|
||||
optional CardAttribute attribute = 3;
|
||||
optional string attr_value = 4;
|
||||
}
|
||||
11
libs/pb/command_set_card_counter.proto
Normal file
11
libs/pb/command_set_card_counter.proto
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
syntax = "proto2";
|
||||
import "game_commands.proto";
|
||||
message Command_SetCardCounter {
|
||||
extend GameCommand {
|
||||
optional Command_SetCardCounter ext = 1014;
|
||||
}
|
||||
optional string zone = 1;
|
||||
optional sint32 card_id = 2 [default = -1];
|
||||
optional sint32 counter_id = 3 [default = -1];
|
||||
optional sint32 counter_value = 4;
|
||||
}
|
||||
9
libs/pb/command_set_counter.proto
Normal file
9
libs/pb/command_set_counter.proto
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
syntax = "proto2";
|
||||
import "game_commands.proto";
|
||||
message Command_SetCounter {
|
||||
extend GameCommand {
|
||||
optional Command_SetCounter ext = 1020;
|
||||
}
|
||||
optional sint32 counter_id = 1 [default = -1];
|
||||
optional sint32 value = 2;
|
||||
}
|
||||
8
libs/pb/command_set_sideboard_lock.proto
Normal file
8
libs/pb/command_set_sideboard_lock.proto
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
syntax = "proto2";
|
||||
import "game_commands.proto";
|
||||
message Command_SetSideboardLock {
|
||||
extend GameCommand {
|
||||
optional Command_SetSideboardLock ext = 1030;
|
||||
}
|
||||
optional bool locked = 1;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue