From 80703adbbbebb5b4b0415b47f64dc3b857924459 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20Br=C3=BCbach?= Date: Wed, 2 Sep 2026 09:49:53 +0200 Subject: [PATCH] [Server] Replay tournament state to late joiners The bracket, phase and standings live in Event_TournamentState, which only flows on mutation. Without a copy a player or spectator joining after round one would sit on an empty bracket until the next advance, so the current state is now enqueued as part of the join snapshot. Extracts Event_TournamentState building into buildStateEvent() so both the broadcast path and the join path share one source of truth. --- .../network/server/remote/game/server_game.cpp | 9 +++++++++ .../network/server/remote/game/server_tournament.cpp | 9 +++++++-- .../network/server/remote/game/server_tournament.h | 2 ++ 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.cpp b/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.cpp index 0098be19d..16c012806 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.cpp +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.cpp @@ -812,6 +812,15 @@ void Server_Game::createGameJoinedEvent(Server_AbstractParticipant *joiningParti } rc.enqueuePostResponseItem(ServerMessage::GAME_EVENT_CONTAINER, prepareGameEvent(event2, -1)); + + // A tournament's bracket/phase/standings live in Event_TournamentState, which + // normally only flows on mutation. Without a copy here a late joiner would sit + // on an empty bracket until the next round advances, so replay the current + // state as part of the join snapshot. + if (tournament) { + rc.enqueuePostResponseItem(ServerMessage::GAME_EVENT_CONTAINER, + prepareGameEvent(tournament->buildStateEvent(), -1)); + } } void Server_Game::sendGameEventContainer(GameEventContainer *cont, diff --git a/libcockatrice_network/libcockatrice/network/server/remote/game/server_tournament.cpp b/libcockatrice_network/libcockatrice/network/server/remote/game/server_tournament.cpp index 527f2e728..65529072f 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/game/server_tournament.cpp +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_tournament.cpp @@ -635,7 +635,7 @@ void Server_Tournament::checkAndAdvanceRound(GameEventStorage &ges) } } -void Server_Tournament::broadcastTournamentState(GameEventStorage &ges) +Event_TournamentState Server_Tournament::buildStateEvent() const { QMutexLocker locker(&tournamentMutex); @@ -682,5 +682,10 @@ void Server_Tournament::broadcastTournamentState(GameEventStorage &ges) p->set_player2_match_wins(pairing.player2MatchWins); } - ges.enqueueGameEvent(state, -1); + return state; +} + +void Server_Tournament::broadcastTournamentState(GameEventStorage &ges) +{ + ges.enqueueGameEvent(buildStateEvent(), -1); } diff --git a/libcockatrice_network/libcockatrice/network/server/remote/game/server_tournament.h b/libcockatrice_network/libcockatrice/network/server/remote/game/server_tournament.h index aeb3ea732..438490e84 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/game/server_tournament.h +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_tournament.h @@ -36,6 +36,8 @@ public: void recordMatchResult(int playerId1, int playerId2, int winnerId, GameEventStorage &ges); bool recordMatchResultByGameId(int gameId, int winnerId, GameEventStorage &ges); void broadcastTournamentState(GameEventStorage &ges); + // Current tournament state message, for replaying to a participant joining late. + Event_TournamentState buildStateEvent() const; bool isStarted() const {