From 2d5734de21ab016b2c9dff74ac5cdcd2ace4136b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20Br=C3=BCbach?= Date: Mon, 24 Aug 2026 09:07:11 +0200 Subject: [PATCH 1/2] [Client] Add tournament event plumbing and player extension points Wires the client game layer for tournament state without touching existing behavior: - GameEventHandler dispatches Event_TournamentState (2027) to a new tournamentStateChanged signal - GameMetaInfo exposes isTournament and parentGameId over the new ServerInfo_Game field - PlayerEventHandler::processGameEvent is virtual and player is protected; PlayerLogic gains a protected constructor accepting a custom handler, so mode-specific subclasses can intercept events --- cockatrice/src/game/game_event_handler.cpp | 10 ++++++++ cockatrice/src/game/game_event_handler.h | 2 ++ cockatrice/src/game/game_meta_info.h | 21 ++++++++++++++++ .../src/game/player/player_event_handler.h | 10 ++++---- cockatrice/src/game/player/player_logic.cpp | 13 ++++++++++ cockatrice/src/game/player/player_logic.h | 25 ++++++++++++++++++- 6 files changed, 75 insertions(+), 6 deletions(-) diff --git a/cockatrice/src/game/game_event_handler.cpp b/cockatrice/src/game/game_event_handler.cpp index bc68d4d7c..03930b0de 100644 --- a/cockatrice/src/game/game_event_handler.cpp +++ b/cockatrice/src/game/game_event_handler.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include @@ -158,6 +159,9 @@ void GameEventHandler::processGameEventContainer(const GameEventContainer &cont, case GameEvent::REVERSE_TURN: eventReverseTurn(event.GetExtension(Event_ReverseTurn::ext), playerId, context); break; + case GameEvent::TOURNAMENT_STATE: + emit tournamentStateChanged(event.GetExtension(Event_TournamentState::ext)); + break; default: { PlayerLogic *player = game->getPlayerManager()->getPlayers().value(playerId, 0); @@ -263,6 +267,12 @@ void GameEventHandler::eventGameStateChanged(const Event_GameStateChanged &event int /*eventPlayerId*/, const GameEventContext & /*context*/) { + // Sub-games of a tournament report their parent hub game so the client can + // route "close game" back to the parent tab. + if (event.parent_game_id() != -1) { + game->getGameMetaInfo()->setParentGameId(event.parent_game_id()); + } + const int playerListSize = event.player_list_size(); QVector>> opponentDecksToDisplay; diff --git a/cockatrice/src/game/game_event_handler.h b/cockatrice/src/game/game_event_handler.h index 99277abb7..7914a3228 100644 --- a/cockatrice/src/game/game_event_handler.h +++ b/cockatrice/src/game/game_event_handler.h @@ -43,6 +43,7 @@ class Event_SetActivePhase; class Event_GameSay; class Event_Kicked; class Event_ReverseTurn; +class Event_TournamentState; class Event_Ping; inline Q_LOGGING_CATEGORY(GameEventHandlerLog, "game_event_handler"); @@ -329,6 +330,7 @@ signals: void gameStopped(); void gameClosed(); + void tournamentStateChanged(const Event_TournamentState &state); void playerPropertiesChanged(const ServerInfo_PlayerProperties &prop, int playerId); void playerJoined(const ServerInfo_PlayerProperties &playerInfo); void playerLeft(int leavingPlayerId); diff --git a/cockatrice/src/game/game_meta_info.h b/cockatrice/src/game/game_meta_info.h index cdba1605f..8585238bd 100644 --- a/cockatrice/src/game/game_meta_info.h +++ b/cockatrice/src/game/game_meta_info.h @@ -84,6 +84,26 @@ public: return roomGameTypes.find(gameInfo_.game_types(index)).value(); } + bool isTournament() const + { + return gameInfo_.is_tournament(); + } + + void setIsTournament(bool t) + { + gameInfo_.set_is_tournament(t); + } + + int parentGameId() const + { + return parentGameId_; + } + + void setParentGameId(int id) + { + parentGameId_ = id; + } + public slots: void setStarted(bool s) { @@ -108,6 +128,7 @@ signals: private: ServerInfo_Game gameInfo_; + int parentGameId_ = -1; }; #endif // GAME_META_INFO_H diff --git a/cockatrice/src/game/player/player_event_handler.h b/cockatrice/src/game/player/player_event_handler.h index 48ad85e88..1e9814ddb 100644 --- a/cockatrice/src/game/player/player_event_handler.h +++ b/cockatrice/src/game/player/player_event_handler.h @@ -90,10 +90,10 @@ public: * @param context Additional context (undo, judge, etc.). * @param options Processing options (UI suppression, reveal behavior). */ - void processGameEvent(GameEvent::GameEventType type, - const GameEvent &event, - const GameEventContext &context, - EventProcessingOptions options); + virtual void processGameEvent(GameEvent::GameEventType type, + const GameEvent &event, + const GameEventContext &context, + EventProcessingOptions options); /** @} */ @@ -266,7 +266,7 @@ signals: void cardZoneChanged(CardItem *card, bool sameZone); void requestCardMenuUpdate(const CardItem *card); -private: +protected: /** Owning player instance. */ PlayerLogic *player; diff --git a/cockatrice/src/game/player/player_logic.cpp b/cockatrice/src/game/player/player_logic.cpp index 45ba09aac..084f3df30 100644 --- a/cockatrice/src/game/player/player_logic.cpp +++ b/cockatrice/src/game/player/player_logic.cpp @@ -37,6 +37,19 @@ PlayerLogic::PlayerLogic(const ServerInfo_User &info, int _id, bool _local, bool initializeZones(); } +PlayerLogic::PlayerLogic(const ServerInfo_User &info, + int _id, + bool _local, + bool _judge, + AbstractGame *_parent, + PlayerEventHandler *customEventHandler) + : QObject(_parent), game(_parent), playerInfo(new PlayerInfo(info, _id, _local, _judge)), + playerEventHandler(customEventHandler), playerActions(new PlayerActions(this)), active(false), conceded(false), + zoneId(0), dialogSemaphore(false) +{ + initializeZones(); +} + void PlayerLogic::initializeZones() { addZone(new PileZoneLogic(this, ZoneNames::DECK, false, true, false, this)); diff --git a/cockatrice/src/game/player/player_logic.h b/cockatrice/src/game/player/player_logic.h index 6923b3afe..26cd63f50 100644 --- a/cockatrice/src/game/player/player_logic.h +++ b/cockatrice/src/game/player/player_logic.h @@ -99,7 +99,30 @@ public: PlayerLogic(const ServerInfo_User &info, int _id, bool _local, bool _judge, AbstractGame *_parent); ~PlayerLogic() override; - void initializeZones(); +protected: + /** + * @brief Constructor for subclasses that need a custom event handler (e.g. DraftPlayerLogic). + * + * @p customEventHandler must be non-null and either QObject-parented to this PlayerLogic + * or deleted externally; it is not owned by PlayerLogic. The handler connects to @c player + * during its own constructor, so passing a freshly built subclass handler from an + * initializer list is safe. + */ + PlayerLogic(const ServerInfo_User &info, + int _id, + bool _local, + bool _judge, + AbstractGame *_parent, + PlayerEventHandler *customEventHandler); + +public: + /** + * @brief Creates the standard zone set. + * + * Not virtually dispatched from constructors — subclasses overriding this must add + * their extra zones in their own constructor body. + */ + virtual void initializeZones(); void updateZones(); void clear(); From 8f6d18df104beb707e675e3c395cc1deadd41137 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20Br=C3=BCbach?= Date: Wed, 2 Sep 2026 10:27:44 +0200 Subject: [PATCH 2/2] [Client] Drop dead player event handler extension points MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The second PlayerLogic constructor taking a custom PlayerEventHandler was UB (converting this to PlayerLogic* in the base initializer, and connecting a freshly-built handler against a not-yet-constructed object), and its abstract extension surface — virtual initializeZones, virtual getPlayerEventHandler, virtual processGameEvent, protected player member — has no subclass consumer in the stack. Revert to the single primary constructor. Also: - Drop the unused GameMetaInfo::setIsTournament setter. Tournament status is authoritative from Event_GameJoined's ServerInfo_Game; the redundant is_tournament field on Event_GameStateChanged is removed in [Protocol]. - Gate the parent-game routing on has_parent_game_id() instead of comparing the proto default, so a future drop of the default keeps working. --- cockatrice/src/game/game_event_handler.cpp | 5 ++-- cockatrice/src/game/game_meta_info.h | 5 ---- .../src/game/player/player_event_handler.h | 10 ++++---- cockatrice/src/game/player/player_logic.cpp | 13 ---------- cockatrice/src/game/player/player_logic.h | 25 +------------------ 5 files changed, 9 insertions(+), 49 deletions(-) diff --git a/cockatrice/src/game/game_event_handler.cpp b/cockatrice/src/game/game_event_handler.cpp index 03930b0de..b7f687030 100644 --- a/cockatrice/src/game/game_event_handler.cpp +++ b/cockatrice/src/game/game_event_handler.cpp @@ -268,8 +268,9 @@ void GameEventHandler::eventGameStateChanged(const Event_GameStateChanged &event const GameEventContext & /*context*/) { // Sub-games of a tournament report their parent hub game so the client can - // route "close game" back to the parent tab. - if (event.parent_game_id() != -1) { + // route "close game" back to the parent tab. Use has_parent_game_id rather + // than comparing the default so a future drop of the proto default still works. + if (event.has_parent_game_id()) { game->getGameMetaInfo()->setParentGameId(event.parent_game_id()); } diff --git a/cockatrice/src/game/game_meta_info.h b/cockatrice/src/game/game_meta_info.h index 8585238bd..6b3569e64 100644 --- a/cockatrice/src/game/game_meta_info.h +++ b/cockatrice/src/game/game_meta_info.h @@ -89,11 +89,6 @@ public: return gameInfo_.is_tournament(); } - void setIsTournament(bool t) - { - gameInfo_.set_is_tournament(t); - } - int parentGameId() const { return parentGameId_; diff --git a/cockatrice/src/game/player/player_event_handler.h b/cockatrice/src/game/player/player_event_handler.h index 1e9814ddb..48ad85e88 100644 --- a/cockatrice/src/game/player/player_event_handler.h +++ b/cockatrice/src/game/player/player_event_handler.h @@ -90,10 +90,10 @@ public: * @param context Additional context (undo, judge, etc.). * @param options Processing options (UI suppression, reveal behavior). */ - virtual void processGameEvent(GameEvent::GameEventType type, - const GameEvent &event, - const GameEventContext &context, - EventProcessingOptions options); + void processGameEvent(GameEvent::GameEventType type, + const GameEvent &event, + const GameEventContext &context, + EventProcessingOptions options); /** @} */ @@ -266,7 +266,7 @@ signals: void cardZoneChanged(CardItem *card, bool sameZone); void requestCardMenuUpdate(const CardItem *card); -protected: +private: /** Owning player instance. */ PlayerLogic *player; diff --git a/cockatrice/src/game/player/player_logic.cpp b/cockatrice/src/game/player/player_logic.cpp index 084f3df30..45ba09aac 100644 --- a/cockatrice/src/game/player/player_logic.cpp +++ b/cockatrice/src/game/player/player_logic.cpp @@ -37,19 +37,6 @@ PlayerLogic::PlayerLogic(const ServerInfo_User &info, int _id, bool _local, bool initializeZones(); } -PlayerLogic::PlayerLogic(const ServerInfo_User &info, - int _id, - bool _local, - bool _judge, - AbstractGame *_parent, - PlayerEventHandler *customEventHandler) - : QObject(_parent), game(_parent), playerInfo(new PlayerInfo(info, _id, _local, _judge)), - playerEventHandler(customEventHandler), playerActions(new PlayerActions(this)), active(false), conceded(false), - zoneId(0), dialogSemaphore(false) -{ - initializeZones(); -} - void PlayerLogic::initializeZones() { addZone(new PileZoneLogic(this, ZoneNames::DECK, false, true, false, this)); diff --git a/cockatrice/src/game/player/player_logic.h b/cockatrice/src/game/player/player_logic.h index 26cd63f50..6923b3afe 100644 --- a/cockatrice/src/game/player/player_logic.h +++ b/cockatrice/src/game/player/player_logic.h @@ -99,30 +99,7 @@ public: PlayerLogic(const ServerInfo_User &info, int _id, bool _local, bool _judge, AbstractGame *_parent); ~PlayerLogic() override; -protected: - /** - * @brief Constructor for subclasses that need a custom event handler (e.g. DraftPlayerLogic). - * - * @p customEventHandler must be non-null and either QObject-parented to this PlayerLogic - * or deleted externally; it is not owned by PlayerLogic. The handler connects to @c player - * during its own constructor, so passing a freshly built subclass handler from an - * initializer list is safe. - */ - PlayerLogic(const ServerInfo_User &info, - int _id, - bool _local, - bool _judge, - AbstractGame *_parent, - PlayerEventHandler *customEventHandler); - -public: - /** - * @brief Creates the standard zone set. - * - * Not virtually dispatched from constructors — subclasses overriding this must add - * their extra zones in their own constructor body. - */ - virtual void initializeZones(); + void initializeZones(); void updateZones(); void clear();