mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-09-24 02:13:02 -07:00
Merge branch 'master' into tooomm-qt5
This commit is contained in:
commit
bbf2412157
219 changed files with 7310 additions and 1824 deletions
|
|
@ -44,7 +44,7 @@
|
|||
#include <libcockatrice/protocol/pb/command_deck_upload.pb.h>
|
||||
#include <libcockatrice/protocol/pb/response.pb.h>
|
||||
#include <libcockatrice/protocol/pending_command.h>
|
||||
#include <libcockatrice/settings/interface_settings.h>
|
||||
#include <libcockatrice/settings/deck_editor_settings.h>
|
||||
#include <libcockatrice/settings/paths_settings.h>
|
||||
#include <libcockatrice/settings/recents_settings.h>
|
||||
#include <libcockatrice/utility/string_limits.h>
|
||||
|
|
@ -203,7 +203,7 @@ void AbstractTabDeckEditor::cleanDeckAndResetModified()
|
|||
*/
|
||||
AbstractTabDeckEditor::DeckOpenLocation AbstractTabDeckEditor::confirmOpen(const bool openInSameTabIfBlank)
|
||||
{
|
||||
if (SettingsCache::instance().interface().getOpenDeckInNewTab()) {
|
||||
if (SettingsCache::instance().deckEditor().getOpenDeckInNewTab()) {
|
||||
if (openInSameTabIfBlank && deckStateManager->isBlankNewDeck()) {
|
||||
return SAME_TAB;
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ public slots:
|
|||
|
||||
protected:
|
||||
void mousePressEvent(QMouseEvent *event) override;
|
||||
void enterEvent(QEnterEvent *event) override;
|
||||
void enterEvent(QEnterEvent *event) override; ///< Hover enter
|
||||
void leaveEvent(QEvent *event) override;
|
||||
|
||||
private:
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@
|
|||
#include <libcockatrice/card/database/card_database_manager.h>
|
||||
#include <libcockatrice/models/database/card/card_completer_proxy_model.h>
|
||||
#include <libcockatrice/models/database/card/card_search_model.h>
|
||||
#include <libcockatrice/settings/visual_deck_storage_settings.h>
|
||||
#include <libcockatrice/settings/cards_display_settings.h>
|
||||
#include <version_string.h>
|
||||
|
||||
TabArchidekt::TabArchidekt(TabSupervisor *_tabSupervisor)
|
||||
|
|
@ -132,8 +132,8 @@ void TabArchidekt::initializeUi()
|
|||
|
||||
// Settings
|
||||
settingsButton = new SettingsButtonWidget(primaryToolbar);
|
||||
cardSizeSlider = new CardSizeWidget(primaryToolbar, nullptr,
|
||||
SettingsCache::instance().visualDeckStorage().getArchidektPreviewSize());
|
||||
cardSizeSlider =
|
||||
new CardSizeWidget(primaryToolbar, nullptr, SettingsCache::instance().cardsDisplay().getArchidektPreviewSize());
|
||||
settingsButton->addSettingsWidget(cardSizeSlider);
|
||||
|
||||
// Assemble primary toolbar
|
||||
|
|
@ -339,8 +339,8 @@ void TabArchidekt::connectSignals()
|
|||
doSearch();
|
||||
});
|
||||
|
||||
connect(cardSizeSlider, &CardSizeWidget::cardSizeSettingUpdated, &SettingsCache::instance().visualDeckStorage(),
|
||||
&VisualDeckStorageSettings::setArchidektPreviewCardSize);
|
||||
connect(cardSizeSlider, &CardSizeWidget::cardSizeSettingUpdated, &SettingsCache::instance().cardsDisplay(),
|
||||
&CardsDisplaySettings::setArchidektPreviewCardSize);
|
||||
|
||||
// Search button triggers immediate search
|
||||
connect(searchButton, &QPushButton::clicked, this, &TabArchidekt::doSearchImmediate);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -38,8 +38,7 @@ private:
|
|||
|
||||
protected slots:
|
||||
void mousePressEvent(QMouseEvent *event) override;
|
||||
|
||||
void enterEvent(QEnterEvent *event) override;
|
||||
void enterEvent(QEnterEvent *event) override; ///< Hover enter
|
||||
void leaveEvent(QEvent *event) override;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@
|
|||
#include <libcockatrice/card/database/card_database_manager.h>
|
||||
#include <libcockatrice/models/database/card/card_completer_proxy_model.h>
|
||||
#include <libcockatrice/models/database/card/card_search_model.h>
|
||||
#include <libcockatrice/settings/visual_deck_storage_settings.h>
|
||||
#include <libcockatrice/settings/cards_display_settings.h>
|
||||
#include <version_string.h>
|
||||
|
||||
static bool canBeCommander(const CardInfoPtr &cardInfo)
|
||||
|
|
@ -95,10 +95,9 @@ TabEdhRecMain::TabEdhRecMain(TabSupervisor *_tabSupervisor) : Tab(_tabSupervisor
|
|||
|
||||
settingsButton = new SettingsButtonWidget(this);
|
||||
|
||||
cardSizeSlider =
|
||||
new CardSizeWidget(this, nullptr, SettingsCache::instance().visualDeckStorage().getEDHRecCardSize());
|
||||
connect(cardSizeSlider, &CardSizeWidget::cardSizeSettingUpdated, &SettingsCache::instance().visualDeckStorage(),
|
||||
&VisualDeckStorageSettings::setEDHRecCardSize);
|
||||
cardSizeSlider = new CardSizeWidget(this, nullptr, SettingsCache::instance().cardsDisplay().getEDHRecCardSize());
|
||||
connect(cardSizeSlider, &CardSizeWidget::cardSizeSettingUpdated, &SettingsCache::instance().cardsDisplay(),
|
||||
&CardsDisplaySettings::setEDHRecCardSize);
|
||||
|
||||
settingsButton->addSettingsWidget(cardSizeSlider);
|
||||
|
||||
|
|
|
|||
|
|
@ -258,16 +258,13 @@ void TabGame::resetChatAndPhase()
|
|||
void TabGame::emitUserEvent()
|
||||
{
|
||||
bool globalEvent = !game->getPlayerManager()->isSpectator() ||
|
||||
SettingsCache::instance().interface().getSpectatorNotificationsEnabled();
|
||||
SettingsCache::instance().userInterface().getSpectatorNotificationsEnabled();
|
||||
emit userEvent(globalEvent);
|
||||
updatePlayerListDockTitle();
|
||||
}
|
||||
|
||||
TabGame::~TabGame()
|
||||
{
|
||||
if (replayWidget) {
|
||||
delete replayWidget->replay;
|
||||
}
|
||||
for (auto &player : game->getPlayerManager()->getPlayers()) {
|
||||
player->clear();
|
||||
}
|
||||
|
|
@ -1183,6 +1180,7 @@ void TabGame::createReplayDock(GameReplay *replay)
|
|||
replayDock->setWidget(replayWidget);
|
||||
replayDock->setFloating(false);
|
||||
|
||||
connect(replayWidget, &ReplayWidget::rewound, this, &TabGame::resetChatAndPhase);
|
||||
connect(replayWidget, &ReplayWidget::eventReplayed, game->getGameEventHandler(),
|
||||
[this](const auto &event, auto options) {
|
||||
game->getGameEventHandler()->processGameEventContainer(event, nullptr, options);
|
||||
|
|
|
|||
|
|
@ -242,7 +242,7 @@ void TabRoom::actClearChat()
|
|||
void TabRoom::actOpenChatSettings()
|
||||
{
|
||||
DlgSettings settings(this);
|
||||
settings.setTab(4);
|
||||
settings.setTab(DlgSettings::MessagesPage);
|
||||
settings.exec();
|
||||
}
|
||||
|
||||
|
|
@ -280,6 +280,7 @@ void TabRoom::processListGamesEvent(const Event_ListGames &event)
|
|||
for (int i = 0; i < gameListSize; ++i) {
|
||||
gameSelector->processGameInfo(event.game_list(i));
|
||||
}
|
||||
emit gameListUpdated();
|
||||
}
|
||||
|
||||
void TabRoom::processJoinRoomEvent(const Event_JoinRoom &event)
|
||||
|
|
|
|||
|
|
@ -78,6 +78,7 @@ signals:
|
|||
void openMessageDialog(const QString &userName, bool focus);
|
||||
void maximizeClient();
|
||||
void notIdle();
|
||||
void gameListUpdated();
|
||||
private slots:
|
||||
void sendMessage();
|
||||
void sayFinished(const Response &response);
|
||||
|
|
@ -127,6 +128,10 @@ public:
|
|||
{
|
||||
return ownUser;
|
||||
}
|
||||
[[nodiscard]] GameSelector *getGameSelector() const
|
||||
{
|
||||
return gameSelector;
|
||||
}
|
||||
|
||||
PendingCommand *prepareRoomCommand(const ::google::protobuf::Message &cmd);
|
||||
void sendRoomCommand(PendingCommand *pend);
|
||||
|
|
|
|||
|
|
@ -191,7 +191,10 @@ void TabServer::joinRoom(int id, bool setCurrent)
|
|||
|
||||
PendingCommand *pend = client->prepareSessionCommand(cmd);
|
||||
pend->setExtraData(setCurrent);
|
||||
connect(pend, &PendingCommand::finished, this, &TabServer::joinRoomFinished);
|
||||
connect(pend, &PendingCommand::finished, this,
|
||||
[this, id](const Response &r, const CommandContainer &c, const QVariant &v) {
|
||||
joinRoomFinished(r, c, v, id);
|
||||
});
|
||||
|
||||
client->sendCommand(pend);
|
||||
|
||||
|
|
@ -205,7 +208,8 @@ void TabServer::joinRoom(int id, bool setCurrent)
|
|||
|
||||
void TabServer::joinRoomFinished(const Response &r,
|
||||
const CommandContainer & /*commandContainer*/,
|
||||
const QVariant &extraData)
|
||||
const QVariant &extraData,
|
||||
int roomId)
|
||||
{
|
||||
switch (r.response_code()) {
|
||||
case Response::RespOk:
|
||||
|
|
@ -213,21 +217,25 @@ void TabServer::joinRoomFinished(const Response &r,
|
|||
case Response::RespNameNotFound:
|
||||
QMessageBox::critical(this, tr("Error"),
|
||||
tr("Failed to join the server room: it doesn't exist on the server."));
|
||||
emit roomJoinFailed(roomId);
|
||||
return;
|
||||
case Response::RespContextError:
|
||||
QMessageBox::critical(
|
||||
this, tr("Error"),
|
||||
tr("The server thinks you are in the server room but your client is unable to display it. "
|
||||
"Try restarting your client."));
|
||||
emit roomJoinFailed(roomId);
|
||||
return;
|
||||
case Response::RespUserLevelTooLow:
|
||||
QMessageBox::critical(this, tr("Error"),
|
||||
tr("You do not have the required permission to join this server room."));
|
||||
emit roomJoinFailed(roomId);
|
||||
return;
|
||||
default:
|
||||
QMessageBox::critical(
|
||||
this, tr("Error"),
|
||||
tr("Failed to join the server room due to an unknown error: %1.").arg(r.response_code()));
|
||||
emit roomJoinFailed(roomId);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -49,10 +49,13 @@ class TabServer : public Tab
|
|||
Q_OBJECT
|
||||
signals:
|
||||
void roomJoined(const ServerInfo_Room &info, bool setCurrent);
|
||||
void roomJoinFailed(int roomId);
|
||||
private slots:
|
||||
void processServerMessageEvent(const Event_ServerMessage &event);
|
||||
void joinRoom(int id, bool setCurrent);
|
||||
void joinRoomFinished(const Response &resp, const CommandContainer &commandContainer, const QVariant &extraData);
|
||||
void joinRoomFinished(const Response &resp,
|
||||
const CommandContainer &commandContainer,
|
||||
const QVariant &extraData,
|
||||
int roomId);
|
||||
|
||||
private:
|
||||
AbstractClient *client;
|
||||
|
|
@ -62,6 +65,7 @@ private:
|
|||
|
||||
public:
|
||||
TabServer(TabSupervisor *_tabSupervisor, AbstractClient *_client);
|
||||
void joinRoom(int id, bool setCurrent);
|
||||
void retranslateUi() override;
|
||||
[[nodiscard]] QString getTabText() const override
|
||||
{
|
||||
|
|
|
|||
|
|
@ -39,9 +39,21 @@
|
|||
#include <libcockatrice/protocol/pb/serverinfo_room.pb.h>
|
||||
#include <libcockatrice/protocol/pb/serverinfo_user.pb.h>
|
||||
#include <libcockatrice/settings/chat_settings.h>
|
||||
#include <libcockatrice/settings/deck_editor_settings.h>
|
||||
#include <libcockatrice/settings/interface_settings.h>
|
||||
#include <libcockatrice/settings/tabs_settings.h>
|
||||
#include <libcockatrice/settings/visual_deck_storage_settings.h>
|
||||
|
||||
QRect MacOSTabFixStyle::subElementRect(SubElement element, const QStyleOption *option, const QWidget *widget) const
|
||||
{
|
||||
if (element != SE_TabBarTabText) {
|
||||
return QProxyStyle::subElementRect(element, option, widget);
|
||||
}
|
||||
|
||||
// Skip over QProxyStyle handling subElementRect,
|
||||
// This fixes an issue on OSX where the labels for tabs with a button and an icon
|
||||
// get cut-off too early
|
||||
return QCommonStyle::subElementRect(element, option, widget);
|
||||
}
|
||||
|
||||
CloseButton::CloseButton(QWidget *parent) : QAbstractButton(parent)
|
||||
{
|
||||
|
|
@ -106,6 +118,12 @@ TabSupervisor::TabSupervisor(AbstractClient *_client, QMenu *tabsMenu, QWidget *
|
|||
setMovable(true);
|
||||
setIconSize(QSize(15, 15));
|
||||
|
||||
#if defined(Q_OS_MAC)
|
||||
// This is necessary to fix an issue on macOS,
|
||||
// where tabs with icons and buttons get drawn incorrectly
|
||||
tabBar()->setStyle(new MacOSTabFixStyle);
|
||||
#endif
|
||||
|
||||
userListManager = new UserListManager(client, this);
|
||||
|
||||
// connect tab changes
|
||||
|
|
@ -565,6 +583,11 @@ void TabSupervisor::actTabServer(bool checked)
|
|||
|
||||
void TabSupervisor::openTabServer()
|
||||
{
|
||||
SettingsCache::instance().tabs().setTabServerOpen(true);
|
||||
if (tabServer) {
|
||||
return;
|
||||
}
|
||||
|
||||
tabServer = new TabServer(this, client);
|
||||
connect(tabServer, &TabServer::roomJoined, this, &TabSupervisor::addRoomTab);
|
||||
myAddTab(tabServer, aTabServer);
|
||||
|
|
@ -888,7 +911,7 @@ void TabSupervisor::talkLeft(TabMessage *tab)
|
|||
*/
|
||||
void TabSupervisor::openDeckInNewTab(const LoadedDeck &deckToOpen)
|
||||
{
|
||||
int type = SettingsCache::instance().visualDeckStorage().getDefaultDeckEditorType();
|
||||
int type = SettingsCache::instance().deckEditor().getDefaultDeckEditorType();
|
||||
switch (type) {
|
||||
case ClassicDeckEditor:
|
||||
addDeckEditorTab(deckToOpen);
|
||||
|
|
@ -987,7 +1010,7 @@ void TabSupervisor::tabUserEvent(bool globalEvent)
|
|||
tab->setContentsChanged(true);
|
||||
setTabIcon(indexOf(tab), QPixmap("theme:icons/tab_changed"));
|
||||
}
|
||||
if (globalEvent && SettingsCache::instance().interface().getNotificationsEnabled()) {
|
||||
if (globalEvent && SettingsCache::instance().userInterface().getNotificationsEnabled()) {
|
||||
QApplication::alert(this);
|
||||
}
|
||||
}
|
||||
|
|
@ -1082,7 +1105,7 @@ void TabSupervisor::processUserJoined(const ServerInfo_User &userInfoJoined)
|
|||
}
|
||||
}
|
||||
|
||||
if (SettingsCache::instance().interface().getBuddyConnectNotificationsEnabled()) {
|
||||
if (SettingsCache::instance().userInterface().getBuddyConnectNotificationsEnabled()) {
|
||||
QApplication::alert(this);
|
||||
this->actShowPopup(tr("Your buddy %1 has signed on!").arg(userName));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -140,6 +140,10 @@ public:
|
|||
{
|
||||
return userListManager;
|
||||
}
|
||||
[[nodiscard]] TabServer *getTabServer() const
|
||||
{
|
||||
return tabServer;
|
||||
}
|
||||
[[nodiscard]] const QMap<int, TabRoom *> &getRoomTabs() const
|
||||
{
|
||||
return roomTabs;
|
||||
|
|
@ -171,6 +175,7 @@ public slots:
|
|||
void maximizeMainWindow();
|
||||
void actTabVisualDeckStorage(bool checked);
|
||||
void actTabReplays(bool checked);
|
||||
void openTabServer();
|
||||
private slots:
|
||||
void refreshShortcuts();
|
||||
|
||||
|
|
@ -183,7 +188,6 @@ private slots:
|
|||
|
||||
void openTabVisualDeckStorage();
|
||||
void openTabHome();
|
||||
void openTabServer();
|
||||
void openTabAccount();
|
||||
void openTabDeckStorage();
|
||||
void openTabReplays();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue