mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-09-23 01:55:10 -07:00
[DeckEditor] Use CommanderSpellbook.com to estimate bracket if format is 'commander' (#6415)
* [DeckEditor] Use CommanderSpellbook.com to estimate bracket if format is 'commander' Took 2 minutes Took 16 minutes * Convert json data holder to structs, rename variables, extract widget - Extract bracket estimation UI from DeckEditorDeckDockWidget into a new CommanderBracketWidget - Move CommanderSpellbook integration settings from CardsDisplaySettings to DeckEditorSettings (matching the settings refactor on master) - Rename CommanderSpellbook integration variables to drop the redundant 'DeckEditor' prefix Took 4 minutes # Commit time for manual adjustment: # Took 6 minutes # Commit time for manual adjustment: # Took 8 minutes --------- Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
This commit is contained in:
parent
e9eab328a4
commit
827e44a4d3
35 changed files with 1642 additions and 8 deletions
|
|
@ -0,0 +1,17 @@
|
|||
#include "card_in_deck_request.h"
|
||||
|
||||
CardInDeckRequest CardInDeckRequest::fromJson(const QJsonObject &json)
|
||||
{
|
||||
CardInDeckRequest request;
|
||||
request.card = json.value("card").toString();
|
||||
request.quantity = json.value("quantity").toInt();
|
||||
return request;
|
||||
}
|
||||
|
||||
QJsonObject CardInDeckRequest::toJson() const
|
||||
{
|
||||
QJsonObject json;
|
||||
json.insert("card", card);
|
||||
json.insert("quantity", quantity);
|
||||
return json;
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
#ifndef COCKATRICE_CARD_IN_DECK_REQUEST_H
|
||||
#define COCKATRICE_CARD_IN_DECK_REQUEST_H
|
||||
#include <QJsonObject>
|
||||
|
||||
struct CardInDeckRequest
|
||||
{
|
||||
static CardInDeckRequest fromJson(const QJsonObject &json);
|
||||
QJsonObject toJson() const;
|
||||
|
||||
QString card;
|
||||
int quantity;
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_CARD_IN_DECK_REQUEST_H
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
#include "commander_spellbook_card_result.h"
|
||||
|
||||
CommanderSpellbookCardResult CommanderSpellbookCardResult::fromJson(const QJsonObject &json)
|
||||
{
|
||||
CommanderSpellbookCardResult result;
|
||||
|
||||
result.id = json.value("id").toString();
|
||||
result.name = json.value("name").toString();
|
||||
result.oracleId = json.value("oracleId").toString();
|
||||
result.spoiler = json.value("spoiler").toBool();
|
||||
result.typeLine = json.value("typeLine").toString();
|
||||
|
||||
result.imageUriFrontPng = json.value("imageUriFrontPng").toString();
|
||||
result.imageUriFrontLarge = json.value("imageUriFrontLarge").toString();
|
||||
result.imageUriFrontNormal = json.value("imageUriFrontNormal").toString();
|
||||
result.imageUriFrontSmall = json.value("imageUriFrontSmall").toString();
|
||||
result.imageUriFrontArtCrop = json.value("imageUriFrontArtCrop").toString();
|
||||
|
||||
result.imageUriBackPng = json.value("imageUriBackPng").toString();
|
||||
result.imageUriBackLarge = json.value("imageUriBackLarge").toString();
|
||||
result.imageUriBackNormal = json.value("imageUriBackNormal").toString();
|
||||
result.imageUriBackSmall = json.value("imageUriBackSmall").toString();
|
||||
result.imageUriBackArtCrop = json.value("imageUriBackArtCrop").toString();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
#ifndef COCKATRICE_COMMANDER_SPELLBOOK_CARD_RESULT_H
|
||||
#define COCKATRICE_COMMANDER_SPELLBOOK_CARD_RESULT_H
|
||||
#include <QJsonObject>
|
||||
#include <QString>
|
||||
|
||||
struct CommanderSpellbookCardResult
|
||||
{
|
||||
static CommanderSpellbookCardResult fromJson(const QJsonObject &json);
|
||||
|
||||
QString id;
|
||||
QString name;
|
||||
QString oracleId;
|
||||
bool spoiler = false;
|
||||
QString typeLine;
|
||||
|
||||
QString imageUriFrontPng;
|
||||
QString imageUriFrontLarge;
|
||||
QString imageUriFrontNormal;
|
||||
QString imageUriFrontSmall;
|
||||
QString imageUriFrontArtCrop;
|
||||
|
||||
QString imageUriBackPng;
|
||||
QString imageUriBackLarge;
|
||||
QString imageUriBackNormal;
|
||||
QString imageUriBackSmall;
|
||||
QString imageUriBackArtCrop;
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_COMMANDER_SPELLBOOK_CARD_RESULT_H
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
#include "commander_spellbook_deck_request.h"
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <libcockatrice/deck_list/tree/deck_list_card_node.h>
|
||||
|
||||
CommanderSpellbookDeckRequest CommanderSpellbookDeckRequest::fromJson(const QJsonObject &json)
|
||||
{
|
||||
CommanderSpellbookDeckRequest request;
|
||||
|
||||
// Main deck
|
||||
const QJsonArray mainArray = json.value("main").toArray();
|
||||
for (const QJsonValue &value : mainArray) {
|
||||
if (!value.isObject()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
request.mainDeck.append(CardInDeckRequest::fromJson(value.toObject()));
|
||||
|
||||
// Max size allowed by commanderspellbook
|
||||
if (request.mainDeck.size() >= 600) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Commanders
|
||||
const QJsonArray commanderArray = json.value("commanders").toArray();
|
||||
for (const QJsonValue &value : commanderArray) {
|
||||
if (!value.isObject()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
request.commanderDeck.append(CardInDeckRequest::fromJson(value.toObject()));
|
||||
|
||||
// Max size allowed by commanderspellbook
|
||||
if (request.commanderDeck.size() >= 12) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
QJsonObject CommanderSpellbookDeckRequest::toJson() const
|
||||
{
|
||||
QJsonObject json;
|
||||
|
||||
QJsonArray mainArray;
|
||||
for (const CardInDeckRequest &card : mainDeck) {
|
||||
mainArray.append(card.toJson());
|
||||
}
|
||||
|
||||
QJsonArray commanderArray;
|
||||
for (const CardInDeckRequest &card : commanderDeck) {
|
||||
commanderArray.append(card.toJson());
|
||||
}
|
||||
|
||||
json.insert("main", mainArray);
|
||||
json.insert("commanders", commanderArray);
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
CommanderSpellbookDeckRequest CommanderSpellbookDeckRequest::fromDeckList(const DeckList &deck)
|
||||
{
|
||||
CommanderSpellbookDeckRequest request;
|
||||
|
||||
// --- Mainboard ---
|
||||
const auto mainCards = deck.getCardNodes({DECK_ZONE_MAIN});
|
||||
for (const DecklistCardNode *node : mainCards) {
|
||||
if (!node) {
|
||||
continue;
|
||||
}
|
||||
|
||||
QJsonObject json;
|
||||
json.insert("card", node->getName());
|
||||
json.insert("quantity", node->getNumber());
|
||||
request.mainDeck.append(CardInDeckRequest::fromJson(json));
|
||||
|
||||
// Max size allowed by commanderspellbook
|
||||
if (request.mainDeck.size() >= 600) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// --- Commander (bannerCard) ---
|
||||
const auto &metadata = deck.getMetadata();
|
||||
if (!metadata.bannerCard.name.isEmpty()) {
|
||||
QJsonObject json;
|
||||
json.insert("card", metadata.bannerCard.name);
|
||||
json.insert("quantity", 1);
|
||||
request.commanderDeck.append(CardInDeckRequest::fromJson(json));
|
||||
}
|
||||
|
||||
return request;
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
#ifndef COCKATRICE_COMMANDER_SPELLBOOK_DECK_REQUEST_H
|
||||
#define COCKATRICE_COMMANDER_SPELLBOOK_DECK_REQUEST_H
|
||||
#include "card_in_deck_request.h"
|
||||
#include "libcockatrice/deck_list/deck_list.h"
|
||||
|
||||
#include <QJsonObject>
|
||||
#include <QList>
|
||||
|
||||
struct CommanderSpellbookDeckRequest
|
||||
{
|
||||
static CommanderSpellbookDeckRequest fromJson(const QJsonObject &json);
|
||||
static CommanderSpellbookDeckRequest fromDeckList(const DeckList &deck);
|
||||
QJsonObject toJson() const;
|
||||
|
||||
QList<CardInDeckRequest> mainDeck; // maxItems: 600
|
||||
QList<CardInDeckRequest> commanderDeck; // maxItems: 12
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_COMMANDER_SPELLBOOK_DECK_REQUEST_H
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
#include "commander_spellbook_estimate_bracket_result.h"
|
||||
|
||||
EstimateBracketResult EstimateBracketResult::fromJson(const QJsonObject &json)
|
||||
{
|
||||
EstimateBracketResult result;
|
||||
|
||||
result.bracketTag = json.value("bracketTag").toString();
|
||||
|
||||
//
|
||||
// Cards
|
||||
//
|
||||
for (const auto &value : json.value("cards").toArray()) {
|
||||
if (!value.isObject()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const QJsonObject obj = value.toObject();
|
||||
|
||||
CommanderSpellbookCardResult card = CommanderSpellbookCardResult::fromJson(obj.value("card").toObject());
|
||||
|
||||
if (obj.value("gameChanger").toBool()) {
|
||||
result.gameChangerCards.append(card);
|
||||
}
|
||||
|
||||
if (obj.value("massLandDenial").toBool()) {
|
||||
result.massLandDenialCards.append(card);
|
||||
}
|
||||
|
||||
if (obj.value("extraTurn").toBool()) {
|
||||
result.extraTurnCards.append(card);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Templates
|
||||
//
|
||||
for (const auto &value : json.value("templates").toArray()) {
|
||||
if (!value.isObject()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const QJsonObject obj = value.toObject();
|
||||
|
||||
CommanderSpellbookVariantResult variant = CommanderSpellbookVariantResult::fromJson(obj);
|
||||
|
||||
if (obj.value("massLandDenial").toBool()) {
|
||||
result.massLandDenialTemplates.append(variant);
|
||||
}
|
||||
|
||||
if (obj.value("extraTurn").toBool()) {
|
||||
result.extraTurnTemplates.append(variant);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Combos
|
||||
//
|
||||
for (const auto &value : json.value("combos").toArray()) {
|
||||
if (!value.isObject()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const QJsonObject obj = value.toObject();
|
||||
|
||||
CommanderSpellbookVariantResult combo = CommanderSpellbookVariantResult::fromJson(obj);
|
||||
|
||||
if (obj.value("massLandDenial").toBool()) {
|
||||
result.massLandDenialCombos.append(combo);
|
||||
}
|
||||
|
||||
if (obj.value("extraTurn").toBool()) {
|
||||
result.extraTurnCombos.append(combo);
|
||||
}
|
||||
|
||||
if (obj.value("lock").toBool()) {
|
||||
result.lockCombos.append(combo);
|
||||
}
|
||||
|
||||
if (obj.value("skipTurns").toBool()) {
|
||||
result.skipTurnsCombos.append(combo);
|
||||
}
|
||||
|
||||
if (obj.value("definitelyTwoCard").toBool()) {
|
||||
result.definitelyTwoCardCombos.append(combo);
|
||||
}
|
||||
|
||||
if (obj.value("arguablyTwoCard").toBool()) {
|
||||
result.arguablyTwoCardCombos.append(combo);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
#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"
|
||||
|
||||
#include <QJsonObject>
|
||||
#include <QList>
|
||||
|
||||
struct EstimateBracketResult
|
||||
{
|
||||
static EstimateBracketResult fromJson(const QJsonObject &json);
|
||||
|
||||
QString bracketTag;
|
||||
|
||||
QList<CommanderSpellbookCardResult> gameChangerCards;
|
||||
QList<CommanderSpellbookCardResult> massLandDenialCards;
|
||||
QList<CommanderSpellbookCardResult> extraTurnCards;
|
||||
|
||||
QList<CommanderSpellbookVariantResult> massLandDenialTemplates;
|
||||
QList<CommanderSpellbookVariantResult> extraTurnTemplates;
|
||||
|
||||
QList<CommanderSpellbookVariantResult> massLandDenialCombos;
|
||||
QList<CommanderSpellbookVariantResult> extraTurnCombos;
|
||||
QList<CommanderSpellbookVariantResult> lockCombos;
|
||||
QList<CommanderSpellbookVariantResult> skipTurnsCombos;
|
||||
|
||||
QList<CommanderSpellbookVariantResult> definitelyTwoCardCombos;
|
||||
QList<CommanderSpellbookVariantResult> arguablyTwoCardCombos;
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_COMMANDER_SPELLBOOK_ESTIMATE_BRACKET_RESULT_H
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
#include "commander_spellbook_variant_result.h"
|
||||
|
||||
CommanderSpellbookVariantResult CommanderSpellbookVariantResult::fromJson(const QJsonObject &json)
|
||||
{
|
||||
CommanderSpellbookVariantResult result;
|
||||
|
||||
result.id = json.value("id").toString();
|
||||
result.status = json.value("status").toString();
|
||||
|
||||
result.uses = json.value("uses").toArray();
|
||||
result.cardRequires = json.value("requires").toArray();
|
||||
result.produces = json.value("produces").toArray();
|
||||
result.of = json.value("of").toArray();
|
||||
result.includes = json.value("includes").toArray();
|
||||
|
||||
result.manaNeeded = json.value("manaNeeded").toArray();
|
||||
result.manaValueNeeded = json.value("manaValueNeeded").toArray();
|
||||
|
||||
result.easyPrerequisites = json.value("easyPrerequisites").toArray();
|
||||
result.notablePrerequisites = json.value("notablePrerequisites").toArray();
|
||||
|
||||
result.description = json.value("description").toString();
|
||||
result.notes = json.value("notes").toString();
|
||||
result.popularity = json.value("popularity").toDouble();
|
||||
|
||||
result.spoiler = json.value("spoiler").toBool();
|
||||
result.bracketTag = json.value("bracketTag").toString();
|
||||
|
||||
result.legalities = json.value("legalities").toObject();
|
||||
result.prices = json.value("prices").toObject();
|
||||
|
||||
result.variantCount = json.value("variantCount").toInt();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
#ifndef COCKATRICE_COMMANDER_SPELLBOOK_VARIANT_RESULT_H
|
||||
#define COCKATRICE_COMMANDER_SPELLBOOK_VARIANT_RESULT_H
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
|
||||
struct CommanderSpellbookVariantResult
|
||||
{
|
||||
static CommanderSpellbookVariantResult fromJson(const QJsonObject &json);
|
||||
|
||||
QString id;
|
||||
QString status;
|
||||
|
||||
QJsonArray uses;
|
||||
QJsonArray cardRequires;
|
||||
QJsonArray produces;
|
||||
QJsonArray of;
|
||||
QJsonArray includes;
|
||||
|
||||
QJsonArray manaNeeded;
|
||||
QJsonArray manaValueNeeded;
|
||||
|
||||
QJsonArray easyPrerequisites;
|
||||
QJsonArray notablePrerequisites;
|
||||
|
||||
QString description;
|
||||
QString notes;
|
||||
double popularity = 0.0;
|
||||
|
||||
bool spoiler = false;
|
||||
QString bracketTag;
|
||||
|
||||
QJsonObject legalities;
|
||||
QJsonObject prices;
|
||||
|
||||
int variantCount = 0;
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_COMMANDER_SPELLBOOK_VARIANT_RESULT_H
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
#include "commander_bracket_service.h"
|
||||
|
||||
#include "../../../../../client/settings/cache_settings.h"
|
||||
|
||||
#include <libcockatrice/settings/commander_bracket_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 &brackets = SettingsCache::instance().commanderBrackets();
|
||||
|
||||
estimate.officialName = brackets.officialName(result.bracketTag);
|
||||
|
||||
estimate.displayName = brackets.displayName(result.bracketTag);
|
||||
|
||||
estimate.explanation = brackets.explanation(result.bracketTag);
|
||||
|
||||
emit estimateFinished(id, requester, estimate);
|
||||
}
|
||||
|
||||
void CommanderBracketService::onEstimateBracketError(CommanderSpellbookApiAccessor::RequestId id,
|
||||
QObject *requester,
|
||||
const QString &error)
|
||||
{
|
||||
emit estimateError(id, requester, error);
|
||||
}
|
||||
|
|
@ -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 <QObject>
|
||||
|
||||
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
|
||||
|
|
@ -0,0 +1,269 @@
|
|||
#include "commander_bracket_widget.h"
|
||||
|
||||
#include "../../../../../client/settings/cache_settings.h"
|
||||
#include "commander_bracket_service.h"
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QDialog>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QFormLayout>
|
||||
#include <QGridLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
#include <QTimer>
|
||||
#include <QToolButton>
|
||||
#include <QVBoxLayout>
|
||||
#include <libcockatrice/settings/deck_editor_settings.h>
|
||||
|
||||
CommanderBracketWidget::CommanderBracketWidget(QWidget *parent) : QWidget(parent)
|
||||
{
|
||||
bracketLabel = new QLabel(tr("Bracket:"), this);
|
||||
|
||||
bracketValueLabel = new QLabel(this);
|
||||
bracketValueLabel->setText("-");
|
||||
bracketValueLabel->setObjectName("bracketValueLabel");
|
||||
|
||||
bracketInfoButton = new QToolButton(this);
|
||||
bracketInfoButton->setText("?");
|
||||
bracketInfoButton->setAutoRaise(true);
|
||||
bracketInfoButton->setEnabled(false);
|
||||
|
||||
bracketRefreshButton = new QToolButton(this);
|
||||
bracketRefreshButton->setIcon(QPixmap("theme:icons/reload"));
|
||||
bracketRefreshButton->setAutoRaise(true);
|
||||
|
||||
connect(bracketRefreshButton, &QToolButton::clicked, this, &CommanderBracketWidget::requestBracketEstimate);
|
||||
|
||||
auto *layout = new QGridLayout(this);
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
layout->addWidget(bracketLabel, 0, 0);
|
||||
|
||||
auto *bracketRow = new QHBoxLayout;
|
||||
bracketRow->addWidget(bracketValueLabel);
|
||||
bracketRow->addWidget(bracketInfoButton);
|
||||
bracketRow->addWidget(bracketRefreshButton);
|
||||
bracketRow->addStretch();
|
||||
|
||||
layout->addLayout(bracketRow, 0, 1);
|
||||
|
||||
connect(&CommanderBracketService::instance(), &CommanderBracketService::estimateFinished, this,
|
||||
&CommanderBracketWidget::onEstimateBracketFinished);
|
||||
connect(&CommanderBracketService::instance(), &CommanderBracketService::estimateError, this,
|
||||
&CommanderBracketWidget::onEstimateBracketError);
|
||||
|
||||
connect(&SettingsCache::instance().deckEditor(), &DeckEditorSettings::commanderSpellbookIntegrationEnabledChanged,
|
||||
this, &CommanderBracketWidget::maybeAutoEstimateBracket);
|
||||
connect(&SettingsCache::instance().deckEditor(),
|
||||
&DeckEditorSettings::commanderSpellbookIntegrationUseOfficialBracketNamesChanged, this,
|
||||
&CommanderBracketWidget::maybeAutoEstimateBracket);
|
||||
|
||||
setVisible(false);
|
||||
}
|
||||
|
||||
void CommanderBracketWidget::setDeck(const QSharedPointer<DeckList> &_deck)
|
||||
{
|
||||
deck = _deck;
|
||||
requestId = 0; // invalidate any in-flight estimate for the previous deck
|
||||
|
||||
// Reset the displayed bracket
|
||||
bracketValueLabel->setText("-");
|
||||
bracketInfoButton->setToolTip({});
|
||||
bracketInfoButton->setEnabled(false);
|
||||
bracketRefreshButton->setEnabled(true);
|
||||
|
||||
maybeAutoEstimateBracket();
|
||||
}
|
||||
|
||||
bool CommanderBracketWidget::promptCommanderSpellbookIntegration()
|
||||
{
|
||||
QDialog dialog(this);
|
||||
dialog.setWindowTitle(tr("CommanderSpellbook integration"));
|
||||
|
||||
auto *mainLayout = new QVBoxLayout(&dialog);
|
||||
|
||||
// Main text
|
||||
auto *label = new QLabel(tr("CommanderSpellbook can analyze your deck and estimate its Commander bracket.\n\n"
|
||||
"This sends your deck list to an external service.\n\n"
|
||||
"CommanderSpellbook uses its own bracket naming system based on their own algorithm. "
|
||||
"These names can be mapped to the official Commander brackets, but the mapping "
|
||||
"is only an approximation."));
|
||||
label->setWordWrap(true);
|
||||
mainLayout->addWidget(label);
|
||||
|
||||
// Naming selector
|
||||
auto *formLayout = new QFormLayout;
|
||||
auto *namingCombo = new QComboBox(&dialog);
|
||||
namingCombo->addItem(tr("CommanderSpellbook bracket names"));
|
||||
namingCombo->addItem(tr("Official Commander bracket names (approximate)"));
|
||||
namingCombo->setCurrentIndex(
|
||||
SettingsCache::instance().deckEditor().getCommanderSpellbookIntegrationUseOfficialBracketNames() ? 1 : 0);
|
||||
|
||||
// Create label + explainer button
|
||||
auto *labelWidget = new QWidget(&dialog);
|
||||
auto *labelLayout = new QHBoxLayout(labelWidget);
|
||||
labelLayout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
auto *namingLabel = new QLabel(tr("Bracket naming:"), labelWidget);
|
||||
auto *explainerButton = new QToolButton(labelWidget);
|
||||
explainerButton->setText("?");
|
||||
explainerButton->setAutoRaise(true);
|
||||
explainerButton->setEnabled(false);
|
||||
explainerButton->setToolTip(CommanderBracketNames::Explainer);
|
||||
|
||||
labelLayout->addWidget(namingLabel);
|
||||
labelLayout->addWidget(explainerButton);
|
||||
labelLayout->addStretch(); // push the button next to label, combo stays aligned
|
||||
|
||||
// Add row with the custom label widget
|
||||
formLayout->addRow(labelWidget, namingCombo);
|
||||
mainLayout->addLayout(formLayout);
|
||||
|
||||
// Buttons
|
||||
auto *buttonBox = new QDialogButtonBox(&dialog);
|
||||
auto *enableBtn = buttonBox->addButton(tr("Enable"), QDialogButtonBox::AcceptRole);
|
||||
auto *automaticBtn = buttonBox->addButton(tr("Automatic"), QDialogButtonBox::ApplyRole);
|
||||
auto *disableBtn = buttonBox->addButton(tr("Disable"), QDialogButtonBox::RejectRole);
|
||||
mainLayout->addWidget(buttonBox);
|
||||
|
||||
// Track which button was clicked
|
||||
QAbstractButton *clickedButton = nullptr;
|
||||
QObject::connect(buttonBox, &QDialogButtonBox::clicked, &dialog, [&](QAbstractButton *btn) {
|
||||
clickedButton = btn;
|
||||
dialog.accept();
|
||||
});
|
||||
|
||||
dialog.exec();
|
||||
|
||||
// Persist naming choice (if not disabled)
|
||||
if (clickedButton != disableBtn) {
|
||||
bool useOfficial = namingCombo->currentIndex() == 1;
|
||||
SettingsCache::instance().deckEditor().setCommanderSpellbookIntegrationUseOfficialBracketNames(useOfficial);
|
||||
}
|
||||
|
||||
// Persist integration mode
|
||||
if (clickedButton == disableBtn) {
|
||||
SettingsCache::instance().deckEditor().setCommanderSpellbookIntegrationEnabled(
|
||||
commanderSpellbookIntegrationEnabledIndexDisabled);
|
||||
return false;
|
||||
}
|
||||
if (clickedButton == enableBtn) {
|
||||
SettingsCache::instance().deckEditor().setCommanderSpellbookIntegrationEnabled(
|
||||
commanderSpellbookIntegrationEnabledIndexEnabled);
|
||||
return true;
|
||||
}
|
||||
if (clickedButton == automaticBtn) {
|
||||
SettingsCache::instance().deckEditor().setCommanderSpellbookIntegrationEnabled(
|
||||
commanderSpellbookIntegrationEnabledIndexAutomatic);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void CommanderBracketWidget::updateBracketVisibility(bool visible)
|
||||
{
|
||||
setVisible(visible);
|
||||
}
|
||||
|
||||
void CommanderBracketWidget::requestBracketEstimate()
|
||||
{
|
||||
bracketRefreshButton->setEnabled(false);
|
||||
bracketInfoButton->setEnabled(false);
|
||||
bracketValueLabel->setText(tr("Calculating…"));
|
||||
|
||||
requestId = CommanderBracketService::instance().estimateBracket(*deck, this);
|
||||
}
|
||||
|
||||
void CommanderBracketWidget::onEstimateBracketFinished(quint64 id,
|
||||
QObject *requester,
|
||||
const CommanderBracketEstimate &result)
|
||||
{
|
||||
if (requester != this || id != requestId) {
|
||||
return;
|
||||
}
|
||||
|
||||
BracketExplainer explainer;
|
||||
lastBracketExplanation = explainer.explain(result.rawResult);
|
||||
|
||||
// Display bracket
|
||||
bracketValueLabel->setText(
|
||||
SettingsCache::instance().deckEditor().getCommanderSpellbookIntegrationUseOfficialBracketNames()
|
||||
? result.officialName
|
||||
: result.displayName);
|
||||
bracketRefreshButton->setEnabled(true);
|
||||
|
||||
// Build tooltip
|
||||
QString tooltip;
|
||||
for (const auto §ion : lastBracketExplanation.sections) {
|
||||
tooltip += "<b>" + section.title + "</b><br>";
|
||||
for (const auto &line : section.bulletPoints) {
|
||||
tooltip += "• " + line + "<br>";
|
||||
}
|
||||
tooltip += "<br>";
|
||||
}
|
||||
|
||||
bracketInfoButton->setToolTip(tooltip);
|
||||
bracketInfoButton->setEnabled(!tooltip.isEmpty());
|
||||
}
|
||||
|
||||
void CommanderBracketWidget::onEstimateBracketError(quint64 id, QObject *requester, const QString & /*error*/)
|
||||
{
|
||||
if (requester != this || id != requestId) {
|
||||
return;
|
||||
}
|
||||
|
||||
bracketValueLabel->setText("-");
|
||||
bracketRefreshButton->setEnabled(true);
|
||||
bracketInfoButton->setToolTip({});
|
||||
bracketInfoButton->setEnabled(false);
|
||||
}
|
||||
|
||||
void CommanderBracketWidget::maybeAutoEstimateBracket()
|
||||
{
|
||||
const QString formatKey = deck->getGameFormat();
|
||||
|
||||
const bool isCommander = (formatKey.compare("commander", Qt::CaseInsensitive) == 0);
|
||||
|
||||
int mode = SettingsCache::instance().deckEditor().getCommanderSpellbookIntegrationEnabled();
|
||||
|
||||
if (!isCommander || mode == commanderSpellbookIntegrationEnabledIndexDisabled) {
|
||||
updateBracketVisibility(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mode == commanderSpellbookIntegrationEnabledIndexUnprompted) {
|
||||
if (prompting) {
|
||||
return;
|
||||
}
|
||||
prompting = true;
|
||||
const bool accepted = promptCommanderSpellbookIntegration();
|
||||
prompting = false;
|
||||
if (!accepted) {
|
||||
updateBracketVisibility(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
updateBracketVisibility(true);
|
||||
|
||||
mode = SettingsCache::instance().deckEditor().getCommanderSpellbookIntegrationEnabled();
|
||||
if (mode != commanderSpellbookIntegrationEnabledIndexAutomatic) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Avoid firing if we already have a result or a request in flight
|
||||
if (!bracketRefreshButton->isEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Defer to avoid races during init / model rebuild
|
||||
QTimer::singleShot(0, this, &CommanderBracketWidget::requestBracketEstimate);
|
||||
}
|
||||
|
||||
void CommanderBracketWidget::retranslateUi()
|
||||
{
|
||||
bracketLabel->setText(tr("Bracket:"));
|
||||
bracketInfoButton->setToolTip(tr("Why this bracket?"));
|
||||
bracketRefreshButton->setToolTip(tr("Recalculate bracket"));
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
#ifndef COCKATRICE_COMMANDER_BRACKET_WIDGET_H
|
||||
#define COCKATRICE_COMMANDER_BRACKET_WIDGET_H
|
||||
|
||||
#include "commander_spellbook_bracket_explainer.h"
|
||||
|
||||
#include <QSharedPointer>
|
||||
#include <QWidget>
|
||||
#include <libcockatrice/deck_list/deck_list.h>
|
||||
|
||||
class QLabel;
|
||||
class QToolButton;
|
||||
struct CommanderBracketEstimate;
|
||||
|
||||
class CommanderBracketWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit CommanderBracketWidget(QWidget *parent = nullptr);
|
||||
|
||||
void setDeck(const QSharedPointer<DeckList> &_deck);
|
||||
void retranslateUi();
|
||||
|
||||
private slots:
|
||||
void requestBracketEstimate();
|
||||
void onEstimateBracketFinished(quint64 id, QObject *requester, const CommanderBracketEstimate &result);
|
||||
void onEstimateBracketError(quint64 id, QObject *requester, const QString &error);
|
||||
void maybeAutoEstimateBracket();
|
||||
|
||||
private:
|
||||
bool promptCommanderSpellbookIntegration();
|
||||
void updateBracketVisibility(bool visible);
|
||||
|
||||
QSharedPointer<DeckList> deck;
|
||||
bool prompting = false;
|
||||
quint64 requestId = 0;
|
||||
|
||||
QLabel *bracketLabel;
|
||||
QLabel *bracketValueLabel;
|
||||
QToolButton *bracketInfoButton;
|
||||
QToolButton *bracketRefreshButton;
|
||||
|
||||
BracketExplanation lastBracketExplanation;
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_COMMANDER_BRACKET_WIDGET_H
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
#include "commander_spellbook_api_accessor.h"
|
||||
|
||||
#include "api_response/commander_spellbook_deck_request.h"
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QNetworkRequest>
|
||||
#include <QUrl>
|
||||
#include <version_string.h>
|
||||
|
||||
static const QUrl ESTIMATE_BRACKET_URL(QStringLiteral("https://backend.commanderspellbook.com/estimate-bracket"));
|
||||
|
||||
CommanderSpellbookApiAccessor &CommanderSpellbookApiAccessor::instance()
|
||||
{
|
||||
static CommanderSpellbookApiAccessor instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
CommanderSpellbookApiAccessor::CommanderSpellbookApiAccessor(QObject *parent) : QObject(parent)
|
||||
{
|
||||
}
|
||||
|
||||
CommanderSpellbookApiAccessor::RequestId CommanderSpellbookApiAccessor::estimateBracket(const DeckList &deck,
|
||||
QObject *requester)
|
||||
{
|
||||
CommanderSpellbookDeckRequest deckRequest = CommanderSpellbookDeckRequest::fromDeckList(deck);
|
||||
|
||||
QJsonDocument doc(deckRequest.toJson());
|
||||
QByteArray body = doc.toJson(QJsonDocument::Compact);
|
||||
|
||||
QNetworkRequest req(ESTIMATE_BRACKET_URL);
|
||||
req.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
req.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING));
|
||||
|
||||
QNetworkReply *reply = network.post(req, body);
|
||||
|
||||
const RequestId id = nextRequestId++;
|
||||
|
||||
reply->setProperty("requestId", QVariant::fromValue(id));
|
||||
reply->setProperty("requester", QVariant::fromValue(requester));
|
||||
|
||||
connect(reply, &QNetworkReply::finished, this, [this, reply]() { onEstimateReplyFinished(reply); });
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
void CommanderSpellbookApiAccessor::onEstimateReplyFinished(QNetworkReply *reply)
|
||||
{
|
||||
reply->deleteLater();
|
||||
|
||||
const RequestId id = reply->property("requestId").toULongLong();
|
||||
QObject *requester = reply->property("requester").value<QObject *>();
|
||||
|
||||
if (!requester) {
|
||||
// Requester died — silently drop
|
||||
return;
|
||||
}
|
||||
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
emit estimateBracketError(id, requester, reply->errorString());
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonParseError err;
|
||||
QJsonDocument doc = QJsonDocument::fromJson(reply->readAll(), &err);
|
||||
|
||||
if (err.error != QJsonParseError::NoError || !doc.isObject()) {
|
||||
emit estimateBracketError(id, requester, QStringLiteral("Invalid JSON response"));
|
||||
return;
|
||||
}
|
||||
|
||||
EstimateBracketResult result = EstimateBracketResult::fromJson(doc.object());
|
||||
|
||||
emit estimateBracketFinished(id, requester, result);
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
#ifndef COCKATRICE_COMMANDER_SPELLBOOK_API_ACCESSOR_H
|
||||
#define COCKATRICE_COMMANDER_SPELLBOOK_API_ACCESSOR_H
|
||||
|
||||
#include "api_response/commander_spellbook_estimate_bracket_result.h"
|
||||
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QObject>
|
||||
#include <libcockatrice/deck_list/deck_list.h>
|
||||
|
||||
class CommanderSpellbookApiAccessor final : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
static CommanderSpellbookApiAccessor &instance();
|
||||
|
||||
using RequestId = quint64;
|
||||
|
||||
RequestId estimateBracket(const DeckList &deck, QObject *requester);
|
||||
|
||||
signals:
|
||||
void estimateBracketFinished(RequestId id, QObject *requester, const EstimateBracketResult &result);
|
||||
|
||||
void estimateBracketError(RequestId id, QObject *requester, const QString &errorMessage);
|
||||
|
||||
private:
|
||||
explicit CommanderSpellbookApiAccessor(QObject *parent = nullptr);
|
||||
Q_DISABLE_COPY_MOVE(CommanderSpellbookApiAccessor)
|
||||
|
||||
void onEstimateReplyFinished(QNetworkReply *reply);
|
||||
|
||||
QNetworkAccessManager network;
|
||||
RequestId nextRequestId = 1;
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_COMMANDER_SPELLBOOK_API_ACCESSOR_H
|
||||
|
|
@ -0,0 +1,120 @@
|
|||
#include "commander_spellbook_bracket_explainer.h"
|
||||
|
||||
static QString cardList(const QList<CommanderSpellbookCardResult> &cards, int max = 5)
|
||||
{
|
||||
QStringList names;
|
||||
for (int i = 0; i < cards.size() && i < max; ++i) {
|
||||
names << cards[i].name;
|
||||
}
|
||||
|
||||
if (cards.size() > max) {
|
||||
names << QString("and %1 more").arg(cards.size() - max);
|
||||
}
|
||||
|
||||
return names.join(", ");
|
||||
}
|
||||
|
||||
static QString comboCount(const QList<CommanderSpellbookVariantResult> &variants)
|
||||
{
|
||||
return QString::number(variants.size());
|
||||
}
|
||||
|
||||
BracketExplanation BracketExplainer::explain(const EstimateBracketResult &r)
|
||||
{
|
||||
BracketExplanation out;
|
||||
out.bracket = r.bracketTag;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if (!r.extraTurnCards.isEmpty() || !r.extraTurnTemplates.isEmpty() || !r.extraTurnCombos.isEmpty()) {
|
||||
|
||||
BracketExplanationSection s;
|
||||
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;
|
||||
}
|
||||
|
||||
if (!r.massLandDenialCards.isEmpty() || !r.massLandDenialTemplates.isEmpty() || !r.massLandDenialCombos.isEmpty()) {
|
||||
|
||||
BracketExplanationSection s;
|
||||
s.title = "Mass land denial";
|
||||
|
||||
if (!r.massLandDenialCards.isEmpty()) {
|
||||
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));
|
||||
}
|
||||
|
||||
out.sections << s;
|
||||
}
|
||||
|
||||
if (!r.lockCombos.isEmpty() || !r.skipTurnsCombos.isEmpty()) {
|
||||
|
||||
BracketExplanationSection s;
|
||||
s.title = "Lock pieces";
|
||||
|
||||
if (!r.lockCombos.isEmpty()) {
|
||||
s.bulletPoints << QString("%1 lock combo variants were detected.").arg(comboCount(r.lockCombos));
|
||||
}
|
||||
|
||||
if (!r.skipTurnsCombos.isEmpty()) {
|
||||
s.bulletPoints << QString("%1 skip-turn combo variants were detected.").arg(comboCount(r.skipTurnsCombos));
|
||||
}
|
||||
|
||||
out.sections << s;
|
||||
}
|
||||
|
||||
if (!r.definitelyTwoCardCombos.isEmpty() || !r.arguablyTwoCardCombos.isEmpty()) {
|
||||
|
||||
BracketExplanationSection s;
|
||||
s.title = "Two-card combos";
|
||||
|
||||
if (!r.definitelyTwoCardCombos.isEmpty()) {
|
||||
s.bulletPoints << QString("%1 definite two-card combo variants were identified.")
|
||||
.arg(comboCount(r.definitelyTwoCardCombos));
|
||||
}
|
||||
|
||||
if (!r.arguablyTwoCardCombos.isEmpty()) {
|
||||
s.bulletPoints << QString("%1 arguable two-card combo variants were identified.")
|
||||
.arg(comboCount(r.arguablyTwoCardCombos));
|
||||
}
|
||||
|
||||
out.sections << s;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
#ifndef COCKATRICE_COMMANDER_SPELLBOOK_BRACKET_EXPLAINER_H
|
||||
#define COCKATRICE_COMMANDER_SPELLBOOK_BRACKET_EXPLAINER_H
|
||||
#include "api_response/commander_spellbook_estimate_bracket_result.h"
|
||||
|
||||
namespace CommanderBracketNames
|
||||
{
|
||||
inline const char *CommanderSpellbookBracketNames = QT_TR_NOOP("CommanderSpellbook");
|
||||
inline const char *OfficialCommanderBracketNames = QT_TR_NOOP("Official (approximate)");
|
||||
inline const char *Explainer = QT_TR_NOOP(
|
||||
"The bracket system combines both objective data, as well as subjective play experience to estimate a "
|
||||
"bracket for a deck.\nCommanderSpellbook's estimation is algorithmical, which means that it can only operate "
|
||||
"on the objective data, not the subjective intent. \nThey have chosen to represent this by defining their "
|
||||
"own bracket system which matches their algorithm.\n"
|
||||
"This custom bracket system maps loosely to the standard system. \nYou may choose to use these mapped "
|
||||
"standardized names if these are more familiar to you, however, you should keep in mind that these are just "
|
||||
"rough estimations.\n\nAlways consider the subjective factors of the bracket system when determing a deck's "
|
||||
"final bracket!");
|
||||
} // namespace CommanderBracketNames
|
||||
|
||||
struct BracketExplanationSection
|
||||
{
|
||||
QString title;
|
||||
QStringList bulletPoints;
|
||||
};
|
||||
|
||||
struct BracketExplanation
|
||||
{
|
||||
QString bracket;
|
||||
QList<BracketExplanationSection> sections;
|
||||
|
||||
bool isEmpty() const
|
||||
{
|
||||
return sections.isEmpty();
|
||||
}
|
||||
};
|
||||
|
||||
class BracketExplainer
|
||||
{
|
||||
public:
|
||||
static BracketExplanation explain(const EstimateBracketResult &result);
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_COMMANDER_SPELLBOOK_BRACKET_EXPLAINER_H
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
#include "handle_commander_brackets.h"
|
||||
|
||||
#include "../../../../../client/settings/cache_settings.h"
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QUrl>
|
||||
#include <libcockatrice/settings/commander_bracket_settings.h>
|
||||
|
||||
static const QUrl COMMANDER_BRACKET_JSON_URL(QStringLiteral("https://cockatrice.github.io/commander-brackets.json"));
|
||||
|
||||
HandleCommanderBrackets::HandleCommanderBrackets(QObject *parent)
|
||||
: QObject(parent), nam(new QNetworkAccessManager(this)), reply(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
void HandleCommanderBrackets::downloadBracketDefinitions()
|
||||
{
|
||||
if (reply) {
|
||||
return;
|
||||
}
|
||||
|
||||
reply = nam->get(QNetworkRequest(COMMANDER_BRACKET_JSON_URL));
|
||||
|
||||
connect(reply, &QNetworkReply::finished, this, &HandleCommanderBrackets::actFinishParsingDownloadedData);
|
||||
}
|
||||
|
||||
void HandleCommanderBrackets::actFinishParsingDownloadedData()
|
||||
{
|
||||
reply = qobject_cast<QNetworkReply *>(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().commanderBrackets().reloadDefinitions(bracketList);
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
#ifndef COCKATRICE_HANDLE_COMMANDER_BRACKETS_H
|
||||
#define COCKATRICE_HANDLE_COMMANDER_BRACKETS_H
|
||||
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QObject>
|
||||
|
||||
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
|
||||
Loading…
Add table
Add a link
Reference in a new issue