[Docs] Add app logo to main page + cleanup (#7471)
Some checks failed
CodeQL / Analyze (cpp) (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
Build Docker / Servatrice (arm) (push) Has been cancelled
Build Docker / Servatrice (x86) (push) Has been cancelled
Build Docker / Publish multi-platform Servatrice image (push) Has been cancelled

* Add app logo to main page

* rename pages folder

* Add link + cleanup

* Cleanup file extension patterns

* Update Doxygen README.md
This commit is contained in:
tooomm 2026-10-10 23:48:37 +02:00 • committed by GitHub
parent efc7e7a079
commit 7fa6234eee
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
54 changed files with 252 additions and 19 deletions

View file

@ -0,0 +1,16 @@
@page card_database_schema_and_parsing Card Database Schema and Parsing
# Card Database Schemas
Cockatrice uses `XML files` to store information of available cards to be used in the app (`cards.xml`).
The token file follows the schema of the card database (`tokens.xml`), too.
Saved decks (`<deckname>.xml`) use a different schema.
- [XSD Schema for `Card Databases`](https://github.com/Cockatrice/Cockatrice/blob/master/doc/carddatabase_v4/cards.xsd) <kbd>v4</kbd>
- [XSD Schema for `Decks`](https://github.com/Cockatrice/Cockatrice/blob/master/doc/deck.xsd) <kbd>v1</kbd>
# Card Database Parsing
TODO

View file

@ -0,0 +1,83 @@
@page displaying_cards Displaying Cards
Cockatrice offers a number of widgets for displaying cards. To pick the right one for your purpose, it is first
important
to determine the context in which you will be displaying the card.
# In-client
In the client (like in your custom widgets), you can use the existing card display widgets.
## Simple Display
The most general purpose of these is @ref CardInfoPictureWidget, which simply displays a picture of a card.
The textual equivalent to this is @ref CardInfoTextWidget, which displays all the properties of a card in text form.
## Detailed Display
The convenience class @ref CardInfoDisplayWidget displays both of these widgets in a tabbed container, which allows
choosing between either visual, textual, or both representations at the same time. It is the class that is most
familiar to our users when a single card is highlighted or inspected, since it is used in the deck editor and in-game
for these purposes.
If you would like the user to be able to inspect a single card in your custom widget, you should
expose a @ref CardInfoDisplayWidget to them and incorporate it as a dock widget inside your custom widget.
@ref CardInfoDisplayWidget is great for ensuring that the user has all the relevant details of a card available.
It is crucial anywhere the user might encounter a card for the first time or needs to make an informed decision.
## Visual Display
However, there is a reason why it is possible to have a visual-only representation of the card in the
@ref CardInfoDisplayWidget.
Cards are, by design, meant to be represented as images and cards which we can reasonably
expect the user to know (for example, cards contained in a user made deck or explicitly chosen by the user) can be
represented with just their image as a sort of "visual shorthand".
The simplest way to do this is of course, the above-mentioned @ref CardInfoPictureWidget which simply displays the
picture without modifications.
However, there exist a couple of other convenience classes which subclass @ref CardInfoPictureWidget to accomplish some
things which you might find useful as well.
- @ref CardInfoPictureWithTextOverlayWidget displays text overlaid on top of the card picture and scales this text to
fit automatically.
- @ref DeckPreviewCardPictureWidget is a @ref CardInfoPictureWithTextOverlayWidget which also features a
raise-on-mouse-enter animation, controlled by the global setting (TODO: specify which).
- @ref CardInfoPictureArtCropWidget attempts to display an 'art crop' of the card by cropping the upper region of the
card and only displaying that.
## Groups of Cards
Sometimes it is useful to display images of cards in groups, for example to represent the deck in the @ref
VisualDeckEditorWidget or during confirmation in the pre-game lobby.
To this end, Cockatrice offers three convenience classes to display cards for an index in a @ref DeckListModel in a
group and one to display all cards in a particular zone of a @ref DeckListModel.
The generic way to do this is @ref CardGroupDisplayWidget, which displays cards in @ref QVBoxLayout. This class is very
generic and it is probably a better choice to either subclass it or use one of the two existing implementations.
@ref FlatCardGroupDisplayWidget displays cards using a horizontal @ref FlowWidget. This means it will fill available
screen space, left to right with no margins, with card pictures, skipping to a new line if it overflows available screen
space horizontally.
@ref OverlappedCardGroupDisplayWidget displays cards using an @ref OverlapWidget, which displays widgets on top of each
other in vertical columns until it exceeds available screen space in which case it will start a new column. The amount
of overlap as well as the overlap direction is configurable.
@ref DeckCardZoneDisplayWidget can be used to visualize all cards in a zone with either a @ref
FlatCardGroupDisplayWidget or a @ref OverlappedCardGroupDisplayWidget and headed by a @ref BannerWidget. It also allows
grouping and sorting.
# In-game
The in-game view is a @ref QGraphicsScene, which means any widget we want to display inside of it should be a @ref
QGraphicsObject.
- @ref CardItem

View file

@ -0,0 +1,13 @@
@page developer_reference Developer Reference
- @subpage logging
- @subpage primer_cards
- @subpage card_database_schema_and_parsing
- @subpage querying_the_card_database
- @subpage loading_card_pictures
- @subpage displaying_cards
- @subpage developer_reference_network_overview

View file

@ -0,0 +1,210 @@
@page loading_card_pictures Loading Card Pictures
Pictures associated with CardInfo%s are retrieved either from on-disk or the network through the CardPictureLoader.
In most cases, you don't need to concern yourself with the internals of CardPictureLoader.
Simply using one of the ways described in @ref displaying_cards is enough to automatically queue a request to the
CardPictureLoader when the chosen widget is shown, emitting signals to refresh the widget when the request is finished.
# How requests are triggered
CardPictureLoader::getPixmap() is called exactly two times in the code base, in CardInfoPictureWidget::loadPixmap(), the
base class
for all widget based card picture display, and AbstractCardItem::paintPicture(), the base class for all QGraphicsItem
based card picture
display. See @ref displaying_cards for more information on the difference between these two display methods.
Because both of these calls are made in the paintEvent() methods of their respective classes, this means that requests
are issued as soon as but not before the widget is shown on screen.
It is also possible to "warm up" the cache by issuing card picture load requests to the CardPictureLoader without using
a display widget and waiting for it to be shown by calling CardPictureLoader::cacheCardPixmaps() with a list of
ExactCard%s.
# The QPixmapCache and QNetworkDiskCache
Cockatrice uses the QPixmapCache from the Qt GUI module to store card pictures in-memory and the QNetworkDiskCache from
the Qt Network module to cache network requests for card pictures on-disk.
What this means is that the CardPictureLoader will first attempt to look up a card in the QPixmapCache according to the
ExactCard::getPixmapCacheKey() method of an ExactCard object. If it does not find it in the in-memory cache, it will
issue a load request, which will first look for local images on-disk and then consult the QNetworkDiskCache and if
found, use the stored binary data from the network cache to populate the in-memory pixmap cache under the card's cache
key. If it is not found, it will then proceed with issuing a network request.
The size of both of these caches can be configured by the user on the "Storage" settings page.
# PixmapCacheKeys and ProviderIDs
Every card picture that is loaded ends up in the QPixmapCache under a key that identifies the exact printing it belongs
to. The key is produced by ExactCard::getPixmapCacheKey() and has the following shape:
```text
card_<card name>_<provider ID>
```
For example, the _Example Card_ printing with provider ID `0b23cdc8-d413-4fb1-8470-474221b10fe2` is stored
under `card_Example Card_0b23cdc8-d413-4fb1-8470-474221b10fe2`. If the printing has no provider ID, the key
drops the suffix and falls back to `card_<card name>`.
The **provider ID** is the Scryfall UUID of the printing. Oracle maps the `scryfallId` of every printing to the `uuid`
property when building the card database, and deck files persist it as the `uuid` attribute of each card entry. Because
the provider ID is part of the pixmap cache key, two different printings of the same card never share a cache entry.
This is exactly what allows the printing selector and exact-card lookups to display the picture of the precise printing
a card was added as.
The base key holds the full-size image. When a widget asks for a scaled version, the scaled pixmap is stored under an
additional key of the form `<key>_<width>x<height>`, with the size adjusted for the device pixel ratio of the screen,
so each widget size is only ever scaled once.
The cache key is also used for bookkeeping outside of the pixmap cache itself:
- CardPictureLoaderWorker keeps a set of keys that are currently being loaded so the same card is never queued twice.
- CardPictureLoader tracks, per key, the last time loading failed. A failed load stores a NULL pixmap under the key; as
long as that marker is present, subsequent requests for the card show the "failed" card back and are only re-queued
after the retry interval of 300 seconds has passed.
- When the CardInfo of a loaded card is destroyed, its cache entries and failure markers are removed.
# The Redirect Cache
Many picture URLs - in particular the Gatherer and Scryfall URLs from the default set of templates - redirect to a CDN
or to a different host. To avoid following the same redirect for every single card, CardPictureLoaderWorker remembers
redirects and applies them without an extra network round trip.
The redirect cache is a hash map from original URL to redirect URL plus timestamp. It is persisted to a `cache.ini`
file (Qt's INI format, under the `redirects` array) inside the redirect cache directory
(`SettingsCache::instance().paths().getRedirectCachePath()`, i.e. `<cache directory>/redirects/`). The cache is loaded when the worker
starts, pruned of entries older than the configured TTL, and written back to disk when the application quits.
Entries are added whenever a network reply reports a redirection (see below) and are consulted before any request is
made: both CardPictureLoaderWorker::queueRequest() and CardPictureLoaderWorker::makeRequest() check for a cached
redirect first and jump straight to the final URL.
The TTL is the "Redirect Cache TTL" setting on the "Storage" settings page and defaults to 30 days. Lowering it makes
Cockatrice re-resolve redirects sooner, which can help when a download URL changed its redirect target.
Because Cockatrice tracks redirects itself, the QNetworkAccessManager is configured with Qt's `ManualRedirectPolicy`.
Redirects found in a reply are handled manually:
- A recursive redirect (a URL redirecting to itself) is treated as a failed load.
- Otherwise the redirect is recorded in the redirect cache and the request is re-issued against the target URL.
- A successful reply with one of the redirect status codes 301, 302, 303, 305, 307 or 308 is handled the same way.
Clearing the network cache (CardPictureLoader::clearNetworkCache()) also clears the redirect cache.
# Local Image Loading
Before any network request is issued, CardPictureLoaderWorker hands the ExactCard to CardPictureLoaderLocal, which
tries to find a matching picture on disk. If a local picture is found, it is used and no network request is made.
CardPictureLoaderLocal searches three locations:
- The **CUSTOM folder** (`<pictures directory>/CUSTOM/`). Every file in it is indexed recursively by its base name
(both `baseName` and `completeBaseName`, so a file named `ExampleCard.jpg` is indexed as `ExampleCard`). The index is rebuilt
every 10 seconds, so new files are picked up without restarting the
client (changing the configured pictures directory only reassigns the search paths; the next timer tick rebuilds the index).
- The **set-named subfolders** of the pictures directory: `<pictures directory>/<set code>/` and
`<pictures directory>/downloadedPics/<set code>/`.
- The **root of the `downloadedPics` folder** (`<pictures directory>/downloadedPics/`). The export naming schemes without a
set-folder part write their files straight into `downloadedPics/`, so this is where flat-scheme downloads and the
local overrides described below are matched.
For each candidate folder, the loader generates file-name variants from the card's corrected name, set code, collector
number and provider ID using the import naming schemes (Card Name + Provider ID, Card Name + Set + Collector,
Set + Collector + Card Name, Card Name + Set, Card Name), each tried with both `_` and `-` as separator. A file is
accepted when its name without the extension *equals* the variant exactly - the extension itself is free - and the
first variant that yields a readable image wins. For example, the file `Example Card_EXM_43.png` in the `EXM` set
folder matches the card with corrected name `Example Card`, set code `EXM` and collector number `43`.
\attention The file-name variants use the *corrected* card name, so split cards are stored under their joined name: the
"Example // Card" card is matched by a file named `ExampleCard.*`.
The naming schemes are also documented in the user-facing page @ref custom_card_pictures, which additionally covers
the CUSTOM folder workflow, `picurl` and download URL templates.
When the filesystem cache method is selected on the "Storage" settings page, downloaded images are additionally written
into `<pictures directory>/downloadedPics/` using the configured export naming scheme (as `.png` files). The two export
schemes with a set-folder part (`Set Folder / Name + Provider ID` and `Set Folder / Name + Set Name + Collector`) write
into `downloadedPics/<set code>/`; the three flat schemes write directly into `downloadedPics/`. Automatic cache writes
never overwrite an existing file, so a provider outage can permanently leave an outdated image in that folder until it
is deleted manually - the user-facing troubleshooting guide @ref fixing_card_pictures covers how to do this. Explicit
image overrides (see below) are the exception and always overwrite.
# Local Image Overrides
Beyond the generic on-disk lookup above, individual printings can be given explicit artwork that wins over every other
source without touching the CUSTOM folder or any download URL. This is the "Image Overrides" submenu of the context menu
that opens when you right-click a card in the deck editor's printing selector.
- **Load Custom Image...** asks for a picture file and installs it for the card through
CardPictureLoader::saveCardImageToLocalStorage() with `allowOverwrite == true`.
- **One entry per alternate printing** (labeled `<set> <collector number>`): selecting one hands the card to
CardPictureLoader::installPrintingOverride(), which resolves that printing's artwork - enqueueing a load and waiting
for the `CardInfo::pixmapUpdated` signal if it is not cached yet - and persists it for the card.
- **Clear Custom Image** calls CardPictureLoader::deleteAllLocalOverrides() to remove every stored override image of the
card, after which normal resolution resumes. The entry is only enabled while CardPictureLoader::hasLocalOverrides()
reports at least one stored file.
Overrides are stored as `.png` files in `downloadedPics/` under the export naming scheme configured on the "Storage"
settings page - which is exactly why the local matcher also looks into the `downloadedPics/` root (see above). They are
written with `allowOverwrite == true`, so an override always replaces whatever the filesystem cache previously saved for
that spelling; only *automatic* cache writes are prevented from clobbering it. Overriding a card with its own current
printing is a no-op (the UI omits it from the menu), and an override whose artwork fails to resolve surfaces the
"failed" card back instead of a silent no-op while any override already on disk is left in place and re-displayed.
# URL Generation and Resolution
When no local image is available and downloading is enabled, the network loader starts working through a list of
candidate URLs. This list is managed by CardPictureToLoad and is built in two steps.
First, CardPictureToLoad::extractSetsSorted() collects all sets the card has printings in and sorts them by set
priority. Unless the user disabled per-printing art ("Override all card art with personal set preference (Pre-ProviderID
change behavior)"), the set that
matches the requested printing's provider ID is moved to the front, so the exact printing is always attempted first.
For each set, CardPictureToLoad::populateSetUrls() builds an ordered URL list:
1. A custom URL defined for that printing via the `picurl` property in the card database, if present.
2. The configured download URL templates, in priority order (Deck Editor → "URL Download Priority").
URL templates are transformed into concrete URLs by CardPictureToLoad::transformUrl(), which substitutes reference
points. `!name!`, `!setcode!` and friends substitute card and printing data, while the `!set:<property>!` and
`!prop:<property>!` reference points resolve a property of the printing or of the card respectively. The canonical list
of all reference points with examples, including the `_fill_with_` and `_substr_` modifiers, lives in
@ref custom_card_pictures.
The `!set:...!` and `!prop:...!` reference points also support two modifiers:
- `_fill_with_<text>` pads the value with the given text, right-aligned, e.g. `!set:num_fill_with_000!` turns collector
number `1` into `001`. If the value is longer than the fill text, the template is invalidated.
- `_substr_<start>_<length>` extracts a substring, e.g. `!set:num_substr_2_2!` takes two characters starting at the
third. If the substring would extend past the end of the value, the template is invalidated.
Substituted values are percent-encoded. If a template asks for a property the card or printing does not have (or one of
the modifiers invalidates it), the template yields no URL and is skipped; the next template is tried instead.
\attention Custom URLs should start with `http://` or `https://`. The scheme is not validated before the URL is handed
to QNetworkAccessManager, so a template without an absolute scheme may silently fail to download; prefer HTTPS where the
provider allows it.
The resolution order is: for the current set, try each URL in the list; when all URLs for a set are exhausted, move to
the next set; when every set is exhausted, the load fails. A failed load is reported through the NULL-pixmap mechanism
described in the PixmapCacheKeys and ProviderIDs section above.
Several mechanisms influence the resolution process:
- **Rate limiting.** The worker allows roughly 10 requests per second globally. A server that answers with HTTP 429
gets its per-host allowance halved; the first 429 for a host is waited out (honoring the `Retry-After` header if
present) and the same URL retried, while a second 429 makes the loader fall through to the other configured sources.
When all sources are exhausted the request is deferred with some random jitter and retried once the back-off expires.
- **Redirects.** Replies with a redirect status (301, 302, 303, 305, 307, 308) are followed and recorded in the
redirect cache as described in the Redirect Cache section above.
- **Blacklisted images.** Gatherer returns the card back image for cards it does not know. A few known MD5 hashes of
that image are blacklisted, so such a "successful" download is treated as not found instead of being shown.
- **WebP.** Images detected as WebP (RIFF/WEBP header) are decoded through QMovie instead of QImageReader.
- **Downloads disabled.** When "Download card pictures on the fly" is disabled and the network cache method is active,
requests use Qt's `AlwaysCache` policy so that only previously cached images are served.
A user-facing reference for writing download URL templates, including more worked examples, is available at
@ref custom_card_pictures.

View file

@ -0,0 +1,184 @@
@page logging Logging
Cockatrice uses QtLogging from the QtCore module for its logging. See
the [official documentation](https://doc.qt.io/qt-6/qtlogging.html) for further details.
# Log Message Pattern
Any message logged through the QtLogging system automatically conforms to this message pattern:
Generic:
```
[<timestamp> <log_level>] [<class:function>] - <message> [<filename>:<line_no>]
```
Example:
```
[2025-12-05 14:48:25.908 I] [MainWindow::startupConfigCheck] - Startup: found config with current version [window_main.cpp:951]
```
For more information, see [Logging Setup](#logging-setup).
# Log Level and Categories
\note The default log level for the application is info.
This means that you should only use qInfo() in production-level code if you are truly sure that this message is
beneficial to end-users and other developers. As a general rule, if your functionality logs to info more than twice in
response to a user interaction, you are advised to consider moving some of these logs down to the debug level.
\warning You are strongly advised to avoid the use of the generic logging macros (e.g. qDebug(), qInfo(), qWarn()).
\note You should instead use the corresponding category logging macros (qCDebug(), qCInfo(), qCWarn()) and define
logging
categories for your log statements.
Example:
```c++
in .h
inline Q_LOGGING_CATEGORY(ExampleCategory, "cockatrice_example_category");
inline Q_LOGGING_CATEGORY(ExampleSubCategory, "cockatrice_example_category.sub_category");
in .cpp
qCInfo(ExampleCategory) << "Info level logs are usually sent through the main category"
qCDebug(ExampleSubCategory) << "Debug level logs are permitted their own category to allow selective silencing"
```
For more information on how to enable or disable logging categories,
see [Logging Configuration](#logging-configuration).
# Logging Configuration
For configuring our logging, we use the qtlogging.ini, located under cockatrice/resources/config/qtlogging.ini, which is
baked into the application in release version and set as the QT_LOGGING_CONF environment variable in main.cpp.
```c++
#ifdef Q_OS_APPLE
// <build>/cockatrice/cockatrice.app/Contents/MacOS/cockatrice
const QByteArray configPath = "../../../qtlogging.ini";
#elif defined(Q_OS_UNIX)
// <build>/cockatrice/cockatrice
const QByteArray configPath = "./qtlogging.ini";
#elif defined(Q_OS_WIN)
// <build>/cockatrice/Debug/cockatrice.exe
const QByteArray configPath = "../qtlogging.ini";
#else
const QByteArray configPath = "";
#endif
if (!qEnvironmentVariableIsSet(("QT_LOGGING_CONF"))) {
// Set the QT_LOGGING_CONF environment variable
qputenv("QT_LOGGING_CONF", configPath);
}
```
For more information on how to use this file and on how Qt evaluates which logging rules/file to use, please
see the [official Qt documentation](https://doc.qt.io/qt-6/qloggingcategory.html#configuring-categories).
Some examples:
```
# Turn off all logging except everything from card_picture_loader and all sub categories
[Rules]
# The default log level is info
*.debug = false
*.info = false
*.warning = false
*.critical = false
*.fatal = false
card_picture_loader.* = true
```
```
# Turn off all logging except info level logs from card_picture_loader and all sub categories
[Rules]
# The default log level is info
*.debug = false
*.info = false
*.warning = false
*.critical = false
*.fatal = false
card_picture_loader.*.info = true
```
```
[Rules]
# Turn on debug level logs for card_picture_loader but keep logging for sub categories suppressed
*.debug = false
card_picture_loader.debug = true
```
```
[Rules]
# Turn on all logs for worker subcategory of card_picture_loader
*.debug = false
card_picture_loader.worker = true
```
```
[Rules]
# Turn off some noisy and irrelevant startup logging for local development
*.debug = false
qt_translator = false
window_main.* = false
release_channel = false
spoiler_background_updater = false
theme_manager = false
sound_engine = false
tapped_out_interface = false
card_database = false
card_database.loading = false
card_database.loading.success_or_failure = true
cockatrice_xml.* = false
```
# Logging Setup
This is achieved through our logging setup in @ref main.cpp, where we set the message pattern and install a custom
logger which replaces the full file path at the end with just the file name (Qt shows the full and quite lengthy path by
default).
```c++
qSetMessagePattern(
"\033[0m[%{time yyyy-MM-dd h:mm:ss.zzz} "
"%{if-debug}\033[36mD%{endif}%{if-info}\033[32mI%{endif}%{if-warning}\033[33mW%{endif}%{if-critical}\033[31mC%{"
"endif}%{if-fatal}\033[1;31mF%{endif}\033[0m] [%{function}] - %{message} [%{file}:%{line}]");
QApplication app(argc, argv);
QObject::connect(&app, &QApplication::lastWindowClosed, &app, &QApplication::quit);
qInstallMessageHandler(CockatriceLogger);
```
```c++
static void CockatriceLogger(QtMsgType type, const QMessageLogContext &ctx, const QString &message)
{
QString logMessage = qFormatLogMessage(type, ctx, message);
// Regular expression to match the full path in the square brackets and extract only the filename and line number
QRegularExpression regex(R"(\[(?:.:)?[\/\\].*[\/\\]([^\/\\]+\:\d+)\])");
QRegularExpressionMatch match = regex.match(logMessage);
if (match.hasMatch()) {
// Extract the filename and line number (e.g., "main.cpp:211")
QString filenameLine = match.captured(1);
// Replace the full path in square brackets with just the filename and line number
logMessage.replace(match.captured(0), QString("[%1]").arg(filenameLine));
}
Logger::getInstance().log(type, ctx, logMessage);
}
```

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -0,0 +1,529 @@
@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 command handler (`Server_Player`, `Server_AbstractParticipant`, or `Server_Game`)
→ GameEvent(s)
→ PlayerEventHandler::event*
## Command Mapping
### `GAME_SAY` (1002)
**Purpose:** Send a chat message during a game.
**Server:**
- `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
---
### `SHUFFLE` (1003)
**Purpose:** Shuffle a card zone (usually library).
**Server:**
- `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`
- Closes affected library views
- Clears any revealed top card if necessary
- Updates deck graphics
- Logs the shuffle
---
### `ROLL_DIE` (1005)
**Purpose:** Roll one or more dice.
**Server:**
- Computes random die results
- Emits `Event_RollDie`
**Client:**
- `PlayerEventHandler::eventRollDie`
- Displays the sorted roll results in the game log
---
### `DRAW_CARDS` (1006)
**Purpose:** Draw cards from deck.
**Server:**
- `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
---
### `UNDO_DRAW` (1007)
**Purpose:** Undo a previous draw.
**Server:**
- `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:**
- Processed as a normal `MOVE_CARD`
- Detects `Context_UndoDraw`
- Logs the undo draw instead of a normal move
---
### `FLIP_CARD` (1008)
**Purpose:** Flip a card face up or face down.
**Server:**
- 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
---
### `ATTACH_CARD` (1009)
**Purpose:** Attach one card to another.
**Server:**
- 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
---
### `CREATE_TOKEN` (1010)
**Purpose:** Create a token card.
**Server:**
- 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
---
### `CREATE_ARROW` (1011)
**Purpose:** Create a visual arrow.
**Server:**
- Creates a visual arrow
- Emits `Event_CreateArrow`
**Client:**
- `PlayerEventHandler::eventCreateArrow`
- Creates the arrow graphics
- Resolves endpoint card names
- Logs arrow creation
---
### `DELETE_ARROW` (1012)
**Purpose:** Remove a visual arrow.
**Server:**
- Removes an existing arrow
- Emits `Event_DeleteArrow`
**Client:**
- `PlayerEventHandler::eventDeleteArrow`
- Removes the arrow graphics
---
### `SET_CARD_ATTR` (1013)
**Purpose:** Set a card attribute.
**Server:**
- 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
---
### `SET_CARD_COUNTER` (1014)
**Purpose:** Set a card counter.
**Server:**
- Updates the specified card counter
- Emits `Event_SetCardCounter`
**Client:**
- `PlayerEventHandler::eventSetCardCounter`
- Updates the displayed counter value
- Refreshes card actions
- Logs the counter change
---
### `INC_CARD_COUNTER` (1015)
**Purpose:** Increment a card counter.
**Server:**
- Normalizes to a set-counter operation
- Emits `Event_SetCardCounter`
**Client:**
- Processed identically to `SET_CARD_COUNTER`
---
### `READY_START` (1016)
**Purpose:** Mark player as ready.
**Server:**
- Marks the player as ready
- Starts the game once all required players are ready
**Client:**
- Reflected through updated game state events
---
### `CONCEDE` (1017)
**Purpose:** Concede the game.
**Server:**
- Marks the player as conceded
- Returns borrowed cards where appropriate
- Ends the game if only one player remains
**Client:**
- Reflected through updated game state events
---
### `INC_COUNTER` (1018)
**Purpose:** Increment a player counter.
**Server:**
- `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 player counter.
**Server:**
- `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 player counter value.
**Server:**
- `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 player counter.
**Server:**
- `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
---
### `NEXT_TURN` (1022)
**Purpose:** Advance to the next turn.
**Server:**
- `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 through subsequent active-player and active-phase events
---
### `SET_ACTIVE_PHASE` (1023)
**Purpose:** Set active phase.
**Server:**
- `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 through updated phase events
---
### `DUMP_ZONE` (1024)
**Purpose:** Dump zone contents.
**Server:**
- Generates a zone summary
- Emits `Event_DumpZone`
**Client:**
- `PlayerEventHandler::eventDumpZone`
- Logs the zone contents summary
---
### `REVEAL_CARDS` (1026)
**Purpose:** Reveal specific cards.
**Server:**
- 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
---
### `MOVE_CARD` (1027)
**Purpose:** Move a card between zones.
**Server:**
- Moves cards between zones
- Updates ownership and attachments as needed
- Emits `Event_MoveCard`
**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
---
### `SET_SIDEBOARD_PLAN` (1028)
**Purpose:** Set sideboard configuration.
**Server:**
- `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:**
- No game event is generated
---
### `DECK_SELECT` (1029)
**Purpose:** Select a deck.
**Server:**
- `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 through updated player properties and command response
---
### `SET_SIDEBOARD_LOCK` (1030)
**Purpose:** Lock or unlock sideboarding.
**Server:**
- `Server_Player::cmdSetSideboardLock`
- Allowed only before readying
- Toggles sideboard locking
- Clears pending sideboard plans when locking
- Emits `Event_PlayerPropertiesChanged`
- Attaches `Context_SetSideboardLock`
**Client:**
- Reflected through updated player properties
---
### `CHANGE_ZONE_PROPERTIES` (1031)
**Purpose:** Change zone properties.
**Server:**
- `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
---
### `UNCONCEDE` (1032)
**Purpose:** Undo a concede.
**Server:**
- Restores the player's active status
**Client:**
- Reflected through updated game state events
---
### `JUDGE` (1033)
**Purpose:** Execute commands on behalf of another player.
**Server:**
- `Server_AbstractParticipant::cmdJudge`
- Requires judge privileges
- Executes embedded commands as the selected player
- Marks resulting events as judge-forced
**Client:**
- Transparent; resulting events are processed normally
---
### `REVERSE_TURN` (1034)
**Purpose:** Reverse turn order.
**Server:**
- `Server_Player::cmdReverseTurn`
- Conceded non-judge players cannot use the command
- Delegates to `Server_AbstractParticipant::cmdReverseTurn`
- Toggles turn order
- Emits `Event_ReverseTurn`
**Client:**
- Reflected by subsequent active-player progression
---
## Notes
- 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.

View file

@ -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.

View file

@ -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

View file

@ -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;
}
```

View file

@ -0,0 +1,71 @@
@page primer_cards A Primer on Cards
# The Cockatrice Card Library
All non-gui code related to cards used by Cockatrice is contained within libcockatrice_card.
# A Basic Card Object: CardInfo
A CardInfo object is the translational unit used by the CardDatabase to represent an on-disk XML entry as an in-memory
Qt object.
For a complete overview of the fields that define a CardInfo object, see the class documentation and more specifically
@ref PrivateCardProperties.
These fields correspond to either entries or a combination of entries in the XML card entry to which the
CardDatabaseParser applies special logic to map, populate or determine their respective values in the CardInfo object.
Any property to which special parsing is not applied is stored in CardInfo::properties and may be retrieved or set with
CardInfo::getProperty() or CardInfo::setProperty() as well as CardInfo::getProperties() to get a collection of the
property keys a CardInfo contains.
For more information on how special fields contained within @ref PrivateCardProperties are parsed, see
CockatriceXml4Parser::loadCardsFromXml().
Apart from access to the basic and extended properties of a card, CardInfo also provides two important mechanisms to us.
The first is the CardInfo::pixmapUpdated() signal, which allows the CardPictureLoader to signal when it is done
manipulating (in most cases: loading) the QPixmap of an ExactCard::getPixmapCacheKey(), where the ExactCard::card
corresponds to this CardInfo object.
Put simply: Whenever the CardPictureLoader loads a new PrintingInfo which is not already in the cache, it emits this
signal so that any widget using a CardInfo object can update the display of it to possibly use this new QPixmap.
\attention It should be noted that it is not possible to use CardPictureLoader::getPixmap() with anything but an
ExactCard, which is a CardInfo object associated with a PrintingInfo, i.e. a definitive printing of a specific card
The other signal, CardInfo::cardInfoChanged() conceptually works much the same way, except it is concerned with the
properties of the CardInfo object.
# Getting specific: PrintingInfo and ExactCard
## Printing Info
A CardInfo object describes the basic properties of a card in much the same way that we say "Two cards with the same
name are and should be equal if their properties are equal". However, there are certain (mostly visual) properties which
might differ between printings of a card but still conceptually classify two instances of a card as "the same card".
The most obvious example to this are cards which fundamentally share all properties except the artwork depicted on the
card.
We refer to such differences as Printings and track them in the PrintingInfo class. A PrintingInfo is always related to
the CardSet that introduced the variation. Multiple variations can exist in the same CardSet and the respective
PrintingInfo::getProperty() can be used to determine the differences, such as the collector numbers on the printings, if
there are any.
## Exact Card
A CardInfo object is used to hold the functional properties of a card and a PrintingInfo object can be used to hold the
visual properties of a card. To represent a 'physical' card, as in, a concrete immutable instance of a card, we can use
ExactCard, which combines a CardInfo and a PrintingInfo object into one class. The class is used as a container around
CardInfo objects whenever the user (and thus the program) expects to be presented with a defined card printing.
# Using Cards
For more information on the XML database schema which the CardDatabaseParser parses to generate CardInfo objects, see
@ref card_database_schema_and_parsing.
For more information on querying the CardDatabase for CardInfo objects, see @ref querying_the_card_database.
For more information on displaying CardInfo and ExactCard objects using Qt Widgets, see @ref displaying_cards.
For more information on how card pictures are loaded from disk or the network, see @ref loading_card_pictures as well as
the CardPictureLoader documentation.

View file

@ -0,0 +1,39 @@
@page querying_the_card_database Querying the Card Database
# The CardDatabaseQuerier Class
The CardDatabaseQuerier is the only class used for querying the database. The CardDatabase is an in-memory map and thus
provides no structured query language. CardDatabaseQuerier offers methods to retrieve cards by name, by providerID or in
bulk, as CardSet%s.
## Obtaining a handle to the CardDatabaseQuerier for usage
To obtain the CardDatabaseQuerier related to the global CardDatabase singleton, use CardDatabaseManager::query().
## Querying for known cards
There are, essentially, two ways to ensure card equality, with the second being optional but necessitating the first.
These two ways are CardInfo name equality and PrintingInfo provider ID equality.
Because of this, most queries require, at the very least, a card name to match against and optionally a providerID to
narrow the results.
### Generic Card Infos
To check if a card with the exact provided name exists as a CardInfo in the CardDatabase use,
CardDatabaseQuerier::getCardInfo() or CardDatabaseQuerier::getCardInfos() for multiple cards.
### Guessing Cards
If the exact name might not be present in the CardDatabase, you can use CardDatabaseQuerier::getCardBySimpleName(),
which automatically simplifies the card name and matches it against simplified card names in the CardDatabase.
Alternatively, you can use CardDatabaseQuerier::lookupCardByName(), which first attempts an exact match search and then
uses CardDatabaseQuerier::getCardBySimpleName() as a fallback.
### ExactCard%s
To obtain an ExactCard from the CardDatabaseQuerier, you must use a CardRef as a parameter to
CardDatabaseQuerier::getCard(), CardDatabaseQuerier::getCards(), or CardDatabaseQuerier::guessCard().
CardRef is a simple struct consisting of a card name and a card provider ID as QString%s.