From 98cb71ab29a2bb2ed11c3c5c435540e675ecbf20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20Br=C3=BCbach?= Date: Wed, 1 Jul 2026 09:21:59 -0400 Subject: [PATCH 1/6] [Networking] Doxygen --- Doxyfile | 8 +- cockatrice/src/game/game_event_handler.h | 288 ++++++++++-- .../src/game/player/player_event_handler.h | 217 +++++++-- .../developer_documentation/index.md | 4 +- .../network/client/game_event_handler.md | 170 +++++++ .../network/client/index.md | 11 + .../network/client/player_event_handler.md | 199 ++++++++ .../developer_documentation/network/index.md | 7 + .../network/protocol/index.md | 15 + .../protocol/protocol_command_container.md | 114 +++++ .../network/protocol/protocol_game_command.md | 440 ++++++++++++++++++ .../network/protocol/protocol_overview.md | 76 +++ .../network/protocol/protocol_response.md | 27 ++ .../protocol/protocol_server_message.md | 171 +++++++ .../protocol/pb/game_commands.proto | 151 +++++- .../libcockatrice/protocol/pb/response.proto | 118 ++--- proto2cpp.py | 285 ++++++++++++ 17 files changed, 2179 insertions(+), 122 deletions(-) create mode 100644 doc/doxygen/extra-pages/developer_documentation/network/client/game_event_handler.md create mode 100644 doc/doxygen/extra-pages/developer_documentation/network/client/index.md create mode 100644 doc/doxygen/extra-pages/developer_documentation/network/client/player_event_handler.md create mode 100644 doc/doxygen/extra-pages/developer_documentation/network/index.md create mode 100644 doc/doxygen/extra-pages/developer_documentation/network/protocol/index.md create mode 100644 doc/doxygen/extra-pages/developer_documentation/network/protocol/protocol_command_container.md create mode 100644 doc/doxygen/extra-pages/developer_documentation/network/protocol/protocol_game_command.md create mode 100644 doc/doxygen/extra-pages/developer_documentation/network/protocol/protocol_overview.md create mode 100644 doc/doxygen/extra-pages/developer_documentation/network/protocol/protocol_response.md create mode 100644 doc/doxygen/extra-pages/developer_documentation/network/protocol/protocol_server_message.md create mode 100644 proto2cpp.py diff --git a/Doxyfile b/Doxyfile index fc96b5f22..dce6bdb72 100644 --- a/Doxyfile +++ b/Doxyfile @@ -349,7 +349,7 @@ OPTIMIZE_OUTPUT_SLICE = NO # # Note see also the list of default file extension mappings. -EXTENSION_MAPPING = +EXTENSION_MAPPING = proto=C++ # If the MARKDOWN_SUPPORT tag is enabled then Doxygen pre-processes all comments # according to the Markdown format, which allows for more readable @@ -1086,7 +1086,8 @@ FILE_PATTERNS = *.cc \ *.h++ \ *.markdown \ *.md \ - *.dox + *.dox \ + *.proto # The RECURSIVE tag can be used to specify whether or not subdirectories should # be searched for input files as well. @@ -1102,6 +1103,7 @@ RECURSIVE = YES # run. EXCLUDE = build/ \ + cmake-build-debug/ \ cmake/ \ doc/doxygen/theme/docs/ \ doc/doxygen/theme/include/ \ @@ -1182,7 +1184,7 @@ IMAGE_PATH = doc/doxygen/images # need to set EXTENSION_MAPPING for the extension otherwise the files are not # properly processed by Doxygen. -INPUT_FILTER = +INPUT_FILTER = "python proto2cpp.py" # The FILTER_PATTERNS tag can be used to specify filters on a per file pattern # basis. Doxygen will compare the file name with each pattern and apply the diff --git a/cockatrice/src/game/game_event_handler.h b/cockatrice/src/game/game_event_handler.h index f47116949..99277abb7 100644 --- a/cockatrice/src/game/game_event_handler.h +++ b/cockatrice/src/game/game_event_handler.h @@ -1,8 +1,16 @@ /** * @file game_event_handler.h * @ingroup GameLogic + * @brief Game-level command sender and event dispatcher. + * + * GameEventHandler sends commands initiated by the local client to the server + * and processes incoming game-wide events. It bridges the networking layer + * (protobuf events received via AbstractClient) with the game model and UI + * (GameState, PlayerManager, logging, widgets). + * + * Player-scoped events are forwarded to PlayerEventHandler instances, while + * spectator and global game events are handled directly here. */ -//! \todo Document this file. #ifndef COCKATRICE_GAME_EVENT_HANDLER_H #define COCKATRICE_GAME_EVENT_HANDLER_H @@ -15,92 +23,310 @@ #include class AbstractClient; -class Response; +class AbstractGame; +class CommandContainer; +class GameCommand; class GameEventContainer; class GameEventContext; -class GameCommand; -class GameState; -class MessageLogWidget; -class CommandContainer; -class Event_GameJoined; +class PendingCommand; +class PlayerLogic; +class Response; + class Event_GameStateChanged; class Event_PlayerPropertiesChanged; class Event_Join; class Event_Leave; class Event_GameHostChanged; class Event_GameClosed; -class Event_GameStart; class Event_SetActivePlayer; class Event_SetActivePhase; -class Event_Ping; class Event_GameSay; class Event_Kicked; class Event_ReverseTurn; -class AbstractGame; -class PendingCommand; -class PlayerLogic; +class Event_Ping; inline Q_LOGGING_CATEGORY(GameEventHandlerLog, "game_event_handler"); +/** + * @class GameEventHandler + * @brief Central dispatcher for game-wide commands and events. + * + * This class owns no game state itself. Instead, it: + * - Sends commands to the server on behalf of local players + * - Receives and dispatches server-side game events + * - Updates the game model indirectly via Player, GameState, and PlayerManager + * - Emits high-level signals for UI updates and logging + */ class GameEventHandler : public QObject { Q_OBJECT private: + /** Pointer to the owning game instance. */ AbstractGame *game; public: + /** @name Construction + * Lifecycle and ownership. + * @{ + */ + + /** + * @brief Construct a GameEventHandler. + * + * The handler is owned by the AbstractGame instance and uses it to + * access the game state, players, and network clients. + * + * @param _game Owning game instance (also used as QObject parent). + */ explicit GameEventHandler(AbstractGame *_game); + /** @} */ + + /** @name Outgoing game commands + * Commands initiated locally and sent to the server. + * + * These methods construct and send protobuf commands corresponding + * to user actions in the UI. + * @{ + */ + + /** @brief Request advancing the game to the next turn. */ void handleNextTurn(); + + /** @brief Request reversing the current turn order. */ void handleReverseTurn(); + /** @brief Concede the game for the currently active local player. */ void handleActiveLocalPlayerConceded(); + + /** @brief Undo a previous concede for the active local player. */ void handleActiveLocalPlayerUnconceded(); + + /** + * @brief Set the active phase of the game. + * + * Typically triggered by the active player selecting a new phase. + * + * @param phase Phase identifier. + */ void handleActivePhaseChanged(int phase); + + /** @brief Leave the current game session. */ void handleGameLeft(); + + /** + * @brief Send a chat message to all players and spectators. + * + * @param chatMessage Message text. + */ void handleChatMessageSent(const QString &chatMessage); + + /** + * @brief Delete an existing arrow. + * + * @param arrowId Unique identifier of the arrow to delete. + */ void handleArrowDeletion(int creatorId, int arrowId); void handleArrowDeletionFinished(const Response &response, int creatorId, int arrowId); + /** @} */ + + /** @name Incoming event processing + * Entry points for server-sent events. + * @{ + */ + + /** + * @brief Process a container of game events received from the server. + * + * This is the main dispatch function for incoming game events. + * Events are routed to spectator handlers, game-level handlers, + * or forwarded to PlayerEventHandler instances as appropriate. + * + * @param cont Game event container from the server. + * @param client Client that received the container. + * @param options Processing flags (e.g. silent, replay). + */ + void + processGameEventContainer(const GameEventContainer &cont, AbstractClient *client, EventProcessingOptions options); + + /** @} */ + + /** @name Command preparation helpers + * Internal helpers for building command containers. + * @{ + */ + + /** + * @brief Wrap a single protobuf command in a PendingCommand. + * + * @param cmd Protobuf command message. + * @return Newly allocated PendingCommand (caller takes ownership). + */ + PendingCommand *prepareGameCommand(const ::google::protobuf::Message &cmd); + + /** + * @brief Wrap multiple protobuf commands in a single PendingCommand. + * + * Ownership of the messages in cmdList is transferred to the handler. + * + * @param cmdList List of protobuf command messages. + * @return Newly allocated PendingCommand. + */ + PendingCommand *prepareGameCommand(const QList &cmdList); + + /** @} */ + + /** @name Spectator event handlers + * Events originating from spectators. + * @{ + */ + + /** + * @brief Handle a spectator chat message. + */ void eventSpectatorSay(const Event_GameSay &event, int eventPlayerId, const GameEventContext &context); + + /** + * @brief Handle a spectator leaving the game. + */ void eventSpectatorLeave(const Event_Leave &event, int eventPlayerId, const GameEventContext &context); + /** @} */ + + /** @name Game state event handlers + * Events that affect global game state. + * @{ + */ + + /** + * @brief Handle a full game state update from the server. + * + * Used during game startup, reconnection, and resynchronization. + */ void eventGameStateChanged(const Event_GameStateChanged &event, int eventPlayerId, const GameEventContext &context); + + /** + * @brief Update card attachment relationships for all players. + * + * Called after a game state update to ensure attachments are resolved + * consistently across all zones. + */ void processCardAttachmentsForPlayers(const Event_GameStateChanged &event); + + /** @brief Handle a change in game host. */ + void eventGameHostChanged(const Event_GameHostChanged &event, int eventPlayerId, const GameEventContext &context); + + /** @brief Handle the game being closed by the server. */ + void eventGameClosed(const Event_GameClosed &event, int eventPlayerId, const GameEventContext &context); + + /** @brief Handle a change of the active player. */ + void eventSetActivePlayer(const Event_SetActivePlayer &event, int eventPlayerId, const GameEventContext &context); + + /** @brief Handle a change of the active phase. */ + void eventSetActivePhase(const Event_SetActivePhase &event, int eventPlayerId, const GameEventContext &context); + + /** @brief Handle a turn reversal event. */ + void eventReverseTurn(const Event_ReverseTurn &event, int eventPlayerId, const GameEventContext &context); + + /** @brief Handle ping / latency updates. */ + void eventPing(const Event_Ping &event, int eventPlayerId, const GameEventContext &context); + + /** @} */ + + /** @name Player lifecycle and property handlers + * Events related to players joining, leaving, or changing state. + * @{ + */ + + /** + * @brief Handle updates to a player's properties. + * + * Includes readiness, concede state, deck selection, sideboard lock, + * and connection state changes. + */ void eventPlayerPropertiesChanged(const Event_PlayerPropertiesChanged &event, int eventPlayerId, const GameEventContext &context); + + /** @brief Handle a player or spectator joining the game. */ void eventJoin(const Event_Join &event, int eventPlayerId, const GameEventContext &context); + + /** @brief Handle a player leaving the game. */ void eventLeave(const Event_Leave &event, int eventPlayerId, const GameEventContext &context); - QString getLeaveReason(Event_Leave::LeaveReason reason); + + /** @brief Handle the local player being kicked from the game. */ void eventKicked(const Event_Kicked &event, int eventPlayerId, const GameEventContext &context); - void eventGameHostChanged(const Event_GameHostChanged &event, int eventPlayerId, const GameEventContext &context); - void eventGameClosed(const Event_GameClosed &event, int eventPlayerId, const GameEventContext &context); - void eventSetActivePlayer(const Event_SetActivePlayer &event, int eventPlayerId, const GameEventContext &context); - void eventSetActivePhase(const Event_SetActivePhase &event, int eventPlayerId, const GameEventContext &context); - void eventPing(const Event_Ping &event, int eventPlayerId, const GameEventContext &context); - void eventReverseTurn(const Event_ReverseTurn &event, int eventPlayerId, const GameEventContext & /*context*/); + /** + * @brief Convert a leave reason enum to a human-readable string. + */ + QString getLeaveReason(Event_Leave::LeaveReason reason); - void commandFinished(const Response &response); + /** @} */ - void - processGameEventContainer(const GameEventContainer &cont, AbstractClient *client, EventProcessingOptions options); - PendingCommand *prepareGameCommand(const ::google::protobuf::Message &cmd); - PendingCommand *prepareGameCommand(const QList &cmdList); public slots: + /** @name Command dispatch slots + * Low-level command transmission. + * @{ + */ + + /** + * @brief Send a prepared PendingCommand. + * + * @param pend Pending command to send. + * @param playerId Player whose client should send the command. + */ void sendGameCommand(PendingCommand *pend, int playerId = -1); + + /** + * @brief Send a single protobuf command. + * + * @param command Protobuf command message. + * @param playerId Player whose client should send the command. + */ void sendGameCommand(const ::google::protobuf::Message &command, int playerId = -1); + /** + * @brief Called when a PendingCommand finishes execution. + * + * Used to detect server-side errors such as chat flood protection. + */ + void commandFinished(const Response &response); + + /** @} */ + signals: + /** @name Core state signals + * @{ + */ + void emitUserEvent(); + void containerProcessingStarted(GameEventContext context); + void containerProcessingDone(); + void gameFlooded(); + void setContextJudgeName(QString judgeName); + + /** @} */ + + /** @name Player and spectator signals + * @{ + */ + void addPlayerToAutoCompleteList(QString playerName); void localPlayerDeckSelected(PlayerLogic *localPlayer, int playerId, ServerInfo_Player playerInfo); void remotePlayerDeckSelected(QString deckList, int playerId, QString playerName); void remotePlayersDecksSelected(QVector>> opponentDecks); void localPlayerSideboardLocked(int playerId, bool sideboardLocked); void localPlayerReadyStateChanged(int playerId, bool ready); + + /** @} */ + + /** @name Game flow signals + * @{ + */ + void gameStopped(); void gameClosed(); void playerPropertiesChanged(const ServerInfo_PlayerProperties &prop, int playerId); @@ -109,11 +335,15 @@ signals: void playerKicked(); void spectatorJoined(const ServerInfo_PlayerProperties &spectatorInfo); void spectatorLeft(int leavingSpectatorId); - void gameFlooded(); - void containerProcessingStarted(GameEventContext context); - void setContextJudgeName(QString judgeName); - void containerProcessingDone(); void arrowDeleted(int creatorId, int arrowId); + + /** @} */ + + /** @name Logging signals + * Signals consumed by MessageLogWidget. + * @{ + */ + void logSpectatorSay(ServerInfo_User userInfo, QString message); void logSpectatorLeave(QString name, QString reason); void logGameStart(); @@ -132,6 +362,8 @@ signals: void logActivePhaseChanged(int activePhase); void logConcede(int playerId); void logUnconcede(int playerId); + + /** @} */ }; #endif // COCKATRICE_GAME_EVENT_HANDLER_H diff --git a/cockatrice/src/game/player/player_event_handler.h b/cockatrice/src/game/player/player_event_handler.h index cfd82933f..3a3a6fda9 100644 --- a/cockatrice/src/game/player/player_event_handler.h +++ b/cockatrice/src/game/player/player_event_handler.h @@ -1,11 +1,24 @@ /** * @file player_event_handler.h * @ingroup GameLogicPlayers + * @brief Player-scoped game event handler. + * + * PlayerEventHandler applies game events that affect a single Player’s + * board state, zones, cards, counters, arrows, and related UI/log output. + * + * It is invoked by GameEventHandler after basic routing and validation. + * Each instance is bound 1:1 to a Player and must never mutate state + * belonging to other players except where explicitly required by events + * (e.g. moving cards between players, attaching cards, arrows). + * + * This class is intentionally stateful and tightly coupled to Player, + * PlayerActions, and the board/zones implementation. It performs both + * model mutation and UI-side bookkeeping (zone views, arrows, menus). */ -//! \todo Document this file. #ifndef COCKATRICE_PLAYER_EVENT_HANDLER_H #define COCKATRICE_PLAYER_EVENT_HANDLER_H + #include "event_processing_options.h" #include @@ -16,6 +29,7 @@ class CardItem; class CardZoneLogic; class PlayerLogic; + class Event_AttachCard; class Event_ChangeZoneProperties; class Event_CreateArrow; @@ -37,11 +51,177 @@ class Event_SetCounter; class Event_Shuffle; class Event_GameLogNotice; + +/** + * @class PlayerEventHandler + * @brief Applies player-specific game events and emits corresponding log signals. + * + * Design notes: + * - All event handlers assume events are authoritative and already validated + * by the server. + * - Most handlers mutate both logical state (CardItem, CardZoneLogic, counters) + * and visual/UI state (views, arrows, menus). + * - Logging signals are emitted *after* or *during* state mutation, depending + * on whether later mutations would invalidate log data. + */ class PlayerEventHandler : public QObject { - Q_OBJECT + +public: + /** + * @brief Construct a PlayerEventHandler bound to a Player. + * @param player Owning player instance. + */ + explicit PlayerEventHandler(PlayerLogic *player); + + /** @name Event dispatch + * @{ + */ + + /** + * @brief Dispatch a generic GameEvent to the appropriate handler. + * + * This is the single entry point used by GameEventHandler. It extracts + * the correct protobuf extension and forwards the event to a typed + * handler method. + * + * @param type Game event type enum. + * @param event Generic protobuf container. + * @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); + + /** @} */ + + /** @name Chat and randomization events + * @{ + */ + + /// Handle in-game chat messages from this player. + void eventGameSay(const Event_GameSay &event); + + /// Handle zone shuffle events (typically libraries). + void eventShuffle(const Event_Shuffle &event); + + /// Handle die roll events. + void eventRollDie(const Event_RollDie &event); + + /** @} */ + + /** @name Arrow and targeting events + * @{ + */ + + /// Create a visual arrow between cards or players. + void eventCreateArrow(const Event_CreateArrow &event); + + /// Delete an existing arrow. + void eventDeleteArrow(const Event_DeleteArrow &event); + + /** @} */ + + /** @name Token and card creation + * @{ + */ + + /// Create a token card in a target zone. + void eventCreateToken(const Event_CreateToken &event); + + /** @} */ + + /** @name Card attribute and counter updates + * @{ + */ + + /** + * @brief Set a card attribute (tapped, PT, annotation, etc.). + * + * May apply to a single card or all cards in a zone if no card ID + * is provided by the event. + */ + void + eventSetCardAttr(const Event_SetCardAttr &event, const GameEventContext &context, EventProcessingOptions options); + + /// Update a counter attached to a card. + void eventSetCardCounter(const Event_SetCardCounter &event); + + /// Create a player-level counter. + void eventCreateCounter(const Event_CreateCounter &event); + + /// Set a player-level counter value. + void eventSetCounter(const Event_SetCounter &event); + + /// Delete a player-level counter. + void eventDelCounter(const Event_DelCounter &event); + + /** @} */ + + /** @name Zone-level operations + * @{ + */ + + /// Log a zone dump (e.g. reveal graveyard/library contents). + void eventDumpZone(const Event_DumpZone &event); + + /** + * @brief Move a card between zones and/or players. + * + * This is one of the most complex handlers: + * - Removes the card from the start zone + * - Updates card identity and ownership if needed + * - Handles attachments and arrows + * - Emits appropriate move or undo-draw logs + * - Inserts the card into the target zone + */ + void eventMoveCard(const Event_MoveCard &event, const GameEventContext &context); + + /// Flip a card face up or face down. + void eventFlipCard(const Event_FlipCard &event); + + /// Destroy a card and clean up attachments. + void eventDestroyCard(const Event_DestroyCard &event); + + /// Attach or detach a card to/from another card. + void eventAttachCard(const Event_AttachCard &event); + + /** @} */ + + /** @name Draw and reveal operations + * @{ + */ + + /// Draw one or more cards from the deck. + void eventDrawCards(const Event_DrawCards &event); + + /** + * @brief Reveal cards from a zone. + * + * Handles peeking, in-place top-card reveals, full reveal windows, + * and write-access granting. + */ + void eventRevealCards(const Event_RevealCards &event, EventProcessingOptions options); + + /** @} */ + + /** @name Zone configuration + * @{ + */ + + /// Update zone visibility and reveal behavior. + void eventChangeZoneProperties(const Event_ChangeZoneProperties &event); + + /** @} */ + + void eventGameLogNotice(const Event_GameLogNotice &event); signals: + /** @name Logging signals + * @{ + */ void logSay(PlayerLogic *player, QString message); void logShuffle(PlayerLogic *player, CardZoneLogic *zone, int start, int end); void logRollDie(PlayerLogic *player, int sides, const QList &rolls); @@ -82,40 +262,13 @@ signals: bool isLentToAnotherPlayer = false); void logAlwaysRevealTopCard(PlayerLogic *player, CardZoneLogic *zone, bool reveal); void logAlwaysLookAtTopCard(PlayerLogic *player, CardZoneLogic *zone, bool reveal); + /** @} */ + void cardZoneChanged(CardItem *card, bool sameZone); void requestCardMenuUpdate(const CardItem *card); -public: - PlayerEventHandler(PlayerLogic *player); - - void processGameEvent(GameEvent::GameEventType type, - const GameEvent &event, - const GameEventContext &context, - EventProcessingOptions options); - - void eventGameSay(const Event_GameSay &event); - void eventShuffle(const Event_Shuffle &event); - void eventRollDie(const Event_RollDie &event); - void eventCreateArrow(const Event_CreateArrow &event); - void eventDeleteArrow(const Event_DeleteArrow &event); - void eventCreateToken(const Event_CreateToken &event); - void - eventSetCardAttr(const Event_SetCardAttr &event, const GameEventContext &context, EventProcessingOptions options); - void eventSetCardCounter(const Event_SetCardCounter &event); - void eventCreateCounter(const Event_CreateCounter &event); - void eventSetCounter(const Event_SetCounter &event); - void eventDelCounter(const Event_DelCounter &event); - void eventDumpZone(const Event_DumpZone &event); - void eventMoveCard(const Event_MoveCard &event, const GameEventContext &context); - void eventFlipCard(const Event_FlipCard &event); - void eventDestroyCard(const Event_DestroyCard &event); - void eventAttachCard(const Event_AttachCard &event); - void eventDrawCards(const Event_DrawCards &event); - void eventRevealCards(const Event_RevealCards &event, EventProcessingOptions options); - void eventChangeZoneProperties(const Event_ChangeZoneProperties &event); - void eventGameLogNotice(const Event_GameLogNotice &event); - private: + /** Owning player instance. */ PlayerLogic *player; void setCardAttrHelper(const GameEventContext &context, diff --git a/doc/doxygen/extra-pages/developer_documentation/index.md b/doc/doxygen/extra-pages/developer_documentation/index.md index da2d444d8..80f4eb492 100644 --- a/doc/doxygen/extra-pages/developer_documentation/index.md +++ b/doc/doxygen/extra-pages/developer_documentation/index.md @@ -8,4 +8,6 @@ - @subpage querying_the_card_database - @subpage loading_card_pictures -- @subpage displaying_cards \ No newline at end of file +- @subpage displaying_cards + +- @subpage developer_reference_network_overview \ No newline at end of file diff --git a/doc/doxygen/extra-pages/developer_documentation/network/client/game_event_handler.md b/doc/doxygen/extra-pages/developer_documentation/network/client/game_event_handler.md new file mode 100644 index 000000000..fbb298d3a --- /dev/null +++ b/doc/doxygen/extra-pages/developer_documentation/network/client/game_event_handler.md @@ -0,0 +1,170 @@ +@page game_event_handler GameEventHandler + +## Overview + +`GameEventHandler` is the central coordinator for **game-wide commands and events**. +It acts as the bridge between the networking layer (protobuf messages received from +the server) and the local game model and UI. + +Unlike `PlayerEventHandler`, which is responsible for player-scoped state and zones, +`GameEventHandler` handles: + +- Global game flow (turns, phases, host changes) +- Player and spectator lifecycle events +- Chat and logging events +- Dispatching incoming events to the appropriate subsystem +- Sending locally initiated commands to the server + +In short, it is the **top-level event dispatcher** for an active game. + +--- + +## Responsibilities + +`GameEventHandler` has four primary responsibilities: + +### 1. Sending game-wide commands + +UI actions that affect the game as a whole (e.g. advancing the turn, changing phases, +sending chat messages) are translated into protobuf commands and sent to the server +via this class. + +Examples include: + +- Advancing or reversing the turn order +- Conceding or unconceding +- Changing the active phase +- Leaving the game +- Sending chat messages + +These commands are wrapped in `PendingCommand` instances so their lifecycle and +responses can be tracked. + +--- + +### 2. Processing incoming game events + +Incoming server messages arrive as a `GameEventContainer`. +`GameEventHandler` is responsible for: + +- Emitting lifecycle signals before and after processing +- Dispatching each event to the correct handler +- Forwarding player-scoped events to `PlayerEventHandler` instances +- Handling spectator-only and game-global events directly + +This design keeps networking concerns isolated from game logic and UI code. + +--- + +### 3. Managing global game state transitions + +Certain events affect the entire game state and require coordinated updates across +multiple subsystems. Examples include: + +- Full game state synchronization +- Active player or phase changes +- Game host changes +- Game closure +- Turn reversal + +`GameEventHandler` ensures these events are processed in a consistent order and +that all interested systems are notified via Qt signals. + +--- + +### 4. Emitting UI and logging signals + +Rather than directly manipulating UI widgets, `GameEventHandler` emits +high-level signals that are consumed by: + +- Game widgets +- Player and spectator lists +- Message and event logs +- Status indicators (ready state, deck selection, connection state) + +This keeps the handler independent of concrete UI implementations. + +--- + +## Relationship to PlayerEventHandler + +`GameEventHandler` and `PlayerEventHandler` work together but have distinct roles: + +| GameEventHandler | PlayerEventHandler | +|------------------|--------------------| +| Global game state | Per-player state | +| Turn / phase flow | Zones and cards | +| Player join/leave | Player actions | +| Spectator events | Player-specific events | +| Chat dispatch | Card and zone updates | + +When a server event is associated with a specific player, `GameEventHandler` +routes it to the corresponding `PlayerEventHandler`. Events without a player +context are handled directly. + +--- + +## Event Processing Flow + +A typical incoming event flow looks like this: + +1. `AbstractClient` receives a `GameEventContainer` +2. `GameEventHandler::processGameEventContainer()` is called +3. `containerProcessingStarted()` is emitted +4. Each event is: + - Handled directly **or** + - Forwarded to a `PlayerEventHandler` +5. Logging and UI signals are emitted as needed +6. `containerProcessingDone()` is emitted + +This structured flow makes it easy to: + +- Suppress UI updates during replays +- Handle reconnections cleanly +- Detect flood protection or error states + +--- + +## Command Lifecycle + +Outgoing commands follow a similar structured path: + +1. UI action triggers a `handle*()` method +2. A protobuf command is constructed +3. The command is wrapped in a `PendingCommand` +4. `sendGameCommand()` sends it to the server +5. `commandFinished()` receives the response +6. Errors or flood conditions are handled centrally + +This approach avoids duplicated error handling across UI code. + +--- + +## Design Goals + +`GameEventHandler` is designed to be: + +- **Centralized** – one entry point for all game-wide events +- **UI-agnostic** – communicates via signals, not widgets +- **Predictable** – well-defined event ordering and lifecycle +- **Composable** – works in concert with `PlayerEventHandler` +- **Testable** – logic is separated from rendering + +--- + +## Related Classes + +- @ref GameEventHandler +- @ref PlayerEventHandler +- `GameEventContainer` +- `PendingCommand` +- `AbstractClient` +- `AbstractGame` + +--- + +## See Also + +- @ref GameLogic +- @ref PlayerEventHandler +- @ref GameState diff --git a/doc/doxygen/extra-pages/developer_documentation/network/client/index.md b/doc/doxygen/extra-pages/developer_documentation/network/client/index.md new file mode 100644 index 000000000..f22640448 --- /dev/null +++ b/doc/doxygen/extra-pages/developer_documentation/network/client/index.md @@ -0,0 +1,11 @@ +@page developer_reference_network_client Client Networking (Overview) + +The clients response to various network protocol events and associated handling are described here. + +For information about game scoped events, see: + +- @subpage game_event_handler + +Certain game scoped events may be forwarded to player based handlers. For more information, see: + +- @subpage player_event_handler \ No newline at end of file diff --git a/doc/doxygen/extra-pages/developer_documentation/network/client/player_event_handler.md b/doc/doxygen/extra-pages/developer_documentation/network/client/player_event_handler.md new file mode 100644 index 000000000..6878758da --- /dev/null +++ b/doc/doxygen/extra-pages/developer_documentation/network/client/player_event_handler.md @@ -0,0 +1,199 @@ +@page player_event_handler PlayerEventHandler + +## Overview + +`PlayerEventHandler` is responsible for applying **player-scoped game events** +to a single `Player` instance. These events modify the player’s board state, +zones, cards, counters, arrows, and associated UI and logging output. + +Each `PlayerEventHandler` instance is bound **1:1 to a Player** and is invoked +exclusively by @ref GameEventHandler after basic routing and validation of +incoming server events. + +This class represents the lowest-level authoritative application of game state +changes on the client. + +--- + +## Scope and Authority + +`PlayerEventHandler` operates under the following guarantees: + +- All incoming events are **authoritative** and already validated by the server +- Events refer to valid players, zones, and card identifiers +- Ordering is guaranteed by the server and preserved by `GameEventHandler` + +As a result, the handler does **not** perform rule validation or permission +checks. Its responsibility is to *apply* state, not *decide* legality. + +--- + +## Responsibilities + +### 1. Applying player-specific state changes + +The primary responsibility of `PlayerEventHandler` is to mutate player-owned +state, including: + +- Cards (`CardItem`) +- Zones (`CardZoneLogic`) +- Counters (card-level and player-level) +- Attachments and arrows +- Zone configuration flags (reveal / peek behavior) + +Many handlers update both **logical state** and **visual/UI state** in tandem. + +--- + +### 2. Coordinating complex card movement + +Some events—most notably card movement—require coordinated updates across +multiple systems. For example, `eventMoveCard()`: + +- Removes the card from the source zone +- Updates ownership, visibility, and identity +- Handles attachments and arrow cleanup +- Emits undo-draw or move logs +- Inserts the card into the destination zone +- Updates menus, hover state, and graphics + +This makes `PlayerEventHandler` intentionally stateful and tightly coupled to +the board implementation. + +--- + +### 3. Emitting logging signals + +Rather than writing directly to logs or widgets, `PlayerEventHandler` emits +structured Qt signals describing *what happened*, including: + +- Chat messages +- Card movement +- Shuffles and randomization +- Reveals and peeks +- Counter and attribute changes + +These signals are consumed by logging systems and UI components, keeping +presentation concerns out of the handler. + +Logging signals may be emitted **before or after mutation**, depending on +whether later changes would invalidate log data (e.g. card identity). + +--- + +### 4. Handling cross-player interactions + +Although bound to a single player, some events necessarily affect other +players’ state, including: + +- Moving cards between players +- Attaching cards to another player’s permanents +- Creating arrows targeting other players or cards + +In these cases, `PlayerEventHandler` performs the minimal required mutation +while respecting ownership boundaries enforced elsewhere. + +--- + +## Event Dispatch Model + +`PlayerEventHandler` exposes a single public entry point: + +- `processGameEvent()` + +This method: + +1. Receives a generic `GameEvent` +2. Switches on `GameEventType` +3. Extracts the appropriate protobuf extension +4. Forwards the event to a typed handler + +This keeps the event routing centralized and makes it easy to audit coverage +when new game events are introduced. + +Unhandled events are logged as warnings. + +--- + +## Event Categories + +For clarity, event handlers are grouped conceptually into: + +- **Chat and randomization** + - Chat messages + - Shuffles + - Dice rolls + +- **Arrows and targeting** + - Create / delete arrows + +- **Card and token creation** + - Token generation + - Counter creation + +- **Card attributes and counters** + - Tapped state + - Power/toughness + - Annotations + - Card- and player-level counters + +- **Zone-level operations** + - Card movement + - Zone dumps + - Card destruction + - Attachments + +- **Draw and reveal** + - Drawing cards + - Reveals, peeks, and reveal windows + +- **Zone configuration** + - Always-reveal and always-look-at-top-card flags + +This grouping mirrors the structure of the header file and reflects the +conceptual responsibilities of the class. + +--- + +## Relationship to GameEventHandler + +`PlayerEventHandler` is never instantiated or invoked directly by UI code. +Instead: + +1. `GameEventHandler` receives a `GameEventContainer` +2. Player-scoped events are routed to the appropriate `PlayerEventHandler` +3. Global or spectator events are handled elsewhere + +This separation keeps game-wide logic decoupled from player board state and +makes reconnection and replay handling simpler. + +--- + +## Design Intent + +`PlayerEventHandler` is designed to be: + +- **Authoritative** – applies server state exactly as received +- **Stateful** – maintains consistency across cards, zones, and UI +- **Explicit** – one handler per event type +- **UI-aware** – updates views and menus as part of state mutation +- **Auditable** – easy to trace what code handles which event + +--- + +## Related Classes + +- @ref PlayerEventHandler +- @ref GameEventHandler +- `Player` +- `CardItem` +- `CardZoneLogic` +- `GameEvent` +- `GameEventContext` + +--- + +## See Also + +- @ref GameLogicPlayers +- @ref GameLogic diff --git a/doc/doxygen/extra-pages/developer_documentation/network/index.md b/doc/doxygen/extra-pages/developer_documentation/network/index.md new file mode 100644 index 000000000..713291d84 --- /dev/null +++ b/doc/doxygen/extra-pages/developer_documentation/network/index.md @@ -0,0 +1,7 @@ +@page developer_reference_network_overview Network (Overview) + +This page provides information about the networking performed by the client and server. + +For information about the protocol, see @subpage developer_reference_protocol + +For information about the clients response to and handling of protocol messages, see @subpage developer_reference_network_client \ No newline at end of file diff --git a/doc/doxygen/extra-pages/developer_documentation/network/protocol/index.md b/doc/doxygen/extra-pages/developer_documentation/network/protocol/index.md new file mode 100644 index 000000000..724b6b994 --- /dev/null +++ b/doc/doxygen/extra-pages/developer_documentation/network/protocol/index.md @@ -0,0 +1,15 @@ +@page developer_reference_protocol Protocol (Overview) + +For an overview of the protocol used by the Cockatrice client and server, see: + +- @subpage developer_reference_protocol_overview +- @subpage protocol_game_command + +For client → server communication, see: + +- @subpage protocol_command_container + +For server → client communication, see: + +- @subpage protocol_server_message +- @subpage protocol_response \ No newline at end of file diff --git a/doc/doxygen/extra-pages/developer_documentation/network/protocol/protocol_command_container.md b/doc/doxygen/extra-pages/developer_documentation/network/protocol/protocol_command_container.md new file mode 100644 index 000000000..0fb634358 --- /dev/null +++ b/doc/doxygen/extra-pages/developer_documentation/network/protocol/protocol_command_container.md @@ -0,0 +1,114 @@ +@page protocol_command_container CommandContainer (Protocol Concept) + +@section cc_overview Overview + +CommandContainer is the client-to-server message envelope used for all +client requests in the Cockatrice protocol. + +Every client-initiated action is transmitted as a CommandContainer. +The server never sends CommandContainer messages. + +This is a protocol-level abstraction and should not be confused with the +generated protobuf accessors or wire-level encoding. + +@section cc_lifecycle Lifetime and Ownership + +- CommandContainers are created by clients and consumed by the server. +- Each container represents one logical request. +- Containers are processed atomically and in order of arrival. + +A container may generate: +- Exactly one RESPONSE message, and +- Zero or more EVENT messages. + +@section cc_cmd_id Command Identification + +The @c cmd_id field is a client-assigned identifier used to correlate +responses with requests. + +Rules: +- @c cmd_id is optional but strongly recommended +- The server echoes @c cmd_id only in RESPONSE messages +- EVENT messages are never associated with a @c cmd_id + +The server does not enforce uniqueness of @c cmd_id values. + +@section cc_context Command Context + +Certain command domains require additional context: + +- Room commands require @c room_id +- Game commands require @c game_id + +Context fields are ignored for command domains that do not require them. + +If a required context field is missing or invalid, the server responds with +@c RespContextError. + +@section cc_invariants Invariants + +The following rules are enforced by the server: + +- Exactly one command domain must be populated +- Commands may be batched only within the same domain +- Context fields must match the active command domain + +Violations of these rules result in @c RespInvalidCommand. + +@section cc_domains Command Domains + +CommandContainer supports the following mutually exclusive command domains: + +- Session commands + - Authentication + - User discovery + - Private messaging + +- Room commands + - Chat + - Game creation + - Room membership management + +- Game commands + - In-game actions + - State mutations + +- Moderator commands + - User moderation + - Room moderation + +- Admin commands + - Server administration + +@section cc_batching Command Batching + +A CommandContainer may contain multiple commands of the same domain. + +Batching guarantees: +- Commands are processed in the order they appear in the container +- All commands in the batch share the same context + +Partial failure behavior is implementation-defined and may vary by domain. + +@section cc_dispatch Dispatch + +CommandContainers are dispatched by +Server_ProtocolHandler::processCommandContainer(). + +Dispatch is performed by inspecting which command domain is populated. +Only the first populated domain is considered. + +@section cc_errors Error Handling + +Common error responses include: +- @c RespLoginNeeded – client is not authenticated +- @c RespInvalidCommand – malformed container or invalid domain usage +- @c RespContextError – missing or invalid room/game context + +@section cc_related Related Concepts + +- @ref protocol_server_message "ServerMessage" +- @ref protocol_client_states "Client State Machine" + +@see Server_ProtocolHandler::processCommandContainer + diff --git a/doc/doxygen/extra-pages/developer_documentation/network/protocol/protocol_game_command.md b/doc/doxygen/extra-pages/developer_documentation/network/protocol/protocol_game_command.md new file mode 100644 index 000000000..257d2ea23 --- /dev/null +++ b/doc/doxygen/extra-pages/developer_documentation/network/protocol/protocol_game_command.md @@ -0,0 +1,440 @@ +@page protocol_game_command GameCommand (Protocol Concept) + +# Game Commands Reference + +This document describes game-level commands (`GameCommandType`) and how they are +handled across the server and client. + +Flow overview: + +Client +→ GameCommand +→ Server_Game::handle* +→ GameEvent(s) +→ PlayerEventHandler::event* + +## Command Mapping + +### `GAME_SAY` (1002) + +**Purpose:** Send a chat message during a game. + +**Server:** +- `Server_Game::handleGameSay` +- Emits `Event_GameSay` + +**Client:** +- `PlayerEventHandler::eventGameSay` + +--- + +### `SHUFFLE` (1003) + +**Purpose:** Shuffle a card zone (usually library). + +**Server:** +- `Server_Game::handleShuffle` +- Reorders cards in zone +- Emits `Event_Shuffle` + +**Client:** +- `PlayerEventHandler::eventShuffle` +- Clears revealed top cards +- Closes affected zone views + +--- + +### `ROLL_DIE` (1005) + +**Purpose:** Roll one or more dice. + +**Server:** +- `Server_Game::handleRollDie` +- Computes random values +- Emits `Event_RollDie` + +**Client:** +- `PlayerEventHandler::eventRollDie` + +--- + +### `DRAW_CARDS` (1006) + +**Purpose:** Draw cards from deck. + +**Server:** +- `Server_Game::handleDrawCards` +- Moves cards from deck → hand +- Emits `Event_DrawCards` + +**Client:** +- `PlayerEventHandler::eventDrawCards` + +--- + +### `UNDO_DRAW` (1007) + +**Purpose:** Undo a previous draw. + +**Server:** +- `Server_Game::handleUndoDraw` +- Uses `Context_UndoDraw` +- Moves card(s) back to deck + +**Client:** +- Handled via `PlayerEventHandler::eventMoveCard` +- Logged as undo-draw context + +--- + +### `FLIP_CARD` (1008) + +**Purpose:** Flip a card face up or face down. + +**Server:** +- `Server_Game::handleFlipCard` +- Updates card visibility +- Emits `Event_FlipCard` + +**Client:** +- `PlayerEventHandler::eventFlipCard` + +--- + +### `ATTACH_CARD` (1009) + +**Purpose:** Attach one card to another. + +**Server:** +- `Server_Game::handleAttachCard` +- Updates attachment graph +- Emits `Event_AttachCard` + +**Client:** +- `PlayerEventHandler::eventAttachCard` + +--- + +### `CREATE_TOKEN` (1010) + +**Purpose:** Create a token card. + +**Server:** +- `Server_Game::handleCreateToken` +- Allocates new card instance +- Emits `Event_CreateToken` + +**Client:** +- `PlayerEventHandler::eventCreateToken` + +--- + +### `CREATE_ARROW` (1011) + +**Purpose:** Create a visual arrow. + +**Server:** +- `Server_Game::handleCreateArrow` +- Registers arrow ownership +- Emits `Event_CreateArrow` + +**Client:** +- `PlayerEventHandler::eventCreateArrow` + +--- + +### `DELETE_ARROW` (1012) + +**Purpose:** Remove a visual arrow. + +**Server:** +- `Server_Game::handleDeleteArrow` +- Removes arrow +- Emits `Event_DeleteArrow` + +**Client:** +- `PlayerEventHandler::eventDeleteArrow` + +--- + +### `SET_CARD_ATTR` (1013) + +**Purpose:** Set a card attribute. + +**Server:** +- `Server_Game::handleSetCardAttr` +- Emits `Event_SetCardAttr` + +**Client:** +- `PlayerEventHandler::eventSetCardAttr` + +--- + +### `SET_CARD_COUNTER` (1014) + +**Purpose:** Set a card counter. + +**Server:** +- `Server_Game::handleSetCardCounter` +- Emits `Event_SetCardCounter` + +**Client:** +- `PlayerEventHandler::eventSetCardCounter` + +--- + +### `INC_CARD_COUNTER` (1015) + +**Purpose:** Increment a card counter. + +**Server:** +- `Server_Game::handleIncCardCounter` +- Normalized to set-counter +- Emits `Event_SetCardCounter` + +**Client:** +- `PlayerEventHandler::eventSetCardCounter` + +--- + +### `READY_START` (1016) + +**Purpose:** Mark player as ready. + +**Server:** +- `Server_Game::handleReadyStart` +- May call `doStartGameIfReady` + +**Client:** +- Reflected via `Event_GameStateChanged` + +--- + +### `CONCEDE` (1017) + +**Purpose:** Concede the game. + +**Server:** +- `Server_Game::handleConcede` +- Updates player state +- May end game + +**Client:** +- Reflected via game state events + +--- + +### `INC_COUNTER` (1018) + +**Purpose:** Increment a global counter. + +**Server:** +- `Server_Game::handleIncCounter` +- Emits `Event_SetCounter` + +**Client:** +- `PlayerEventHandler::eventSetCounter` + +--- + +### `CREATE_COUNTER` (1019) + +**Purpose:** Create a global counter. + +**Server:** +- `Server_Game::handleCreateCounter` +- Emits `Event_CreateCounter` + +**Client:** +- `PlayerEventHandler::eventCreateCounter` + +--- + +### `SET_COUNTER` (1020) + +**Purpose:** Set a global counter value. + +**Server:** +- `Server_Game::handleSetCounter` +- Emits `Event_SetCounter` + +**Client:** +- `PlayerEventHandler::eventSetCounter` + +--- + +### `DEL_COUNTER` (1021) + +**Purpose:** Delete a global counter. + +**Server:** +- `Server_Game::handleDelCounter` +- Emits `Event_DelCounter` + +**Client:** +- `PlayerEventHandler::eventDelCounter` + +--- + +### `NEXT_TURN` (1022) + +**Purpose:** Advance to the next turn. + +**Server:** +- `Server_Game::handleNextTurn` +- Emits: + - `Event_SetActivePlayer` + - `Event_SetActivePhase` + +**Client:** +- Reflected via game state events + +--- + +### `SET_ACTIVE_PHASE` (1023) + +**Purpose:** Set active phase. + +**Server:** +- `Server_Game::handleSetActivePhase` +- Emits `Event_SetActivePhase` + +**Client:** +- Reflected via game state events + +--- + +### `DUMP_ZONE` (1024) + +**Purpose:** Dump zone contents. + +**Server:** +- `Server_Game::handleDumpZone` +- Emits `Event_DumpZone` + +**Client:** +- `PlayerEventHandler::eventDumpZone` + +--- + +### `REVEAL_CARDS` (1026) + +**Purpose:** Reveal specific cards. + +**Server:** +- `Server_Game::handleRevealCards` +- Emits `Event_RevealCards` + +**Client:** +- `PlayerEventHandler::eventRevealCards` + +--- + +### `MOVE_CARD` (1027) + +**Purpose:** Move a card between zones. + +**Server:** +- `Server_Game::handleMoveCard` +- Emits `Event_MoveCard` +- May emit arrow cleanup events + +**Client:** +- `PlayerEventHandler::eventMoveCard` + +--- + +### `SET_SIDEBOARD_PLAN` (1028) + +**Purpose:** Set sideboard configuration. + +**Server:** +- `Server_Game::handleSetSideboardPlan` +- Stored server-side only + +**Client:** +- Not forwarded as a game event + +--- + +### `DECK_SELECT` (1029) + +**Purpose:** Select a deck. + +**Server:** +- `Server_Game::handleDeckSelect` + +**Client:** +- Reflected via lobby / game state + +--- + +### `SET_SIDEBOARD_LOCK` (1030) + +**Purpose:** Lock or unlock sideboarding. + +**Server:** +- `Server_Game::handleSetSideboardLock` + +**Client:** +- Reflected via game state + +--- + +### `CHANGE_ZONE_PROPERTIES` (1031) + +**Purpose:** Change zone properties. + +**Server:** +- `Server_Game::handleChangeZoneProperties` +- Emits `Event_ChangeZoneProperties` + +**Client:** +- `PlayerEventHandler::eventChangeZoneProperties` + +--- + +### `UNCONCEDE` (1032) + +**Purpose:** Undo a concede. + +**Server:** +- `Server_Game::handleUnconcede` + +**Client:** +- Reflected via game state + +--- + +### `JUDGE` (1033) + +**Purpose:** Execute a command on behalf of another player. + +**Server:** +- `Server_Game::handleJudge` +- Unwraps `Command_Judge` +- Dispatches embedded `GameCommand` + +**Client:** +- Transparent; handled as normal commands + +--- + +### `REVERSE_TURN` (1034) + +**Purpose:** Reverse turn order. + +**Server:** +- `Server_Game::handleReverseTurn` +- Toggles turn order flag + +**Client:** +- Reflected via subsequent turn events + +--- + +## Notes + +- Only commands emitting `GameEvent`s appear in `PlayerEventHandler` +- Some commands affect game state without direct client events +- Judge commands are transport-level wrappers, not gameplay primitives diff --git a/doc/doxygen/extra-pages/developer_documentation/network/protocol/protocol_overview.md b/doc/doxygen/extra-pages/developer_documentation/network/protocol/protocol_overview.md new file mode 100644 index 000000000..f91b94c5f --- /dev/null +++ b/doc/doxygen/extra-pages/developer_documentation/network/protocol/protocol_overview.md @@ -0,0 +1,76 @@ +@page developer_reference_protocol_overview Protocol (Overview) + +# Cockatrice Server Protocol – Overview + +## Purpose + +This document describes the Cockatrice client/server protocol as implemented +by the Cockatrice server. It is intended for developers implementing clients +or modifying the server. + +The protocol is **stateful**, **event-driven**, and **asynchronous**. + +--- + +## High-Level Properties + +- Encoding: Google Protocol Buffers +- Transport: TCP or WebSocket (transport-agnostic at protocol level) +- Directionality: + - Clients send `CommandContainer` + - Server sends `ServerMessage` +- Responses and events are interleaved +- Clients must handle unsolicited events at any time + +--- + +## Message Envelopes + +### Client → Server: CommandContainer + +A `CommandContainer` represents one logical client request. + +Protocol invariants: +- Exactly one command domain may be used per container +- Multiple commands of the same domain may be batched +- Context is provided via `room_id` or `game_id` when required + +Command domains: +- Session commands +- Room commands +- Game commands +- Moderator commands +- Admin commands + +--- + +### Server → Client: ServerMessage + +A `ServerMessage` represents either: +- A response to a command (`RESPONSE`) +- An unsolicited event (`*_EVENT`) + +Protocol invariants: +- Events may be delivered before or after responses +- Responses reference the original command via `cmd_id` +- Events are never correlated to a command + +--- + +## Asynchronous Model + +Clients MUST NOT assume request/response ordering. + +Example valid sequence: + +1. Client sends JOIN_ROOM +2. Server sends room chat history events +3. Server sends room join notifications +4. Server sends JOIN_ROOM response + +--- + +## Versioning + +The server reports a protocol version during connection initialization. +Clients MUST verify compatibility before sending commands. diff --git a/doc/doxygen/extra-pages/developer_documentation/network/protocol/protocol_response.md b/doc/doxygen/extra-pages/developer_documentation/network/protocol/protocol_response.md new file mode 100644 index 000000000..ce588114b --- /dev/null +++ b/doc/doxygen/extra-pages/developer_documentation/network/protocol/protocol_response.md @@ -0,0 +1,27 @@ +@page protocol_response Response (Protocol Concept) + +## Overview + +`Response` is the server-to-client message sent immediately after a command, using the same `cmd_id` to link to the originating command. +It provides a `Response::ResponseCode` and optionally a `Response::ResponseType` to guide client handling. + +## Fields + +- **cmd_id** (`uint64`, required) — Command ID this response corresponds to +- **response_code** (`Response::ResponseCode`, optional) — Outcome of the command + +## Response Codes + +All possible outcome codes are defined in Response::ResponseCode + +These describe the result of the command, e.g., success, failure, or special conditions like bans or registration requirements. + +## Response Types + +Responses are routed according to Response::ResponseType + +Each `ResponseType` corresponds to a high-level category, like `JOIN_ROOM` or `DECK_UPLOAD`. + +## Extensions + +- Protobuf extensions from 100 to max are reserved for future use diff --git a/doc/doxygen/extra-pages/developer_documentation/network/protocol/protocol_server_message.md b/doc/doxygen/extra-pages/developer_documentation/network/protocol/protocol_server_message.md new file mode 100644 index 000000000..b8dac9c43 --- /dev/null +++ b/doc/doxygen/extra-pages/developer_documentation/network/protocol/protocol_server_message.md @@ -0,0 +1,171 @@ +@page protocol_server_message ServerMessage (Protocol Concept) + +## Overview + +`ServerMessage` is the server-to-client message envelope used for all +communication originating from the Cockatrice server. + +The server never sends `CommandContainer` messages, and clients never send +`ServerMessage` messages. + +A `ServerMessage` represents either: + +- A direct response to a client command, or +- An unsolicited event emitted by the server + +This document describes `ServerMessage` as a protocol-level concept. +It should not be confused with generated protobuf accessors or wire-level +encoding details. + +--- + +## Lifetime and Delivery + +`ServerMessage` instances are emitted by the server and delivered +asynchronously to clients. + +Delivery guarantees: + +- Messages are delivered in the order sent by the server +- Messages may be delivered at any time +- Events may be delivered before or after responses + +Clients **must** be prepared to handle `ServerMessage` objects at all times, +regardless of pending requests. + +--- + +## Message Type + +Each `ServerMessage` contains **exactly one payload**, identified by the +`message_type` field. + +The following message types are supported: + +- `RESPONSE` +- `SESSION_EVENT` +- `ROOM_EVENT` +- `GAME_EVENT_CONTAINER` + +A message containing multiple payloads or an unknown type is considered +malformed. + +--- + +## RESPONSE + +`RESPONSE` messages are direct replies to client-issued `CommandContainer` +messages. + +Characteristics: + +- Contain a `Response` payload +- Echo the client-assigned `cmd_id` +- Indicate success or failure of the request + +A single `CommandContainer` results in **at most one** `RESPONSE`. + +`RESPONSE` messages may be preceded or followed by event messages. + +--- + +## Events + +Event messages are unsolicited notifications emitted by the server. + +Event message types include: + +- `SESSION_EVENT` +- `ROOM_EVENT` +- `GAME_EVENT_CONTAINER` + +Event messages: + +- Are not correlated to a specific client command +- Do not include a `cmd_id` +- May be delivered at any time + +Clients **must** process events independently of responses. + +--- + +## Event Scopes + +### Session Events + +Session events are global to the client session and may include: + +- Server notifications +- Private messages +- User status updates + +--- + +### Room Events + +Room events are scoped to a specific room and are delivered only to clients +currently present in that room. + +--- + +### Game Events + +Game events are scoped to a specific game and are delivered only to +participating clients. + +Game events are grouped within a `GameEventContainer` to allow batching. + +--- + +## Event Batching + +Some `ServerMessage` types may contain multiple logical events. + +- `GameEventContainer` may batch multiple game events +- Other event types represent a single logical event + +Clients must process all contained events **in order**. + +--- + +## Error Semantics + +Errors are communicated **exclusively** via `RESPONSE` messages. + +Event messages are never used to signal command failure. + +Common error responses include: + +- `RespInvalidCommand` +- `RespLoginNeeded` +- `RespContextError` +- `RespChatFlood` + +--- + +## Related Concepts + +- CommandContainer +- Client State Machine +- Response + +--- + +## Reference Proto Definition + +```proto +message ServerMessage { + enum MessageType { + RESPONSE = 0; + SESSION_EVENT = 1; + GAME_EVENT_CONTAINER = 2; + ROOM_EVENT = 3; + } + + optional MessageType message_type = 1; + optional Response response = 2; + optional SessionEvent session_event = 3; + optional GameEventContainer game_event_container = 4; + optional RoomEvent room_event = 5; +} +``` \ No newline at end of file diff --git a/libcockatrice_protocol/libcockatrice/protocol/pb/game_commands.proto b/libcockatrice_protocol/libcockatrice/protocol/pb/game_commands.proto index 796f4fc68..66d105a9f 100644 --- a/libcockatrice_protocol/libcockatrice/protocol/pb/game_commands.proto +++ b/libcockatrice_protocol/libcockatrice/protocol/pb/game_commands.proto @@ -1,56 +1,197 @@ syntax = "proto2"; -// Commands that are sent during a game to change the game state +/// Commands that are sent during a game to change the game state message GameCommand { + + /// Identifies the concrete game command to execute enum GameCommandType { + + /// Kick a player from the game. + /// Server: Server_Game::handleKickFromGame + /// Client: reflected via game state events KICK_FROM_GAME = 1000; + + /// Leave the current game voluntarily. + /// Server: Server_Game::handleLeaveGame + /// Client: reflected via game state events LEAVE_GAME = 1001; + + /// Send an in-game chat message. + /// Server: Server_Game::handleGameSay + /// Client: PlayerEventHandler::eventGameSay GAME_SAY = 1002; + + /// Shuffle a zone (typically the library). + /// Server: Server_Game::handleShuffle + /// Client: PlayerEventHandler::eventShuffle SHUFFLE = 1003; + + /// Take a mulligan at game start. + /// Server: Server_Game::handleMulligan + /// Client: reflected via game state events MULLIGAN = 1004; + + /// Roll one or more dice. + /// Server: Server_Game::handleRollDie + /// Client: PlayerEventHandler::eventRollDie ROLL_DIE = 1005; + + /// Draw cards from the deck. + /// Server: Server_Game::handleDrawCards + /// Client: PlayerEventHandler::eventDrawCards DRAW_CARDS = 1006; + + /// Undo a previous draw action. + /// Server: Server_Game::handleUndoDraw + /// Client: PlayerEventHandler::eventMoveCard (Context_UndoDraw) UNDO_DRAW = 1007; + + /// Flip a card face up or face down. + /// Server: Server_Game::handleFlipCard + /// Client: PlayerEventHandler::eventFlipCard FLIP_CARD = 1008; + + /// Attach one card to another. + /// Server: Server_Game::handleAttachCard + /// Client: PlayerEventHandler::eventAttachCard ATTACH_CARD = 1009; + + /// Create a token card. + /// Server: Server_Game::handleCreateToken + /// Client: PlayerEventHandler::eventCreateToken CREATE_TOKEN = 1010; + + /// Create a visual arrow between objects. + /// Server: Server_Game::handleCreateArrow + /// Client: PlayerEventHandler::eventCreateArrow CREATE_ARROW = 1011; + + /// Delete a visual arrow. + /// Server: Server_Game::handleDeleteArrow + /// Client: PlayerEventHandler::eventDeleteArrow DELETE_ARROW = 1012; + + /// Set a card attribute (e.g. tapped, flipped). + /// Server: Server_Game::handleSetCardAttr + /// Client: PlayerEventHandler::eventSetCardAttr SET_CARD_ATTR = 1013; + + /// Set a counter value on a card. + /// Server: Server_Game::handleSetCardCounter + /// Client: PlayerEventHandler::eventSetCardCounter SET_CARD_COUNTER = 1014; + + /// Increment a counter on a card. + /// Server: Server_Game::handleIncCardCounter + /// Client: PlayerEventHandler::eventSetCardCounter INC_CARD_COUNTER = 1015; + + /// Mark the player as ready to start the game. + /// Server: Server_Game::handleReadyStart + /// Client: reflected via game state events READY_START = 1016; + + /// Concede the game. + /// Server: Server_Game::handleConcede + /// Client: reflected via game state events CONCEDE = 1017; + + /// Increment a global (non-card) counter. + /// Server: Server_Game::handleIncCounter + /// Client: PlayerEventHandler::eventSetCounter INC_COUNTER = 1018; + + /// Create a new global counter. + /// Server: Server_Game::handleCreateCounter + /// Client: PlayerEventHandler::eventCreateCounter CREATE_COUNTER = 1019; + + /// Set a global counter value. + /// Server: Server_Game::handleSetCounter + /// Client: PlayerEventHandler::eventSetCounter SET_COUNTER = 1020; + + /// Delete a global counter. + /// Server: Server_Game::handleDelCounter + /// Client: PlayerEventHandler::eventDelCounter DEL_COUNTER = 1021; + + /// Advance to the next turn. + /// Server: Server_Game::handleNextTurn + /// Client: reflected via active player / phase events NEXT_TURN = 1022; + + /// Set the active phase of the turn. + /// Server: Server_Game::handleSetActivePhase + /// Client: reflected via game state events SET_ACTIVE_PHASE = 1023; + + /// Dump the contents of a zone. + /// Server: Server_Game::handleDumpZone + /// Client: PlayerEventHandler::eventDumpZone DUMP_ZONE = 1024; - // STOP_DUMP_ZONE = 1025; // obsolete + + /// Reveal specific cards to players. + /// Server: Server_Game::handleRevealCards + /// Client: PlayerEventHandler::eventRevealCards REVEAL_CARDS = 1026; + + /// Move a card between zones. + /// Server: Server_Game::handleMoveCard + /// Client: PlayerEventHandler::eventMoveCard MOVE_CARD = 1027; + + /// Set the sideboard plan for the game. + /// Server: Server_Game::handleSetSideboardPlan + /// Client: not forwarded as a game event SET_SIDEBOARD_PLAN = 1028; + + /// Select a deck for the game. + /// Server: Server_Game::handleDeckSelect + /// Client: reflected via lobby / game state DECK_SELECT = 1029; + + /// Lock or unlock sideboarding. + /// Server: Server_Game::handleSetSideboardLock + /// Client: reflected via game state SET_SIDEBOARD_LOCK = 1030; + + /// Change zone properties (visibility, reveal-top, etc.). + /// Server: Server_Game::handleChangeZoneProperties + /// Client: PlayerEventHandler::eventChangeZoneProperties CHANGE_ZONE_PROPERTIES = 1031; + + /// Undo a previous concede. + /// Server: Server_Game::handleUnconcede + /// Client: reflected via game state UNCONCEDE = 1032; + + /// Execute a command on behalf of another player. + /// Server: Server_Game::handleJudge + /// Client: transparent (wrapped commands handled normally) JUDGE = 1033; + + /// Reverse the current turn order. + /// Server: Server_Game::handleReverseTurn + /// Client: reflected via subsequent turn events REVERSE_TURN = 1034; } + extensions 100 to max; } -// A wrapper around a normal game command that allows a privileged user to send a command on behalf of another player +/// A wrapper around a normal game command that allows a privileged user +/// to send a command on behalf of another player. message Command_Judge { + extend GameCommand { + /// Judge command extension payload optional Command_Judge ext = 1033; } - // The player on whose behalf this command is sent + /// The player on whose behalf this command is sent. optional sint32 target_id = 1 [default = -1]; - // The wrapped game command + /// One or more wrapped game commands to execute. repeated GameCommand game_command = 2; } diff --git a/libcockatrice_protocol/libcockatrice/protocol/pb/response.proto b/libcockatrice_protocol/libcockatrice/protocol/pb/response.proto index e719f3e92..2bbf5c55b 100644 --- a/libcockatrice_protocol/libcockatrice/protocol/pb/response.proto +++ b/libcockatrice_protocol/libcockatrice/protocol/pb/response.proto @@ -1,35 +1,44 @@ syntax = "proto2"; -// Sent immediately after a command with the same cmd_id, connecting it to the command sent to the server +/// Server response envelope linking to a command +/** + * This message is sent immediately after a client command, using the same `cmd_id` + * to connect the response to the command. + * + * Responses may contain standard outcome codes (`ResponseCode`) and indicate + * what type of response the server is sending (`ResponseType`). + */ message Response { + + // Outcome of the command sent by the client enum ResponseCode { - RespNotConnected = -1; - RespNothing = 0; - RespOk = 1; - RespNotInRoom = 2; - RespInternalError = 3; - RespInvalidCommand = 4; - RespInvalidData = 5; - RespNameNotFound = 6; - RespLoginNeeded = 7; - RespFunctionNotAllowed = 8; - RespGameNotStarted = 9; - RespGameFull = 10; - RespContextError = 11; - RespWrongPassword = 12; - RespSpectatorsNotAllowed = 13; - RespOnlyBuddies = 14; - RespUserLevelTooLow = 15; - RespInIgnoreList = 16; - RespWouldOverwriteOldSession = 17; - RespChatFlood = 18; - RespUserIsBanned = 19; - RespAccessDenied = 20; - RespUsernameInvalid = 21; - RespRegistrationRequired = 22; - RespRegistrationAccepted = 23; // Server agrees to process client's registration request - RespUserAlreadyExists = 24; // Client attempted to register a name which is already registered - RespEmailRequiredToRegister = 25; // Server requires email to register accounts but client did not provide one + RespNotConnected = -1; // Client is not connected or session expired + RespNothing = 0; // No response required + RespOk = 1; // Command succeeded + RespNotInRoom = 2; // Client is not in the room for this command + RespInternalError = 3; // Server encountered an unexpected error + RespInvalidCommand = 4; // Command was invalid or unrecognized + RespInvalidData = 5; // Command data was invalid or malformed + RespNameNotFound = 6; // Target user not found + RespLoginNeeded = 7; // Client must log in first + RespFunctionNotAllowed = 8; // Client tried to perform a restricted action + RespGameNotStarted = 9; // Game has not started yet + RespGameFull = 10; // Game is full + RespContextError = 11; // Context (room/game) mismatch + RespWrongPassword = 12; // Password for this room or game is incorrect + RespSpectatorsNotAllowed = 13; // Spectators are not allowed in this room/game + RespOnlyBuddies = 14; // Only buddies can perform this action + RespUserLevelTooLow = 15; // User’s permission level is insufficient + RespInIgnoreList = 16; // Target user has ignored the client + RespWouldOverwriteOldSession = 17; // Would overwrite an existing session + RespChatFlood = 18; // Too many messages sent in short time + RespUserIsBanned = 19; // Client is banned + RespAccessDenied = 20; // Access to this action is denied + RespUsernameInvalid = 21; // Username is invalid + RespRegistrationRequired = 22; // Registration is required + RespRegistrationAccepted = 23; // Server agrees to process client's registration request + RespUserAlreadyExists = 24; // Client attempted to register a name which is already registered + RespEmailRequiredToRegister = 25; // Server requires email to register accounts but client did not provide one RespTooManyRequests = 26; // Server refused to complete command because client has sent too many too quickly RespPasswordTooShort = 27; // Server requires a decent password RespAccountNotActivated = @@ -45,33 +54,36 @@ message Response { RespServerFull = 36; // Server user limit reached RespEmailBlackListed = 37; // Server has blocked the email address provided for registration for some reason } + + // Type of response, used to route handling on the client enum ResponseType { - JOIN_ROOM = 1000; - LIST_USERS = 1001; - GET_GAMES_OF_USER = 1002; - GET_USER_INFO = 1003; - DUMP_ZONE = 1004; - LOGIN = 1005; - DECK_LIST = 1006; - DECK_DOWNLOAD = 1007; - DECK_UPLOAD = 1008; - REGISTER = 1009; - ACTIVATE = 1010; - ADJUST_MOD = 1011; - BAN_HISTORY = 1012; - WARN_HISTORY = 1013; - WARN_LIST = 1014; - VIEW_LOG = 1015; - FORGOT_PASSWORD_REQUEST = 1016; - PASSWORD_SALT = 1017; - GET_ADMIN_NOTES = 1018; - REPLAY_LIST = 1100; - REPLAY_DOWNLOAD = 1101; - REPLAY_GET_CODE = 1102; + JOIN_ROOM = 1000; // Response for joining a room + LIST_USERS = 1001; // Response listing users + GET_GAMES_OF_USER = 1002; // Response with user's games + GET_USER_INFO = 1003; // Response with user info + DUMP_ZONE = 1004; // Response with zone dump + LOGIN = 1005; // Response to login attempt + DECK_LIST = 1006; // Response with deck list + DECK_DOWNLOAD = 1007; // Response for deck download + DECK_UPLOAD = 1008; // Response for deck upload + REGISTER = 1009; // Response to registration + ACTIVATE = 1010; // Response to activation + ADJUST_MOD = 1011; // Response to mod adjustments + BAN_HISTORY = 1012; // Response with ban history + WARN_HISTORY = 1013; // Response with warn history + WARN_LIST = 1014; // Response with current warnings + VIEW_LOG = 1015; // Response with logs + FORGOT_PASSWORD_REQUEST = 1016; // Response to password reset request + PASSWORD_SALT = 1017; // Response containing password salt + GET_ADMIN_NOTES = 1018; // Response with admin notes + REPLAY_LIST = 1100; // Response listing replays + REPLAY_DOWNLOAD = 1101; // Response for replay download + REPLAY_GET_CODE = 1102; // Response containing replay code CARD_ART_RULE_LIST = 1200; } - required uint64 cmd_id = 1; - optional ResponseCode response_code = 2; - extensions 100 to max; + required uint64 cmd_id = 1; // Command ID that this response corresponds to + optional ResponseCode response_code = 2; // Outcome code of the command + + extensions 100 to max; // Protobuf extensions reserved for future use } diff --git a/proto2cpp.py b/proto2cpp.py new file mode 100644 index 000000000..360935a48 --- /dev/null +++ b/proto2cpp.py @@ -0,0 +1,285 @@ +#!/usr/bin/env python +## +# Doxygen filter for Google Protocol Buffers .proto files. +# This script converts .proto files into C++ style ones +# and prints the output to standard output. +# +# version 0.8-beta OSI +# +# How to enable this filter in Doxygen: +# 1. Generate Doxygen configuration file with command 'doxygen -g ' +# e.g. doxygen -g doxyfile +# 2. In the Doxygen configuration file, find FILE_PATTERNS and add *.proto +# FILE_PATTERNS = *.proto +# 3. In the Doxygen configuration file, find EXTENSION_MAPPING and add proto=C++ +# EXTENSION_MAPPING = proto=C++ +# 4. In the Doxygen configuration file, find INPUT_FILTER and add this script +# INPUT_FILTER = "python proto2cpp.py" +# 5. Run Doxygen with the modified configuration +# doxygen doxyfile +# +# +# Version 0.8 2018 Bugfix regarding long comments, remove typo +# Version 0.7 2018 Bugfix and extensions have been made by Open Simulation Interface (OSI) Carsten Kuebler https://github.com/OpenSimulationInterface, +# Copyright (C) 2016 Regents of the University of California https://github.com/vgteam/vg +# Copyright (C) 2012-2015 Timo Marjoniemi https://sourceforge.net/p/proto2cpp/wiki/Home/ +# All rights reserved. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +# +## + +import fnmatch +import inspect +import os +import re +import sys + + +## Class for converting Google Protocol Buffers .proto files into C++ style output to enable Doxygen usage. +## +## The C++ style output is printed into standard output.
+## There are three different logging levels for the class: +##
  • #logNone: do not log anything
  • +##
  • #logErrors: log errors only
  • +##
  • #logAll: log everything
+## Logging level is determined by \c #logLevel.
+## Error logs are written to file determined by \c #errorLogFile.
+## Debug logs are written to file determined by \c #logFile. +# +class proto2cpp: + + ## Logging level: do not log anything. + logNone = 0 + ## Logging level: log errors only. + logErrors = 1 + ## Logging level: log everything. + logAll = 2 + + ## Constructor + # + def __init__(self): + ## Debug log file name. + self.logFile = "proto2cpp.log" + ## Error log file name. + self.errorLogFile = "proto2cpp.error.log" + ## Logging level. + self.logLevel = self.logNone + + ## Handles a file. + ## + ## If @p fileName has .proto suffix, it is processed through parseFile(). + ## Otherwise it is printed to stdout as is except for file \c proto2cpp.py without + ## path since it's the script given to python for processing. + ## + ## @param fileName Name of the file to be handled. + # + def handleFile(self, fileName): + if fnmatch.fnmatch(filename, '*.proto'): + self.log('\nXXXXXXXXXX\nXX ' + filename + '\nXXXXXXXXXX\n\n') + # Open the file. Use try to detect whether or not we have an actual file. + if (sys.version_info >= (3, 0)): + try: + with open(filename, 'r', encoding='utf8') as inputFile: + self.parseFile(inputFile) + pass + except IOError as e: + self.logError('the file ' + filename + ' could not be opened for reading') + else: + # Python 2 code in this block + try: + with open(filename, 'r') as inputFile: + self.parseFile(inputFile) + pass + except IOError as e: + self.logError('the file ' + filename + ' could not be opened for reading') + + elif not fnmatch.fnmatch(filename, os.path.basename(inspect.getfile(inspect.currentframe()))): + self.log('\nXXXXXXXXXX\nXX ' + filename + '\nXXXXXXXXXX\n\n') + if (sys.version_info > (3, 0)): + try: + with open(filename, 'r', encoding='utf8') as theFile: + output = '' + for theLine in theFile: + output += theLine + print(output) + self.log(output) + pass + except IOError as e: + self.logError('the file ' + filename + ' could not be opened for reading') + else: + # Python 2 code in this block + try: + with open(filename, 'r') as theFile: + output = '' + for theLine in theFile: + output += theLine + print(output) + self.log(output) + pass + except IOError as e: + self.logError('the file ' + filename + ' could not be opened for reading') + + else: + self.log('\nXXXXXXXXXX\nXX ' + filename + ' --skipped--\nXXXXXXXXXX\n\n') + + ## Parser function. + ## + ## The function takes a .proto file object as input + ## parameter and modifies the contents into C++ style. + ## The modified data is printed into standard output. + ## + ## @param inputFile Input file object + # + def parseFile(self, inputFile): + # Go through the input file line by line. + isEnum = False + isPackage = False + isMultilineComment = False + # This variable is here as a workaround for not getting extra line breaks (each line + # ends with a line separator and print() method will add another one). + # We will be adding lines into this var and then print the var out at the end. + theOutput = '' + for line in inputFile: + # Search for comment ("//") and add one more slash character ("/") to the comment + # block to make Doxygen detect it. + matchComment = re.search("//", line) + # Search for semicolon and if one is found before comment, add a third slash character + # ("/") and a smaller than ("<") character to the comment to make Doxygen detect it. + matchSemicolon = re.search(";", line) + if matchSemicolon is not None and (matchComment is not None and matchSemicolon.start() < matchComment.start()): + comment = "///<" + line[matchComment.end():] + # Replace '.' in nested message references with '::' + # don't work for multi-nested references and generates problems with URLs and acronyms + #comment = re.sub(r'\s(\w+)\.(\w+)\s', r' \1::\2 ', comment) + line = line[:matchComment.start()] + elif matchComment is not None: + if isMultilineComment: + comment = " * " + line[matchComment.end():] + else: + comment = "/** " + line[matchComment.end():] + isMultilineComment = True + # replace '.' in nested message references with '::' + # don't work for multi-nested references and generates problems with URLs and acronyms + #comment = re.sub(r'\s(\w+)\.(\w+)\s', r' \1::\2 ', comment) + line = line[:matchComment.start()] + else: + comment = "" + + # End multiline comment, if there is no comment or if there are some chars before the comment. + if (matchComment is None or len(line.strip())>0) and isMultilineComment: + theOutput += " */\n" + isMultilineComment = False + + # line = line.replace(".", "::") but not in quoted strings (Necessary for import statement) + line = re.sub(r'\.(?=(?:[^"]*"[^"]*")*[^"]*$)',r'::',line) + + # Search for " option ...;", remove it + line = re.sub(r'\boption\b[^;]+;', r'', line) + + # Search for " package ", make a namespace + matchPackage = re.search(r"\bpackage\b", line) + if matchPackage is not None: + isPackage = True + # Convert to C++-style separator and block instead of statement + line = "namespace" + line[:matchPackage.start()] + line[matchPackage.end():].replace(";", " {") + + # Search for " repeated " fields and make them ... + #matchRepeated = re.search(r"\brepeated\b", line) + #if matchRepeated is not None: + # # Convert + # line = re.sub(r'\brepeated\s+(\S+)', r' repeated \1', line) + + # Search for "enum", start changing all semicolons (";") to commas (","). + matchEnum = re.search(r"\benum\b", line) + if matchEnum is not None: + isEnum = True + + # Search semicolon if we have detected an enum, and replace semicolon with comma. + if isEnum is True and matchSemicolon is not None: + line = line.replace(";", ",") + + # Search for a closing brace. + matchClosingBrace = re.search("}", line) + if isEnum is True and matchClosingBrace is not None: + line = line[:matchClosingBrace.start()] + "};" + line[matchClosingBrace.end():] + isEnum = False + elif isEnum is False and matchClosingBrace is not None: + # Message (to be struct) ends => add semicolon so that it'll + # be a proper C(++) struct and Doxygen will handle it correctly. + line = line[:matchClosingBrace.start()] + "};" + line[matchClosingBrace.end():] + + # Replacements change start of comment... + matchMsg = re.search(r"\bmessage\b", line) + if matchMsg is not None: + line = line[:matchMsg.start()] + "struct" + line[matchMsg.end():] + + # Replacements change start of comment... + matchExt = re.search(r"\bextend\b", line) + if matchExt is not None: + a_extend = line[matchExt.end():] + matchName = re.search(r"\b\w[\S:]*\b", a_extend) + if matchName is not None: + name = a_extend[matchName.start():matchName.end()] + name = re.sub(r'\w+::',r'',name) + a_extend = a_extend[:matchName.start()] + name + ": public " + a_extend[matchName.start():] + else: + a_extend = "_Dummy: public " + a_extend; + line = line[:matchExt.start()] + "struct " + a_extend + + theOutput += (line.strip() + ' ' + comment.strip()).strip() + '\n' + + if isPackage: + # Close the package namespace + theOutput += "}" + isPackage = False + + # Now that we've got all lines in the string let's split the lines and print out + # one by one. + # This is a workaround to get rid of extra empty line at the end which print() method adds. + lines = theOutput.splitlines() + for line in lines: + if len(line) > 0: + print(line) # Add linebreak to generate documentation correct. + self.log(line + '\n') + + ## Writes @p string to log file. + ## + ## #logLevel must be #logAll or otherwise the logging is skipped. + ## + ## @param string String to be written to log file. + # + def log(self, string): + if self.logLevel >= self.logAll: + with open(self.logFile, 'a') as theFile: + theFile.write(string) + + ## Writes @p string to error log file. + ## + ## #logLevel must be #logError or #logAll or otherwise the logging is skipped. + ## + ## @param string String to be written to error log file. + # + def logError(self, string): + if self.logLevel >= self.logError: + with open(self.errorLogFile, 'a') as theFile: + theFile.write(string) + +converter = proto2cpp() +# Doxygen will give us the file names +for filename in sys.argv[1:]: + converter.handleFile(filename) + +# end of file \ No newline at end of file From 88e90c059a466c935ca669a47d848038c704d31e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20Br=C3=BCbach?= Date: Wed, 1 Jul 2026 11:24:16 -0400 Subject: [PATCH 2/6] Lint cause my autolinter is broken lol --- cockatrice/src/game/player/player_event_handler.h | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/cockatrice/src/game/player/player_event_handler.h b/cockatrice/src/game/player/player_event_handler.h index 3a3a6fda9..48ad85e88 100644 --- a/cockatrice/src/game/player/player_event_handler.h +++ b/cockatrice/src/game/player/player_event_handler.h @@ -51,7 +51,6 @@ class Event_SetCounter; class Event_Shuffle; class Event_GameLogNotice; - /** * @class PlayerEventHandler * @brief Applies player-specific game events and emits corresponding log signals. @@ -75,7 +74,7 @@ public: */ explicit PlayerEventHandler(PlayerLogic *player); - /** @name Event dispatch + /** @name Event dispatch * @{ */ @@ -220,8 +219,8 @@ public: void eventGameLogNotice(const Event_GameLogNotice &event); signals: /** @name Logging signals - * @{ - */ + * @{ + */ void logSay(PlayerLogic *player, QString message); void logShuffle(PlayerLogic *player, CardZoneLogic *zone, int start, int end); void logRollDie(PlayerLogic *player, int sides, const QList &rolls); From 9feb583af7b0f4acedc860cd5e1b9b062790abb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20Br=C3=BCbach?= Date: Wed, 1 Jul 2026 11:51:02 -0400 Subject: [PATCH 3/6] Update. --- .../network/protocol/protocol_game_command.md | 249 ++++++++++++------ 1 file changed, 169 insertions(+), 80 deletions(-) diff --git a/doc/doxygen/extra-pages/developer_documentation/network/protocol/protocol_game_command.md b/doc/doxygen/extra-pages/developer_documentation/network/protocol/protocol_game_command.md index 257d2ea23..18e31064a 100644 --- a/doc/doxygen/extra-pages/developer_documentation/network/protocol/protocol_game_command.md +++ b/doc/doxygen/extra-pages/developer_documentation/network/protocol/protocol_game_command.md @@ -9,7 +9,7 @@ Flow overview: Client → GameCommand -→ Server_Game::handle* +→ Server command handler (`Server_Player`, `Server_AbstractParticipant`, or `Server_Game`) → GameEvent(s) → PlayerEventHandler::event* @@ -20,11 +20,15 @@ Client **Purpose:** Send a chat message during a game. **Server:** -- `Server_Game::handleGameSay` +- `Server_AbstractParticipant::cmdGameSay` +- Validates spectator chat permissions +- Applies chat flood protection - Emits `Event_GameSay` +- Logs the message to the server database **Client:** - `PlayerEventHandler::eventGameSay` +- Adds the chat message to the game log --- @@ -33,14 +37,19 @@ Client **Purpose:** Shuffle a card zone (usually library). **Server:** -- `Server_Game::handleShuffle` -- Reorders cards in zone +- `Server_Player::cmdShuffle` +- Only allows shuffling the deck zone +- Rejects if the game has not started or the player has conceded +- Supports partial-range shuffles (`start` / `end`) - Emits `Event_Shuffle` +- Re-reveals the top card if the deck is being tracked **Client:** - `PlayerEventHandler::eventShuffle` -- Clears revealed top cards -- Closes affected zone views +- Closes affected library views +- Clears any revealed top card if necessary +- Updates deck graphics +- Logs the shuffle --- @@ -49,12 +58,12 @@ Client **Purpose:** Roll one or more dice. **Server:** -- `Server_Game::handleRollDie` -- Computes random values +- Computes random die results - Emits `Event_RollDie` **Client:** - `PlayerEventHandler::eventRollDie` +- Displays the sorted roll results in the game log --- @@ -63,12 +72,21 @@ Client **Purpose:** Draw cards from deck. **Server:** -- `Server_Game::handleDrawCards` -- Moves cards from deck → hand +- `Server_Player::cmdDrawCards` +- Rejects if the game has not started or the player has conceded +- Moves cards from deck to hand +- Sends full card information privately to the drawing player +- Sends only the draw count to all other players +- Tracks drawn cards for undo +- Updates revealed-top-card state when necessary - Emits `Event_DrawCards` **Client:** - `PlayerEventHandler::eventDrawCards` +- Moves cards from library to hand +- Reveals card identities only when included in the event +- Updates both zones +- Logs the draw --- @@ -77,13 +95,16 @@ Client **Purpose:** Undo a previous draw. **Server:** -- `Server_Game::handleUndoDraw` -- Uses `Context_UndoDraw` -- Moves card(s) back to deck +- `Server_Player::cmdUndoDraw` +- Rejects if the game has not started or the player has conceded +- Uses the tracked draw history (`lastDrawList`) +- Moves the most recently drawable card from hand back to the top of the deck +- Emits `Event_GameLogNotice` if undo is no longer possible **Client:** -- Handled via `PlayerEventHandler::eventMoveCard` -- Logged as undo-draw context +- Processed as a normal `MOVE_CARD` +- Detects `Context_UndoDraw` +- Logs the undo draw instead of a normal move --- @@ -92,12 +113,16 @@ Client **Purpose:** Flip a card face up or face down. **Server:** -- `Server_Game::handleFlipCard` -- Updates card visibility +- Updates the card's face-down state +- Reveals identity when turning face up - Emits `Event_FlipCard` **Client:** - `PlayerEventHandler::eventFlipCard` +- Updates the card identity when revealed +- Changes face-up / face-down state +- Updates card menus +- Logs the flip --- @@ -106,12 +131,15 @@ Client **Purpose:** Attach one card to another. **Server:** -- `Server_Game::handleAttachCard` -- Updates attachment graph +- Updates attachment relationships - Emits `Event_AttachCard` **Client:** - `PlayerEventHandler::eventAttachCard` +- Updates parent/child attachment links +- Reorganizes affected zones +- Logs attach or detach operations +- Refreshes card actions --- @@ -120,12 +148,15 @@ Client **Purpose:** Create a token card. **Server:** -- `Server_Game::handleCreateToken` -- Allocates new card instance +- Creates a new token card - Emits `Event_CreateToken` **Client:** - `PlayerEventHandler::eventCreateToken` +- Creates the token object +- Applies token properties (PT, color, annotation, face-down state) +- Adds it to the requested zone +- Logs token creation --- @@ -134,12 +165,14 @@ Client **Purpose:** Create a visual arrow. **Server:** -- `Server_Game::handleCreateArrow` -- Registers arrow ownership +- Creates a visual arrow - Emits `Event_CreateArrow` **Client:** - `PlayerEventHandler::eventCreateArrow` +- Creates the arrow graphics +- Resolves endpoint card names +- Logs arrow creation --- @@ -148,12 +181,12 @@ Client **Purpose:** Remove a visual arrow. **Server:** -- `Server_Game::handleDeleteArrow` -- Removes arrow +- Removes an existing arrow - Emits `Event_DeleteArrow` **Client:** - `PlayerEventHandler::eventDeleteArrow` +- Removes the arrow graphics --- @@ -162,11 +195,13 @@ Client **Purpose:** Set a card attribute. **Server:** -- `Server_Game::handleSetCardAttr` +- Updates one or more card attributes - Emits `Event_SetCardAttr` **Client:** - `PlayerEventHandler::eventSetCardAttr` +- Updates tapped state, color, annotation, power/toughness, face-down state, attacking state, and related visuals +- Logs attribute changes where appropriate --- @@ -175,11 +210,14 @@ Client **Purpose:** Set a card counter. **Server:** -- `Server_Game::handleSetCardCounter` +- Updates the specified card counter - Emits `Event_SetCardCounter` **Client:** - `PlayerEventHandler::eventSetCardCounter` +- Updates the displayed counter value +- Refreshes card actions +- Logs the counter change --- @@ -188,12 +226,11 @@ Client **Purpose:** Increment a card counter. **Server:** -- `Server_Game::handleIncCardCounter` -- Normalized to set-counter +- Normalizes to a set-counter operation - Emits `Event_SetCardCounter` **Client:** -- `PlayerEventHandler::eventSetCardCounter` +- Processed identically to `SET_CARD_COUNTER` --- @@ -202,11 +239,11 @@ Client **Purpose:** Mark player as ready. **Server:** -- `Server_Game::handleReadyStart` -- May call `doStartGameIfReady` +- Marks the player as ready +- Starts the game once all required players are ready **Client:** -- Reflected via `Event_GameStateChanged` +- Reflected through updated game state events --- @@ -215,64 +252,79 @@ Client **Purpose:** Concede the game. **Server:** -- `Server_Game::handleConcede` -- Updates player state -- May end game +- Marks the player as conceded +- Returns borrowed cards where appropriate +- Ends the game if only one player remains **Client:** -- Reflected via game state events +- Reflected through updated game state events --- ### `INC_COUNTER` (1018) -**Purpose:** Increment a global counter. +**Purpose:** Increment a player counter. **Server:** -- `Server_Game::handleIncCounter` -- Emits `Event_SetCounter` +- `Server_Player::cmdIncCounter` +- Rejects if the game has not started or the player has conceded +- Updates the counter value +- Emits `Event_SetCounter` only if the value changed **Client:** - `PlayerEventHandler::eventSetCounter` +- Updates the displayed player counter +- Logs the change --- ### `CREATE_COUNTER` (1019) -**Purpose:** Create a global counter. +**Purpose:** Create a player counter. **Server:** -- `Server_Game::handleCreateCounter` +- `Server_Player::cmdCreateCounter` +- Rejects if the game has not started or the player has conceded +- Allocates a new counter ID +- Creates the counter - Emits `Event_CreateCounter` **Client:** - `PlayerEventHandler::eventCreateCounter` +- Creates the local player counter --- ### `SET_COUNTER` (1020) -**Purpose:** Set a global counter value. +**Purpose:** Set a player counter value. **Server:** -- `Server_Game::handleSetCounter` -- Emits `Event_SetCounter` +- `Server_Player::cmdSetCounter` +- Rejects if the game has not started or the player has conceded +- Updates the counter value +- Emits `Event_SetCounter` only if the value changed **Client:** - `PlayerEventHandler::eventSetCounter` +- Updates the counter value +- Logs the change --- ### `DEL_COUNTER` (1021) -**Purpose:** Delete a global counter. +**Purpose:** Delete a player counter. **Server:** -- `Server_Game::handleDelCounter` +- `Server_Player::cmdDelCounter` +- Rejects if the game has not started or the player has conceded +- Deletes the counter - Emits `Event_DelCounter` **Client:** - `PlayerEventHandler::eventDelCounter` +- Removes the counter --- @@ -281,13 +333,13 @@ Client **Purpose:** Advance to the next turn. **Server:** -- `Server_Game::handleNextTurn` -- Emits: - - `Event_SetActivePlayer` - - `Event_SetActivePhase` +- `Server_Player::cmdNextTurn` +- Rejects if the game has not started +- Conceded players cannot advance turns unless acting as a judge +- Calls `Server_Game::nextTurn()` **Client:** -- Reflected via game state events +- Reflected through subsequent active-player and active-phase events --- @@ -296,11 +348,14 @@ Client **Purpose:** Set active phase. **Server:** -- `Server_Game::handleSetActivePhase` -- Emits `Event_SetActivePhase` +- `Server_Player::cmdSetActivePhase` +- Rejects if the game has not started +- Judges may change the phase at any time +- Normal players may only change the phase during their own active turn +- Calls `Server_Game::setActivePhase()` **Client:** -- Reflected via game state events +- Reflected through updated phase events --- @@ -309,11 +364,12 @@ Client **Purpose:** Dump zone contents. **Server:** -- `Server_Game::handleDumpZone` +- Generates a zone summary - Emits `Event_DumpZone` **Client:** - `PlayerEventHandler::eventDumpZone` +- Logs the zone contents summary --- @@ -322,11 +378,15 @@ Client **Purpose:** Reveal specific cards. **Server:** -- `Server_Game::handleRevealCards` +- Reveals cards to one or more players - Emits `Event_RevealCards` **Client:** - `PlayerEventHandler::eventRevealCards` +- Reveals cards in-place or in a reveal window +- Supports temporary peeks +- Updates revealed top cards +- Logs the reveal --- @@ -335,12 +395,17 @@ Client **Purpose:** Move a card between zones. **Server:** -- `Server_Game::handleMoveCard` +- Moves cards between zones +- Updates ownership and attachments as needed - Emits `Event_MoveCard` -- May emit arrow cleanup events **Client:** - `PlayerEventHandler::eventMoveCard` +- Moves the card between zones +- Updates ownership, attachments, IDs and revealed information +- Handles undo-draw context +- Refreshes menus and zone layouts +- Logs the move --- @@ -349,11 +414,14 @@ Client **Purpose:** Set sideboard configuration. **Server:** -- `Server_Game::handleSetSideboardPlan` -- Stored server-side only +- `Server_Player::cmdSetSideboardPlan` +- Allowed only before readying for the game +- Requires a loaded deck +- Requires sideboarding to be unlocked +- Stores the current sideboard plan **Client:** -- Not forwarded as a game event +- No game event is generated --- @@ -362,10 +430,17 @@ Client **Purpose:** Select a deck. **Server:** -- `Server_Game::handleDeckSelect` +- `Server_Player::cmdDeckSelect` +- Allowed only before the game starts +- Loads a deck from either the database or serialized deck data +- Locks sideboarding +- Updates player properties +- Emits `Event_PlayerPropertiesChanged` +- Attaches `Context_DeckSelect` +- Returns the selected deck in the command response **Client:** -- Reflected via lobby / game state +- Reflected through updated player properties and command response --- @@ -374,10 +449,15 @@ Client **Purpose:** Lock or unlock sideboarding. **Server:** -- `Server_Game::handleSetSideboardLock` +- `Server_Player::cmdSetSideboardLock` +- Allowed only before readying +- Toggles sideboard locking +- Clears pending sideboard plans when locking +- Emits `Event_PlayerPropertiesChanged` +- Attaches `Context_SetSideboardLock` **Client:** -- Reflected via game state +- Reflected through updated player properties --- @@ -386,11 +466,15 @@ Client **Purpose:** Change zone properties. **Server:** -- `Server_Game::handleChangeZoneProperties` +- `Server_Player::cmdChangeZoneProperties` +- Delegates to the abstract implementation +- Updates top-card revelation if required - Emits `Event_ChangeZoneProperties` **Client:** - `PlayerEventHandler::eventChangeZoneProperties` +- Updates always-reveal-top-card and always-look-at-top-card settings +- Logs property changes --- @@ -399,24 +483,25 @@ Client **Purpose:** Undo a concede. **Server:** -- `Server_Game::handleUnconcede` +- Restores the player's active status **Client:** -- Reflected via game state +- Reflected through updated game state events --- ### `JUDGE` (1033) -**Purpose:** Execute a command on behalf of another player. +**Purpose:** Execute commands on behalf of another player. **Server:** -- `Server_Game::handleJudge` -- Unwraps `Command_Judge` -- Dispatches embedded `GameCommand` +- `Server_AbstractParticipant::cmdJudge` +- Requires judge privileges +- Executes embedded commands as the selected player +- Marks resulting events as judge-forced **Client:** -- Transparent; handled as normal commands +- Transparent; resulting events are processed normally --- @@ -425,16 +510,20 @@ Client **Purpose:** Reverse turn order. **Server:** -- `Server_Game::handleReverseTurn` -- Toggles turn order flag +- `Server_Player::cmdReverseTurn` +- Conceded non-judge players cannot use the command +- Delegates to `Server_AbstractParticipant::cmdReverseTurn` +- Toggles turn order +- Emits `Event_ReverseTurn` **Client:** -- Reflected via subsequent turn events +- Reflected by subsequent active-player progression --- ## Notes -- Only commands emitting `GameEvent`s appear in `PlayerEventHandler` -- Some commands affect game state without direct client events -- Judge commands are transport-level wrappers, not gameplay primitives +- Game commands are handled by `Server_Player`, `Server_AbstractParticipant`, or `Server_Game`, depending on the command. +- Only commands that emit `GameEvent`s produce corresponding `PlayerEventHandler` callbacks. +- Some commands modify server state without generating a dedicated client event. +- Judge commands are transport wrappers that execute other game commands on behalf of another player. \ No newline at end of file From 845c13013aab6f5429698e8b06c8933a893dfe5a Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Thu, 2 Jul 2026 07:49:45 -0400 Subject: [PATCH 4/6] Update Doxyfile Co-authored-by: tooomm --- Doxyfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doxyfile b/Doxyfile index dce6bdb72..e24e367ae 100644 --- a/Doxyfile +++ b/Doxyfile @@ -349,7 +349,7 @@ OPTIMIZE_OUTPUT_SLICE = NO # # Note see also the list of default file extension mappings. -EXTENSION_MAPPING = proto=C++ +EXTENSION_MAPPING = proto=C++ # If the MARKDOWN_SUPPORT tag is enabled then Doxygen pre-processes all comments # according to the Markdown format, which allows for more readable From a2203197c468aea4ce1a7475cc286545a2354c91 Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Thu, 2 Jul 2026 07:50:10 -0400 Subject: [PATCH 5/6] Alphabetical ordering Co-authored-by: tooomm --- Doxyfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doxyfile b/Doxyfile index e24e367ae..15521cc71 100644 --- a/Doxyfile +++ b/Doxyfile @@ -1103,8 +1103,8 @@ RECURSIVE = YES # run. EXCLUDE = build/ \ - cmake-build-debug/ \ cmake/ \ + cmake-build-debug/ \ doc/doxygen/theme/docs/ \ doc/doxygen/theme/include/ \ vcpkg/ From 90c467f2d54ef6b9e12a5c99501ef1e6805e0654 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20Br=C3=BCbach?= Date: Thu, 2 Jul 2026 09:29:17 -0400 Subject: [PATCH 6/6] Add new card art rule comment --- libcockatrice_protocol/libcockatrice/protocol/pb/response.proto | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libcockatrice_protocol/libcockatrice/protocol/pb/response.proto b/libcockatrice_protocol/libcockatrice/protocol/pb/response.proto index 2bbf5c55b..e1f415ce6 100644 --- a/libcockatrice_protocol/libcockatrice/protocol/pb/response.proto +++ b/libcockatrice_protocol/libcockatrice/protocol/pb/response.proto @@ -79,7 +79,7 @@ message Response { REPLAY_LIST = 1100; // Response listing replays REPLAY_DOWNLOAD = 1101; // Response for replay download REPLAY_GET_CODE = 1102; // Response containing replay code - CARD_ART_RULE_LIST = 1200; + CARD_ART_RULE_LIST = 1200; // Response containing a list of card art rules } required uint64 cmd_id = 1; // Command ID that this response corresponds to