diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index 15f0e6947..f8db89b76 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -350,6 +350,9 @@ set(cockatrice_SOURCES src/interface/widgets/tabs/visual_deck_storage/tab_deck_storage_visual.cpp src/interface/key_signals.cpp src/interface/logger.cpp + src/interface/widgets/tabs/api/commander_spellbook/commander_bracket_service.cpp + src/interface/widgets/tabs/api/commander_spellbook/commander_bracket_definitions.cpp + src/interface/widgets/tabs/api/commander_spellbook/handle_commander_brackets.cpp src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_bracket_navigation_widget.cpp src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_bracket_navigation_widget.h src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_budget_navigation_widget.cpp diff --git a/cockatrice/src/client/settings/cache_settings.cpp b/cockatrice/src/client/settings/cache_settings.cpp index 2e4662e2b..5a13f7234 100644 --- a/cockatrice/src/client/settings/cache_settings.cpp +++ b/cockatrice/src/client/settings/cache_settings.cpp @@ -193,6 +193,7 @@ SettingsCache::SettingsCache() shortcutsSettings = new ShortcutsSettings(settingsPath, this); cardDatabaseSettings = new CardDatabaseSettings(settingsPath, this); serversSettings = new ServersSettings(settingsPath, this); + commanderBracketSettings = new CommanderBracketSettings(settingsPath, this); messageSettings = new MessageSettings(settingsPath, this); gameFiltersSettings = new GameFiltersSettings(settingsPath, this); layoutsSettings = new LayoutsSettings(settingsPath, this); @@ -330,6 +331,7 @@ SettingsCache::SettingsCache() deckEditorBannerCardComboBoxVisible = settings->value("interface/deckeditorbannercardcomboboxvisible", true).toBool(); deckEditorTagsWidgetVisible = settings->value("interface/deckeditortagswidgetvisible", true).toBool(); + deckEditorCommanderSpellbookIntegrationEnabled = settings ->value("interface/deck_editor/commander_spellbook_integration/enabled", @@ -338,6 +340,13 @@ SettingsCache::SettingsCache() deckEditorCommanderSpellbookIntegrationUseOfficialBracketNames = settings->value("interface/deck_editor/commander_spellbook_integration/use_official_bracket_names", false) .toBool(); + + auto definitions = commanderBracketSettings->loadDefinitions(); + if (definitions.isEmpty()) { + definitions = CommanderBracketSettings::defaultDefinitions(); + } + reloadBracketDefinitions(definitions); + visualDeckStorageCardSize = settings->value("interface/visualdeckstoragecardsize", 100).toInt(); visualDeckStorageSortingOrder = settings->value("interface/visualdeckstoragesortingorder", 0).toInt(); visualDeckStorageShowFolders = settings->value("interface/visualdeckstorageshowfolders", true).toBool(); @@ -436,6 +445,22 @@ SettingsCache::SettingsCache() clientVersion = settings->value("personal/clientversion", CLIENT_INFO_NOT_SET).toString(); } +void SettingsCache::reloadBracketDefinitions(const QVariantList &definitions) +{ + bracketDefinitions.clear(); + for (const auto &entry : definitions) { + const auto map = entry.toMap(); + CommanderBracketDefinition def; + def.tag = map.value("tag").toString(); + def.officialName = map.value("officialName").toString(); + def.displayName = map.value("displayName").toString(); + def.explanation = map.value("explanation").toString(); + if (!def.tag.isEmpty()) { + bracketDefinitions.addDefinition(def); + } + } +} + void SettingsCache::setUseTearOffMenus(bool _useTearOffMenus) { useTearOffMenus = _useTearOffMenus; diff --git a/cockatrice/src/client/settings/cache_settings.h b/cockatrice/src/client/settings/cache_settings.h index b81cfec6e..b89a0b242 100644 --- a/cockatrice/src/client/settings/cache_settings.h +++ b/cockatrice/src/client/settings/cache_settings.h @@ -9,6 +9,7 @@ #include "../../interface/card_picture_loader/card_picture_loader_cache_method.h" #include "../../interface/card_picture_loader/card_picture_loader_local_schemes.h" +#include "../../interface/widgets/tabs/api/commander_spellbook/commander_bracket_definitions.h" #include "shortcuts_settings.h" #include @@ -19,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -203,6 +205,7 @@ private: ShortcutsSettings *shortcutsSettings; CardDatabaseSettings *cardDatabaseSettings; ServersSettings *serversSettings; + CommanderBracketSettings *commanderBracketSettings; MessageSettings *messageSettings; GameFiltersSettings *gameFiltersSettings; LayoutsSettings *layoutsSettings; @@ -212,6 +215,8 @@ private: DebugSettings *debugSettings; CardCounterSettings *cardCounterSettings; + CommanderBracketDefinitions bracketDefinitions; + QString lang; QString deckPath, filtersPath, replaysPath, picsPath, redirectCachePath, customPicsPath, cardDatabasePath, customCardDatabasePath, themesPath, spoilerDatabasePath, tokenDatabasePath, themeName, homeTabBackgroundSource; @@ -991,6 +996,15 @@ public: { return *serversSettings; } + [[nodiscard]] CommanderBracketSettings &commanderBrackets() const + { + return *commanderBracketSettings; + } + void reloadBracketDefinitions(const QVariantList &definitions); + CommanderBracketDefinitions &commanderBracketDefs() + { + return bracketDefinitions; + } [[nodiscard]] MessageSettings &messages() const { return *messageSettings; diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.cpp b/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.cpp index 51f996ca8..3969ee060 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.cpp +++ b/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.cpp @@ -2,6 +2,7 @@ #include "../../../client/settings/cache_settings.h" #include "../settings_page/user_interface_settings_page.h" +#include "../tabs/api/commander_spellbook/commander_bracket_service.h" #include "../tabs/api/commander_spellbook/commander_spellbook_api_accessor.h" #include "../tabs/api/commander_spellbook/commander_spellbook_bracket_explainer.h" #include "deck_list_style_proxy.h" @@ -168,6 +169,9 @@ void DeckEditorDeckDockWidget::createDeckDock() bracketInfoButton->setVisible(false); bracketRefreshButton->setVisible(false); + connect(&CommanderBracketService::instance(), &CommanderBracketService::estimateFinished, this, + &DeckEditorDeckDockWidget::onEstimateBracketFinished); + commentsLabel = new QLabel(); commentsLabel->setObjectName("commentsLabel"); commentsEdit = new QTextEdit; @@ -433,29 +437,25 @@ void DeckEditorDeckDockWidget::requestBracketEstimate() bracketInfoButton->setEnabled(false); bracketValueLabel->setText(tr("Calculating…")); - requestId = CommanderSpellbookApiAccessor::instance().estimateBracket(deckStateManager->getDeckList(), this); - - connect(&CommanderSpellbookApiAccessor::instance(), &CommanderSpellbookApiAccessor::estimateBracketFinished, this, - &DeckEditorDeckDockWidget::onEstimateBracketFinished); + requestId = CommanderBracketService::instance().estimateBracket(deckStateManager->getDeckList(), this); } -void DeckEditorDeckDockWidget::onEstimateBracketFinished(CommanderSpellbookApiAccessor::RequestId id, +void DeckEditorDeckDockWidget::onEstimateBracketFinished(quint64 id, QObject *requester, - const EstimateBracketResult &result) + const CommanderBracketEstimate &result) { - if (requester != this || static_cast(id) != requestId) { + if (requester != this || id != requestId) { return; } BracketExplainer explainer; - lastBracketExplanation = explainer.explain(result); + lastBracketExplanation = explainer.explain(result.rawResult); // Display bracket - if (SettingsCache::instance().getDeckEditorCommanderSpellbookIntegrationUseOfficialBracketNames()) { - bracketValueLabel->setText(CommanderSpellbookBracketTag::bracketTagToOfficialString(result.bracketTag)); - } else { - bracketValueLabel->setText(CommanderSpellbookBracketTag::bracketTagToString(result.bracketTag)); - } + bracketValueLabel->setText( + SettingsCache::instance().getDeckEditorCommanderSpellbookIntegrationUseOfficialBracketNames() + ? result.officialName + : result.displayName); bracketRefreshButton->setEnabled(true); // Build tooltip diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h b/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h index ec6eed7f0..1606d00f1 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h +++ b/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h @@ -22,7 +22,7 @@ #include #include -class EstimateBracketResult; +struct CommanderBracketEstimate; class DeckListModel; class AbstractTabDeckEditor; class DeckEditorDeckDockWidget : public QDockWidget @@ -65,7 +65,7 @@ public slots: void actRemoveCard(); void initializeFormats(); void maybeAutoEstimateBracket(); - void onEstimateBracketFinished(quint64 id, QObject *requester, const EstimateBracketResult &result); + void onEstimateBracketFinished(quint64 id, QObject *requester, const CommanderBracketEstimate &result); signals: void selectedCardChanged(const ExactCard &card); diff --git a/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/api_response/commander_spellbook_bracket_tag.h b/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/api_response/commander_spellbook_bracket_tag.h deleted file mode 100644 index 1728b8d44..000000000 --- a/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/api_response/commander_spellbook_bracket_tag.h +++ /dev/null @@ -1,85 +0,0 @@ -#ifndef COCKATRICE_COMMANDER_SPELLBOOK_BRACKET_TAG_H -#define COCKATRICE_COMMANDER_SPELLBOOK_BRACKET_TAG_H -#include - -namespace CommanderSpellbookBracketTag -{ -enum class BracketTag -{ - Ruthless, - Spicy, - Powerful, - Oddball, - PreconAppropriate, - Casual, - Unknown -}; - -inline static BracketTag bracketTagFromString(const QString &s) -{ - if (s == "R") { - return BracketTag::Ruthless; - } - if (s == "S") { - return BracketTag::Spicy; - } - if (s == "P") { - return BracketTag::Powerful; - } - if (s == "O") { - return BracketTag::Oddball; - } - if (s == "PA") { - return BracketTag::PreconAppropriate; - } - if (s == "C") { - return BracketTag::Casual; - } - return BracketTag::Unknown; -} - -inline static QString bracketTagToString(BracketTag tag) -{ - switch (tag) { - case BracketTag::Ruthless: - return "Ruthless"; - case BracketTag::Spicy: - return "Spicy"; - case BracketTag::Powerful: - return "Powerful"; - case BracketTag::Oddball: - return "Oddball"; - case BracketTag::PreconAppropriate: - return "Precon Appropriate"; - case BracketTag::Casual: - return "Casual"; - case BracketTag::Unknown: - return "Unknown"; - } - return {}; -} - -inline static QString bracketTagToOfficialString(BracketTag tag) -{ - switch (tag) { - case BracketTag::Ruthless: - return "[5] cEDH"; - case BracketTag::Spicy: - return "[4] Optimized"; - case BracketTag::Powerful: - return "[3] Upgraded"; - case BracketTag::Oddball: - return "[2] Core"; - case BracketTag::PreconAppropriate: - return "[1] Exhibition"; - case BracketTag::Casual: - return "[1] Casual"; - case BracketTag::Unknown: - return "Unknown"; - } - return {}; -} - -} // namespace CommanderSpellbookBracketTag - -#endif // COCKATRICE_COMMANDER_SPELLBOOK_BRACKET_TAG_H diff --git a/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/api_response/commander_spellbook_estimate_bracket_result.cpp b/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/api_response/commander_spellbook_estimate_bracket_result.cpp index 11d10583d..16be8cb52 100644 --- a/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/api_response/commander_spellbook_estimate_bracket_result.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/api_response/commander_spellbook_estimate_bracket_result.cpp @@ -1,49 +1,107 @@ #include "commander_spellbook_estimate_bracket_result.h" -static void parseCards(const QJsonObject &json, const QString &key, QVector &out) -{ - out.clear(); - for (const auto &v : json.value(key).toArray()) { - if (!v.isObject()) { - continue; - } - CommanderSpellbookCardResult c; - c.fromJson(v.toObject()); - out.append(c); - } -} - -static void parseVariants(const QJsonObject &json, const QString &key, QVector &out) -{ - out.clear(); - for (const auto &v : json.value(key).toArray()) { - if (!v.isObject()) { - continue; - } - CommanderSpellbookVariantResult vr; - vr.fromJson(v.toObject()); - out.append(vr); - } -} - void EstimateBracketResult::fromJson(const QJsonObject &json) { - bracketTag = CommanderSpellbookBracketTag::bracketTagFromString(json.value("bracketTag").toString()); + bracketTag = json.value("bracketTag").toString(); - parseCards(json, "gameChangerCards", gameChangerCards); - parseCards(json, "massLandDenialCards", massLandDenialCards); - parseCards(json, "extraTurnCards", extraTurnCards); - parseCards(json, "tutorCards", tutorCards); + gameChangerCards.clear(); + massLandDenialCards.clear(); + extraTurnCards.clear(); - parseVariants(json, "massLandDenialTemplates", massLandDenialTemplates); - parseVariants(json, "massLandDenialCombos", massLandDenialCombos); - parseVariants(json, "extraTurnTemplates", extraTurnTemplates); - parseVariants(json, "extraTurnsCombos", extraTurnsCombos); - parseVariants(json, "tutorTemplates", tutorTemplates); - parseVariants(json, "lockCombos", lockCombos); - parseVariants(json, "skipTurnsCombos", skipTurnsCombos); - parseVariants(json, "definitelyEarlyGameTwoCardCombos", definitelyEarlyGameTwoCardCombos); - parseVariants(json, "arguablyEarlyGameTwoCardCombos", arguablyEarlyGameTwoCardCombos); - parseVariants(json, "definitelyLateGameTwoCardCombos", definitelyLateGameTwoCardCombos); - parseVariants(json, "borderlineLateGameTwoCardCombos", borderlineLateGameTwoCardCombos); -} + massLandDenialTemplates.clear(); + extraTurnTemplates.clear(); + + massLandDenialCombos.clear(); + extraTurnCombos.clear(); + lockCombos.clear(); + skipTurnsCombos.clear(); + + definitelyTwoCardCombos.clear(); + arguablyTwoCardCombos.clear(); + + // + // Cards + // + for (const auto &value : json.value("cards").toArray()) { + if (!value.isObject()) { + continue; + } + + const QJsonObject obj = value.toObject(); + + CommanderSpellbookCardResult card; + card.fromJson(obj.value("card").toObject()); + + if (obj.value("gameChanger").toBool()) { + gameChangerCards.append(card); + } + + if (obj.value("massLandDenial").toBool()) { + massLandDenialCards.append(card); + } + + if (obj.value("extraTurn").toBool()) { + extraTurnCards.append(card); + } + } + + // + // Templates + // + for (const auto &value : json.value("templates").toArray()) { + if (!value.isObject()) { + continue; + } + + const QJsonObject obj = value.toObject(); + + CommanderSpellbookVariantResult variant; + variant.fromJson(obj); + + if (obj.value("massLandDenial").toBool()) { + massLandDenialTemplates.append(variant); + } + + if (obj.value("extraTurn").toBool()) { + extraTurnTemplates.append(variant); + } + } + + // + // Combos + // + for (const auto &value : json.value("combos").toArray()) { + if (!value.isObject()) { + continue; + } + + const QJsonObject obj = value.toObject(); + + CommanderSpellbookVariantResult combo; + combo.fromJson(obj); + + if (obj.value("massLandDenial").toBool()) { + massLandDenialCombos.append(combo); + } + + if (obj.value("extraTurn").toBool()) { + extraTurnCombos.append(combo); + } + + if (obj.value("lock").toBool()) { + lockCombos.append(combo); + } + + if (obj.value("skipTurns").toBool()) { + skipTurnsCombos.append(combo); + } + + if (obj.value("definitelyTwoCard").toBool()) { + definitelyTwoCardCombos.append(combo); + } + + if (obj.value("arguablyTwoCard").toBool()) { + arguablyTwoCardCombos.append(combo); + } + } +} \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/api_response/commander_spellbook_estimate_bracket_result.h b/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/api_response/commander_spellbook_estimate_bracket_result.h index 701da4fc0..8ddd3a78a 100644 --- a/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/api_response/commander_spellbook_estimate_bracket_result.h +++ b/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/api_response/commander_spellbook_estimate_bracket_result.h @@ -1,5 +1,6 @@ #ifndef COCKATRICE_COMMANDER_SPELLBOOK_ESTIMATE_BRACKET_RESULT_H #define COCKATRICE_COMMANDER_SPELLBOOK_ESTIMATE_BRACKET_RESULT_H + #include "commander_spellbook_card_result.h" #include "commander_spellbook_variant_result.h" @@ -10,24 +11,22 @@ class EstimateBracketResult public: void fromJson(const QJsonObject &json); - CommanderSpellbookBracketTag::BracketTag bracketTag = CommanderSpellbookBracketTag::BracketTag::Unknown; + QString bracketTag; QVector gameChangerCards; QVector massLandDenialCards; QVector extraTurnCards; - QVector tutorCards; QVector massLandDenialTemplates; - QVector massLandDenialCombos; QVector extraTurnTemplates; - QVector extraTurnsCombos; - QVector tutorTemplates; + + QVector massLandDenialCombos; + QVector extraTurnCombos; QVector lockCombos; QVector skipTurnsCombos; - QVector definitelyEarlyGameTwoCardCombos; - QVector arguablyEarlyGameTwoCardCombos; - QVector definitelyLateGameTwoCardCombos; - QVector borderlineLateGameTwoCardCombos; + + QVector definitelyTwoCardCombos; + QVector arguablyTwoCardCombos; }; -#endif // COCKATRICE_COMMANDER_SPELLBOOK_ESTIMATE_BRACKET_RESULT_H +#endif \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/api_response/commander_spellbook_variant_result.cpp b/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/api_response/commander_spellbook_variant_result.cpp index 0e0687605..05f31c535 100644 --- a/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/api_response/commander_spellbook_variant_result.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/api_response/commander_spellbook_variant_result.cpp @@ -22,7 +22,7 @@ void CommanderSpellbookVariantResult::fromJson(const QJsonObject &json) popularity = json.value("popularity").toDouble(); spoiler = json.value("spoiler").toBool(); - bracketTag = CommanderSpellbookBracketTag::bracketTagFromString(json.value("bracketTag").toString()); + bracketTag = json.value("bracketTag").toString(); legalities = json.value("legalities").toObject(); prices = json.value("prices").toObject(); diff --git a/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/api_response/commander_spellbook_variant_result.h b/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/api_response/commander_spellbook_variant_result.h index 76b5d1c8e..80893eee3 100644 --- a/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/api_response/commander_spellbook_variant_result.h +++ b/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/api_response/commander_spellbook_variant_result.h @@ -1,6 +1,5 @@ #ifndef COCKATRICE_COMMANDER_SPELLBOOK_VARIANT_RESULT_H #define COCKATRICE_COMMANDER_SPELLBOOK_VARIANT_RESULT_H -#include "commander_spellbook_bracket_tag.h" #include #include @@ -30,7 +29,7 @@ public: double popularity = 0.0; bool spoiler = false; - CommanderSpellbookBracketTag::BracketTag bracketTag = CommanderSpellbookBracketTag::BracketTag::Unknown; + QString bracketTag; QJsonObject legalities; QJsonObject prices; diff --git a/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/commander_bracket_definitions.cpp b/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/commander_bracket_definitions.cpp new file mode 100644 index 000000000..c183d1eee --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/commander_bracket_definitions.cpp @@ -0,0 +1,49 @@ +#include "commander_bracket_definitions.h" + +void CommanderBracketDefinitions::clear() +{ + definitions.clear(); +} + +void CommanderBracketDefinitions::addDefinition(const CommanderBracketDefinition &definition) +{ + definitions.insert(definition.tag, definition); +} + +QString CommanderBracketDefinitions::officialName(const QString &tag) const +{ + auto it = definitions.find(tag); + + if (it == definitions.end()) { + return tag; + } + + return it->officialName; +} + +QString CommanderBracketDefinitions::displayName(const QString &tag) const +{ + auto it = definitions.find(tag); + + if (it == definitions.end()) { + return tag; + } + + return it->displayName; +} + +QString CommanderBracketDefinitions::explanation(const QString &tag) const +{ + auto it = definitions.find(tag); + + if (it == definitions.end()) { + return {}; + } + + return it->explanation; +} + +bool CommanderBracketDefinitions::contains(const QString &tag) const +{ + return definitions.contains(tag); +} \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/commander_bracket_definitions.h b/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/commander_bracket_definitions.h new file mode 100644 index 000000000..248cffbbf --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/commander_bracket_definitions.h @@ -0,0 +1,34 @@ +#ifndef COCKATRICE_COMMANDER_BRACKET_DEFINITIONS_H +#define COCKATRICE_COMMANDER_BRACKET_DEFINITIONS_H + +#include +#include + +struct CommanderBracketDefinition +{ + QString tag; + + QString officialName; + QString displayName; + + QString explanation; +}; + +class CommanderBracketDefinitions +{ +public: + void clear(); + + void addDefinition(const CommanderBracketDefinition &definition); + + QString officialName(const QString &tag) const; + QString displayName(const QString &tag) const; + QString explanation(const QString &tag) const; + + bool contains(const QString &tag) const; + +private: + QHash definitions; +}; + +#endif // COCKATRICE_COMMANDER_BRACKET_DEFINITIONS_H diff --git a/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/commander_bracket_service.cpp b/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/commander_bracket_service.cpp new file mode 100644 index 000000000..8c983375a --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/commander_bracket_service.cpp @@ -0,0 +1,51 @@ +#include "commander_bracket_service.h" + +#include "../../../../../client/settings/cache_settings.h" + +CommanderBracketService &CommanderBracketService::instance() +{ + static CommanderBracketService service; + return service; +} + +CommanderBracketService::CommanderBracketService(QObject *parent) : QObject(parent) +{ + connect(&CommanderSpellbookApiAccessor::instance(), &CommanderSpellbookApiAccessor::estimateBracketFinished, this, + &CommanderBracketService::onEstimateBracketFinished); + + connect(&CommanderSpellbookApiAccessor::instance(), &CommanderSpellbookApiAccessor::estimateBracketError, this, + &CommanderBracketService::onEstimateBracketError); +} + +quint64 CommanderBracketService::estimateBracket(const DeckList &deck, QObject *requester) +{ + return CommanderSpellbookApiAccessor::instance().estimateBracket(deck, requester); +} + +void CommanderBracketService::onEstimateBracketFinished(CommanderSpellbookApiAccessor::RequestId id, + QObject *requester, + const EstimateBracketResult &result) +{ + CommanderBracketEstimate estimate; + + estimate.bracketTag = result.bracketTag; + + estimate.rawResult = result; + + auto &definitions = SettingsCache::instance().commanderBracketDefs(); + + estimate.officialName = definitions.officialName(result.bracketTag); + + estimate.displayName = definitions.displayName(result.bracketTag); + + estimate.explanation = definitions.explanation(result.bracketTag); + + emit estimateFinished(id, requester, estimate); +} + +void CommanderBracketService::onEstimateBracketError(CommanderSpellbookApiAccessor::RequestId id, + QObject *requester, + const QString &error) +{ + emit estimateError(id, requester, error); +} \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/commander_bracket_service.h b/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/commander_bracket_service.h new file mode 100644 index 000000000..b04a8cacc --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/commander_bracket_service.h @@ -0,0 +1,45 @@ +#ifndef COCKATRICE_COMMANDER_BRACKET_SERVICE_H +#define COCKATRICE_COMMANDER_BRACKET_SERVICE_H + +#include "commander_spellbook_api_accessor.h" +#include "libcockatrice/deck_list/deck_list.h" + +#include + +struct CommanderBracketEstimate +{ + QString bracketTag; + + QString officialName; + QString displayName; + QString explanation; + + EstimateBracketResult rawResult; +}; + +class CommanderBracketService : public QObject +{ + Q_OBJECT + +public: + static CommanderBracketService &instance(); + + quint64 estimateBracket(const DeckList &deck, QObject *requester); + +signals: + void estimateFinished(quint64 requestId, QObject *requester, const CommanderBracketEstimate &estimate); + + void estimateError(quint64 requestId, QObject *requester, const QString &error); + +private slots: + void onEstimateBracketFinished(CommanderSpellbookApiAccessor::RequestId id, + QObject *requester, + const EstimateBracketResult &result); + + void onEstimateBracketError(CommanderSpellbookApiAccessor::RequestId id, QObject *requester, const QString &error); + +private: + explicit CommanderBracketService(QObject *parent = nullptr); +}; + +#endif \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/commander_spellbook_bracket_explainer.cpp b/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/commander_spellbook_bracket_explainer.cpp index d7fc406b6..f420b5d0e 100644 --- a/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/commander_spellbook_bracket_explainer.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/commander_spellbook_bracket_explainer.cpp @@ -24,47 +24,56 @@ BracketExplanation BracketExplainer::explain(const EstimateBracketResult &r) BracketExplanation out; out.bracket = r.bracketTag; - // --- Game changers --- if (!r.gameChangerCards.isEmpty()) { BracketExplanationSection s; s.title = "Game-changing cards"; + s.bulletPoints << QString("Your deck contains %1 game-changing cards, such as %2.") .arg(r.gameChangerCards.size()) .arg(cardList(r.gameChangerCards)); + out.sections << s; } - // --- Tutors --- - if (!r.tutorCards.isEmpty()) { + if (!r.extraTurnCards.isEmpty() || !r.extraTurnTemplates.isEmpty() || !r.extraTurnCombos.isEmpty()) { + BracketExplanationSection s; - s.title = "Tutors"; - s.bulletPoints << QString("The deck runs %1 tutor cards, including %2.") - .arg(r.tutorCards.size()) - .arg(cardList(r.tutorCards)); + s.title = "Extra turns"; + + if (!r.extraTurnCards.isEmpty()) { + s.bulletPoints << QString("The deck contains %1 extra-turn cards (%2).") + .arg(r.extraTurnCards.size()) + .arg(cardList(r.extraTurnCards)); + } + + if (!r.extraTurnTemplates.isEmpty()) { + s.bulletPoints << QString("%1 extra-turn templates were identified.").arg(comboCount(r.extraTurnTemplates)); + } + + if (!r.extraTurnCombos.isEmpty()) { + s.bulletPoints + << QString("%1 extra-turn combo variants were identified.").arg(comboCount(r.extraTurnCombos)); + } + out.sections << s; } - // --- Extra turns --- - if (!r.extraTurnCards.isEmpty()) { - BracketExplanationSection s; - s.title = "Extra turn effects"; - s.bulletPoints << QString("Extra turn spells were detected (%1), such as %2.") - .arg(r.extraTurnCards.size()) - .arg(cardList(r.extraTurnCards)); - out.sections << s; - } + if (!r.massLandDenialCards.isEmpty() || !r.massLandDenialTemplates.isEmpty() || !r.massLandDenialCombos.isEmpty()) { - // --- Mass land denial --- - if (!r.massLandDenialCards.isEmpty() || !r.massLandDenialCombos.isEmpty()) { BracketExplanationSection s; s.title = "Mass land denial"; if (!r.massLandDenialCards.isEmpty()) { - s.bulletPoints << QString("The deck includes %1 mass land denial cards (%2).") + s.bulletPoints << QString("The deck contains %1 mass land denial cards (%2).") .arg(r.massLandDenialCards.size()) .arg(cardList(r.massLandDenialCards)); } + if (!r.massLandDenialTemplates.isEmpty()) { + s.bulletPoints + << QString("%1 mass land denial templates were identified.").arg(comboCount(r.massLandDenialTemplates)); + } + if (!r.massLandDenialCombos.isEmpty()) { s.bulletPoints << QString("%1 mass land denial combo variants were identified.") .arg(comboCount(r.massLandDenialCombos)); @@ -73,10 +82,10 @@ BracketExplanation BracketExplainer::explain(const EstimateBracketResult &r) out.sections << s; } - // --- Lock / skip turns --- if (!r.lockCombos.isEmpty() || !r.skipTurnsCombos.isEmpty()) { + BracketExplanationSection s; - s.title = "Lock or skip-turn combos"; + s.title = "Lock pieces"; if (!r.lockCombos.isEmpty()) { s.bulletPoints << QString("%1 lock combo variants were detected.").arg(comboCount(r.lockCombos)); @@ -89,39 +98,19 @@ BracketExplanation BracketExplainer::explain(const EstimateBracketResult &r) out.sections << s; } - // --- Early-game combos --- - if (!r.definitelyEarlyGameTwoCardCombos.isEmpty() || !r.arguablyEarlyGameTwoCardCombos.isEmpty()) { + if (!r.definitelyTwoCardCombos.isEmpty() || !r.arguablyTwoCardCombos.isEmpty()) { BracketExplanationSection s; - s.title = "Early-game two-card combos"; + s.title = "Two-card combos"; - if (!r.definitelyEarlyGameTwoCardCombos.isEmpty()) { - s.bulletPoints << QString("%1 definitely early-game two-card combos were found.") - .arg(comboCount(r.definitelyEarlyGameTwoCardCombos)); + if (!r.definitelyTwoCardCombos.isEmpty()) { + s.bulletPoints << QString("%1 definite two-card combo variants were identified.") + .arg(comboCount(r.definitelyTwoCardCombos)); } - if (!r.arguablyEarlyGameTwoCardCombos.isEmpty()) { - s.bulletPoints << QString("%1 arguably early-game two-card combos were found.") - .arg(comboCount(r.arguablyEarlyGameTwoCardCombos)); - } - - out.sections << s; - } - - // --- Late-game combos --- - if (!r.definitelyLateGameTwoCardCombos.isEmpty() || !r.borderlineLateGameTwoCardCombos.isEmpty()) { - - BracketExplanationSection s; - s.title = "Late-game two-card combos"; - - if (!r.definitelyLateGameTwoCardCombos.isEmpty()) { - s.bulletPoints << QString("%1 definitely late-game two-card combos were found.") - .arg(comboCount(r.definitelyLateGameTwoCardCombos)); - } - - if (!r.borderlineLateGameTwoCardCombos.isEmpty()) { - s.bulletPoints << QString("%1 borderline late-game two-card combos were found.") - .arg(comboCount(r.borderlineLateGameTwoCardCombos)); + if (!r.arguablyTwoCardCombos.isEmpty()) { + s.bulletPoints << QString("%1 arguable two-card combo variants were identified.") + .arg(comboCount(r.arguablyTwoCardCombos)); } out.sections << s; diff --git a/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/commander_spellbook_bracket_explainer.h b/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/commander_spellbook_bracket_explainer.h index 53ff94dd2..b4c95a343 100644 --- a/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/commander_spellbook_bracket_explainer.h +++ b/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/commander_spellbook_bracket_explainer.h @@ -10,7 +10,7 @@ struct BracketExplanationSection struct BracketExplanation { - CommanderSpellbookBracketTag::BracketTag bracket; + QString bracket; QList sections; bool isEmpty() const diff --git a/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/handle_commander_brackets.cpp b/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/handle_commander_brackets.cpp new file mode 100644 index 000000000..1bb00b319 --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/handle_commander_brackets.cpp @@ -0,0 +1,58 @@ +#include "handle_commander_brackets.h" + +#include "../../../../../client/settings/cache_settings.h" +#include "commander_bracket_definitions.h" + +#include +#include +#include + +#define COMMANDER_BRACKET_JSON "https://cockatrice.github.io/commander-brackets.json" + +HandleCommanderBrackets::HandleCommanderBrackets(QObject *parent) + : QObject(parent), nam(new QNetworkAccessManager(this)), reply(nullptr) +{ +} + +void HandleCommanderBrackets::downloadBracketDefinitions() +{ + reply = nam->get(QNetworkRequest(QUrl(COMMANDER_BRACKET_JSON))); + + connect(reply, &QNetworkReply::finished, this, &HandleCommanderBrackets::actFinishParsingDownloadedData); +} + +void HandleCommanderBrackets::actFinishParsingDownloadedData() +{ + reply = qobject_cast(sender()); + + if (reply->error() != QNetworkReply::NoError) { + emit sigBracketDefinitionsDownloadFailed(reply->error()); + + reply->deleteLater(); + return; + } + + QJsonParseError parseError; + + auto document = QJsonDocument::fromJson(reply->readAll(), &parseError); + + if (parseError.error != QJsonParseError::NoError) { + emit sigBracketDefinitionsDownloadFailed(QNetworkReply::UnknownContentError); + + reply->deleteLater(); + return; + } + + updateBracketDefinitions(document.toVariant().toMap()); + + emit sigBracketDefinitionsDownloaded(); + + reply->deleteLater(); +} + +void HandleCommanderBrackets::updateBracketDefinitions(const QVariantMap &jsonMap) +{ + const auto bracketList = jsonMap.value("brackets").toList(); + SettingsCache::instance().commanderBrackets().saveDefinitions(bracketList); + SettingsCache::instance().reloadBracketDefinitions(bracketList); +} \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/handle_commander_brackets.h b/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/handle_commander_brackets.h new file mode 100644 index 000000000..b8348b13c --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/handle_commander_brackets.h @@ -0,0 +1,31 @@ +#ifndef COCKATRICE_HANDLE_COMMANDER_BRACKETS_H +#define COCKATRICE_HANDLE_COMMANDER_BRACKETS_H + +#include +#include +#include + +class HandleCommanderBrackets : public QObject +{ + Q_OBJECT + +public: + explicit HandleCommanderBrackets(QObject *parent = nullptr); + + void downloadBracketDefinitions(); + +signals: + void sigBracketDefinitionsDownloaded(); + void sigBracketDefinitionsDownloadFailed(QNetworkReply::NetworkError error); + +private slots: + void actFinishParsingDownloadedData(); + +private: + void updateBracketDefinitions(const QVariantMap &jsonMap); + + QNetworkAccessManager *nam; + QNetworkReply *reply; +}; + +#endif // COCKATRICE_HANDLE_COMMANDER_BRACKETS_H diff --git a/cockatrice/src/interface/window_main.cpp b/cockatrice/src/interface/window_main.cpp index 69d3260bc..79e25b0e3 100644 --- a/cockatrice/src/interface/window_main.cpp +++ b/cockatrice/src/interface/window_main.cpp @@ -37,6 +37,7 @@ #include "version_string.h" #include "widgets/dialogs/dlg_connect.h" #include "widgets/server/handle_public_servers.h" +#include "widgets/tabs/api/commander_spellbook/handle_commander_brackets.h" #include "widgets/utility/get_text_with_max.h" #include @@ -640,6 +641,7 @@ void MainWindow::alertForcedOracleRun(const QString &version, bool isUpdate) actCheckCardUpdates(); actCheckServerUpdates(); + actCheckCommanderBracketDefinitionUpdates(); } MainWindow::~MainWindow() @@ -984,6 +986,16 @@ void MainWindow::checkClientUpdatesFinished(bool needToUpdate, bool /* isCompati } } +void MainWindow::actCheckCommanderBracketDefinitionUpdates() +{ + auto *handler = new HandleCommanderBrackets(this); + + connect(handler, &HandleCommanderBrackets::sigBracketDefinitionsDownloaded, this, + []() { qDebug() << "Bracket definitions loaded"; }); + + handler->downloadBracketDefinitions(); +} + void MainWindow::refreshShortcuts() { ShortcutsSettings &shortcuts = SettingsCache::instance().shortcuts(); diff --git a/cockatrice/src/interface/window_main.h b/cockatrice/src/interface/window_main.h index 5f631ddc3..bbd1e2dd9 100644 --- a/cockatrice/src/interface/window_main.h +++ b/cockatrice/src/interface/window_main.h @@ -92,6 +92,7 @@ private slots: void cardDatabaseAllNewSetsEnabled(); void checkClientUpdatesFinished(bool needToUpdate, bool isCompatible, Release *release); + void actCheckCommanderBracketDefinitionUpdates(); void actOpenCustomFolder(); void actOpenCustomsetsFolder(); diff --git a/libcockatrice_settings/CMakeLists.txt b/libcockatrice_settings/CMakeLists.txt index 3afe6e00a..e1f24f4f5 100644 --- a/libcockatrice_settings/CMakeLists.txt +++ b/libcockatrice_settings/CMakeLists.txt @@ -5,6 +5,7 @@ set(CMAKE_AUTORCC ON) set(HEADERS libcockatrice/settings/card_database_settings.h libcockatrice/settings/card_override_settings.h + libcockatrice/settings/commander_bracket_settings.h libcockatrice/settings/debug_settings.h libcockatrice/settings/download_settings.h libcockatrice/settings/game_filters_settings.h @@ -26,6 +27,7 @@ add_library( ${MOC_SOURCES} libcockatrice/settings/card_database_settings.cpp libcockatrice/settings/card_override_settings.cpp + libcockatrice/settings/commander_bracket_settings.cpp libcockatrice/settings/debug_settings.cpp libcockatrice/settings/download_settings.cpp libcockatrice/settings/game_filters_settings.cpp diff --git a/libcockatrice_settings/libcockatrice/settings/commander_bracket_settings.cpp b/libcockatrice_settings/libcockatrice/settings/commander_bracket_settings.cpp new file mode 100644 index 000000000..d9b39bbd7 --- /dev/null +++ b/libcockatrice_settings/libcockatrice/settings/commander_bracket_settings.cpp @@ -0,0 +1,137 @@ +#include "commander_bracket_settings.h" + +#include + +QVariantList CommanderBracketSettings::defaultDefinitions() +{ + return { + QVariantMap{{"tag", "R"}, + {"officialName", "[5] cEDH"}, + {"displayName", "Ruthless"}, + {"explanation", + "Top-tier competitive decks with maximum optimization, fast combos, and minimal variance."}}, + QVariantMap{{"tag", "S"}, + {"officialName", "[4] Optimized"}, + {"displayName", "Spicy"}, + {"explanation", "Highly tuned decks with strong synergy and occasional combo finishes."}}, + QVariantMap{{"tag", "P"}, + {"officialName", "[3] Upgraded"}, + {"displayName", "Powerful"}, + {"explanation", "Focused decks with clear win conditions and solid consistency."}}, + QVariantMap{{"tag", "O"}, + {"officialName", "[2] Core"}, + {"displayName", "Oddball"}, + {"explanation", "Unconventional or thematic decks with some structure but non-standard choices."}}, + QVariantMap{{"tag", "PA"}, + {"officialName", "[1] Exhibition"}, + {"displayName", "Precon Appropriate"}, + {"explanation", "Lightly upgraded preconstructed decks or very casual builds."}}, + QVariantMap{{"tag", "C"}, + {"officialName", "[1] Casual"}, + {"displayName", "Casual"}, + {"explanation", "Relaxed decks with no strict optimization goals."}}, + QVariantMap{{"tag", "U"}, + {"officialName", "Unknown"}, + {"displayName", "Unknown"}, + {"explanation", "Unclassified or missing bracket definition."}}, + }; +} + +CommanderBracketSettings::CommanderBracketSettings(const QString &settingPath, QObject *parent) + : SettingsManager(settingPath + "commander_brackets.ini", "commander_brackets", QString(), parent) +{ +} + +void CommanderBracketSettings::setSchemaVersion(int version) +{ + setValue(version, "schemaVersion"); +} + +int CommanderBracketSettings::getSchemaVersion() const +{ + QVariant value = getValue("schemaVersion"); + return value.isValid() ? value.toInt() : 0; +} + +void CommanderBracketSettings::clearDefinitions() +{ + auto settings = getSettings(); + + settings.beginGroup("commander_brackets"); + settings.remove(""); + settings.endGroup(); + + settings.sync(); +} + +void CommanderBracketSettings::saveDefinitions(const QVariantList &definitions) +{ + auto settings = getSettings(); + + settings.beginGroup("commander_brackets"); + + settings.remove(""); + + settings.setValue("schemaVersion", CurrentSchemaVersion); + + for (const auto &entry : definitions) { + QVariantMap map = entry.toMap(); + + QString tag = map.value("tag").toString(); + + if (tag.isEmpty()) { + continue; + } + + settings.beginGroup(tag); + + settings.setValue("officialName", map.value("officialName")); + + settings.setValue("displayName", map.value("displayName")); + + settings.setValue("explanation", map.value("explanation")); + + settings.endGroup(); + } + + settings.endGroup(); + + settings.sync(); +} + +QVariantList CommanderBracketSettings::loadDefinitions() const +{ + QVariantList result; + + auto settings = getSettings(); + + settings.beginGroup("commander_brackets"); + + int version = settings.value("schemaVersion", 0).toInt(); + + if (version != CurrentSchemaVersion) { + settings.endGroup(); + return result; + } + + QStringList groups = settings.childGroups(); + + for (const QString &tag : groups) { + settings.beginGroup(tag); + + QVariantMap map; + + map["tag"] = tag; + map["officialName"] = settings.value("officialName"); + map["displayName"] = settings.value("displayName"); + map["explanation"] = settings.value("explanation"); + + result.append(map); + + settings.endGroup(); + } + + settings.endGroup(); + + return result; +} \ No newline at end of file diff --git a/libcockatrice_settings/libcockatrice/settings/commander_bracket_settings.h b/libcockatrice_settings/libcockatrice/settings/commander_bracket_settings.h new file mode 100644 index 000000000..8ce476d34 --- /dev/null +++ b/libcockatrice_settings/libcockatrice/settings/commander_bracket_settings.h @@ -0,0 +1,35 @@ +#ifndef COMMANDER_BRACKET_SETTINGS_H +#define COMMANDER_BRACKET_SETTINGS_H + +#include "settings_manager.h" + +#include +#include + +class CommanderBracketSettings : public SettingsManager +{ + Q_OBJECT + friend class SettingsCache; + +public: + static constexpr int CurrentSchemaVersion = 1; + + static QVariantList defaultDefinitions(); + + void clearDefinitions(); + + void saveDefinitions(const QVariantList &definitions); + + QVariantList loadDefinitions() const; + + void setSchemaVersion(int version); + int getSchemaVersion() const; + +private: + explicit CommanderBracketSettings(const QString &settingPath, QObject *parent = nullptr); + + CommanderBracketSettings(const CommanderBracketSettings &) = delete; + CommanderBracketSettings &operator=(const CommanderBracketSettings &) = delete; +}; + +#endif // COMMANDER_BRACKET_SETTINGS_H \ No newline at end of file diff --git a/oracle/CMakeLists.txt b/oracle/CMakeLists.txt index a51982625..5b4a31938 100644 --- a/oracle/CMakeLists.txt +++ b/oracle/CMakeLists.txt @@ -32,6 +32,7 @@ set(oracle_SOURCES ../cockatrice/src/interface/theme_manager.cpp ../cockatrice/src/interface/widgets/quick_settings/settings_button_widget.cpp ../cockatrice/src/interface/widgets/quick_settings/settings_popup_widget.cpp + ../cockatrice/src/interface/widgets/tabs/api/commander_spellbook/commander_bracket_definitions.cpp ${VERSION_STRING_CPP} )