From aa4592dc9e26c2ea0d08ae68ab7d3b09b77797a8 Mon Sep 17 00:00:00 2001 From: DawnFire42 Date: Thu, 12 Mar 2026 17:30:01 -0400 Subject: [PATCH] Add local game options (#6669) * Add local game options dialog. Introduces LocalGameOptions struct and DlgLocalGameOptions dialog to replace the previous QInputDialog for starting local games. Encapsulates game configuration with a simple interface that prevents parameter explosion as options are added. The dialog provides UI with settings persistence via SettingsCache * integrate local game options into main window. Replaces QInputDialog with DlgLocalGameOptions in actSinglePlayer(). The startLocalGame() function now accepts LocalGameOptions, enabling configuration of starting life total and spectator visibility in addition to player count. Also adds user documentation for the local game options flow. * Removed superfluous documentation file * removed spectator option and moved structure definition * Now remember settings separately and & shortcuts removed * re-run checks --- cockatrice/CMakeLists.txt | 1 + .../src/client/settings/cache_settings.cpp | 25 ++++++ .../src/client/settings/cache_settings.h | 21 +++++ .../dialogs/dlg_local_game_options.cpp | 83 +++++++++++++++++++ .../widgets/dialogs/dlg_local_game_options.h | 53 ++++++++++++ cockatrice/src/interface/window_main.cpp | 22 ++--- cockatrice/src/interface/window_main.h | 4 +- .../extra-pages/user_documentation/index.md | 2 +- 8 files changed, 199 insertions(+), 12 deletions(-) create mode 100644 cockatrice/src/interface/widgets/dialogs/dlg_local_game_options.cpp create mode 100644 cockatrice/src/interface/widgets/dialogs/dlg_local_game_options.h diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index d675b809d..1ca3c77c2 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -39,6 +39,7 @@ set(cockatrice_SOURCES src/interface/widgets/dialogs/dlg_load_deck_from_clipboard.cpp src/interface/widgets/dialogs/dlg_load_deck_from_website.cpp src/interface/widgets/dialogs/dlg_load_remote_deck.cpp + src/interface/widgets/dialogs/dlg_local_game_options.cpp src/interface/widgets/dialogs/dlg_manage_sets.cpp src/interface/widgets/dialogs/dlg_register.cpp src/interface/widgets/dialogs/dlg_select_set_for_cards.cpp diff --git a/cockatrice/src/client/settings/cache_settings.cpp b/cockatrice/src/client/settings/cache_settings.cpp index e7a5d114e..1d8121b19 100644 --- a/cockatrice/src/client/settings/cache_settings.cpp +++ b/cockatrice/src/client/settings/cache_settings.cpp @@ -385,6 +385,13 @@ SettingsCache::SettingsCache() defaultStartingLifeTotal = settings->value("game/defaultstartinglifetotal", 20).toInt(); shareDecklistsOnLoad = settings->value("game/sharedecklistsonload", false).toBool(); rememberGameSettings = settings->value("game/remembergamesettings", true).toBool(); + + // Local game settings use "localgameoptions/" prefix to keep them separate + // from server game settings which use "game/" prefix + localGameRememberSettings = settings->value("localgameoptions/remembersettings", false).toBool(); + localGameMaxPlayers = settings->value("localgameoptions/maxplayers", 1).toInt(); + localGameStartingLifeTotal = settings->value("localgameoptions/startinglifetotal", 20).toInt(); + clientID = settings->value("personal/clientid", CLIENT_INFO_NOT_SET).toString(); clientVersion = settings->value("personal/clientversion", CLIENT_INFO_NOT_SET).toString(); } @@ -1242,6 +1249,24 @@ void SettingsCache::setRememberGameSettings(const bool _rememberGameSettings) settings->setValue("game/remembergamesettings", rememberGameSettings); } +void SettingsCache::setLocalGameRememberSettings(bool value) +{ + localGameRememberSettings = value; + settings->setValue("localgameoptions/remembersettings", value); +} + +void SettingsCache::setLocalGameMaxPlayers(int value) +{ + localGameMaxPlayers = value; + settings->setValue("localgameoptions/maxplayers", value); +} + +void SettingsCache::setLocalGameStartingLifeTotal(int value) +{ + localGameStartingLifeTotal = value; + settings->setValue("localgameoptions/startinglifetotal", value); +} + void SettingsCache::setNotifyAboutUpdate(QT_STATE_CHANGED_T _notifyaboutupdate) { notifyAboutUpdates = static_cast(_notifyaboutupdate); diff --git a/cockatrice/src/client/settings/cache_settings.h b/cockatrice/src/client/settings/cache_settings.h index 5c5054105..2bbf85352 100644 --- a/cockatrice/src/client/settings/cache_settings.h +++ b/cockatrice/src/client/settings/cache_settings.h @@ -330,6 +330,12 @@ private: [[nodiscard]] QString getSafeConfigFilePath(QString configEntry, QString defaultPath) const; void loadPaths(); bool rememberGameSettings; + + // Local game settings (separate from server game settings in game/*) + bool localGameRememberSettings; + int localGameMaxPlayers; + int localGameStartingLifeTotal; + QList releaseChannels; bool isPortableBuild; bool roundCardCorners; @@ -862,6 +868,18 @@ public: { return rememberGameSettings; } + [[nodiscard]] bool getLocalGameRememberSettings() const + { + return localGameRememberSettings; + } + [[nodiscard]] int getLocalGameMaxPlayers() const + { + return localGameMaxPlayers; + } + [[nodiscard]] int getLocalGameStartingLifeTotal() const + { + return localGameStartingLifeTotal; + } [[nodiscard]] int getKeepAlive() const override { return keepalive; @@ -1089,6 +1107,9 @@ public slots: void setDefaultStartingLifeTotal(const int _defaultStartingLifeTotal); void setShareDecklistsOnLoad(const bool _shareDecklistsOnLoad); void setRememberGameSettings(const bool _rememberGameSettings); + void setLocalGameRememberSettings(bool value); + void setLocalGameMaxPlayers(int value); + void setLocalGameStartingLifeTotal(int value); void setCheckUpdatesOnStartup(QT_STATE_CHANGED_T value); void setStartupCardUpdateCheckPromptForUpdate(bool value); void setStartupCardUpdateCheckAlwaysUpdate(bool value); diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_local_game_options.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_local_game_options.cpp new file mode 100644 index 000000000..52466ff10 --- /dev/null +++ b/cockatrice/src/interface/widgets/dialogs/dlg_local_game_options.cpp @@ -0,0 +1,83 @@ +#include "dlg_local_game_options.h" + +#include "../../../client/settings/cache_settings.h" + +#include +#include +#include +#include +#include +#include +#include + +DlgLocalGameOptions::DlgLocalGameOptions(QWidget *parent) : QDialog(parent) +{ + numberPlayersLabel = new QLabel(tr("Players:"), this); + numberPlayersEdit = new QSpinBox(this); + numberPlayersEdit->setMinimum(1); + numberPlayersEdit->setMaximum(8); + numberPlayersEdit->setValue(1); + numberPlayersLabel->setBuddy(numberPlayersEdit); + + auto *generalGrid = new QGridLayout; + generalGrid->addWidget(numberPlayersLabel, 0, 0); + generalGrid->addWidget(numberPlayersEdit, 0, 1); + generalGroupBox = new QGroupBox(tr("General"), this); + generalGroupBox->setLayout(generalGrid); + + startingLifeTotalLabel = new QLabel(tr("Starting life total:"), this); + startingLifeTotalEdit = new QSpinBox(this); + startingLifeTotalEdit->setMinimum(1); + startingLifeTotalEdit->setMaximum(99999); + startingLifeTotalEdit->setValue(20); + startingLifeTotalLabel->setBuddy(startingLifeTotalEdit); + + auto *gameSetupGrid = new QGridLayout; + gameSetupGrid->addWidget(startingLifeTotalLabel, 0, 0); + gameSetupGrid->addWidget(startingLifeTotalEdit, 0, 1); + gameSetupOptionsGroupBox = new QGroupBox(tr("Game setup options"), this); + gameSetupOptionsGroupBox->setLayout(gameSetupGrid); + + rememberSettingsCheckBox = new QCheckBox(tr("Remember settings"), this); + + buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); + connect(buttonBox, &QDialogButtonBox::accepted, this, &DlgLocalGameOptions::actOK); + connect(buttonBox, &QDialogButtonBox::rejected, this, &DlgLocalGameOptions::reject); + + auto *mainLayout = new QVBoxLayout; + mainLayout->addWidget(generalGroupBox); + mainLayout->addWidget(gameSetupOptionsGroupBox); + mainLayout->addWidget(rememberSettingsCheckBox); + mainLayout->addWidget(buttonBox); + setLayout(mainLayout); + + rememberSettingsCheckBox->setChecked(SettingsCache::instance().getLocalGameRememberSettings()); + if (rememberSettingsCheckBox->isChecked()) { + numberPlayersEdit->setValue(SettingsCache::instance().getLocalGameMaxPlayers()); + startingLifeTotalEdit->setValue(SettingsCache::instance().getLocalGameStartingLifeTotal()); + } + + setWindowTitle(tr("Local game options")); + setFixedHeight(sizeHint().height()); + + numberPlayersEdit->setFocus(); +} + +void DlgLocalGameOptions::actOK() +{ + SettingsCache::instance().setLocalGameRememberSettings(rememberSettingsCheckBox->isChecked()); + if (rememberSettingsCheckBox->isChecked()) { + SettingsCache::instance().setLocalGameMaxPlayers(numberPlayersEdit->value()); + SettingsCache::instance().setLocalGameStartingLifeTotal(startingLifeTotalEdit->value()); + } + + accept(); +} + +LocalGameOptions DlgLocalGameOptions::getOptions() const +{ + return LocalGameOptions{ + .numberPlayers = numberPlayersEdit->value(), + .startingLifeTotal = startingLifeTotalEdit->value(), + }; +} diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_local_game_options.h b/cockatrice/src/interface/widgets/dialogs/dlg_local_game_options.h new file mode 100644 index 000000000..4307581a4 --- /dev/null +++ b/cockatrice/src/interface/widgets/dialogs/dlg_local_game_options.h @@ -0,0 +1,53 @@ +/** + * @file dlg_local_game_options.h + * @ingroup RoomDialogs + * @brief Dialog for configuring local game options. + * + * Provides a user interface for setting up local games with configurable + * number of players and starting life total. + */ + +#ifndef DLG_LOCAL_GAME_OPTIONS_H +#define DLG_LOCAL_GAME_OPTIONS_H + +#include + +struct LocalGameOptions +{ + int numberPlayers = 1; + int startingLifeTotal = 20; +}; + +class QCheckBox; +class QDialogButtonBox; +class QGroupBox; +class QLabel; +class QSpinBox; + +class DlgLocalGameOptions : public QDialog +{ + Q_OBJECT +public: + explicit DlgLocalGameOptions(QWidget *parent = nullptr); + + [[nodiscard]] LocalGameOptions getOptions() const; + +private slots: + void actOK(); + +private: + QGroupBox *generalGroupBox; + QGroupBox *gameSetupOptionsGroupBox; + + QLabel *numberPlayersLabel; + QSpinBox *numberPlayersEdit; + + QLabel *startingLifeTotalLabel; + QSpinBox *startingLifeTotalEdit; + + QCheckBox *rememberSettingsCheckBox; + + QDialogButtonBox *buttonBox; +}; + +#endif // DLG_LOCAL_GAME_OPTIONS_H diff --git a/cockatrice/src/interface/window_main.cpp b/cockatrice/src/interface/window_main.cpp index 3724ff29d..fbba6c3f5 100644 --- a/cockatrice/src/interface/window_main.cpp +++ b/cockatrice/src/interface/window_main.cpp @@ -27,6 +27,7 @@ #include "../interface/widgets/dialogs/dlg_forgot_password_challenge.h" #include "../interface/widgets/dialogs/dlg_forgot_password_request.h" #include "../interface/widgets/dialogs/dlg_forgot_password_reset.h" +#include "../interface/widgets/dialogs/dlg_local_game_options.h" #include "../interface/widgets/dialogs/dlg_manage_sets.h" #include "../interface/widgets/dialogs/dlg_register.h" #include "../interface/widgets/dialogs/dlg_settings.h" @@ -49,7 +50,6 @@ #include #include #include -#include #include #include #include @@ -225,16 +225,15 @@ void MainWindow::actDisconnect() void MainWindow::actSinglePlayer() { - bool ok; - int numberPlayers = - QInputDialog::getInt(this, tr("Number of players"), tr("Please enter the number of players."), 1, 1, 8, 1, &ok); - if (!ok) + DlgLocalGameOptions dlg(this); + if (dlg.exec() != QDialog::Accepted) { return; + } - startLocalGame(numberPlayers); + startLocalGame(dlg.getOptions()); } -void MainWindow::startLocalGame(int numberPlayers) +void MainWindow::startLocalGame(const LocalGameOptions &options) { aConnect->setEnabled(false); aRegister->setEnabled(false); @@ -248,7 +247,7 @@ void MainWindow::startLocalGame(int numberPlayers) QList localClients; localClients.append(mainClient); - for (int i = 0; i < numberPlayers - 1; ++i) { + for (int i = 0; i < options.numberPlayers - 1; ++i) { LocalServerInterface *slaveLsi = localServer->newConnection(); LocalClient *slaveClient = new LocalClient(slaveLsi, tr("Player %1").arg(i + 2), SettingsCache::instance().getClientID(), this); @@ -257,7 +256,8 @@ void MainWindow::startLocalGame(int numberPlayers) tabSupervisor->startLocal(localClients); Command_CreateGame createCommand; - createCommand.set_max_players(static_cast(numberPlayers)); + createCommand.set_max_players(static_cast(options.numberPlayers)); + createCommand.set_starting_life_total(options.startingLifeTotal); mainClient->sendCommand(LocalClient::prepareRoomCommand(createCommand, 0)); } @@ -913,7 +913,9 @@ MainWindow::MainWindow(QWidget *parent) void MainWindow::startupConfigCheck() { if (SettingsCache::instance().debug().getLocalGameOnStartup()) { - startLocalGame(SettingsCache::instance().debug().getLocalGamePlayerCount()); + LocalGameOptions options; + options.numberPlayers = SettingsCache::instance().debug().getLocalGamePlayerCount(); + startLocalGame(options); } if (SettingsCache::instance().getCheckUpdatesOnStartup()) { diff --git a/cockatrice/src/interface/window_main.h b/cockatrice/src/interface/window_main.h index 75b0f5062..ed6de5b0d 100644 --- a/cockatrice/src/interface/window_main.h +++ b/cockatrice/src/interface/window_main.h @@ -25,6 +25,8 @@ #ifndef WINDOW_H #define WINDOW_H +#include "widgets/dialogs/dlg_local_game_options.h" + #include #include #include @@ -137,7 +139,7 @@ private: void createCardUpdateProcess(bool background = false); void exitCardDatabaseUpdate(); - void startLocalGame(int numberPlayers); + void startLocalGame(const LocalGameOptions &options); QList tabMenus; QMenu *cockatriceMenu, *dbMenu, *tabsMenu, *helpMenu, *trayIconMenu; diff --git a/doc/doxygen/extra-pages/user_documentation/index.md b/doc/doxygen/extra-pages/user_documentation/index.md index 3f10a0ac6..d7e9d529d 100644 --- a/doc/doxygen/extra-pages/user_documentation/index.md +++ b/doc/doxygen/extra-pages/user_documentation/index.md @@ -1,5 +1,5 @@ @page user_reference User Reference - + ## Deck Management - @subpage creating_decks