Merge remote-tracking branch 'upstream/master' into first-run-tutorial
|
|
@ -29,12 +29,17 @@
|
|||
<xs:attribute type="xs:anyURI" name="picURL" use="optional" />
|
||||
<xs:attribute type="xs:string" name="num" use="optional" />
|
||||
<xs:attribute type="xs:string" name="rarity" use="optional" />
|
||||
<xs:attribute type="xs:boolean" name="isOnlineOnly" use="optional" />
|
||||
<xs:attribute type="xs:boolean" name="isRebalanced" use="optional" />
|
||||
<xs:attribute type="xs:string" name="flavorName" use="optional"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="localizationType">
|
||||
<xs:sequence>
|
||||
<xs:element type="xs:string" name="name" minOccurs="0" maxOccurs="1" />
|
||||
<xs:element type="xs:string" name="text" minOccurs="0" maxOccurs="1" />
|
||||
</xs:sequence>
|
||||
<xs:attribute type="xs:string" name="lang" use="required" />
|
||||
</xs:complexType>
|
||||
<xs:group name="cardPropertyGroup">
|
||||
<xs:sequence>
|
||||
<xs:any processContents="skip" minOccurs="0" maxOccurs="unbounded" />
|
||||
|
|
@ -49,6 +54,13 @@
|
|||
<xs:group ref="cardPropertyGroup"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="localizations" minOccurs="0" maxOccurs="1">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element type="localizationType" name="localization" minOccurs="0" maxOccurs="unbounded" />
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element type="cardInSetType" name="set" minOccurs="1" maxOccurs="unbounded" />
|
||||
<xs:choice minOccurs="0" maxOccurs="unbounded">
|
||||
<xs:element type="relatedType" name="related" minOccurs="0" maxOccurs="unbounded" />
|
||||
|
|
|
|||
|
|
@ -1,3 +1,17 @@
|
|||
## Building Doxygen Documentation
|
||||
|
||||
We use [Doxygen](https://www.doxygen.nl) for our code documentation.
|
||||
After installing the tool, you can build a HTML webpage into the `/docs` folder according to our [configuration](https://github.com/Cockatrice/Cockatrice/blob/master/Doxyfile):
|
||||
```bash
|
||||
doxygen Doxyfile
|
||||
```
|
||||
Open the `index.html` file in the `/docs/html/` folder to browse the result locally.
|
||||
There is also an online version we host at the Cockatrice webpage: [cockatrice.github.io/docs](https://cockatrice.github.io/docs)
|
||||
|
||||
Doxygen's own documenation can be found at [www.doxygen.nl/manual](https://www.doxygen.nl/manual).
|
||||
|
||||
<br>
|
||||
|
||||
## Doxygen Documentation Theme
|
||||
|
||||
Required changes to the `Doxyfile` config from the [theme docs](https://jothepro.github.io/doxygen-awesome-css/index.html#autotoc_md16) to make [doxygen-awesome-css](https://github.com/jothepro/doxygen-awesome-css) work:
|
||||
|
|
|
|||
|
|
@ -1,52 +0,0 @@
|
|||
@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 in the "Card Sources" settings page.
|
||||
|
||||
# PixmapCacheKeys and ProviderIDs
|
||||
|
||||
TODO
|
||||
|
||||
# The Redirect Cache
|
||||
|
||||
TODO
|
||||
|
||||
# Local Image Loading
|
||||
|
||||
TODO
|
||||
|
||||
# URL Generation and Resolution
|
||||
|
||||
TODO
|
||||
|
|
@ -1,170 +0,0 @@
|
|||
@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
|
||||
|
|
@ -1,199 +0,0 @@
|
|||
@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
|
||||
232
doc/doxygen/images/cockatrice_logo.svg
Normal file
|
After Width: | Height: | Size: 51 KiB |
|
Before Width: | Height: | Size: 98 KiB After Width: | Height: | Size: 98 KiB |
|
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 5.3 KiB |
|
Before Width: | Height: | Size: 398 KiB After Width: | Height: | Size: 398 KiB |
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 885 B After Width: | Height: | Size: 885 B |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 94 KiB After Width: | Height: | Size: 94 KiB |
|
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 92 KiB |
|
Before Width: | Height: | Size: 194 KiB After Width: | Height: | Size: 194 KiB |
|
Before Width: | Height: | Size: 245 KiB After Width: | Height: | Size: 245 KiB |
|
Before Width: | Height: | Size: 303 KiB After Width: | Height: | Size: 303 KiB |
|
Before Width: | Height: | Size: 278 KiB After Width: | Height: | Size: 278 KiB |
|
Before Width: | Height: | Size: 227 KiB After Width: | Height: | Size: 227 KiB |
|
|
@ -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.
|
||||
|
|
@ -90,13 +90,13 @@ This keeps the handler independent of concrete UI implementations.
|
|||
|
||||
`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 |
|
||||
| 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
|
||||
|
|
@ -1,5 +1,9 @@
|
|||
@mainpage Documentation
|
||||
|
||||
<div align="center">
|
||||
<img src="cockatrice_logo.svg" style="width: 300px; max-width: 100%; height: auto;">
|
||||
</div>
|
||||
|
||||
Welcome to the Cockatrice code documentation.
|
||||
|
||||
- @subpage user_reference
|
||||
|
|
@ -0,0 +1,135 @@
|
|||
@page custom_card_pictures Custom Card Pictures
|
||||
|
||||
There are four ways to make Cockatrice use custom artwork for your cards:
|
||||
|
||||
- Placing image files in the **CUSTOM pictures folder**.
|
||||
- Providing a **custom card database** that points each printing at a picture URL via the `picurl` property.
|
||||
- Writing your **own download URL templates**.
|
||||
- Setting an **image override** for a single card from inside the deck editor.
|
||||
|
||||
Each of these is described below. If pictures are missing or wrong, see @ref fixing_card_pictures instead.
|
||||
|
||||
# Custom Pictures Folder (CUSTOM)
|
||||
|
||||
Any image file placed in the CUSTOM folder is used as the card picture, and no download is attempted for cards that
|
||||
match a file there.
|
||||
|
||||
- The folder is `<pictures directory>/CUSTOM/`. The pictures directory is configured on the 'General' settings tab,
|
||||
under 'Directories' → 'Pictures directory'.
|
||||
- Any image format Qt can decode is accepted (PNG, JPG/JPEG, WebP, GIF, BMP, ...); the file extension is not filtered,
|
||||
so even an extension-less file is picked up if the decoder recognizes its content.
|
||||
- Files are indexed by their name, so you can organize them into subfolders freely.
|
||||
- New or changed files are picked up automatically within a few seconds — no client restart is required.
|
||||
|
||||
The file name must match the card using one of the naming schemes below. Both `_` and `-` are accepted as separators,
|
||||
and the file extension is ignored when matching:
|
||||
|
||||
| Scheme | Example file name |
|
||||
| --------------------------- | ------------------------------------------------------- |
|
||||
| Card Name | `Example Card.png` |
|
||||
| Card Name + Set | `Example Card_DDL.png` |
|
||||
| Card Name + Set + Collector | `Example Card_DDL_43.png` |
|
||||
| Set + Collector + Card Name | `DDL_43_Example Card.png` |
|
||||
| Card Name + Provider ID | `Example Card_0b23cdc8-d413-4fb1-8470-474221b10fe2.png` |
|
||||
|
||||
The name used for matching is the *corrected* card name. Correction removes the split-card separator ` // ` and the
|
||||
characters reserved in Windows file names (`* < > : " \ ?` and control characters), and turns `/` into a space, so the
|
||||
"Example // Card" card is matched by a file named `ExampleCard.png`, not `Example // Card.png`. Most other punctuation
|
||||
(commas, apostrophes, `!`, ...) is left untouched.
|
||||
|
||||
\attention A file in the CUSTOM folder always wins over downloaded pictures, even if it is the wrong image. Delete the
|
||||
file if you want to see the downloaded artwork again.
|
||||
|
||||
The naming conventions are the same as those recognized in the set-named subfolders and in `downloadedPics`, and are
|
||||
documented for developers in @ref loading_card_pictures.
|
||||
|
||||
# Custom Card Database (picurl)
|
||||
|
||||
If you maintain your own card database (see the
|
||||
[Custom Cards & Sets](https://github.com/Cockatrice/Cockatrice/wiki/Custom-Cards-&-Sets) wiki), each printing's `<set>`
|
||||
tag can carry a `picurl` attribute containing a full URL for that printing's picture:
|
||||
|
||||
```xml
|
||||
<set picurl="https://example.com/cards/example-card.jpg" ...>
|
||||
```
|
||||
|
||||
Cockatrice tries this URL **before** the configured download URL templates, so it is the most direct way to provide
|
||||
custom artwork for a specific printing.
|
||||
|
||||
- The URL should start with `http://` or `https://`; the scheme is not validated, so make sure it is absolute or the
|
||||
download may silently fail.
|
||||
- When you change a `picurl` for a card whose picture was already downloaded and cached, delete the stored images
|
||||
(Storage tab → 'Delete Saved Images' / 'Delete Cached Images') so Cockatrice fetches the new URL.
|
||||
|
||||
# Custom Download URL Templates
|
||||
|
||||
The built-in download URLs are templates: Cockatrice replaces reference points in the URL with information about the
|
||||
card and its printing. You can write your own templates in 'Cockatrice → Settings' (Ctrl + Shift + P by default), on
|
||||
the 'Deck Editor' tab, in the 'URL Download Priority' section.
|
||||
|
||||
The following reference points are available:
|
||||
|
||||
| Reference point | Description | Example |
|
||||
| ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- |
|
||||
| `!name!` | Card name | `Example Card` |
|
||||
| `!name_lower!` | Card name, lower case | `example card` |
|
||||
| `!corrected_name!` | Corrected card name | `ExampleCard` (instead of "Example // Card") |
|
||||
| `!corrected_name_lower!` | Corrected card name, lower case | `examplecard` |
|
||||
| `!sflang!` | Scryfall language code for the current client language; defaults to English when the language has no localized images | `en`, `zhs` |
|
||||
| `!setcode!` / `!setcode_lower!` | Set code | `EXM` / `exm` |
|
||||
| `!setname!` / `!setname_lower!` | Full set name | `Exemplary Set` / `exemplary set` |
|
||||
| `!set:<property>!` | A property of this printing, e.g. `muid` (Gatherer multiverse ID), `uuid` (Scryfall UUID), `num` (collector number), `rarity` | `373549` |
|
||||
| `!prop:<property>!` | A property of the card, e.g. `side` (front/back), `colors`, `cmc`, `coloridentity`, `type`, `pt`, and the format legality statuses | `front` |
|
||||
|
||||
The `!set:...!` and `!prop:...!` reference points 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 skipped.
|
||||
- `_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 skipped.
|
||||
|
||||
Substituted values are URL-encoded. A template that asks for a property the card or printing does not have is skipped,
|
||||
and the next template in the list is tried instead.
|
||||
|
||||
\attention Custom URLs should start with `http://` or `https://`. As with `picurl`, the scheme is not validated before
|
||||
the URL is handed to QNetworkAccessManager, so use an absolute URL or the download may silently fail.
|
||||
|
||||
Some working examples:
|
||||
|
||||
```text
|
||||
https://cards.scryfall.io/large/!prop:side!/!set:uuid_substr_0_1!/!set:uuid_substr_1_1!/!set:uuid!.jpg
|
||||
https://api.scryfall.com/cards/!set:uuid!?format=image&face=!prop:side!
|
||||
https://api.scryfall.com/cards/multiverse/!set:muid!?format=image
|
||||
https://gatherer.wizards.com/Handlers/Image.ashx?multiverseid=!set:muid!&type=card
|
||||
https://gatherer.wizards.com/Handlers/Image.ashx?name=!name!&type=card
|
||||
```
|
||||
|
||||
See the [Custom Picture Download URLs](https://github.com/Cockatrice/Cockatrice/wiki/Custom-Picture-Download-URLs)
|
||||
wiki for more examples and ideas.
|
||||
|
||||
\attention Keep in mind that templates using `!name!` or `!set:muid!` resolve by name or multiverse ID, not by the
|
||||
exact printing. Only the Scryfall `!set:uuid!` templates always return the exact printing requested. See
|
||||
@ref fixing_card_pictures for more on this.
|
||||
|
||||
# Image Overrides
|
||||
|
||||
The quickest way to give one card custom art is an image override: right-click the card in the deck editor's printing
|
||||
selector and open the **Image Overrides** submenu of the context menu.
|
||||
|
||||
- **Load Custom Image...** — choose a picture file (the dialog suggests PNG, JPG/JPEG and WebP); it becomes that card's
|
||||
artwork immediately.
|
||||
- **One entry per alternate printing** of the card, labeled `<set> <collector number>` (hovering an entry previews that
|
||||
printing's artwork). Selecting one makes the card use that exact printing's picture, so e.g. a basic land can be shown
|
||||
with any of its artworks.
|
||||
- **Clear Custom Image** — removes the stored override and returns the card to normal resolution. It is only available
|
||||
while the card has a stored override.
|
||||
|
||||
Overrides are stored as `.png` files in `<pictures directory>/downloadedPics/`, under the "Naming scheme" configured on
|
||||
the Storage settings page, and are matched the same way as downloaded images. Because local files are checked before any
|
||||
URL is requested, an override always wins over downloaded artwork and `picurl` for that card. The override exists only on
|
||||
the machine it was created on - it is not part of the deck file - so a card with a stored override shows normally on
|
||||
another computer.
|
||||
|
||||
\attention If you also keep a matching file in the CUSTOM folder, that file is matched before the override. When you
|
||||
change an override, use **Clear Custom Image** so the stored `.png` is replaced; manually deleting the file in
|
||||
`downloadedPics/` has the same effect.
|
||||
|
|
@ -11,6 +11,19 @@
|
|||
|
||||
- @subpage beta_release
|
||||
|
||||
## Settings
|
||||
|
||||
- @subpage settings_folder
|
||||
|
||||
## Card Pictures
|
||||
|
||||
- @subpage custom_card_pictures
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- @subpage fixing_card_pictures
|
||||
- @subpage enabling_debug_logs
|
||||
|
||||
## Syntax Help
|
||||
|
||||
- @subpage search_syntax_help
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
@page settings_folder Settings Folder and Backups
|
||||
|
||||
Cockatrice keeps all of its preferences in a settings folder on your computer, one `.ini` file per
|
||||
group of settings (appearance, sound, decks, and so on). Open it from the main window via
|
||||
Help → Open Settings Folder.
|
||||
|
||||
# The Per-File Migration
|
||||
|
||||
Older versions of Cockatrice stored every setting in a single `global.ini` file. The settings were
|
||||
split into per-group `.ini` files so that unrelated groups no longer share one file. The first time
|
||||
you start a version that uses the new layout, your settings are moved into the new files
|
||||
automatically and nothing is lost. The old `global.ini` is kept next to the new files as
|
||||
`global.ini.old`.
|
||||
|
||||
# Automatic Backups
|
||||
|
||||
Both migrations overwrite files in the settings folder in place, so before either one runs,
|
||||
Cockatrice copies the whole folder to a timestamped backup folder:
|
||||
|
||||
```
|
||||
<data folder>/settings-backup-20261002-193000/
|
||||
```
|
||||
|
||||
The backup folder sits next to the settings folder, not inside it. Every backup is kept, so if
|
||||
settings ever end up wrong you can compare against, or copy files back from, the earlier snapshot.
|
||||
Backups are only created when a migration actually has work to do, so a normal start creates
|
||||
nothing.
|
||||
|
||||
# Downgrading
|
||||
|
||||
Settings migrations only run forwards. If you ever go back to an older version of Cockatrice, that
|
||||
version does not read the new per-group files, so it will start with default settings.
|
||||
|
||||
Before downgrading, copy your settings folder somewhere safe, for example:
|
||||
|
||||
- Linux: `~/.local/share/Cockatrice/settings/`
|
||||
- macOS: `~/Library/Application Support/Cockatrice/settings/`
|
||||
- Windows: `%LOCALAPPDATA%\Cockatrice\settings\`
|
||||
|
||||
Portable installs keep the settings folder in the `data` folder next to the executable.
|
||||
|
||||
Copying that folder back after downgrading restores your settings. The automatic backup folder from
|
||||
the migration is there as a second copy of the same data if you need it.
|
||||
|
|
@ -0,0 +1,142 @@
|
|||
@page enabling_debug_logs Enabling Debug Logs
|
||||
|
||||
Cockatrice ships with a "diagnostics mode" that prints detailed debug messages about what the client is doing. This is
|
||||
extremely useful when asking for help, because it shows exactly what happened: which URL the card picture loader tried,
|
||||
whether it found or missed a file on disk, whether a download succeeded or failed, which redirects were followed, and
|
||||
much more. Each of these messages belongs to a category, and you can enable or disable categories individually.
|
||||
|
||||
Don't worry, this sounds more technical than it is. You only need to do two things: create one small text file, and tell
|
||||
Cockatrice where it is. There are no installation steps and you can undo everything later (see [When you are
|
||||
done](#when-you-are-done)).
|
||||
|
||||
# Step 1: Create the file
|
||||
|
||||
Open a plain text editor (Notepad on Windows, TextEdit on macOS, or any text editor on Linux) and paste the following
|
||||
content:
|
||||
|
||||
```ini
|
||||
[Rules]
|
||||
# The default log level is info
|
||||
*.debug = false
|
||||
|
||||
# Turn on debug level logs for the card picture loader and all its sub categories
|
||||
card_picture_loader.* = true
|
||||
```
|
||||
|
||||
Save the file with the exact name `qtlogging.ini` in a place you can find again, for example your Documents folder.
|
||||
|
||||
\attention The file name matters: it must be `qtlogging.ini`, not `qtlogging.ini.txt`. If your text editor adds a
|
||||
`.txt` extension automatically, you need to stop it from doing so (see below). On macOS, TextEdit must be switched to
|
||||
plain text mode first via 'Format → Make Plain Text'.
|
||||
|
||||
The file contains one rule per line. The `*.debug = false` rule turns off debug messages everywhere by default, and the
|
||||
`card_picture_loader.* = true` line then re-enables them for the card picture loader. The `.*` at the end means "this
|
||||
category and all of its sub categories". To enable a different category instead, just replace that line with the
|
||||
category name of your choice, for example `card_database.loading = true` or `window_main.startup = true`.
|
||||
|
||||
# Step 2: Tell Cockatrice where the file is
|
||||
|
||||
Cockatrice does not know about the file yet. You have to point it there by setting an environment variable called
|
||||
`QT_LOGGING_CONF` to the full location of your file. How to do this depends on your operating system:
|
||||
|
||||
**Windows**
|
||||
|
||||
1. Press the Windows key, type "environment variables", and open "Edit the system environment variables".
|
||||
2. Click "Environment Variables...", then under "User variables" click "New...".
|
||||
3. Set "Variable name" to `QT_LOGGING_CONF` and "Variable value" to the full path of your file, for example
|
||||
`C:\Users\YourName\Documents\qtlogging.ini`.
|
||||
4. Confirm all dialogs, then close and reopen Cockatrice.
|
||||
|
||||
Alternatively, if Cockatrice is installed in a folder you can write to, you can simply place the `qtlogging.ini` file
|
||||
directly next to the Cockatrice executable (in the same folder as `cockatrice.exe`) and skip the environment variable
|
||||
altogether. Note that this copy may be replaced when you update the client.
|
||||
|
||||
**macOS**
|
||||
|
||||
Open the Terminal app (it is in 'Applications → Utilities') and run the following two commands, replacing the path
|
||||
with the full location of your file:
|
||||
|
||||
```shell
|
||||
launchctl setenv QT_LOGGING_CONF /path/to/qtlogging.ini
|
||||
open -a Cockatrice
|
||||
```
|
||||
|
||||
You can also use `export QT_LOGGING_CONF=/path/to/qtlogging.ini` to set the variable for the current terminal session.
|
||||
|
||||
The setting stays active until you log out or restart your Mac. If you have multiple users on the same Mac, be aware
|
||||
that this setting only applies to your user account.
|
||||
|
||||
**Linux**
|
||||
|
||||
For a quick test, open a terminal and start Cockatrice with the file on the command line, replacing the path with the
|
||||
full location of your file:
|
||||
|
||||
```shell
|
||||
QT_LOGGING_CONF=/path/to/qtlogging.ini cockatrice
|
||||
```
|
||||
|
||||
If this works and you want it to apply every time you start Cockatrice, add the following line to your `~/.profile`
|
||||
file and log in again:
|
||||
|
||||
```shell
|
||||
export QT_LOGGING_CONF="/path/to/qtlogging.ini"
|
||||
```
|
||||
|
||||
# Step 3: See the logs
|
||||
|
||||
Now that debug logging is enabled, open Cockatrice and trigger the behavior you are investigating, for example by
|
||||
opening a deck, reloading the card database, or starting a game.
|
||||
|
||||
The easiest way to see the logs is to use the built-in log viewer inside Cockatrice itself: open 'Help → View Debug
|
||||
Log'. A window appears that shows the log messages live and keeps the most recent entries. It even has a 'Copy to
|
||||
clipboard' button so you can paste the output into a bug report or a Discord message. This works the same on every
|
||||
operating system.
|
||||
|
||||
If you prefer to capture everything to a file instead, start Cockatrice with the `--debug-output` option:
|
||||
|
||||
```shell
|
||||
cockatrice --debug-output
|
||||
```
|
||||
|
||||
Cockatrice then writes the full log to a file called `qdebug.txt` in the folder it was started from.
|
||||
|
||||
# Which categories are available?
|
||||
|
||||
Every message Cockatrice logs belongs to a category. The following table lists the most useful ones for troubleshooting,
|
||||
grouped by area. Enable a category by adding a line like `category = true` to your `qtlogging.ini` file (or use a `.*`
|
||||
suffix, e.g. `card_picture_loader.*`, to include all sub categories).
|
||||
|
||||
| What you want to see | Categories |
|
||||
| ------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
|
||||
| Card picture loading (URLs, local file hits/misses, downloads, redirects) | `card_picture_loader.*` |
|
||||
| Card database loading and parsing | `card_database`, `card_database.loading`, `card_database.loading.success_or_failure`, `cockatrice_xml.*` |
|
||||
| Card database update (running Oracle, progress, exit status, errors) | `card_database.update` |
|
||||
| Card, set, and deck information | `card_info`, `card_list`, `deck_loader` |
|
||||
| Startup sequence and update checks | `window_main.startup.*`, `release_channel`, `spoiler_background_updater` |
|
||||
| User interface and themes | `theme_manager`, `sound_engine`, `flow_layout`, `flow_widget.*`, `pixel_map_generator`, `card_info_picture_widget` |
|
||||
| Networking and servers | `local_client`, `remote_client`, `tapped_out_interface`, `servers_settings` |
|
||||
| In-game logic | `player`, `game_scene.*`, `card_zone.*`, `view_zone`, `game_event_handler` |
|
||||
| Dialogs and tabs | `dlg_settings`, `dlg_update`, `dlg_tip_of_the_day`, `tab_game`, `tab_message`, `tab_supervisor` |
|
||||
| Settings and shortcuts | `settings_cache`, `shortcuts_settings` |
|
||||
| Deck and card filtering | `filter_string`, `deck_filter_string`, `syntax_help` |
|
||||
|
||||
For example, to investigate why a card database update seems to fail, enable the card database categories:
|
||||
|
||||
```ini
|
||||
[Rules]
|
||||
*.debug = false
|
||||
|
||||
card_database = true
|
||||
card_database.loading = true
|
||||
card_database.loading.success_or_failure = true
|
||||
card_database.update = true
|
||||
cockatrice_xml.* = true
|
||||
```
|
||||
|
||||
# When you are done
|
||||
|
||||
To turn the diagnostics back off, just reverse what you did: remove the `QT_LOGGING_CONF` environment variable (or
|
||||
unset it again via `launchctl unsetenv QT_LOGGING_CONF` on macOS) and/or delete the `qtlogging.ini` file, then restart
|
||||
Cockatrice. Leaving it on is harmless, but the extra logging can make the client slightly slower.
|
||||
|
||||
For the full details on how Cockatrice logging works, including the complete list of categories, see @ref logging.
|
||||
|
|
@ -0,0 +1,118 @@
|
|||
@page fixing_card_pictures Fixing Card Pictures
|
||||
|
||||
This guide collects the common causes for card pictures not showing up, showing the wrong printing, or showing the
|
||||
card back instead of the artwork, and how to fix them.
|
||||
|
||||
Work through the steps in order. In most cases the problem is caused by an outdated client, misconfigured download
|
||||
URLs, stale local images, or a corrupted cache.
|
||||
|
||||
# Update Your Client
|
||||
|
||||
Picture handling bugs are fixed on a regular basis, so the first thing to try is updating your client.
|
||||
|
||||
Use the update check in 'Help → Check for Updates' (or look for the update prompt shown on startup).
|
||||
|
||||
If a fix has not yet made it into the latest stable release, it may already be available in the beta release. The beta
|
||||
ships very frequently and usually receives a follow-up fix within a day or two if something breaks.
|
||||
|
||||
See @subpage beta_release for instructions on how to switch to the beta channel.
|
||||
|
||||
# Check Your Download URLs
|
||||
|
||||
Card pictures are downloaded from a list of URL templates. Each template is tried in order until one produces a valid
|
||||
image, so the order matters: URLs at the top of the list are tried first.
|
||||
|
||||
The list can be found in 'Cockatrice → Settings' (or Ctrl + Shift + P by default), on the 'Deck Editor' tab, in the
|
||||
'URL Download Priority' section. Make sure 'Download card pictures on the fly' is enabled and that the list contains
|
||||
valid URLs. If you suspect the list has been modified or corrupted, press 'Reset Download URLs' to restore the
|
||||
defaults.
|
||||
|
||||
For information on how to add your own custom URL templates, see the 'How to add a custom URL' link in the same
|
||||
settings section, or @ref custom_card_pictures for a full reference of the URL reference points, the CUSTOM
|
||||
pictures folder, and custom card databases.
|
||||
|
||||
# Check Your Local Picture Folder
|
||||
|
||||
Before any network request is made, Cockatrice looks for local image files. If a matching file is found on disk it is
|
||||
shown instead of anything downloaded, even if it is the wrong image.
|
||||
|
||||
The pictures directory is configured on the 'General' settings tab, under 'Directories' → 'Pictures directory'.
|
||||
Cockatrice checks the following locations, in order:
|
||||
|
||||
- The custom pictures folder (recursively indexed by file name).
|
||||
- `<pictures directory>/<set code>/<card file name>`
|
||||
- `<pictures directory>/downloadedPics/<set code>/<card file name>`
|
||||
- `<pictures directory>/downloadedPics/<card file name>` (for export naming schemes without a set folder)
|
||||
|
||||
A file only matches when its name without the extension equals one of the recognized scheme patterns exactly.
|
||||
|
||||
The following import naming schemes are recognized (using both `_` and `-` as separators). The canonical table with
|
||||
concrete example file names is on @ref custom_card_pictures:
|
||||
|
||||
| Scheme | Pattern |
|
||||
| --------------------------- | -------------------------- |
|
||||
| Card Name + Provider ID | `{name}_{providerId}` |
|
||||
| Card Name + Set + Collector | `{name}_{set}_{collector}` |
|
||||
| Set + Collector + Card Name | `{set}_{collector}_{name}` |
|
||||
| Card Name + Set | `{name}_{set}` |
|
||||
| Card Name | `{name}` |
|
||||
|
||||
If a picture you downloaded or placed manually is wrong, stale, or corrupted, delete the offending file. Pay special
|
||||
attention to the `downloadedPics` subfolder: this is where the filesystem caching method writes downloaded images, and
|
||||
after a provider outage it can permanently contain the wrong printing until you delete it manually.
|
||||
|
||||
If a card persistently shows artwork you assigned yourself, you may have an **image override** set for it. Right-click
|
||||
the card in the deck editor's printing selector and use 'Image Overrides' → 'Clear Custom Image' to remove it (or
|
||||
delete the stored `.png` in `downloadedPics/`). See @ref custom_card_pictures for details.
|
||||
|
||||
See @ref loading_card_pictures for details on how local images are loaded.
|
||||
|
||||
# Clear Caches
|
||||
|
||||
Cockatrice caches card pictures in three places. All of them can be managed on the 'Storage' settings tab:
|
||||
|
||||
- **Network cache** — downloaded images stored on disk. Press 'Delete Cached Images' to clear it.
|
||||
- **Filesystem / image backup** — downloaded images written directly to `downloadedPics`. Press 'Delete Saved Images'
|
||||
to clear it.
|
||||
- **In-memory (pixmap) cache** — images currently held in RAM. Press 'Clear In-Memory Images' to clear it.
|
||||
|
||||
If a provider outage caused the wrong pictures to be downloaded and cached, clearing the network cache (and the
|
||||
'Delete Saved Images' button if you use the filesystem caching method) will force Cockatrice to download the correct
|
||||
images again. The redirect cache TTL (also on the Storage tab) controls how long previously seen redirects for
|
||||
download URLs are remembered; lowering it can help if a URL used to redirect somewhere else.
|
||||
|
||||
# Restart the Client
|
||||
|
||||
After updating the client, changing the download URLs, moving or deleting local image files, or clearing caches, it is
|
||||
recommended to restart Cockatrice so that all changes are fully picked up.
|
||||
|
||||
# Check Logs
|
||||
|
||||
Before changing any settings, check the existing logs first. Rate limit errors and most download errors are already
|
||||
logged at warn level, so you may find the cause without enabling debug mode.
|
||||
|
||||
Open 'Help → View Debug Log' and look for error or warning messages related to card picture loading. If you need more
|
||||
detail than the default log level provides, see below.
|
||||
|
||||
# Enable Picture Loader Debug Logs
|
||||
|
||||
If the steps above did not solve the problem, you can turn on a "diagnostics mode" that prints what the picture loader
|
||||
is actually doing: which URL it is trying, whether it found or missed a file on disk, whether the download succeeded or
|
||||
failed, and which redirects it followed. This information is extremely useful when asking for help.
|
||||
|
||||
See @subpage enabling_debug_logs for a step-by-step guide on how to enable the logs, including instructions
|
||||
for Windows, macOS, and Linux.
|
||||
|
||||
# Provider Accuracy
|
||||
|
||||
Cards in Cockatrice are identified by a provider ID, which is the Scryfall UUID of a specific printing. Decks store
|
||||
this ID, which is why the exact printing a card was added as can be looked up again.
|
||||
|
||||
The Scryfall URL templates built into Cockatrice use this provider ID directly (`!set:uuid!`), so they always download
|
||||
the exact printing that was requested.
|
||||
|
||||
The Gatherer URL templates, on the other hand, do **not** use the provider ID. They resolve pictures by multiverse ID
|
||||
(`!set:muid!`) or by card name (`!name!`) only. As a result they may return a different printing than the one the
|
||||
provider ID refers to, or no picture at all for cards Gatherer does not know. If you need pictures to match the exact
|
||||
printing of a card, make sure the Scryfall URLs are at the top of your download URL priority list and consider removing
|
||||
or demoting the Gatherer URLs.
|
||||