From 6dfd354973004c4aa7cecc94508e235d28bf04fe Mon Sep 17 00:00:00 2001 From: Zach H Date: Thu, 26 Dec 2024 18:32:20 -0500 Subject: [PATCH] Support starting games with fewer than max players (#5338) --- cockatrice/src/client/tabs/tab_game.cpp | 16 ++++++++++++++ cockatrice/src/client/tabs/tab_game.h | 3 ++- common/pb/command_ready_start.proto | 1 + common/server_game.cpp | 29 +++++++++++++++++-------- common/server_game.h | 6 ++--- common/server_player.cpp | 11 +++++++--- 6 files changed, 50 insertions(+), 16 deletions(-) diff --git a/cockatrice/src/client/tabs/tab_game.cpp b/cockatrice/src/client/tabs/tab_game.cpp index d858a9362..13eeb3376 100644 --- a/cockatrice/src/client/tabs/tab_game.cpp +++ b/cockatrice/src/client/tabs/tab_game.cpp @@ -106,11 +106,14 @@ DeckViewContainer::DeckViewContainer(int _playerId, TabGame *parent) loadRemoteButton = new QPushButton; readyStartButton = new ToggleButton; readyStartButton->setEnabled(false); + forceStartGameButton = new QPushButton; + forceStartGameButton->setEnabled(parent->isHost()); sideboardLockButton = new ToggleButton; sideboardLockButton->setEnabled(false); connect(loadLocalButton, SIGNAL(clicked()), this, SLOT(loadLocalDeck())); connect(readyStartButton, SIGNAL(clicked()), this, SLOT(readyStart())); + connect(forceStartGameButton, &QPushButton::clicked, this, &DeckViewContainer::forceStart); connect(sideboardLockButton, SIGNAL(clicked()), this, SLOT(sideboardLockButtonClicked())); connect(sideboardLockButton, SIGNAL(stateChanged()), this, SLOT(updateSideboardLockButtonText())); @@ -125,6 +128,9 @@ DeckViewContainer::DeckViewContainer(int _playerId, TabGame *parent) buttonHBox->addWidget(loadRemoteButton); buttonHBox->addWidget(readyStartButton); buttonHBox->addWidget(sideboardLockButton); + if (forceStartGameButton->isEnabled()) { + buttonHBox->addWidget(forceStartGameButton); + } buttonHBox->setContentsMargins(0, 0, 0, 0); buttonHBox->addStretch(); deckView = new DeckView; @@ -147,6 +153,7 @@ void DeckViewContainer::retranslateUi() loadLocalButton->setText(tr("Load deck...")); loadRemoteButton->setText(tr("Load remote deck...")); readyStartButton->setText(tr("Ready to start")); + forceStartGameButton->setText(tr("Force start")); updateSideboardLockButtonText(); } @@ -155,6 +162,7 @@ void DeckViewContainer::setButtonsVisible(bool _visible) loadLocalButton->setVisible(_visible); loadRemoteButton->setVisible(_visible); readyStartButton->setVisible(_visible); + forceStartGameButton->setVisible(_visible); sideboardLockButton->setVisible(_visible); } @@ -338,6 +346,14 @@ void DeckViewContainer::readyStart() parentGame->sendGameCommand(cmd, playerId); } +void DeckViewContainer::forceStart() +{ + Command_ReadyStart cmd; + cmd.set_force_start(true); + cmd.set_ready(true); + parentGame->sendGameCommand(cmd, playerId); +} + void DeckViewContainer::sideboardLockButtonClicked() { Command_SetSideboardLock cmd; diff --git a/cockatrice/src/client/tabs/tab_game.h b/cockatrice/src/client/tabs/tab_game.h index 3d8ebaa70..e1c2c207e 100644 --- a/cockatrice/src/client/tabs/tab_game.h +++ b/cockatrice/src/client/tabs/tab_game.h @@ -86,7 +86,7 @@ class DeckViewContainer : public QWidget { Q_OBJECT private: - QPushButton *loadLocalButton, *loadRemoteButton; + QPushButton *loadLocalButton, *loadRemoteButton, *forceStartGameButton; ToggleButton *readyStartButton, *sideboardLockButton; DeckView *deckView; TabGame *parentGame; @@ -95,6 +95,7 @@ private slots: void loadLocalDeck(); void loadRemoteDeck(); void readyStart(); + void forceStart(); void deckSelectFinished(const Response &r); void sideboardPlanChanged(); void sideboardLockButtonClicked(); diff --git a/common/pb/command_ready_start.proto b/common/pb/command_ready_start.proto index 1d7d203c2..912ed55af 100644 --- a/common/pb/command_ready_start.proto +++ b/common/pb/command_ready_start.proto @@ -5,4 +5,5 @@ message Command_ReadyStart { optional Command_ReadyStart ext = 1016; } optional bool ready = 1; + optional bool force_start = 2; } diff --git a/common/server_game.cpp b/common/server_game.cpp index 484bf8926..17ab01dda 100644 --- a/common/server_game.cpp +++ b/common/server_game.cpp @@ -81,7 +81,7 @@ Server_Game::Server_Game(const ServerInfo_User &_creatorInfo, currentReplay->set_replay_id(room->getServer()->getDatabaseInterface()->getNextReplayId()); description = _description.simplified(); - connect(this, SIGNAL(sigStartGameIfReady()), this, SLOT(doStartGameIfReady()), Qt::QueuedConnection); + connect(this, &Server_Game::sigStartGameIfReady, this, &Server_Game::doStartGameIfReady, Qt::QueuedConnection); getInfo(*currentReplay->mutable_game_info()); @@ -311,20 +311,31 @@ void Server_Game::sendGameStateToPlayers() } } -void Server_Game::doStartGameIfReady() +void Server_Game::doStartGameIfReady(bool forceStartGame) { Server_DatabaseInterface *databaseInterface = room->getServer()->getDatabaseInterface(); QMutexLocker locker(&gameMutex); - if (getPlayerCount() < maxPlayers) + if (getPlayerCount() < maxPlayers && !forceStartGame) { return; - for (Server_Player *player : players.values()) { - if (!player->getReadyStart() && !player->getSpectator()) - return; } + for (Server_Player *player : players.values()) { - if (!player->getSpectator()) + if (!player->getReadyStart() && !player->getSpectator()) { + if (forceStartGame) { + // Player is not ready to start, so kick them + // TODO: Move them to Spectators instead + kickPlayer(player->getPlayerId()); + } else { + return; + } + } + } + + for (Server_Player *player : players.values()) { + if (!player->getSpectator()) { player->setupZones(); + } } gameStarted = true; @@ -369,9 +380,9 @@ void Server_Game::doStartGameIfReady() emit gameInfoChanged(gameInfo); } -void Server_Game::startGameIfReady() +void Server_Game::startGameIfReady(bool forceStartGame) { - emit sigStartGameIfReady(); + emit sigStartGameIfReady(forceStartGame); } void Server_Game::stopGameIfFinished() diff --git a/common/server_game.h b/common/server_game.h index 73418a2a4..d1a8dda96 100644 --- a/common/server_game.h +++ b/common/server_game.h @@ -82,11 +82,11 @@ private: bool withUserInfo); void storeGameInformation(); signals: - void sigStartGameIfReady(); + void sigStartGameIfReady(bool override); void gameInfoChanged(ServerInfo_Game gameInfo); private slots: void pingClockTimeout(); - void doStartGameIfReady(); + void doStartGameIfReady(bool forceStartGame = false); public: #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) @@ -180,7 +180,7 @@ public: void removeArrowsRelatedToPlayer(GameEventStorage &ges, Server_Player *player); void unattachCards(GameEventStorage &ges, Server_Player *player); bool kickPlayer(int playerId); - void startGameIfReady(); + void startGameIfReady(bool forceStartGame); void stopGameIfFinished(); int getActivePlayer() const { diff --git a/common/server_player.cpp b/common/server_player.cpp index 8e769ccdc..9b64428d8 100644 --- a/common/server_player.cpp +++ b/common/server_player.cpp @@ -944,7 +944,7 @@ Server_Player::cmdReadyStart(const Command_ReadyStart &cmd, ResponseContainer & return Response::RespContextError; } - if (readyStart == cmd.ready()) { + if (readyStart == cmd.ready() && !cmd.force_start()) { return Response::RespContextError; } @@ -955,8 +955,13 @@ Server_Player::cmdReadyStart(const Command_ReadyStart &cmd, ResponseContainer & ges.enqueueGameEvent(event, playerId); ges.setGameEventContext(Context_ReadyStart()); - if (cmd.ready()) { - game->startGameIfReady(); + if (cmd.force_start()) { + if (game->getHostId() != playerId) { + return Response::RespFunctionNotAllowed; + } + game->startGameIfReady(true); + } else if (cmd.ready()) { + game->startGameIfReady(false); } return Response::RespOk;