[Client] Add shuffle players option and turn order dialog

Adds the 'Shuffle players at start' checkbox to game creation and a
host-only 'Set turn order...' action that opens a dialog to reorder or
randomize the seats before the game starts. Seating changes are shown in
the game's message log.
This commit is contained in:
Lukas Brübach 2026-08-04 22:51:22 +02:00
parent d2d65836ab
commit 17a1f8cf78
15 changed files with 312 additions and 1 deletions

View file

@ -56,6 +56,7 @@ set(cockatrice_SOURCES
src/interface/widgets/dialogs/dlg_settings.cpp
src/interface/widgets/dialogs/dlg_startup_card_check.cpp
src/interface/widgets/dialogs/dlg_tip_of_the_day.cpp
src/interface/widgets/dialogs/dlg_turn_order.cpp
src/interface/widgets/dialogs/dlg_update.cpp
src/interface/widgets/dialogs/dlg_view_log.cpp
src/interface/widgets/dialogs/override_printing_warning.cpp

View file

@ -605,6 +605,20 @@ void MessageLogWidget::logReverseTurn(PlayerLogic *player, bool reversed)
.arg(reversed ? tr("reversed") : tr("normal")));
}
void MessageLogWidget::logTurnOrderChanged(QStringList playerNames, bool randomized)
{
QStringList sanitizedNames;
for (const QString &name : playerNames) {
sanitizedNames.append(sanitizeHtml(name));
}
const QString order = sanitizedNames.join(" -> ");
if (randomized) {
appendHtmlServerMessage(tr("Randomized turn order: %1.").arg(order));
} else {
appendHtmlServerMessage(tr("New turn order set: %1.").arg(order));
}
}
void MessageLogWidget::logRollDie(PlayerLogic *player, int sides, const QList<uint> &rolls)
{
if (rolls.length() == 1) {

View file

@ -94,6 +94,7 @@ public slots:
int amount,
bool isLentToAnotherPlayer);
void logReverseTurn(PlayerLogic *player, bool reversed);
void logTurnOrderChanged(QStringList playerNames, bool randomized);
void logRollDie(PlayerLogic *player, int sides, const QList<uint> &rolls);
void logSay(PlayerLogic *player, QString message);
void logSetActivePhase(int phase);

View file

@ -116,12 +116,15 @@ void DlgCreateGame::sharedCtor()
shareDecklistsOnLoadCheckBox = new QCheckBox(tr("Open decklists in lobby"));
shufflePlayersCheckBox = new QCheckBox(tr("Shuffle players at start"));
createGameAsJudgeCheckBox = new QCheckBox(tr("Create game as judge"));
auto *gameSetupOptionsLayout = new QGridLayout;
gameSetupOptionsLayout->addWidget(startingLifeTotalLabel, 0, 0);
gameSetupOptionsLayout->addWidget(startingLifeTotalEdit, 0, 1);
gameSetupOptionsLayout->addWidget(shareDecklistsOnLoadCheckBox, 1, 0);
gameSetupOptionsLayout->addWidget(shufflePlayersCheckBox, 1, 1);
if (room && room->getUserInfo()->user_level() & ServerInfo_User::IsJudge) {
gameSetupOptionsLayout->addWidget(createGameAsJudgeCheckBox, 2, 0);
} else {
@ -184,6 +187,7 @@ DlgCreateGame::DlgCreateGame(TabRoom *_room, const QMap<int, QString> &_gameType
createGameAsSpectatorCheckBox->setChecked(SettingsCache::instance().game().getCreateGameAsSpectator());
startingLifeTotalEdit->setValue(SettingsCache::instance().game().getDefaultStartingLifeTotal());
shareDecklistsOnLoadCheckBox->setChecked(SettingsCache::instance().game().getShareDecklistsOnLoad());
shufflePlayersCheckBox->setChecked(SettingsCache::instance().game().getShufflePlayers());
if (!rememberGameSettings->isChecked()) {
actReset();
@ -217,6 +221,7 @@ DlgCreateGame::DlgCreateGame(const ServerInfo_Game &gameInfo, const QMap<int, QS
createGameAsSpectatorCheckBox->setEnabled(false);
startingLifeTotalEdit->setEnabled(false);
shareDecklistsOnLoadCheckBox->setEnabled(false);
shufflePlayersCheckBox->setEnabled(false);
descriptionEdit->setText(QString::fromStdString(gameInfo.description()));
maxPlayersEdit->setValue(gameInfo.max_players());
@ -227,6 +232,7 @@ DlgCreateGame::DlgCreateGame(const ServerInfo_Game &gameInfo, const QMap<int, QS
spectatorsCanTalkCheckBox->setChecked(gameInfo.spectators_can_chat());
spectatorsSeeEverythingCheckBox->setChecked(gameInfo.spectators_omniscient());
shareDecklistsOnLoadCheckBox->setChecked(gameInfo.share_decklists_on_load());
shufflePlayersCheckBox->setChecked(gameInfo.shuffle_players());
QSet<int> types;
for (int i = 0; i < gameInfo.game_types_size(); ++i) {
@ -264,6 +270,7 @@ void DlgCreateGame::actReset()
startingLifeTotalEdit->setValue(20);
shareDecklistsOnLoadCheckBox->setChecked(false);
shufflePlayersCheckBox->setChecked(false);
createGameAsJudgeCheckBox->setChecked(false);
QMapIterator<int, QRadioButton *> gameTypeCheckBoxIterator(gameTypeCheckBoxes);
@ -294,6 +301,7 @@ void DlgCreateGame::actOK()
cmd.set_join_as_spectator(createGameAsSpectatorCheckBox->isChecked());
cmd.set_starting_life_total(startingLifeTotalEdit->value());
cmd.set_share_decklists_on_load(shareDecklistsOnLoadCheckBox->isChecked());
cmd.set_shuffle_players(shufflePlayersCheckBox->isChecked());
auto _gameTypes = QString();
QMapIterator<int, QRadioButton *> gameTypeCheckBoxIterator(gameTypeCheckBoxes);
@ -318,6 +326,7 @@ void DlgCreateGame::actOK()
SettingsCache::instance().game().setCreateGameAsSpectator(createGameAsSpectatorCheckBox->isChecked());
SettingsCache::instance().game().setDefaultStartingLifeTotal(startingLifeTotalEdit->value());
SettingsCache::instance().game().setShareDecklistsOnLoad(shareDecklistsOnLoadCheckBox->isChecked());
SettingsCache::instance().game().setShufflePlayers(shufflePlayersCheckBox->isChecked());
SettingsCache::instance().game().setGameTypes(_gameTypes);
}
PendingCommand *pend = room->prepareRoomCommand(cmd);

View file

@ -51,6 +51,7 @@ private:
QCheckBox *spectatorsAllowedCheckBox, *spectatorsNeedPasswordCheckBox, *spectatorsCanTalkCheckBox,
*spectatorsSeeEverythingCheckBox, *createGameAsJudgeCheckBox, *createGameAsSpectatorCheckBox;
QCheckBox *shareDecklistsOnLoadCheckBox;
QCheckBox *shufflePlayersCheckBox;
QDialogButtonBox *buttonBox;
QPushButton *clearButton;
QCheckBox *rememberGameSettings;

View file

@ -0,0 +1,114 @@
#include "dlg_turn_order.h"
#include <QDialogButtonBox>
#include <QHBoxLayout>
#include <QListWidget>
#include <QListWidgetItem>
#include <QPushButton>
#include <QRandomGenerator>
#include <QVBoxLayout>
DlgTurnOrder::DlgTurnOrder(const QStringList &_playerNames, QWidget *parent)
: QDialog(parent), randomizeRequested(false)
{
listWidget = new QListWidget(this);
listWidget->addItems(_playerNames);
listWidget->setCurrentRow(0);
connect(listWidget, &QListWidget::currentRowChanged, this, &DlgTurnOrder::selectionChanged);
moveUpButton = new QPushButton(tr("Move up"), this);
connect(moveUpButton, &QPushButton::clicked, this, &DlgTurnOrder::actMoveUp);
moveDownButton = new QPushButton(tr("Move down"), this);
connect(moveDownButton, &QPushButton::clicked, this, &DlgTurnOrder::actMoveDown);
randomizeButton = new QPushButton(tr("Randomize"), this);
connect(randomizeButton, &QPushButton::clicked, this, &DlgTurnOrder::actRandomize);
auto *buttonColumn = new QVBoxLayout;
buttonColumn->addWidget(moveUpButton);
buttonColumn->addWidget(moveDownButton);
buttonColumn->addWidget(randomizeButton);
buttonColumn->addStretch();
auto *listLayout = new QHBoxLayout;
listLayout->addWidget(listWidget);
listLayout->addLayout(buttonColumn);
auto *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
connect(buttonBox, &QDialogButtonBox::accepted, this, &DlgTurnOrder::actOk);
connect(buttonBox, &QDialogButtonBox::rejected, this, &DlgTurnOrder::reject);
auto *mainLayout = new QVBoxLayout;
mainLayout->addLayout(listLayout);
mainLayout->addWidget(buttonBox);
setLayout(mainLayout);
updateButtons();
setWindowTitle(tr("Set turn order"));
setMinimumWidth(300);
}
QStringList DlgTurnOrder::order() const
{
QStringList playerNames;
for (int i = 0; i < listWidget->count(); ++i) {
playerNames.append(listWidget->item(i)->text());
}
return playerNames;
}
void DlgTurnOrder::moveSelected(int delta)
{
const int row = listWidget->currentRow();
const int newRow = row + delta;
if (row < 0 || newRow < 0 || newRow >= listWidget->count()) {
return;
}
QListWidgetItem *item = listWidget->takeItem(row);
listWidget->insertItem(newRow, item);
listWidget->setCurrentRow(newRow);
randomizeRequested = false;
updateButtons();
}
void DlgTurnOrder::updateButtons()
{
const int row = listWidget->currentRow();
const int count = listWidget->count();
moveUpButton->setEnabled(row > 0);
moveDownButton->setEnabled(row >= 0 && row < count - 1);
}
void DlgTurnOrder::actMoveUp()
{
moveSelected(-1);
}
void DlgTurnOrder::actMoveDown()
{
moveSelected(1);
}
void DlgTurnOrder::actRandomize()
{
QList<QListWidgetItem *> items;
while (listWidget->count() > 0) {
items.append(listWidget->takeItem(0));
}
while (!items.isEmpty()) {
const int index = QRandomGenerator::global()->bounded(items.size());
listWidget->addItem(items.takeAt(index));
}
randomizeRequested = true;
updateButtons();
}
void DlgTurnOrder::actOk()
{
accept();
}
void DlgTurnOrder::selectionChanged()
{
updateButtons();
}

View file

@ -0,0 +1,46 @@
/**
* @file dlg_turn_order.h
* @ingroup GameDialogs
*/
//! \todo Document this file.
#ifndef DLG_TURN_ORDER_H
#define DLG_TURN_ORDER_H
#include <QDialog>
#include <QStringList>
class QListWidget;
class QPushButton;
class DlgTurnOrder : public QDialog
{
Q_OBJECT
private:
QListWidget *listWidget;
QPushButton *moveUpButton;
QPushButton *moveDownButton;
QPushButton *randomizeButton;
bool randomizeRequested;
void moveSelected(int delta);
void updateButtons();
private slots:
void actMoveUp();
void actMoveDown();
void actRandomize();
void actOk();
void selectionChanged();
public:
explicit DlgTurnOrder(const QStringList &_playerNames, QWidget *parent = nullptr);
[[nodiscard]] bool randomize() const
{
return randomizeRequested;
}
[[nodiscard]] QStringList order() const;
};
#endif

View file

@ -20,6 +20,7 @@
#include "../interface/widgets/cards/card_info_frame_widget.h"
#include "../interface/widgets/dialogs/dlg_create_game.h"
#include "../interface/widgets/dialogs/dlg_invite_to_game.h"
#include "../interface/widgets/dialogs/dlg_turn_order.h"
#include "../interface/widgets/server/game_link.h"
#include "../interface/widgets/server/user/user_list_manager.h"
#include "../interface/widgets/utility/completer_utils.h"
@ -259,6 +260,8 @@ void TabGame::connectMessageLogToGameEventHandler()
connect(game->getGameEventHandler(), &GameEventHandler::logTurnReversed, messageLog,
&MessageLogWidget::logReverseTurn);
connect(game->getGameEventHandler(), &GameEventHandler::logTurnOrderChanged, messageLog,
&MessageLogWidget::logTurnOrderChanged);
connect(game->getGameEventHandler(), &GameEventHandler::logConcede, messageLog, &MessageLogWidget::logConcede);
connect(game->getGameEventHandler(), &GameEventHandler::logUnconcede, messageLog, &MessageLogWidget::logUnconcede);
@ -369,6 +372,9 @@ void TabGame::retranslateUi()
if (aReverseTurn) {
aReverseTurn->setText(tr("Reverse turn order"));
}
if (aTurnOrder) {
aTurnOrder->setText(tr("&Set turn order..."));
}
if (aRemoveLocalArrows) {
aRemoveLocalArrows->setText(tr("&Remove all local arrows"));
}
@ -625,6 +631,25 @@ void TabGame::actConcede()
}
}
void TabGame::actTurnOrder()
{
if (!game->isHost() || game->getGameMetaInfo()->started()) {
return;
}
QStringList playerNames;
for (PlayerLogic *player : game->getPlayerManager()->getPlayers()) {
playerNames.append(player->getPlayerInfo()->getName());
}
DlgTurnOrder dlg(playerNames, this);
if (dlg.exec() != QDialog::Accepted) {
return;
}
game->getGameEventHandler()->handleSetPlayerOrder(dlg.order(), dlg.randomize());
}
/**
* Confirms the leave game and sends the leave game command, if applicable.
*
@ -836,6 +861,15 @@ void TabGame::processPlayerLeave(PlayerLogic *leavingPlayer)
}
}
// A shuffled local player has their seat changed, so the deck view container
// keyed by their previous id must be cleaned up alongside the leaving player.
int leavingPlayerId = leavingPlayer->getPlayerInfo()->getId();
if (deckViewContainers.contains(leavingPlayerId)) {
TabbedDeckViewContainer *deckView = deckViewContainers.take(leavingPlayerId);
deckViewContainerLayout->removeWidget(deckView);
deckView->deleteLater();
}
scene->removePlayer(leavingPlayer);
}
@ -1090,6 +1124,11 @@ void TabGame::createMenuItems()
connect(aNextTurn, &QAction::triggered, game->getGameEventHandler(), &GameEventHandler::handleNextTurn);
aReverseTurn = new QAction(this);
connect(aReverseTurn, &QAction::triggered, game->getGameEventHandler(), &GameEventHandler::handleReverseTurn);
aTurnOrder = new QAction(this);
connect(aTurnOrder, &QAction::triggered, this, &TabGame::actTurnOrder);
if (!game->isHost() || game->getGameMetaInfo()->started()) {
aTurnOrder->setVisible(false);
}
aRemoveLocalArrows = new QAction(this);
connect(aRemoveLocalArrows, &QAction::triggered, this, &TabGame::actRemoveLocalArrows);
aRotateViewCW = new QAction(this);
@ -1135,6 +1174,7 @@ void TabGame::createMenuItems()
gameMenu->addMenu(phasesMenu);
gameMenu->addAction(aNextTurn);
gameMenu->addAction(aReverseTurn);
gameMenu->addAction(aTurnOrder);
gameMenu->addSeparator();
gameMenu->addAction(aRemoveLocalArrows);
gameMenu->addAction(aRotateViewCW);
@ -1163,6 +1203,7 @@ void TabGame::createReplayMenuItems()
aNextPhaseAction = nullptr;
aNextTurn = nullptr;
aReverseTurn = nullptr;
aTurnOrder = nullptr;
aRemoveLocalArrows = nullptr;
aRotateViewCW = nullptr;
aRotateViewCCW = nullptr;

View file

@ -94,7 +94,8 @@ private:
QMenu *gameMenu, *viewMenu;
TearOffMenu *phasesMenu;
QAction *aGameInfo, *aConcede, *aCopyGameLink, *aLeaveGame, *aNextPhase, *aNextPhaseAction, *aNextTurn,
*aReverseTurn, *aRemoveLocalArrows, *aRotateViewCW, *aRotateViewCCW, *aResetLayout, *aResetReplayLayout;
*aReverseTurn, *aTurnOrder, *aRemoveLocalArrows, *aRotateViewCW, *aRotateViewCCW, *aResetLayout,
*aResetReplayLayout;
QAction *aFocusChat;
QAction *aInviteToGame = nullptr;
QList<QAction *> phaseActions;
@ -162,6 +163,7 @@ private slots:
void actInviteToGame();
void actConcede();
void actCopyGameLink();
void actTurnOrder();
void actRemoveLocalArrows();
void actRotateViewCW();
void actRotateViewCCW();

View file

@ -21,6 +21,7 @@
#include <QMap>
#include <QMapIterator>
#include <QMessageLogger>
#include <QSet>
#include <QSharedPointer>
#include <google/protobuf/descriptor.h>
#include <google/protobuf/message.h>
@ -38,6 +39,7 @@
#include <libcockatrice/protocol/pb/command_reverse_turn.pb.h>
#include <libcockatrice/protocol/pb/command_set_active_phase.pb.h>
#include <libcockatrice/protocol/pb/command_set_card_attr.pb.h>
#include <libcockatrice/protocol/pb/command_set_player_order.pb.h>
#include <libcockatrice/protocol/pb/command_set_playmat.pb.h>
#include <libcockatrice/protocol/pb/command_set_sideboard_lock.pb.h>
#include <libcockatrice/protocol/pb/command_set_sideboard_plan.pb.h>
@ -50,6 +52,7 @@
#include <libcockatrice/protocol/pb/event_join.pb.h>
#include <libcockatrice/protocol/pb/event_kicked.pb.h>
#include <libcockatrice/protocol/pb/event_leave.pb.h>
#include <libcockatrice/protocol/pb/event_player_order_changed.pb.h>
#include <libcockatrice/protocol/pb/event_player_properties_changed.pb.h>
#include <libcockatrice/protocol/pb/event_reverse_turn.pb.h>
#include <libcockatrice/protocol/pb/event_set_active_phase.pb.h>
@ -189,6 +192,9 @@ void GameEventHandler::processGameEventContainer(const GameEventContainer &cont,
case GameEvent::REVERSE_TURN:
eventReverseTurn(event.GetExtension(Event_ReverseTurn::ext), playerId, context);
break;
case GameEvent::PLAYER_ORDER_CHANGED:
eventPlayerOrderChanged(event.GetExtension(Event_PlayerOrderChanged::ext), playerId, context);
break;
default: {
PlayerLogic *player = game->getPlayerManager()->getPlayers().value(playerId, 0);
@ -215,6 +221,16 @@ void GameEventHandler::handleReverseTurn()
sendGameCommand(Command_ReverseTurn());
}
void GameEventHandler::handleSetPlayerOrder(const QList<QString> &playerNames, bool randomize)
{
Command_SetPlayerOrder command;
command.set_randomize(randomize);
for (const QString &name : playerNames) {
command.add_player_names(name.toStdString());
}
sendGameCommand(command);
}
void GameEventHandler::handleActiveLocalPlayerConceded()
{
sendGameCommand(Command_Concede());
@ -377,6 +393,27 @@ void GameEventHandler::eventGameStateChanged(const Event_GameStateChanged &event
{
const int playerListSize = event.player_list_size();
if (event.has_local_player_id()) {
game->getPlayerManager()->setLocalPlayerId(event.local_player_id());
}
QSet<int> authoritativePlayerIds;
for (int i = 0; i < playerListSize; ++i) {
const ServerInfo_PlayerProperties &prop = event.player_list(i).properties();
if (!prop.spectator()) {
authoritativePlayerIds.insert(prop.player_id());
}
}
// Players whose seats were shuffled away from the previous game are no longer
// part of the authoritative list and must be removed.
for (int stalePlayerId : game->getPlayerManager()->getPlayers().keys()) {
if (!authoritativePlayerIds.contains(stalePlayerId)) {
game->getPlayerManager()->removePlayer(stalePlayerId);
emit playerLeft(stalePlayerId);
}
}
QVector<QPair<int, QPair<QString, QString>>> opponentDecksToDisplay;
for (int i = 0; i < playerListSize; ++i) {
@ -625,6 +662,17 @@ void GameEventHandler::eventReverseTurn(const Event_ReverseTurn &event,
emit logTurnReversed(player, event.reversed());
}
void GameEventHandler::eventPlayerOrderChanged(const Event_PlayerOrderChanged &event,
int /*eventPlayerId*/,
const GameEventContext & /*context*/)
{
QStringList playerNames;
for (const auto &name : event.player_names()) {
playerNames.append(QString::fromStdString(name));
}
emit logTurnOrderChanged(playerNames, event.randomized());
}
void GameEventHandler::eventGameHostChanged(const Event_GameHostChanged & /*event*/,
int eventPlayerId,
const GameEventContext & /*context*/)

View file

@ -51,6 +51,7 @@ class Event_GameSay;
class Event_Kicked;
class Event_ReverseTurn;
class Event_Ping;
class Event_PlayerOrderChanged;
class ServerInfo_PlayerProperties;
namespace google
{
@ -113,6 +114,16 @@ public:
/** @brief Request reversing the current turn order. */
void handleReverseTurn();
/**
* @brief Request a new seating / turn order.
*
* Only the game host (or a moderator) may do this before the game starts.
*
* @param playerNames Ordered list of player names. Ignored when randomize is true.
* @param randomize Ask the server to shuffle the seats instead.
*/
void handleSetPlayerOrder(const QList<QString> &playerNames, bool randomize);
/** @brief Concede the game for the currently active local player. */
void handleActiveLocalPlayerConceded();
@ -316,6 +327,10 @@ public:
/** @brief Handle a turn reversal event. */
void eventReverseTurn(const Event_ReverseTurn &event, int eventPlayerId, const GameEventContext &context);
/** @brief Handle a change of the seating / turn order. */
void
eventPlayerOrderChanged(const Event_PlayerOrderChanged &event, int eventPlayerId, const GameEventContext &context);
/** @brief Handle ping / latency updates. */
void eventPing(const Event_Ping &event, int eventPlayerId, const GameEventContext &context);
@ -443,6 +458,7 @@ signals:
void logLeave(PlayerLogic *player, QString reason);
void logKicked();
void logTurnReversed(PlayerLogic *player, bool reversed);
void logTurnOrderChanged(QStringList playerNames, bool randomized);
void logGameClosed();
void logActivePlayer(PlayerLogic *activePlayer);
void logActivePhaseChanged(int activePhase);

View file

@ -57,6 +57,11 @@ public:
return localPlayerId;
}
void setLocalPlayerId(int _localPlayerId)
{
localPlayerId = _localPlayerId;
}
[[nodiscard]] const QMap<int, PlayerLogic *> &getPlayers() const
{
return players;

View file

@ -20,6 +20,7 @@ public:
[[nodiscard]] virtual bool getCreateGameAsSpectator() const = 0;
[[nodiscard]] virtual int getDefaultStartingLifeTotal() const = 0;
[[nodiscard]] virtual bool getShareDecklistsOnLoad() const = 0;
[[nodiscard]] virtual bool getShufflePlayers() const = 0;
[[nodiscard]] virtual bool getRememberGameSettings() const = 0;
[[nodiscard]] virtual bool getLocalGameRememberSettings() const = 0;
[[nodiscard]] virtual int getLocalGameMaxPlayers() const = 0;

View file

@ -71,6 +71,11 @@ bool GameSettings::getShareDecklistsOnLoad() const
return getValue("shareDecklistsOnLoad", "game").toBool();
}
bool GameSettings::getShufflePlayers() const
{
return getValue("shuffleplayers", "game").toBool();
}
bool GameSettings::getRememberGameSettings() const
{
return getValue("rememberGameSettings", "game", QString(), true).toBool();
@ -151,6 +156,11 @@ void GameSettings::setShareDecklistsOnLoad(bool _shareDecklistsOnLoad)
setValue(_shareDecklistsOnLoad, "shareDecklistsOnLoad", "game");
}
void GameSettings::setShufflePlayers(bool _shufflePlayers)
{
setValue(_shufflePlayers, "shuffleplayers", "game");
}
void GameSettings::setRememberGameSettings(bool _rememberGameSettings)
{
setValue(_rememberGameSettings, "rememberGameSettings", "game");

View file

@ -27,6 +27,7 @@ public:
[[nodiscard]] bool getCreateGameAsSpectator() const override;
[[nodiscard]] int getDefaultStartingLifeTotal() const override;
[[nodiscard]] bool getShareDecklistsOnLoad() const override;
[[nodiscard]] bool getShufflePlayers() const override;
[[nodiscard]] bool getRememberGameSettings() const override;
[[nodiscard]] bool getLocalGameRememberSettings() const override;
[[nodiscard]] int getLocalGameMaxPlayers() const override;
@ -44,6 +45,7 @@ public:
void setCreateGameAsSpectator(bool _createGameAsSpectator);
void setDefaultStartingLifeTotal(int _defaultStartingLifeTotal);
void setShareDecklistsOnLoad(bool _shareDecklistsOnLoad);
void setShufflePlayers(bool _shufflePlayers);
void setRememberGameSettings(bool _rememberGameSettings);
void setLocalGameRememberSettings(bool value);
void setLocalGameMaxPlayers(int value);