From e82b311c8b4bd59fb6e489fce19811d9be865303 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20Br=C3=BCbach?= Date: Mon, 24 Aug 2026 08:43:46 +0200 Subject: [PATCH 1/3] [Protocol] Add tournament state event and tournament game commands Adds the protocol layer for the tournament game mode: - Event_TournamentState (2027) carrying phase, round counters, player standings, pairings, and settings (games_per_match) - Command_ReportMatchResult / Command_AdvanceTournament / Command_TournamentSettingsSelect game command extensions (1037-1039) - is_tournament flag on Command_CreateGame and ServerInfo_Game; is_tournament and parent_game_id on Event_GameStateChanged Took 13 minutes --- .../libcockatrice/protocol/pb/CMakeLists.txt | 2 + .../protocol/pb/command_tournament.proto | 26 +++++++++++ .../pb/event_game_state_changed.proto | 6 +++ .../protocol/pb/event_tournament_state.proto | 43 +++++++++++++++++++ .../protocol/pb/game_commands.proto | 9 ++++ .../protocol/pb/game_event.proto | 1 + .../protocol/pb/room_commands.proto | 6 +++ .../protocol/pb/serverinfo_game.proto | 3 ++ 8 files changed, 96 insertions(+) create mode 100644 libcockatrice_protocol/libcockatrice/protocol/pb/command_tournament.proto create mode 100644 libcockatrice_protocol/libcockatrice/protocol/pb/event_tournament_state.proto diff --git a/libcockatrice_protocol/libcockatrice/protocol/pb/CMakeLists.txt b/libcockatrice_protocol/libcockatrice/protocol/pb/CMakeLists.txt index f22828f46..1164e1523 100644 --- a/libcockatrice_protocol/libcockatrice/protocol/pb/CMakeLists.txt +++ b/libcockatrice_protocol/libcockatrice/protocol/pb/CMakeLists.txt @@ -60,6 +60,7 @@ set(PROTO_FILES command_set_sideboard_lock.proto command_set_sideboard_plan.proto command_shuffle.proto + command_tournament.proto command_undo_draw.proto commands.proto context_concede.proto @@ -117,6 +118,7 @@ set(PROTO_FILES event_set_card_counter.proto event_set_counter.proto event_shuffle.proto + event_tournament_state.proto event_user_joined.proto event_user_left.proto event_user_message.proto diff --git a/libcockatrice_protocol/libcockatrice/protocol/pb/command_tournament.proto b/libcockatrice_protocol/libcockatrice/protocol/pb/command_tournament.proto new file mode 100644 index 000000000..ee958f587 --- /dev/null +++ b/libcockatrice_protocol/libcockatrice/protocol/pb/command_tournament.proto @@ -0,0 +1,26 @@ +syntax = "proto2"; +import "game_commands.proto"; +import "event_tournament_state.proto"; + +message Command_ReportMatchResult { + extend GameCommand { + optional Command_ReportMatchResult ext = 1037; + } + + optional sint32 game_id = 1 [default = -1]; + optional sint32 winner_id = 2 [default = -1]; +} + +message Command_AdvanceTournament { + extend GameCommand { + optional Command_AdvanceTournament ext = 1038; + } +} + +message Command_TournamentSettingsSelect { + extend GameCommand { + optional Command_TournamentSettingsSelect ext = 1039; + } + + optional TournamentSettings settings = 1; +} diff --git a/libcockatrice_protocol/libcockatrice/protocol/pb/event_game_state_changed.proto b/libcockatrice_protocol/libcockatrice/protocol/pb/event_game_state_changed.proto index 5c8aee3fe..c085e6a59 100644 --- a/libcockatrice_protocol/libcockatrice/protocol/pb/event_game_state_changed.proto +++ b/libcockatrice_protocol/libcockatrice/protocol/pb/event_game_state_changed.proto @@ -24,4 +24,10 @@ message Event_GameStateChanged { // the amount of seconds since the game started optional uint32 seconds_elapsed = 5; + + // whether this game is a tournament game + optional bool is_tournament = 9; + + // for tournament sub-games: the ID of the parent tournament game (-1 if not a sub-game) + optional sint32 parent_game_id = 10 [default = -1]; } diff --git a/libcockatrice_protocol/libcockatrice/protocol/pb/event_tournament_state.proto b/libcockatrice_protocol/libcockatrice/protocol/pb/event_tournament_state.proto new file mode 100644 index 000000000..5fb003f50 --- /dev/null +++ b/libcockatrice_protocol/libcockatrice/protocol/pb/event_tournament_state.proto @@ -0,0 +1,43 @@ +syntax = "proto2"; +import "game_event.proto"; + +message TournamentPlayer { + optional sint32 player_id = 1; + optional string player_name = 2; + optional uint32 wins = 3; + optional uint32 losses = 4; + optional uint32 draws = 5; + optional bool deck_submitted = 6; +} + +message TournamentPairing { + optional sint32 player1_id = 1; + optional sint32 player2_id = 2 [default = -1]; + optional sint32 game_id = 3; + optional sint32 winner_id = 4 [default = -1]; + optional sint32 player1_match_wins = 5 [default = 0]; + optional sint32 player2_match_wins = 6 [default = 0]; +} + +message TournamentSettings { + optional uint32 games_per_match = 1 [default = 1]; +} + +message Event_TournamentState { + extend GameEvent { + optional Event_TournamentState ext = 2027; + } + + enum TournamentPhase { + PHASE_DECK_BUILDING = 0; + PHASE_PLAYING = 1; + PHASE_FINISHED = 2; + } + + optional TournamentPhase phase = 1; + optional uint32 current_round = 2; + optional uint32 total_rounds = 3; + repeated TournamentPlayer players = 4; + repeated TournamentPairing pairings = 5; + optional TournamentSettings settings = 6; +} diff --git a/libcockatrice_protocol/libcockatrice/protocol/pb/game_commands.proto b/libcockatrice_protocol/libcockatrice/protocol/pb/game_commands.proto index 2e5b88978..68a79eb6b 100644 --- a/libcockatrice_protocol/libcockatrice/protocol/pb/game_commands.proto +++ b/libcockatrice_protocol/libcockatrice/protocol/pb/game_commands.proto @@ -180,6 +180,15 @@ message GameCommand { /// Server: Server_Player::cmdSetPlaymat /// Client: reflected via player properties changed event SET_PLAYMAT = 1035; + + /// Report the result of a tournament match sub-game. + REPORT_MATCH_RESULT = 1037; + + /// Advance the tournament to the next round. + ADVANCE_TOURNAMENT = 1038; + + /// Select tournament settings. + TOURNAMENT_SETTINGS_SELECT = 1039; } extensions 100 to max; diff --git a/libcockatrice_protocol/libcockatrice/protocol/pb/game_event.proto b/libcockatrice_protocol/libcockatrice/protocol/pb/game_event.proto index 7d3147701..b952bfdef 100644 --- a/libcockatrice_protocol/libcockatrice/protocol/pb/game_event.proto +++ b/libcockatrice_protocol/libcockatrice/protocol/pb/game_event.proto @@ -34,6 +34,7 @@ message GameEvent { CHANGE_ZONE_PROPERTIES = 2020; REVERSE_TURN = 2021; GAME_LOG_NOTICE = 2022; + TOURNAMENT_STATE = 2027; } optional sint32 player_id = 1 [default = -1]; extensions 100 to max; diff --git a/libcockatrice_protocol/libcockatrice/protocol/pb/room_commands.proto b/libcockatrice_protocol/libcockatrice/protocol/pb/room_commands.proto index a8c90ec6c..bf4d5310f 100644 --- a/libcockatrice_protocol/libcockatrice/protocol/pb/room_commands.proto +++ b/libcockatrice_protocol/libcockatrice/protocol/pb/room_commands.proto @@ -69,6 +69,12 @@ message Command_CreateGame { // share decklists with all players when selected optional bool share_decklists_on_load = 14; + + // number of games per match in tournament mode (e.g. 3 for best of 3) + optional uint32 games_per_match = 15 [default = 1]; + + // whether this is a tournament game + optional bool is_tournament = 16; } message Command_JoinGame { diff --git a/libcockatrice_protocol/libcockatrice/protocol/pb/serverinfo_game.proto b/libcockatrice_protocol/libcockatrice/protocol/pb/serverinfo_game.proto index 9989ae18a..4ac004cf4 100644 --- a/libcockatrice_protocol/libcockatrice/protocol/pb/serverinfo_game.proto +++ b/libcockatrice_protocol/libcockatrice/protocol/pb/serverinfo_game.proto @@ -65,4 +65,7 @@ message ServerInfo_Game { // the current host of the game, which may differ from the creator after a host transfer optional ServerInfo_User host_info = 53; + + // whether this game is a tournament game + optional bool is_tournament = 54; } From dca6fdf17fb00a98e25848843a47b9bfa9ca1d3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20Br=C3=BCbach?= Date: Wed, 2 Sep 2026 08:43:25 +0200 Subject: [PATCH 2/3] [Protocol] Share tournament messages and report draws explicitly - TournamentPlayer/TournamentPairing/TournamentSettings move into serverinfo_tournament.proto, imported by both the tournament event and the tournament game commands, so Command_CreateGame reaches them without crossing the command/event boundary - Command_CreateGame carries the full TournamentSettings instead of a duplicate games_per_match field, keeping game creation and mid-game changes on one wire representation - draw results are explicit: Command_ReportMatchResult and TournamentPairing gain is_draw, so a report stays distinguishable from an unset -1 winner - pairing match-wins are uint32 like the other game counts --- .../libcockatrice/protocol/pb/CMakeLists.txt | 1 + .../protocol/pb/command_tournament.proto | 9 ++++-- .../protocol/pb/event_tournament_state.proto | 25 ++------------- .../protocol/pb/room_commands.proto | 5 +-- .../protocol/pb/serverinfo_tournament.proto | 32 +++++++++++++++++++ 5 files changed, 45 insertions(+), 27 deletions(-) create mode 100644 libcockatrice_protocol/libcockatrice/protocol/pb/serverinfo_tournament.proto diff --git a/libcockatrice_protocol/libcockatrice/protocol/pb/CMakeLists.txt b/libcockatrice_protocol/libcockatrice/protocol/pb/CMakeLists.txt index 1164e1523..fd254a564 100644 --- a/libcockatrice_protocol/libcockatrice/protocol/pb/CMakeLists.txt +++ b/libcockatrice_protocol/libcockatrice/protocol/pb/CMakeLists.txt @@ -185,6 +185,7 @@ set(PROTO_FILES serverinfo_replay_match.proto serverinfo_report.proto serverinfo_room.proto + serverinfo_tournament.proto serverinfo_user.proto serverinfo_user_alt.proto serverinfo_user_session.proto diff --git a/libcockatrice_protocol/libcockatrice/protocol/pb/command_tournament.proto b/libcockatrice_protocol/libcockatrice/protocol/pb/command_tournament.proto index ee958f587..f465ed6f7 100644 --- a/libcockatrice_protocol/libcockatrice/protocol/pb/command_tournament.proto +++ b/libcockatrice_protocol/libcockatrice/protocol/pb/command_tournament.proto @@ -1,6 +1,6 @@ syntax = "proto2"; import "game_commands.proto"; -import "event_tournament_state.proto"; +import "serverinfo_tournament.proto"; message Command_ReportMatchResult { extend GameCommand { @@ -8,7 +8,12 @@ message Command_ReportMatchResult { } optional sint32 game_id = 1 [default = -1]; + // The winning player's ID. A drawn match is reported with is_draw set and + // winner_id left unset so a missing report stays distinguishable from a + // reported draw via has_winner_id(). optional sint32 winner_id = 2 [default = -1]; + // Whether the match ended in a draw; winner_id is meaningless then. + optional bool is_draw = 3; } message Command_AdvanceTournament { @@ -23,4 +28,4 @@ message Command_TournamentSettingsSelect { } optional TournamentSettings settings = 1; -} +} \ No newline at end of file diff --git a/libcockatrice_protocol/libcockatrice/protocol/pb/event_tournament_state.proto b/libcockatrice_protocol/libcockatrice/protocol/pb/event_tournament_state.proto index 5fb003f50..cfaaa6f01 100644 --- a/libcockatrice_protocol/libcockatrice/protocol/pb/event_tournament_state.proto +++ b/libcockatrice_protocol/libcockatrice/protocol/pb/event_tournament_state.proto @@ -1,27 +1,6 @@ syntax = "proto2"; import "game_event.proto"; - -message TournamentPlayer { - optional sint32 player_id = 1; - optional string player_name = 2; - optional uint32 wins = 3; - optional uint32 losses = 4; - optional uint32 draws = 5; - optional bool deck_submitted = 6; -} - -message TournamentPairing { - optional sint32 player1_id = 1; - optional sint32 player2_id = 2 [default = -1]; - optional sint32 game_id = 3; - optional sint32 winner_id = 4 [default = -1]; - optional sint32 player1_match_wins = 5 [default = 0]; - optional sint32 player2_match_wins = 6 [default = 0]; -} - -message TournamentSettings { - optional uint32 games_per_match = 1 [default = 1]; -} +import "serverinfo_tournament.proto"; message Event_TournamentState { extend GameEvent { @@ -40,4 +19,4 @@ message Event_TournamentState { repeated TournamentPlayer players = 4; repeated TournamentPairing pairings = 5; optional TournamentSettings settings = 6; -} +} \ No newline at end of file diff --git a/libcockatrice_protocol/libcockatrice/protocol/pb/room_commands.proto b/libcockatrice_protocol/libcockatrice/protocol/pb/room_commands.proto index bf4d5310f..e2f46e338 100644 --- a/libcockatrice_protocol/libcockatrice/protocol/pb/room_commands.proto +++ b/libcockatrice_protocol/libcockatrice/protocol/pb/room_commands.proto @@ -1,4 +1,5 @@ syntax = "proto2"; +import "serverinfo_tournament.proto"; message RoomCommand { enum RoomCommandType { LEAVE_ROOM = 1000; @@ -70,8 +71,8 @@ message Command_CreateGame { // share decklists with all players when selected optional bool share_decklists_on_load = 14; - // number of games per match in tournament mode (e.g. 3 for best of 3) - optional uint32 games_per_match = 15 [default = 1]; + // tournament settings shared by game creation and mid-game changes + optional TournamentSettings tournament_settings = 15; // whether this is a tournament game optional bool is_tournament = 16; diff --git a/libcockatrice_protocol/libcockatrice/protocol/pb/serverinfo_tournament.proto b/libcockatrice_protocol/libcockatrice/protocol/pb/serverinfo_tournament.proto new file mode 100644 index 000000000..217967ff8 --- /dev/null +++ b/libcockatrice_protocol/libcockatrice/protocol/pb/serverinfo_tournament.proto @@ -0,0 +1,32 @@ +syntax = "proto2"; + +// Player-entered tournament metadata as shown in the standings and bracket views. +message TournamentPlayer { + optional sint32 player_id = 1; + optional string player_name = 2; + optional uint32 wins = 3; + optional uint32 losses = 4; + optional uint32 draws = 5; + optional bool deck_submitted = 6; +} + +// A single match pairing within a tournament round. +message TournamentPairing { + optional sint32 player1_id = 1; + // -1 when the pairing is a bye. + optional sint32 player2_id = 2 [default = -1]; + optional sint32 game_id = 3; + // The winning player's ID, or -1 when the match ended in a draw (is_draw). + // An unset field reads back as -1 too; has_winner_id() distinguishes those. + optional sint32 winner_id = 4 [default = -1]; + optional uint32 player1_match_wins = 5; + optional uint32 player2_match_wins = 6; + // Whether the match ended in a draw; winner_id is meaningless then. + optional bool is_draw = 7; +} + +// Settings governing all matches of a tournament. +message TournamentSettings { + // Number of games per match (e.g. 3 for best of 3). + optional uint32 games_per_match = 1 [default = 1]; +} \ No newline at end of file From 5cea39a1fcc2f8b30fb398121254665df05cefd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20Br=C3=BCbach?= Date: Wed, 2 Sep 2026 10:27:51 +0200 Subject: [PATCH 3/3] [Protocol] Drop redundant is_tournament from the game-state event MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The client already learns a game is a tournament from Event_GameJoined's ServerInfo_Game.is_tournament, so mirroring it on every Event_GameStateChanged is dead wire data — nothing on the client reads it. Removed. --- .../libcockatrice/protocol/pb/event_game_state_changed.proto | 3 --- 1 file changed, 3 deletions(-) diff --git a/libcockatrice_protocol/libcockatrice/protocol/pb/event_game_state_changed.proto b/libcockatrice_protocol/libcockatrice/protocol/pb/event_game_state_changed.proto index c085e6a59..ae14cb44b 100644 --- a/libcockatrice_protocol/libcockatrice/protocol/pb/event_game_state_changed.proto +++ b/libcockatrice_protocol/libcockatrice/protocol/pb/event_game_state_changed.proto @@ -25,9 +25,6 @@ message Event_GameStateChanged { // the amount of seconds since the game started optional uint32 seconds_elapsed = 5; - // whether this game is a tournament game - optional bool is_tournament = 9; - // for tournament sub-games: the ID of the parent tournament game (-1 if not a sub-game) optional sint32 parent_game_id = 10 [default = -1]; }