mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-03-21 17:55:21 -05:00
Merge d6124ad484 into c5cd7d8700
This commit is contained in:
commit
f49c4f6b50
|
|
@ -276,6 +276,13 @@ set(cockatrice_SOURCES
|
|||
src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_entry_display_widget.cpp
|
||||
src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_listings_display_widget.cpp
|
||||
src/interface/widgets/tabs/api/archidekt/display/archidekt_deck_preview_image_display_widget.cpp
|
||||
src/interface/widgets/tabs/api/commander_spellbook/api_response/card_in_deck_request.cpp
|
||||
src/interface/widgets/tabs/api/commander_spellbook/api_response/commander_spellbook_deck_request.cpp
|
||||
src/interface/widgets/tabs/api/commander_spellbook/api_response/commander_spellbook_card_result.cpp
|
||||
src/interface/widgets/tabs/api/commander_spellbook/api_response/commander_spellbook_variant_result.cpp
|
||||
src/interface/widgets/tabs/api/commander_spellbook/api_response/commander_spellbook_estimate_bracket_result.cpp
|
||||
src/interface/widgets/tabs/api/commander_spellbook/commander_spellbook_bracket_explainer.cpp
|
||||
src/interface/widgets/tabs/api/commander_spellbook/commander_spellbook_api_accessor.cpp
|
||||
src/interface/widgets/tabs/api/edhrec/api_response/archidekt_links/edhrec_api_response_archidekt_links.cpp
|
||||
src/interface/widgets/tabs/api/edhrec/api_response/average_deck/edhrec_average_deck_api_response.cpp
|
||||
src/interface/widgets/tabs/api/edhrec/api_response/average_deck/edhrec_deck_api_response.cpp
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#include "cache_settings.h"
|
||||
|
||||
#include "../../interface/widgets/dialogs/dlg_settings.h"
|
||||
#include "../network/update/client/release_channel.h"
|
||||
#include "card_counter_settings.h"
|
||||
#include "version_string.h"
|
||||
|
|
@ -302,6 +303,14 @@ SettingsCache::SettingsCache()
|
|||
deckEditorBannerCardComboBoxVisible =
|
||||
settings->value("interface/deckeditorbannercardcomboboxvisible", true).toBool();
|
||||
deckEditorTagsWidgetVisible = settings->value("interface/deckeditortagswidgetvisible", true).toBool();
|
||||
deckEditorCommanderSpellbookIntegrationEnabled =
|
||||
settings
|
||||
->value("interface/deck_editor/commander_spellbook_integration/enabled",
|
||||
deckEditorCommanderSpellbookIntegrationEnabledIndexUnprompted)
|
||||
.toInt();
|
||||
deckEditorCommanderSpellbookIntegrationUseOfficialBracketNames =
|
||||
settings->value("interface/deck_editor/commander_spellbook_integration/use_official_bracket_names", false)
|
||||
.toBool();
|
||||
visualDeckStorageCardSize = settings->value("interface/visualdeckstoragecardsize", 100).toInt();
|
||||
visualDeckStorageSortingOrder = settings->value("interface/visualdeckstoragesortingorder", 0).toInt();
|
||||
visualDeckStorageShowFolders = settings->value("interface/visualdeckstorageshowfolders", true).toBool();
|
||||
|
|
@ -798,6 +807,26 @@ void SettingsCache::setDeckEditorTagsWidgetVisible(QT_STATE_CHANGED_T _deckEdito
|
|||
emit deckEditorTagsWidgetVisibleChanged(deckEditorTagsWidgetVisible);
|
||||
}
|
||||
|
||||
void SettingsCache::setDeckEditorCommanderSpellbookIntegrationEnabled(
|
||||
int _deckEditorCommanderSpellbookIntegrationEnabled)
|
||||
{
|
||||
deckEditorCommanderSpellbookIntegrationEnabled = _deckEditorCommanderSpellbookIntegrationEnabled;
|
||||
settings->setValue("interface/deck_editor/commander_spellbook_integration/enabled",
|
||||
deckEditorCommanderSpellbookIntegrationEnabled);
|
||||
emit deckEditorCommanderSpellbookIntegrationEnabledChanged(deckEditorCommanderSpellbookIntegrationEnabled);
|
||||
}
|
||||
|
||||
void SettingsCache::setDeckEditorCommanderSpellbookIntegrationUseOfficialBracketNames(
|
||||
bool _deckEditorCommanderSpellbookIntegrationUseOfficialBracketNames)
|
||||
{
|
||||
deckEditorCommanderSpellbookIntegrationUseOfficialBracketNames =
|
||||
_deckEditorCommanderSpellbookIntegrationUseOfficialBracketNames;
|
||||
settings->setValue("interface/deck_editor/commander_spellbook_integration/use_official_bracket_names",
|
||||
deckEditorCommanderSpellbookIntegrationUseOfficialBracketNames);
|
||||
emit deckEditorCommanderSpellbookIntegrationUseOfficialBracketNamesChanged(
|
||||
deckEditorCommanderSpellbookIntegrationUseOfficialBracketNames);
|
||||
}
|
||||
|
||||
void SettingsCache::setVisualDeckStorageSortingOrder(int _visualDeckStorageSortingOrder)
|
||||
{
|
||||
visualDeckStorageSortingOrder = _visualDeckStorageSortingOrder;
|
||||
|
|
|
|||
|
|
@ -156,6 +156,8 @@ signals:
|
|||
void printingSelectorNavigationButtonsVisibleChanged();
|
||||
void deckEditorBannerCardComboBoxVisibleChanged(bool _visible);
|
||||
void deckEditorTagsWidgetVisibleChanged(bool _visible);
|
||||
void deckEditorCommanderSpellbookIntegrationEnabledChanged(int _enabled);
|
||||
void deckEditorCommanderSpellbookIntegrationUseOfficialBracketNamesChanged(bool _useOfficialBracketNames);
|
||||
void visualDeckStorageShowTagFilterChanged(bool _visible);
|
||||
void visualDeckStorageDefaultTagsListChanged();
|
||||
void visualDeckStorageShowColorIdentityChanged(bool _visible);
|
||||
|
|
@ -246,6 +248,8 @@ private:
|
|||
bool printingSelectorNavigationButtonsVisible;
|
||||
bool deckEditorBannerCardComboBoxVisible;
|
||||
bool deckEditorTagsWidgetVisible;
|
||||
int deckEditorCommanderSpellbookIntegrationEnabled;
|
||||
bool deckEditorCommanderSpellbookIntegrationUseOfficialBracketNames;
|
||||
int visualDeckStorageSortingOrder;
|
||||
bool visualDeckStorageShowFolders;
|
||||
bool visualDeckStorageShowColorIdentity;
|
||||
|
|
@ -719,6 +723,14 @@ public:
|
|||
{
|
||||
return openDeckInNewTab;
|
||||
}
|
||||
[[nodiscard]] int getDeckEditorCommanderSpellbookIntegrationEnabled() const
|
||||
{
|
||||
return deckEditorCommanderSpellbookIntegrationEnabled;
|
||||
}
|
||||
[[nodiscard]] bool getDeckEditorCommanderSpellbookIntegrationUseOfficialBracketNames() const
|
||||
{
|
||||
return deckEditorCommanderSpellbookIntegrationUseOfficialBracketNames;
|
||||
}
|
||||
[[nodiscard]] int getRewindBufferingMs() const
|
||||
{
|
||||
return rewindBufferingMs;
|
||||
|
|
@ -1050,6 +1062,9 @@ public slots:
|
|||
void setPrintingSelectorNavigationButtonsVisible(QT_STATE_CHANGED_T _navigationButtonsVisible);
|
||||
void setDeckEditorBannerCardComboBoxVisible(QT_STATE_CHANGED_T _deckEditorBannerCardComboBoxVisible);
|
||||
void setDeckEditorTagsWidgetVisible(QT_STATE_CHANGED_T _deckEditorTagsWidgetVisible);
|
||||
void setDeckEditorCommanderSpellbookIntegrationEnabled(int _deckEditorCommanderSpellbookIntegrationEnabled);
|
||||
void setDeckEditorCommanderSpellbookIntegrationUseOfficialBracketNames(
|
||||
bool _deckEditorCommanderSpellbookIntegrationUseOfficialBracketNames);
|
||||
void setVisualDeckStorageSortingOrder(int _visualDeckStorageSortingOrder);
|
||||
void setVisualDeckStorageShowFolders(QT_STATE_CHANGED_T value);
|
||||
void setVisualDeckStorageShowTagFilter(QT_STATE_CHANGED_T _showTags);
|
||||
|
|
|
|||
|
|
@ -1,13 +1,19 @@
|
|||
#include "deck_editor_deck_dock_widget.h"
|
||||
|
||||
#include "../../../client/settings/cache_settings.h"
|
||||
#include "../dialogs/dlg_settings.h"
|
||||
#include "../tabs/api/commander_spellbook/commander_spellbook_api_accessor.h"
|
||||
#include "../tabs/api/commander_spellbook/commander_spellbook_bracket_explainer.h"
|
||||
#include "deck_list_style_proxy.h"
|
||||
#include "deck_state_manager.h"
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QDockWidget>
|
||||
#include <QFormLayout>
|
||||
#include <QHeaderView>
|
||||
#include <QLabel>
|
||||
#include <QMessageBox>
|
||||
#include <QSplitter>
|
||||
#include <QTextEdit>
|
||||
#include <libcockatrice/card/database/card_database_manager.h>
|
||||
|
|
@ -131,6 +137,37 @@ void DeckEditorDeckDockWidget::createDeckDock()
|
|||
formatComboBox->addItem(tr("Loading Database..."));
|
||||
formatComboBox->setEnabled(false); // Disable until loaded
|
||||
|
||||
// --- Commander bracket row (hidden, unless format is 'commander') ---
|
||||
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, &DeckEditorDeckDockWidget::requestBracketEstimate);
|
||||
if (SettingsCache::instance().getDeckEditorCommanderSpellbookIntegrationEnabled() !=
|
||||
deckEditorCommanderSpellbookIntegrationEnabledIndexUnprompted) {
|
||||
connect(&SettingsCache::instance(), &SettingsCache::deckEditorCommanderSpellbookIntegrationEnabledChanged, this,
|
||||
&DeckEditorDeckDockWidget::maybeAutoEstimateBracket);
|
||||
connect(&SettingsCache::instance(),
|
||||
&SettingsCache::deckEditorCommanderSpellbookIntegrationUseOfficialBracketNamesChanged, this,
|
||||
&DeckEditorDeckDockWidget::maybeAutoEstimateBracket);
|
||||
}
|
||||
|
||||
bracketLabel->setVisible(false);
|
||||
bracketValueLabel->setVisible(false);
|
||||
bracketInfoButton->setVisible(false);
|
||||
bracketRefreshButton->setVisible(false);
|
||||
|
||||
commentsLabel = new QLabel();
|
||||
commentsLabel->setObjectName("commentsLabel");
|
||||
commentsEdit = new QTextEdit;
|
||||
|
|
@ -216,13 +253,23 @@ void DeckEditorDeckDockWidget::createDeckDock()
|
|||
upperLayout->addWidget(formatLabel, 2, 0);
|
||||
upperLayout->addWidget(formatComboBox, 2, 1);
|
||||
|
||||
upperLayout->addWidget(bannerCardLabel, 3, 0);
|
||||
upperLayout->addWidget(bannerCardComboBox, 3, 1);
|
||||
upperLayout->addWidget(bracketLabel, 3, 0);
|
||||
|
||||
upperLayout->addWidget(deckTagsDisplayWidget, 4, 1);
|
||||
auto *bracketRow = new QHBoxLayout;
|
||||
bracketRow->addWidget(bracketValueLabel);
|
||||
bracketRow->addWidget(bracketInfoButton);
|
||||
bracketRow->addWidget(bracketRefreshButton);
|
||||
bracketRow->addStretch();
|
||||
|
||||
upperLayout->addWidget(activeGroupCriteriaLabel, 5, 0);
|
||||
upperLayout->addWidget(activeGroupCriteriaComboBox, 5, 1);
|
||||
upperLayout->addLayout(bracketRow, 3, 1);
|
||||
|
||||
upperLayout->addWidget(bannerCardLabel, 4, 0);
|
||||
upperLayout->addWidget(bannerCardComboBox, 4, 1);
|
||||
|
||||
upperLayout->addWidget(deckTagsDisplayWidget, 5, 1);
|
||||
|
||||
upperLayout->addWidget(activeGroupCriteriaLabel, 6, 0);
|
||||
upperLayout->addWidget(activeGroupCriteriaComboBox, 6, 1);
|
||||
|
||||
hashLabel1 = new QLabel();
|
||||
hashLabel1->setObjectName("hashLabel1");
|
||||
|
|
@ -280,6 +327,151 @@ void DeckEditorDeckDockWidget::createDeckDock()
|
|||
}
|
||||
}
|
||||
|
||||
bool DeckEditorDeckDockWidget::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().getDeckEditorCommanderSpellbookIntegrationUseOfficialBracketNames() ? 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().setDeckEditorCommanderSpellbookIntegrationUseOfficialBracketNames(useOfficial);
|
||||
}
|
||||
|
||||
connect(&SettingsCache::instance(), &SettingsCache::deckEditorCommanderSpellbookIntegrationEnabledChanged, this,
|
||||
&DeckEditorDeckDockWidget::maybeAutoEstimateBracket);
|
||||
connect(&SettingsCache::instance(),
|
||||
&SettingsCache::deckEditorCommanderSpellbookIntegrationUseOfficialBracketNamesChanged, this,
|
||||
&DeckEditorDeckDockWidget::maybeAutoEstimateBracket);
|
||||
|
||||
// Persist integration mode
|
||||
if (clickedButton == disableBtn) {
|
||||
SettingsCache::instance().setDeckEditorCommanderSpellbookIntegrationEnabled(
|
||||
deckEditorCommanderSpellbookIntegrationEnabledIndexDisabled);
|
||||
return false;
|
||||
}
|
||||
if (clickedButton == enableBtn) {
|
||||
SettingsCache::instance().setDeckEditorCommanderSpellbookIntegrationEnabled(
|
||||
deckEditorCommanderSpellbookIntegrationEnabledIndexEnabled);
|
||||
return true;
|
||||
}
|
||||
if (clickedButton == automaticBtn) {
|
||||
SettingsCache::instance().setDeckEditorCommanderSpellbookIntegrationEnabled(
|
||||
deckEditorCommanderSpellbookIntegrationEnabledIndexAutomatic);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void DeckEditorDeckDockWidget::updateBracketVisibility(bool visible)
|
||||
{
|
||||
bracketLabel->setVisible(visible);
|
||||
bracketValueLabel->setVisible(visible);
|
||||
bracketInfoButton->setVisible(visible);
|
||||
bracketRefreshButton->setVisible(visible);
|
||||
}
|
||||
|
||||
void DeckEditorDeckDockWidget::requestBracketEstimate()
|
||||
{
|
||||
bracketRefreshButton->setEnabled(false);
|
||||
bracketInfoButton->setEnabled(false);
|
||||
bracketValueLabel->setText(tr("Calculating…"));
|
||||
|
||||
requestId = CommanderSpellbookApiAccessor::instance().estimateBracket(*deckStateManager->getModel()->getDeckList(), this);
|
||||
|
||||
connect(&CommanderSpellbookApiAccessor::instance(), &CommanderSpellbookApiAccessor::estimateBracketFinished, this,
|
||||
&DeckEditorDeckDockWidget::onEstimateBracketFinished);
|
||||
}
|
||||
|
||||
void DeckEditorDeckDockWidget::onEstimateBracketFinished(CommanderSpellbookApiAccessor::RequestId id,
|
||||
QObject *requester,
|
||||
const EstimateBracketResult &result)
|
||||
{
|
||||
if (requester != this || static_cast<int>(id) != requestId) {
|
||||
return;
|
||||
}
|
||||
|
||||
BracketExplainer explainer;
|
||||
lastBracketExplanation = explainer.explain(result);
|
||||
|
||||
// Display bracket
|
||||
if (SettingsCache::instance().getDeckEditorCommanderSpellbookIntegrationUseOfficialBracketNames()) {
|
||||
bracketValueLabel->setText(CommanderSpellbookBracketTag::bracketTagToOfficialString(result.bracketTag));
|
||||
} else {
|
||||
bracketValueLabel->setText(CommanderSpellbookBracketTag::bracketTagToString(result.bracketTag));
|
||||
}
|
||||
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 DeckEditorDeckDockWidget::initializeFormats()
|
||||
{
|
||||
QStringList allFormats = CardDatabaseManager::query()->getAllFormatsWithCount().keys();
|
||||
|
|
@ -300,15 +492,70 @@ void DeckEditorDeckDockWidget::initializeFormats()
|
|||
// Ensure no selection is visible initially
|
||||
formatComboBox->setCurrentIndex(-1);
|
||||
}
|
||||
|
||||
connect(formatComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [this](int index) {
|
||||
QString formatKey;
|
||||
if (index >= 0) {
|
||||
QString formatKey = formatComboBox->itemData(index).toString();
|
||||
deckStateManager->setFormat(formatKey);
|
||||
} else {
|
||||
deckStateManager->setFormat(""); // clear format if deselected
|
||||
}
|
||||
|
||||
const bool isCommander = (formatKey.compare("commander", Qt::CaseInsensitive) == 0);
|
||||
const bool commanderSpellbookIntegrationEnabled =
|
||||
SettingsCache::instance().getDeckEditorCommanderSpellbookIntegrationEnabled() !=
|
||||
deckEditorCommanderSpellbookIntegrationEnabledIndexDisabled;
|
||||
|
||||
const bool bracketVisible = isCommander && commanderSpellbookIntegrationEnabled;
|
||||
|
||||
updateBracketVisibility(bracketVisible);
|
||||
|
||||
if (!isCommander) {
|
||||
bracketValueLabel->setText("-");
|
||||
bracketInfoButton->setToolTip({});
|
||||
bracketInfoButton->setEnabled(false);
|
||||
bracketRefreshButton->setEnabled(false);
|
||||
} else {
|
||||
bracketRefreshButton->setEnabled(true);
|
||||
maybeAutoEstimateBracket();
|
||||
}
|
||||
});
|
||||
|
||||
maybeAutoEstimateBracket();
|
||||
}
|
||||
|
||||
void DeckEditorDeckDockWidget::maybeAutoEstimateBracket()
|
||||
{
|
||||
const QString formatKey = deckStateManager->getModel()->getDeckList()->getGameFormat();
|
||||
|
||||
const bool isCommander = (formatKey.compare("commander", Qt::CaseInsensitive) == 0);
|
||||
|
||||
int mode = SettingsCache::instance().getDeckEditorCommanderSpellbookIntegrationEnabled();
|
||||
|
||||
if (!isCommander || mode == deckEditorCommanderSpellbookIntegrationEnabledIndexDisabled) {
|
||||
updateBracketVisibility(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mode == deckEditorCommanderSpellbookIntegrationEnabledIndexUnprompted) {
|
||||
if (!promptCommanderSpellbookIntegration()) {
|
||||
updateBracketVisibility(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
updateBracketVisibility(true);
|
||||
mode = SettingsCache::instance().getDeckEditorCommanderSpellbookIntegrationEnabled();
|
||||
if (mode != deckEditorCommanderSpellbookIntegrationEnabledIndexAutomatic) {
|
||||
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, &DeckEditorDeckDockWidget::requestBracketEstimate);
|
||||
}
|
||||
|
||||
ExactCard DeckEditorDeckDockWidget::getCurrentCard()
|
||||
|
|
@ -740,6 +987,8 @@ void DeckEditorDeckDockWidget::retranslateUi()
|
|||
commentsLabel->setText(tr("&Comments:"));
|
||||
activeGroupCriteriaLabel->setText(tr("Group by:"));
|
||||
formatLabel->setText(tr("Format:"));
|
||||
bracketInfoButton->setToolTip(tr("Why this bracket?"));
|
||||
bracketRefreshButton->setToolTip(tr("Recalculate bracket"));
|
||||
|
||||
hashLabel1->setText(tr("Hash:"));
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
|
||||
#include "../../../interface/widgets/tabs/abstract_tab_deck_editor.h"
|
||||
#include "../../key_signals.h"
|
||||
#include "../tabs/api/commander_spellbook/commander_spellbook_bracket_explainer.h"
|
||||
#include "../utility/custom_line_edit.h"
|
||||
#include "../visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.h"
|
||||
#include "deck_list_history_manager_widget.h"
|
||||
|
|
@ -21,6 +22,7 @@
|
|||
#include <QTreeView>
|
||||
#include <libcockatrice/card/card_info.h>
|
||||
|
||||
class EstimateBracketResult;
|
||||
class DeckListModel;
|
||||
class AbstractTabDeckEditor;
|
||||
class DeckEditorDeckDockWidget : public QDockWidget
|
||||
|
|
@ -33,6 +35,9 @@ public:
|
|||
QTreeView *deckView;
|
||||
QComboBox *bannerCardComboBox;
|
||||
void createDeckDock();
|
||||
bool promptCommanderSpellbookIntegration();
|
||||
void updateBracketVisibility(bool visible);
|
||||
void requestBracketEstimate();
|
||||
ExactCard getCurrentCard();
|
||||
void retranslateUi();
|
||||
|
||||
|
|
@ -59,6 +64,8 @@ public slots:
|
|||
void actSwapSelection();
|
||||
void actRemoveCard();
|
||||
void initializeFormats();
|
||||
void maybeAutoEstimateBracket();
|
||||
void onEstimateBracketFinished(quint64 id, QObject *requester, const EstimateBracketResult &result);
|
||||
|
||||
signals:
|
||||
void selectedCardChanged(const ExactCard &card);
|
||||
|
|
@ -89,6 +96,15 @@ private:
|
|||
|
||||
QAction *aRemoveCard, *aIncrement, *aDecrement, *aSwapCard;
|
||||
|
||||
QLabel *bracketLabel;
|
||||
QLabel *bracketValueLabel;
|
||||
QToolButton *bracketInfoButton;
|
||||
QToolButton *bracketRefreshButton;
|
||||
|
||||
BracketExplanation lastBracketExplanation;
|
||||
|
||||
int requestId;
|
||||
|
||||
DeckListModel *getModel() const;
|
||||
[[nodiscard]] QModelIndexList getSelectedCardNodeSourceIndices() const;
|
||||
void offsetCountAtIndex(const QModelIndex &idx, bool isIncrement);
|
||||
|
|
|
|||
|
|
@ -905,6 +905,56 @@ UserInterfaceSettingsPage::UserInterfaceSettingsPage()
|
|||
connect(&defaultDeckEditorTypeSelector, QOverload<int>::of(&QComboBox::currentIndexChanged),
|
||||
&SettingsCache::instance(), &SettingsCache::setDefaultDeckEditorType);
|
||||
|
||||
deckEditorCommanderSpellBookIntegrationUseOfficialBracketNamesExplainer.setText("?");
|
||||
deckEditorCommanderSpellBookIntegrationUseOfficialBracketNamesExplainer.setAutoRaise(true);
|
||||
deckEditorCommanderSpellBookIntegrationUseOfficialBracketNamesExplainer.setEnabled(false);
|
||||
|
||||
// Add items with userData = internal enum
|
||||
deckEditorCommanderSpellbookIntegrationEnabledSelector.addItem(
|
||||
tr("Disabled"), deckEditorCommanderSpellbookIntegrationEnabledIndexDisabled);
|
||||
deckEditorCommanderSpellbookIntegrationEnabledSelector.addItem(
|
||||
tr("Enabled"), deckEditorCommanderSpellbookIntegrationEnabledIndexEnabled);
|
||||
deckEditorCommanderSpellbookIntegrationEnabledSelector.addItem(
|
||||
tr("Automatic"), deckEditorCommanderSpellbookIntegrationEnabledIndexAutomatic);
|
||||
|
||||
int storedMode = SettingsCache::instance().getDeckEditorCommanderSpellbookIntegrationEnabled();
|
||||
for (int i = 0; i < deckEditorCommanderSpellbookIntegrationEnabledSelector.count(); ++i) {
|
||||
if (deckEditorCommanderSpellbookIntegrationEnabledSelector.itemData(i).toInt() == storedMode) {
|
||||
deckEditorCommanderSpellbookIntegrationEnabledSelector.setCurrentIndex(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
connect(&deckEditorCommanderSpellbookIntegrationEnabledSelector,
|
||||
QOverload<int>::of(&QComboBox::currentIndexChanged), this, [this](int index) {
|
||||
int mode = deckEditorCommanderSpellbookIntegrationEnabledSelector.itemData(index).toInt();
|
||||
SettingsCache::instance().setDeckEditorCommanderSpellbookIntegrationEnabled(mode);
|
||||
updateCommanderSpellbookUiState();
|
||||
});
|
||||
|
||||
deckEditorCommanderSpellbookIntegrationBracketNamingSelector.addItem(
|
||||
tr("CommanderSpellbook bracket names")); // index 0 = false
|
||||
deckEditorCommanderSpellbookIntegrationBracketNamingSelector.addItem(
|
||||
tr("Official Commander bracket names (approximate)")); // index 1 = true
|
||||
|
||||
deckEditorCommanderSpellbookIntegrationBracketNamingSelector.setCurrentIndex(
|
||||
SettingsCache::instance().getDeckEditorCommanderSpellbookIntegrationUseOfficialBracketNames() ? 1 : 0);
|
||||
|
||||
connect(&deckEditorCommanderSpellbookIntegrationBracketNamingSelector,
|
||||
QOverload<int>::of(&QComboBox::currentIndexChanged), &SettingsCache::instance(), [](int index) {
|
||||
SettingsCache::instance().setDeckEditorCommanderSpellbookIntegrationUseOfficialBracketNames(index == 1);
|
||||
});
|
||||
|
||||
updateCommanderSpellbookUiState();
|
||||
|
||||
auto *labelLayout = new QHBoxLayout;
|
||||
labelLayout->setContentsMargins(0, 0, 0, 0);
|
||||
labelLayout->addWidget(&deckEditorCommanderSpellbookIntegrationUseOfficialBracketNamesLabel);
|
||||
labelLayout->addWidget(&deckEditorCommanderSpellBookIntegrationUseOfficialBracketNamesExplainer);
|
||||
|
||||
auto *labelWidget = new QWidget;
|
||||
labelWidget->setLayout(labelLayout);
|
||||
|
||||
auto *deckEditorGrid = new QGridLayout;
|
||||
deckEditorGrid->addWidget(&openDeckInNewTabCheckBox, 0, 0);
|
||||
deckEditorGrid->addWidget(&visualDeckStorageInGameCheckBox, 1, 0);
|
||||
|
|
@ -913,6 +963,10 @@ UserInterfaceSettingsPage::UserInterfaceSettingsPage()
|
|||
deckEditorGrid->addWidget(&visualDeckStoragePromptForConversionSelector, 3, 1);
|
||||
deckEditorGrid->addWidget(&defaultDeckEditorTypeLabel, 4, 0);
|
||||
deckEditorGrid->addWidget(&defaultDeckEditorTypeSelector, 4, 1);
|
||||
deckEditorGrid->addWidget(&deckEditorCommanderSpellbookIntegrationEnabledLabel, 5, 0);
|
||||
deckEditorGrid->addWidget(&deckEditorCommanderSpellbookIntegrationEnabledSelector, 5, 1);
|
||||
deckEditorGrid->addWidget(labelWidget, 6, 0);
|
||||
deckEditorGrid->addWidget(&deckEditorCommanderSpellbookIntegrationBracketNamingSelector, 6, 1);
|
||||
|
||||
deckEditorGroupBox = new QGroupBox;
|
||||
deckEditorGroupBox->setLayout(deckEditorGrid);
|
||||
|
|
@ -955,6 +1009,27 @@ void UserInterfaceSettingsPage::setNotificationEnabled(QT_STATE_CHANGED_T i)
|
|||
}
|
||||
}
|
||||
|
||||
void UserInterfaceSettingsPage::updateCommanderSpellbookUiState()
|
||||
{
|
||||
const int mode = SettingsCache::instance().getDeckEditorCommanderSpellbookIntegrationEnabled();
|
||||
|
||||
const bool enabled = mode != deckEditorCommanderSpellbookIntegrationEnabledIndexDisabled &&
|
||||
mode != deckEditorCommanderSpellbookIntegrationEnabledIndexUnprompted;
|
||||
|
||||
deckEditorCommanderSpellbookIntegrationBracketNamingSelector.setEnabled(enabled);
|
||||
deckEditorCommanderSpellBookIntegrationUseOfficialBracketNamesExplainer.setEnabled(enabled);
|
||||
deckEditorCommanderSpellbookIntegrationUseOfficialBracketNamesLabel.setVisible(enabled);
|
||||
deckEditorCommanderSpellBookIntegrationUseOfficialBracketNamesExplainer.setVisible(enabled);
|
||||
deckEditorCommanderSpellbookIntegrationBracketNamingSelector.setVisible(enabled);
|
||||
|
||||
if (enabled) {
|
||||
// Sync selector with the current stored bool
|
||||
const bool useOfficial =
|
||||
SettingsCache::instance().getDeckEditorCommanderSpellbookIntegrationUseOfficialBracketNames();
|
||||
deckEditorCommanderSpellbookIntegrationBracketNamingSelector.setCurrentIndex(useOfficial ? 1 : 0);
|
||||
}
|
||||
}
|
||||
|
||||
void UserInterfaceSettingsPage::retranslateUi()
|
||||
{
|
||||
generalGroupBox->setTitle(tr("General interface settings"));
|
||||
|
|
@ -989,6 +1064,22 @@ void UserInterfaceSettingsPage::retranslateUi()
|
|||
defaultDeckEditorTypeLabel.setText(tr("Default deck editor type"));
|
||||
defaultDeckEditorTypeSelector.setItemText(TabSupervisor::ClassicDeckEditor, tr("Classic Deck Editor"));
|
||||
defaultDeckEditorTypeSelector.setItemText(TabSupervisor::VisualDeckEditor, tr("Visual Deck Editor"));
|
||||
deckEditorCommanderSpellbookIntegrationEnabledLabel.setText(
|
||||
tr("CommanderSpellbook integration to estimate commander bracket"));
|
||||
deckEditorCommanderSpellbookIntegrationEnabledSelector.setItemText(
|
||||
deckEditorCommanderSpellbookIntegrationEnabledIndexDisabled, tr("Disabled"));
|
||||
deckEditorCommanderSpellbookIntegrationEnabledSelector.setItemText(
|
||||
deckEditorCommanderSpellbookIntegrationEnabledIndexEnabled, tr("Enabled"));
|
||||
deckEditorCommanderSpellbookIntegrationEnabledSelector.setItemText(
|
||||
deckEditorCommanderSpellbookIntegrationEnabledIndexAutomatic, tr("Automatic"));
|
||||
deckEditorCommanderSpellbookIntegrationUseOfficialBracketNamesLabel.setText(tr("Bracket naming"));
|
||||
deckEditorCommanderSpellbookIntegrationBracketNamingSelector.setItemText(
|
||||
0, CommanderBracketNames::CommanderSpellbookBracketNames);
|
||||
deckEditorCommanderSpellbookIntegrationBracketNamingSelector.setItemText(
|
||||
1, CommanderBracketNames::OfficialCommanderBracketNames);
|
||||
|
||||
deckEditorCommanderSpellBookIntegrationUseOfficialBracketNamesExplainer.setToolTip(
|
||||
CommanderBracketNames::Explainer);
|
||||
replayGroupBox->setTitle(tr("Replay settings"));
|
||||
rewindBufferingMsLabel.setText(tr("Buffer time for backwards skip via shortcut:"));
|
||||
rewindBufferingMsBox.setSuffix(" ms");
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
#include <QLoggingCategory>
|
||||
#include <QPushButton>
|
||||
#include <QSpinBox>
|
||||
#include <QToolButton>
|
||||
#include <libcockatrice/utility/macros.h>
|
||||
|
||||
inline Q_LOGGING_CATEGORY(DlgSettingsLog, "dlg_settings");
|
||||
|
|
@ -154,11 +155,35 @@ public:
|
|||
void retranslateUi() override;
|
||||
};
|
||||
|
||||
enum deckEditorCommanderSpellbookIntegrationEnabledIndex
|
||||
{
|
||||
deckEditorCommanderSpellbookIntegrationEnabledIndexDisabled,
|
||||
deckEditorCommanderSpellbookIntegrationEnabledIndexEnabled,
|
||||
deckEditorCommanderSpellbookIntegrationEnabledIndexAutomatic,
|
||||
deckEditorCommanderSpellbookIntegrationEnabledIndexUnprompted,
|
||||
};
|
||||
|
||||
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
|
||||
|
||||
class UserInterfaceSettingsPage : public AbstractSettingsPage
|
||||
{
|
||||
Q_OBJECT
|
||||
private slots:
|
||||
void setNotificationEnabled(QT_STATE_CHANGED_T);
|
||||
void updateCommanderSpellbookUiState();
|
||||
|
||||
private:
|
||||
QCheckBox notificationsEnabledCheckBox;
|
||||
|
|
@ -182,6 +207,11 @@ private:
|
|||
QCheckBox visualDeckStorageSelectionAnimationCheckBox;
|
||||
QLabel defaultDeckEditorTypeLabel;
|
||||
QComboBox defaultDeckEditorTypeSelector;
|
||||
QLabel deckEditorCommanderSpellbookIntegrationEnabledLabel;
|
||||
QComboBox deckEditorCommanderSpellbookIntegrationEnabledSelector;
|
||||
QLabel deckEditorCommanderSpellbookIntegrationUseOfficialBracketNamesLabel;
|
||||
QToolButton deckEditorCommanderSpellBookIntegrationUseOfficialBracketNamesExplainer;
|
||||
QComboBox deckEditorCommanderSpellbookIntegrationBracketNamingSelector;
|
||||
QLabel rewindBufferingMsLabel;
|
||||
QSpinBox rewindBufferingMsBox;
|
||||
QGroupBox *generalGroupBox;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
#include "card_in_deck_request.h"
|
||||
|
||||
void CardInDeckRequest::fromJson(const QJsonObject &json)
|
||||
{
|
||||
card = json.value("card").toString();
|
||||
quantity = json.value("quantity").toInt();
|
||||
}
|
||||
|
||||
QJsonObject CardInDeckRequest::toJson() const
|
||||
{
|
||||
QJsonObject json;
|
||||
json.insert("card", card);
|
||||
json.insert("quantity", quantity);
|
||||
return json;
|
||||
}
|
||||
|
||||
void CardInDeckRequest::debugPrint() const
|
||||
{
|
||||
qDebug() << "Card:" << card;
|
||||
qDebug() << "Quantity:" << quantity;
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
#ifndef COCKATRICE_CARD_IN_DECK_REQUEST_H
|
||||
#define COCKATRICE_CARD_IN_DECK_REQUEST_H
|
||||
#include <QJsonObject>
|
||||
|
||||
class CardInDeckRequest
|
||||
{
|
||||
public:
|
||||
// Constructor
|
||||
CardInDeckRequest() = default;
|
||||
|
||||
// Parse deck-related data from JSON
|
||||
void fromJson(const QJsonObject &json);
|
||||
QJsonObject toJson() const;
|
||||
|
||||
// Debug method for logging
|
||||
void debugPrint() const;
|
||||
|
||||
private:
|
||||
QString card;
|
||||
int quantity;
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_CARD_IN_DECK_REQUEST_H
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
#ifndef COCKATRICE_COMMANDER_SPELLBOOK_BRACKET_TAG_H
|
||||
#define COCKATRICE_COMMANDER_SPELLBOOK_BRACKET_TAG_H
|
||||
#include <QString>
|
||||
|
||||
namespace CommanderSpellbookBracketTag
|
||||
{
|
||||
enum class BracketTag
|
||||
{
|
||||
Ruthless,
|
||||
Spicy,
|
||||
Powerful,
|
||||
Oddball,
|
||||
PreconAppropriate,
|
||||
Casual,
|
||||
Unknown
|
||||
};
|
||||
|
||||
inline static BracketTag bracketTagFromString(const QString &s)
|
||||
{
|
||||
if (s == "R")
|
||||
return BracketTag::Ruthless;
|
||||
if (s == "S")
|
||||
return BracketTag::Spicy;
|
||||
if (s == "P")
|
||||
return BracketTag::Powerful;
|
||||
if (s == "O")
|
||||
return BracketTag::Oddball;
|
||||
if (s == "PA")
|
||||
return BracketTag::PreconAppropriate;
|
||||
if (s == "C")
|
||||
return BracketTag::Casual;
|
||||
return BracketTag::Unknown;
|
||||
}
|
||||
|
||||
inline static QString bracketTagToString(BracketTag tag)
|
||||
{
|
||||
switch (tag) {
|
||||
case BracketTag::Ruthless:
|
||||
return "Ruthless";
|
||||
case BracketTag::Spicy:
|
||||
return "Spicy";
|
||||
case BracketTag::Powerful:
|
||||
return "Powerful";
|
||||
case BracketTag::Oddball:
|
||||
return "Oddball";
|
||||
case BracketTag::PreconAppropriate:
|
||||
return "Precon Appropriate";
|
||||
case BracketTag::Casual:
|
||||
return "Casual";
|
||||
case BracketTag::Unknown:
|
||||
return "Unknown";
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
inline static QString bracketTagToOfficialString(BracketTag tag)
|
||||
{
|
||||
switch (tag) {
|
||||
case BracketTag::Ruthless:
|
||||
return "[5] cEDH";
|
||||
case BracketTag::Spicy:
|
||||
return "[4] Optimized";
|
||||
case BracketTag::Powerful:
|
||||
return "[3] Upgraded";
|
||||
case BracketTag::Oddball:
|
||||
return "[2] Core";
|
||||
case BracketTag::PreconAppropriate:
|
||||
return "[1] Exhibition";
|
||||
case BracketTag::Casual:
|
||||
return "[1] Casual";
|
||||
case BracketTag::Unknown:
|
||||
return "Unknown";
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace CommanderSpellbookBracketTag
|
||||
|
||||
#endif // COCKATRICE_COMMANDER_SPELLBOOK_BRACKET_TAG_H
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
#include "commander_spellbook_card_result.h"
|
||||
|
||||
void CommanderSpellbookCardResult::fromJson(const QJsonObject &json)
|
||||
{
|
||||
id = json.value("id").toString();
|
||||
name = json.value("name").toString();
|
||||
oracleId = json.value("oracleId").toString();
|
||||
spoiler = json.value("spoiler").toBool();
|
||||
typeLine = json.value("typeLine").toString();
|
||||
|
||||
imageUriFrontPng = json.value("imageUriFrontPng").toString();
|
||||
imageUriFrontLarge = json.value("imageUriFrontLarge").toString();
|
||||
imageUriFrontNormal = json.value("imageUriFrontNormal").toString();
|
||||
imageUriFrontSmall = json.value("imageUriFrontSmall").toString();
|
||||
imageUriFrontArtCrop = json.value("imageUriFrontArtCrop").toString();
|
||||
|
||||
imageUriBackPng = json.value("imageUriBackPng").toString();
|
||||
imageUriBackLarge = json.value("imageUriBackLarge").toString();
|
||||
imageUriBackNormal = json.value("imageUriBackNormal").toString();
|
||||
imageUriBackSmall = json.value("imageUriBackSmall").toString();
|
||||
imageUriBackArtCrop = json.value("imageUriBackArtCrop").toString();
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
#ifndef COCKATRICE_COMMANDER_SPELLBOOK_CARD_RESULT_H
|
||||
#define COCKATRICE_COMMANDER_SPELLBOOK_CARD_RESULT_H
|
||||
#include <QJsonObject>
|
||||
#include <QString>
|
||||
|
||||
class CommanderSpellbookCardResult
|
||||
{
|
||||
public:
|
||||
void 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,118 @@
|
|||
#include "commander_spellbook_deck_request.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QJsonArray>
|
||||
#include <libcockatrice/deck_list/deck_list.h>
|
||||
#include <libcockatrice/deck_list/tree/deck_list_card_node.h>
|
||||
|
||||
void CommanderSpellbookDeckRequest::fromJson(const QJsonObject &json)
|
||||
{
|
||||
mainDeck.clear();
|
||||
commanderDeck.clear();
|
||||
|
||||
// Main deck
|
||||
const QJsonArray mainArray = json.value("main").toArray();
|
||||
for (const QJsonValue &value : mainArray) {
|
||||
if (!value.isObject()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
CardInDeckRequest card;
|
||||
card.fromJson(value.toObject());
|
||||
mainDeck.append(card);
|
||||
|
||||
// Max size allowed by commanderspellbook
|
||||
if (mainDeck.size() >= 600) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Commanders
|
||||
const QJsonArray commanderArray = json.value("commanders").toArray();
|
||||
for (const QJsonValue &value : commanderArray) {
|
||||
if (!value.isObject()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
CardInDeckRequest card;
|
||||
card.fromJson(value.toObject());
|
||||
commanderDeck.append(card);
|
||||
|
||||
// Max size allowed by commanderspellbook
|
||||
if (commanderDeck.size() >= 12) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
void CommanderSpellbookDeckRequest::fromDeckList(const DeckList &deck)
|
||||
{
|
||||
mainDeck.clear();
|
||||
commanderDeck.clear();
|
||||
|
||||
// --- Mainboard ---
|
||||
const auto mainCards = deck.getCardNodes({DECK_ZONE_MAIN});
|
||||
for (const DecklistCardNode *node : mainCards) {
|
||||
if (!node) {
|
||||
continue;
|
||||
}
|
||||
|
||||
CardInDeckRequest req;
|
||||
QJsonObject json;
|
||||
json.insert("card", node->getName());
|
||||
json.insert("quantity", node->getNumber());
|
||||
req.fromJson(json);
|
||||
|
||||
mainDeck.append(req);
|
||||
|
||||
// Max size allowed by commanderspellbook
|
||||
if (mainDeck.size() >= 600) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// --- Commander (bannerCard) ---
|
||||
const auto &metadata = deck.getMetadata();
|
||||
if (!metadata.bannerCard.name.isEmpty()) {
|
||||
CardInDeckRequest commander;
|
||||
QJsonObject json;
|
||||
json.insert("card", metadata.bannerCard.name);
|
||||
json.insert("quantity", 1);
|
||||
commander.fromJson(json);
|
||||
|
||||
commanderDeck.append(commander);
|
||||
}
|
||||
}
|
||||
|
||||
void CommanderSpellbookDeckRequest::debugPrint() const
|
||||
{
|
||||
qDebug() << "Main deck:";
|
||||
for (const CardInDeckRequest &card : mainDeck) {
|
||||
card.debugPrint();
|
||||
}
|
||||
|
||||
qDebug() << "Commanders:";
|
||||
for (const CardInDeckRequest &card : commanderDeck) {
|
||||
card.debugPrint();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
#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 <QVector>
|
||||
|
||||
class CommanderSpellbookDeckRequest
|
||||
{
|
||||
public:
|
||||
CommanderSpellbookDeckRequest() = default;
|
||||
|
||||
void fromJson(const QJsonObject &json);
|
||||
QJsonObject toJson() const;
|
||||
void fromDeckList(const DeckList &deck);
|
||||
|
||||
void debugPrint() const;
|
||||
|
||||
const QVector<CardInDeckRequest> &main() const
|
||||
{
|
||||
return mainDeck;
|
||||
}
|
||||
const QVector<CardInDeckRequest> &commanders() const
|
||||
{
|
||||
return commanderDeck;
|
||||
}
|
||||
|
||||
private:
|
||||
QVector<CardInDeckRequest> mainDeck; // maxItems: 600
|
||||
QVector<CardInDeckRequest> commanderDeck; // maxItems: 12
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_COMMANDER_SPELLBOOK_DECK_REQUEST_H
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
#include "commander_spellbook_estimate_bracket_result.h"
|
||||
|
||||
static void parseCards(const QJsonObject &json, const QString &key, QVector<CommanderSpellbookCardResult> &out)
|
||||
{
|
||||
out.clear();
|
||||
for (const auto &v : json.value(key).toArray()) {
|
||||
if (!v.isObject())
|
||||
continue;
|
||||
CommanderSpellbookCardResult c;
|
||||
c.fromJson(v.toObject());
|
||||
out.append(c);
|
||||
}
|
||||
}
|
||||
|
||||
static void parseVariants(const QJsonObject &json, const QString &key, QVector<CommanderSpellbookVariantResult> &out)
|
||||
{
|
||||
out.clear();
|
||||
for (const auto &v : json.value(key).toArray()) {
|
||||
if (!v.isObject())
|
||||
continue;
|
||||
CommanderSpellbookVariantResult vr;
|
||||
vr.fromJson(v.toObject());
|
||||
out.append(vr);
|
||||
}
|
||||
}
|
||||
|
||||
void EstimateBracketResult::fromJson(const QJsonObject &json)
|
||||
{
|
||||
bracketTag = CommanderSpellbookBracketTag::bracketTagFromString(json.value("bracketTag").toString());
|
||||
|
||||
parseCards(json, "gameChangerCards", gameChangerCards);
|
||||
parseCards(json, "massLandDenialCards", massLandDenialCards);
|
||||
parseCards(json, "extraTurnCards", extraTurnCards);
|
||||
parseCards(json, "tutorCards", tutorCards);
|
||||
|
||||
parseVariants(json, "massLandDenialTemplates", massLandDenialTemplates);
|
||||
parseVariants(json, "massLandDenialCombos", massLandDenialCombos);
|
||||
parseVariants(json, "extraTurnTemplates", extraTurnTemplates);
|
||||
parseVariants(json, "extraTurnsCombos", extraTurnsCombos);
|
||||
parseVariants(json, "tutorTemplates", tutorTemplates);
|
||||
parseVariants(json, "lockCombos", lockCombos);
|
||||
parseVariants(json, "skipTurnsCombos", skipTurnsCombos);
|
||||
parseVariants(json, "definitelyEarlyGameTwoCardCombos", definitelyEarlyGameTwoCardCombos);
|
||||
parseVariants(json, "arguablyEarlyGameTwoCardCombos", arguablyEarlyGameTwoCardCombos);
|
||||
parseVariants(json, "definitelyLateGameTwoCardCombos", definitelyLateGameTwoCardCombos);
|
||||
parseVariants(json, "borderlineLateGameTwoCardCombos", borderlineLateGameTwoCardCombos);
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
#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 <QVector>
|
||||
|
||||
class EstimateBracketResult
|
||||
{
|
||||
public:
|
||||
void fromJson(const QJsonObject &json);
|
||||
|
||||
CommanderSpellbookBracketTag::BracketTag bracketTag = CommanderSpellbookBracketTag::BracketTag::Unknown;
|
||||
|
||||
QVector<CommanderSpellbookCardResult> gameChangerCards;
|
||||
QVector<CommanderSpellbookCardResult> massLandDenialCards;
|
||||
QVector<CommanderSpellbookCardResult> extraTurnCards;
|
||||
QVector<CommanderSpellbookCardResult> tutorCards;
|
||||
|
||||
QVector<CommanderSpellbookVariantResult> massLandDenialTemplates;
|
||||
QVector<CommanderSpellbookVariantResult> massLandDenialCombos;
|
||||
QVector<CommanderSpellbookVariantResult> extraTurnTemplates;
|
||||
QVector<CommanderSpellbookVariantResult> extraTurnsCombos;
|
||||
QVector<CommanderSpellbookVariantResult> tutorTemplates;
|
||||
QVector<CommanderSpellbookVariantResult> lockCombos;
|
||||
QVector<CommanderSpellbookVariantResult> skipTurnsCombos;
|
||||
QVector<CommanderSpellbookVariantResult> definitelyEarlyGameTwoCardCombos;
|
||||
QVector<CommanderSpellbookVariantResult> arguablyEarlyGameTwoCardCombos;
|
||||
QVector<CommanderSpellbookVariantResult> definitelyLateGameTwoCardCombos;
|
||||
QVector<CommanderSpellbookVariantResult> borderlineLateGameTwoCardCombos;
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_COMMANDER_SPELLBOOK_ESTIMATE_BRACKET_RESULT_H
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
#include "commander_spellbook_variant_result.h"
|
||||
|
||||
void CommanderSpellbookVariantResult::fromJson(const QJsonObject &json)
|
||||
{
|
||||
id = json.value("id").toString();
|
||||
status = json.value("status").toString();
|
||||
|
||||
uses = json.value("uses").toArray();
|
||||
cardRequires = json.value("requires").toArray();
|
||||
produces = json.value("produces").toArray();
|
||||
of = json.value("of").toArray();
|
||||
includes = json.value("includes").toArray();
|
||||
|
||||
manaNeeded = json.value("manaNeeded").toArray();
|
||||
manaValueNeeded = json.value("manaValueNeeded").toArray();
|
||||
|
||||
easyPrerequisites = json.value("easyPrerequisites").toArray();
|
||||
notablePrerequisites = json.value("notablePrerequisites").toArray();
|
||||
|
||||
description = json.value("description").toString();
|
||||
notes = json.value("notes").toString();
|
||||
popularity = json.value("popularity").toDouble();
|
||||
|
||||
spoiler = json.value("spoiler").toBool();
|
||||
bracketTag = CommanderSpellbookBracketTag::bracketTagFromString(json.value("bracketTag").toString());
|
||||
|
||||
legalities = json.value("legalities").toObject();
|
||||
prices = json.value("prices").toObject();
|
||||
|
||||
variantCount = json.value("variantCount").toInt();
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
#ifndef COCKATRICE_COMMANDER_SPELLBOOK_VARIANT_RESULT_H
|
||||
#define COCKATRICE_COMMANDER_SPELLBOOK_VARIANT_RESULT_H
|
||||
#include "commander_spellbook_bracket_tag.h"
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
|
||||
class CommanderSpellbookVariantResult
|
||||
{
|
||||
public:
|
||||
void 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;
|
||||
CommanderSpellbookBracketTag::BracketTag bracketTag = CommanderSpellbookBracketTag::BracketTag::Unknown;
|
||||
|
||||
QJsonObject legalities;
|
||||
QJsonObject prices;
|
||||
|
||||
int variantCount = 0;
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_COMMANDER_SPELLBOOK_VARIANT_RESULT_H
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
#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;
|
||||
deckRequest.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;
|
||||
result.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,131 @@
|
|||
#include "commander_spellbook_bracket_explainer.h"
|
||||
|
||||
static QString cardList(const QVector<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 QVector<CommanderSpellbookVariantResult> &variants)
|
||||
{
|
||||
return QString::number(variants.size());
|
||||
}
|
||||
|
||||
BracketExplanation BracketExplainer::explain(const EstimateBracketResult &r)
|
||||
{
|
||||
BracketExplanation out;
|
||||
out.bracket = r.bracketTag;
|
||||
|
||||
// --- Game changers ---
|
||||
if (!r.gameChangerCards.isEmpty()) {
|
||||
BracketExplanationSection s;
|
||||
s.title = "Game-changing cards";
|
||||
s.bulletPoints << QString("Your deck contains %1 game-changing cards, such as %2.")
|
||||
.arg(r.gameChangerCards.size())
|
||||
.arg(cardList(r.gameChangerCards));
|
||||
out.sections << s;
|
||||
}
|
||||
|
||||
// --- Tutors ---
|
||||
if (!r.tutorCards.isEmpty()) {
|
||||
BracketExplanationSection s;
|
||||
s.title = "Tutors";
|
||||
s.bulletPoints << QString("The deck runs %1 tutor cards, including %2.")
|
||||
.arg(r.tutorCards.size())
|
||||
.arg(cardList(r.tutorCards));
|
||||
out.sections << s;
|
||||
}
|
||||
|
||||
// --- Extra turns ---
|
||||
if (!r.extraTurnCards.isEmpty()) {
|
||||
BracketExplanationSection s;
|
||||
s.title = "Extra turn effects";
|
||||
s.bulletPoints << QString("Extra turn spells were detected (%1), such as %2.")
|
||||
.arg(r.extraTurnCards.size())
|
||||
.arg(cardList(r.extraTurnCards));
|
||||
out.sections << s;
|
||||
}
|
||||
|
||||
// --- Mass land denial ---
|
||||
if (!r.massLandDenialCards.isEmpty() || !r.massLandDenialCombos.isEmpty()) {
|
||||
BracketExplanationSection s;
|
||||
s.title = "Mass land denial";
|
||||
|
||||
if (!r.massLandDenialCards.isEmpty()) {
|
||||
s.bulletPoints << QString("The deck includes %1 mass land denial cards (%2).")
|
||||
.arg(r.massLandDenialCards.size())
|
||||
.arg(cardList(r.massLandDenialCards));
|
||||
}
|
||||
|
||||
if (!r.massLandDenialCombos.isEmpty()) {
|
||||
s.bulletPoints << QString("%1 mass land denial combo variants were identified.")
|
||||
.arg(comboCount(r.massLandDenialCombos));
|
||||
}
|
||||
|
||||
out.sections << s;
|
||||
}
|
||||
|
||||
// --- Lock / skip turns ---
|
||||
if (!r.lockCombos.isEmpty() || !r.skipTurnsCombos.isEmpty()) {
|
||||
BracketExplanationSection s;
|
||||
s.title = "Lock or skip-turn combos";
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// --- Early-game combos ---
|
||||
if (!r.definitelyEarlyGameTwoCardCombos.isEmpty() || !r.arguablyEarlyGameTwoCardCombos.isEmpty()) {
|
||||
|
||||
BracketExplanationSection s;
|
||||
s.title = "Early-game two-card combos";
|
||||
|
||||
if (!r.definitelyEarlyGameTwoCardCombos.isEmpty()) {
|
||||
s.bulletPoints << QString("%1 definitely early-game two-card combos were found.")
|
||||
.arg(comboCount(r.definitelyEarlyGameTwoCardCombos));
|
||||
}
|
||||
|
||||
if (!r.arguablyEarlyGameTwoCardCombos.isEmpty()) {
|
||||
s.bulletPoints << QString("%1 arguably early-game two-card combos were found.")
|
||||
.arg(comboCount(r.arguablyEarlyGameTwoCardCombos));
|
||||
}
|
||||
|
||||
out.sections << s;
|
||||
}
|
||||
|
||||
// --- Late-game combos ---
|
||||
if (!r.definitelyLateGameTwoCardCombos.isEmpty() || !r.borderlineLateGameTwoCardCombos.isEmpty()) {
|
||||
|
||||
BracketExplanationSection s;
|
||||
s.title = "Late-game two-card combos";
|
||||
|
||||
if (!r.definitelyLateGameTwoCardCombos.isEmpty()) {
|
||||
s.bulletPoints << QString("%1 definitely late-game two-card combos were found.")
|
||||
.arg(comboCount(r.definitelyLateGameTwoCardCombos));
|
||||
}
|
||||
|
||||
if (!r.borderlineLateGameTwoCardCombos.isEmpty()) {
|
||||
s.bulletPoints << QString("%1 borderline late-game two-card combos were found.")
|
||||
.arg(comboCount(r.borderlineLateGameTwoCardCombos));
|
||||
}
|
||||
|
||||
out.sections << s;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
#ifndef COCKATRICE_COMMANDER_SPELLBOOK_BRACKET_EXPLAINER_H
|
||||
#define COCKATRICE_COMMANDER_SPELLBOOK_BRACKET_EXPLAINER_H
|
||||
#include "api_response/commander_spellbook_estimate_bracket_result.h"
|
||||
|
||||
struct BracketExplanationSection
|
||||
{
|
||||
QString title;
|
||||
QStringList bulletPoints;
|
||||
};
|
||||
|
||||
struct BracketExplanation
|
||||
{
|
||||
CommanderSpellbookBracketTag::BracketTag 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
|
||||
Loading…
Reference in New Issue
Block a user