Compare commits

...

722 commits

Author SHA1 Message Date
Lukas Brübach
88ee27b7f0 [Oracle] Skip low-memory prompt in background mode, guard with HAS_LZMA 2026-10-13 07:19:45 +02:00
Lukas Brübach
d5094a8d00 [Oracle] Fix Qt6 build and treat unknown memory as sufficient
Use QRegularExpression behind the same Qt version guard as
totalMemoryBytes() in availableMemoryBytes(), where a bare QRegExp
broke every Qt6 Linux build. hasAtLeastGiB() now treats a 0 reading
(unsupported platform or failed query) as unknown and skips the
low-memory prompt instead of misreading it as low memory.
2026-10-13 07:19:45 +02:00
Brübach, Lukas
d36b366f1a [Oracle] Qt version guard for regexp 2026-10-13 07:19:45 +02:00
Brübach, Lukas
ed32c4ce6b [Oracle] Include QRegularExpression. 2026-10-13 07:19:35 +02:00
Brübach, Lukas
8243143e4e [Oracle] Correct size and only do this if the user has not set a non-default url. 2026-10-13 07:19:35 +02:00
Brübach, Lukas
e46ac131a3 [Oracle] Add low-memory check (<=4GiB) and use mtgxml url instead. 2026-10-13 07:19:35 +02:00
BruebachL
a712553c81
[CardDB] Reload after the first run enables all sets without freezing startup (#7470)
* [CardDB] Reload when the first run enables all sets

checkUnknownSets() enabled every set when none was enabled, but the
database had already been parsed under that empty enablement, so the
parsers had stripped every printing. It only notified the models and never
reloaded, leaving the deck editor's card database display empty until the
next launch.

Route the all-sets-enabled case through the same reload the new-sets path
uses, and skip caching a snapshot whose sets are all disabled.

* [CardDB] Own checkUnknownSets after startup and fix the cache skip

The loader's initialLoadComplete guard skipped checkUnknownSets() on the
first successful load of the session, so a download that landed after
startupConfigCheck() and the onboarding wizard had already run (e.g.
skipping the wizard download and fetching later from the menu) left every
set disabled and the deck editor blank until the next launch. The guard
also let the check run on the worker before the queued snapshot swap, and
alongside reloadCardDatabasesAndNotify()'s own check, double-emitting
cardDatabaseNewSetsFound on manual reloads.

Move the check to the callers that load after startup: the post-update,
custom-import, and spoiler paths run it via a QFutureWatcher once the
finished snapshot has been swapped in, and the loader's synchronous
path-change reload runs it directly.

Judge the cache skip by the parsed sets' own enablement, what the parsers
actually saw, instead of getEnabledSetNames(), which reports empty for the
noop controller even though it enables every set. Wait for the first-run
reparse before opening Manage Sets so the dialog cannot bind to the
pre-reload set instances.

* [CardDB] Queue post-reload check and unblock first-run Manage Sets

reloadCardDatabasesAndNotify() no longer emits cardDatabaseEnabledSetsChanged after the swap's Reset already rebuilt the models (second 36k rebuild froze startup), and its follow-up checkUnknownSets() is queued to the GUI thread so it sees swapped-in data. cardDatabaseAllNewSetsEnabled() chains Welcome/Manage Sets off a QFutureWatcher instead of waitForFinished() blocking the event loop during the reparse.

* [CardDB] Batch the first-run enable-all into a single settings flush

checkUnknownSets() enabled every set with ~3 QSettings rewrite cycles per set (sort key, known, enabled), and each setValue re-opens and re-syncs the whole cardDatabase.ini. With a full database (874 sets) that froze the GUI thread for ~9s after the database download (LagMonitor: event loop stalled for 9129 ms, measured offscreen with a real 59MB cards.xml), reading as a total application hang that recovers on its own.

Persist the enable-all through the controller's batched saveSets() in a single flush instead, mirroring what SetsModel::save() already does: CardSetList::enableAllBatched() assigns fresh sort keys and marks every set known+enabled in memory, then writes all records at once. SetSaveData gains the isKnown flag so the batch covers all three options, and SetsModel::save() passes the sets' known state through (unchanged behavior there).

Also drop the cardDatabaseEnabledSetsChanged emission from the all-sets-enabled branch: the reload chained off cardDatabaseAllNewSetsEnabled rebuilds the models from the re-parsed snapshot via cardDatabaseReset, so emitting both rebuilt the 36k-card models twice on the GUI thread.

Adds a cache-invalidation test driving the real checkUnknownSets() first-run path.

* [CardDB] Batch the new-sets-found enable-all the same way

The first-run batching fixed the 874-set case, but the new-sets-found dialog path (enableAllUnknownSets/markAllSetsAsKnown) still wrote per set and per option, each setValue re-opening and re-syncing the whole cardDatabase.ini. Users with hundreds of custom sets hit the same multi-second GUI stall when answering the dialog.

Persist both through the controller's batched saveSets() in a single flush, writing only changed sets (so the common no-op check skips the flush entirely). Removes the now-unused write-through CardSetList methods (enableAll, enableAllUnknown, markAllAsKnown, guessSortKeys) so the batched flush is the only way to persist bulk set changes.

Extends the test controller mock with per-write/batched-flush counters and adds tests asserting both dialog paths persist with exactly one flush and zero individual writes.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-10-13 06:29:16 +02:00
BruebachL
bac2d285e3
[Versioning] Bump version number for 3.1.2 beta release. (#7501)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 cover (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 26 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-10-12 21:14:29 +02:00
BruebachL
96b819d7c8
Support tab-separated deck imports (#4222) (#7267)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 cover (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 26 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
Third-party deck tools (e.g. Delver Lens) export decks as tab-separated rows
such as "2	Card Name	Set Name". Card names never contain tab characters,
so a leading numeric column marks a tabular record: the first two columns are read
as the quantity and card name, and any further columns are discarded as
printer/set metadata.

- deck_list: parse tab-delimited rows before whitespace collapsing
- tests: cover tab-separated mainboard and sideboard imports
- docs: document the accepted plain-text import format (fills the TODO)

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-10-11 21:19:10 +02:00
BruebachL
8968709642
[SMTP] Add reliable email delivery with retry and database-backed queue (#7240)
* [SMTP] Add reliable email delivery with retry and database-backed queue

Rework the SMTP client from fire-and-forget sending into a persistent
queue with exponential backoff and per-message retry budget, backed by
the existing database rows as the durable source of truth.

- Keep pending emails in an in-memory queue with attempts/backoff
  tracking; enqueue without deleting database rows, and only remove the
  row once delivery is confirmed via the mailDelivered signal.
- Add exponential backoff (doubling, capped at 30 min) between delivery
  attempts, bounded by new smtp/maxretries and smtp/retrydelay settings.
- Track connection-layer failures (connect/auth/encrypt) with their own
  backoff; route failures to recordFailed/recordConnectionFailure so
  undeliverable mail eventually drops from the queue instead of
  retrying forever.
- Treat rejected recipients/senders as permanent failures.
- Rebuild the SMTP buffer from the authoritative queue before each send
  via a new QxtSmtp::reset() so reconnects never duplicate deliveries.
- Start the idle timeout after authentication, not on TCP connect, so
  slow SSL/STARTTLS handshakes are not cut short.

* [SMTP] Fix retry accounting, backoff reset, and token dedupe

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-10-11 20:59:32 +02:00
BruebachL
1a4d89ff8f
[Game] Don't alert game tab for spectator activity (#1848) (#7263)
Spectator join/leave events were triggering the game tab's attention
indicator (exclamation mark and taskbar alert) for players who often
leave the tab open while waiting for an opponent. This made spectator
activity a false alarm.

Only emit the user event when an actual player joins or leaves, keeping
spectator joins/leaves quiet.

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-10-11 16:54:03 +02:00
BruebachL
4feb31bd1a
[Game] Show loaded deck messages to players joining a game (#7268)
* Show loaded deck messages to players joining a game (#552)

The "Player X has loaded a deck" message is only broadcast when a deck is
selected, so players and spectators who join a game afterwards never see it.

On game join, replay a deck-select event context privately to the joining
participant for every player that already has a deck loaded, reusing the
existing Event_PlayerPropertiesChanged + Context_DeckSelect mechanism so no
protocol version bump is needed.

- Server_AbstractPlayer: expose the sideboard-locked state
- Server_Game: synthesize deck-select contexts in createGameJoinedEvent

* [Server] Skip deck replay on rejoin and after start

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-10-11 16:53:26 +02:00
BruebachL
4c5a5e3e55
[Game] Invalidate view when hiding replaced card menu (#7453)
* [Game] Invalidate view when hiding replaced card menu

Hide any visible card menu before replacing and invalidate viewport.

* [Game] Fix viewport update compile error in setCardMenu

QWidget::update has no QRectF overload, and sceneRect is in scene
coordinates rather than viewport coordinates anyway, so invalidate the
whole viewport instead.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-10-11 15:36:11 +02:00
BruebachL
5d06bb903d
[Chat] Fix card completion after an @ mention (#7469)
An open [[ card link now takes precedence over a space-preceded @
mention, so typing an @ before a card no longer shadows the card
completer.

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-10-11 15:35:10 +02:00
BruebachL
57a6a0d9cb
[Client] Warn when data paths point inside the program directory (#7473)
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-10-11 15:34:49 +02:00
BruebachL
f83a61979f
[Installer] Uninstaller deletes only the installed payload, never the whole tree (#7472)
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-10-11 15:34:49 +02:00
BruebachL
07eb9f0103
[Report] Prompt for a note on Resolve and drop the redundant option (#7445)
- Remove the third close option (Resolve with note...) so closing a
  report is one of two clear actions
- Make Resolve always prompt for the optional resolution note, matching
  Dismiss, and rename the button to Resolve...
- Halve the resolveReport signature now that every caller prompts

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-10-11 15:33:48 +02:00
BruebachL
38acddeede
[Report] Add context menu to report queue rows (#7444)
* [Report] Add report reopen action for closed reports

- New Command_ReportReopen moderator command (extension 1206) with server
  handler that reopens resolved/dismissed reports, resets resolution state
  and notified, and records the reopening note in the audit log
- Reopen... button in the report queue action bar, gated to resolved and
  dismissed reports, with optional note prompt and tooltip
- Success confirmations for Assign, Resolve/Dismiss and Reopen now survive
  the list refresh via a pending status message instead of being clobbered
- Dispatch/auth coverage in server_moderator_role_test

* [Report] Surface resolution audit info in report details

- Add resolved_by_name to ServerInfo_Report and join resolved_by users in
  the report list, my-list and details queries
- Render resolution note and resolved-at/by in the report detail pane
  instead of duplicating the description into the chat log context

* [Report] Add context menu to report queue rows

- Open the standard UserContextMenu on right-click/Menu key for the
  reported user, selecting the row first so the details panel matches
  the menu target
- Carry the reported user's level in ServerInfo_Report so the menu
  shows the same actions as the user list, with an IsUser fallback for
  deleted or unregistered users
- Wire the Private chat action through TabReport to TabSupervisor and
  retranslate the menu on live language switches

* [Server] Fix reported_user_level column indices in report queries

The SELECT lists resolved_by_name at index 10, shifting room_id to 11,
replay_id to 12, reported_user_id to 13 and reported_user_admin to 14.
The code read 12/13, so 'user exists' meant 'a replay exists' and the
admin flags came from the user id (e.g. id 5 -> admin+judge; users
without a replay reported as unregistered, hiding registered-only
actions). Fixed in cmdReportList and cmdReportMyList, plus the column
comment.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-10-11 15:33:47 +02:00
BruebachL
74a6acde91
[Report] Surface resolution audit info in report details (#7443)
- Add resolved_by_name to ServerInfo_Report and join resolved_by users in
  the report list, my-list and details queries
- Render resolution note and resolved-at/by in the report detail pane
  instead of duplicating the description into the chat log context

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-10-11 15:29:09 +02:00
BruebachL
4a010bd17f
[Report] Add report reopen action for closed reports (#7442)
- New Command_ReportReopen moderator command (extension 1206) with server
  handler that reopens resolved/dismissed reports, resets resolution state
  and notified, and records the reopening note in the audit log
- Reopen... button in the report queue action bar, gated to resolved and
  dismissed reports, with optional note prompt and tooltip
- Success confirmations for Assign, Resolve/Dismiss and Reopen now survive
  the list refresh via a pending status message instead of being clobbered
- Dispatch/auth coverage in server_moderator_role_test

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-10-11 15:28:45 +02:00
BruebachL
315cbae345
[Build] Extend PCH to libraries and grow IWYU-driven header sets (#7456)
The executables precompile Qt since #7235, but the libcockatrice_*
static libraries still reparse the full Qt Core stack in every TU.
Measured over the tree after the IWYU pass: 380 MB of header parse in
the libraries, 92% of it reachable from qtcore_pch.h.

- Apply qtcore_pch.h to the 14 Qt Core static library targets
  (skips libcockatrice_rng: its SFMT.c is C and cannot use a C++
  precompiled header).
- Grow qtcore_pch.h with 25 QtCore headers that IWYU shows are
  re-parsed across many TUs and were not already reachable from the
  existing list (JSON/QUuid cluster, QSettings, item models,
  threading, XmlStream, QTemporaryDir, ...).
- Grow qtwidgets_pch.h with 30 QtGui/QtWidgets headers (dialog
  cluster, item views, QImageReader, ...).

Only stable Qt headers go into the PCH, so it rarely invalidates.
Test executables stay PCH-less: 1-3 TUs per target does not amortize
the extra precompile.

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-10-11 15:07:30 +02:00
BruebachL
bdda3dedfa
[CI] Publish a gcovr line-coverage report from the test run (#7404)
* [Tests] Register each test case with CTest instead of each test binary

Registering a test cost four edits across a dozen CMakeLists files: add_test,
add_executable, a conditional add_dependencies on gtest, and target_link_libraries.
The conditional branch was the trap -- it is only needed when GTest was fetched and
add_subdirectory'd, and omitting it yields undefined gtest symbols at link time --
so it was easy to leave out and impossible to notice until the link failed.

Fold all four steps into cockatrice_add_test() and migrate every test directory
onto it, with a cockatrice_add_manual_test() wrapper for the three benchmarks that
are built but deliberately kept out of ctest. The calls now read as a declaration
of what a test is rather than a transcript of how to register one.

While rewriting the registrations, replace add_test(NAME x COMMAND x) with
gtest_discover_tests(). Naming the binary as a single CTest entry meant a CI failure
reported only which binary failed, and ctest -R Suite.Case could not select an
individual case. Discovery raises the suite from 35 opaque entries to 469 real ones:
ctest -R ThemeMigrationTest.IsIdempotent now runs exactly that case, and
ctest -L server / -LE bench work off labels applied at registration.

Give every test a 60s TIMEOUT. CTest's implicit default is 1500s, so one hung
socket wait would stall CI for 25 minutes, and single_instance_manager_test already
does real QLocalSocket waits.

Dropping per-case granularity costs wall-clock time, since the suite becomes
process-spawn bound rather than CPU bound: serial ctest went from 3.8s to 30.8s. Run
it with -j in CI to recover it -- 7.6s at -j4, and 3.5s at -j8, both at or below
the old serial time. The binaries are parallel-safe: the only test claiming a fixed
resource scopes its local socket name to its own pid, so concurrent copies cannot
collide.

Also delete dummy_test, which asserted 1 == 1 and only proved the harness was
wired up -- something the other 468 cases already establish.

No test is added, removed, or changed by this commit; the 469 cases are the same
ones the 35 binaries contained.

* [Tests] Make QtTest available to test targets

QtTest was not among the modules test targets could link, so the Qt model and
signal contracts had no way to be verified from a test: QAbstractItemModelTester
and QSignalSpy both live there.

Add Test to _TEST_NEEDED in cmake/FindQtRuntime.cmake. That is the single place the
union of test-only Qt modules is declared, so this is the only file that needs to
change to make the module reachable from every test target.

Nothing uses it yet. This lands the capability ahead of the model-layer tests that
need it, so those can be added as tests alone rather than as test plus build-system
plumbing.

* [Tests] Correct the note on the oracle test compression defines

add_definitions() populates the directory's COMPILE_DEFINITIONS property, which is
consumed at generate time, so it reaches every target in the directory no matter
where it is called relative to add_executable(). The previous note claimed the
flags stayed confined to the benchmark because the find_package block came after
it; oracle_importer_test, created above that block, carries both defines too.

Verified harmless rather than a live bug: HAS_ZLIB and HAS_LZMA gate code only in
oracle/src/pages.cpp and oracle/src/pagetemplates.cpp, and no test target compiles
either. The ordering and the leaked defines are both pre-existing, so this only
corrects the explanation.

* [Tests] Carry one CTest label per test instead of several

gtest_discover_tests() can hand CTest exactly one label. GoogleTestAddTests.cmake
accumulates its generated script with `foreach(arg ${ARGN})`, so a label list reaches
the CTestTestfile as bare `LABELS ci server`, and CTest reads only the first value of a
property and drops the rest without a word.

The `ci` label that is appended to every registered test was therefore absent from every
test that also asked for `server` or `bench`: `ctest -L ci` reported 400 of 469 cases,
and `ctest --print-labels` never listed a second label anywhere in the build.

Reduce each test to exactly one of `ci`, `server`, or `bench`, which is all the
selections need. The three now partition the suite exactly, 400 + 64 + 5 = 469, and
`ctest -LE bench` still means everything but benchmarks. A second label is now a
configure-time FATAL_ERROR rather than a line that quietly means something else.

Anything orthogonal to a test's kind belongs in a helper flag beside the label, so it
composes with the group instead of competing for the single slot.

Also raise DISCOVERY_TIMEOUT off GoogleTestAddTests' 5s default. A plain debug binary
answers --gtest_list_tests in 0.04s, but that budget is thin once several binaries
discover concurrently behind a -j build, and being over it fails at the *link* step with
a bare "Process terminated due to timeout" that points nowhere near the cause.

* [Tests] Link gtest_main on the fetched-gtest path

The downloaded-gtest branch set GTEST_BOTH_LIBRARIES to `gtest` alone, while
FindGTest sets it to both halves. Every test target links that list rather than
picking an entry point, so on this path nothing provides main() and any test
source that does not define its own fails at link with `undefined reference to
main` pointing at crt1.o, naming neither gtest nor the line responsible.

That stayed latent while every test carried its own four-line main(). A test that
omits it now fails, and the error points at the C runtime rather than at gtest,
which is a long way from the cause. Restoring both halves makes the two paths
agree and drops the boilerplate requirement on new test sources.

The 18 sources that still define main() keep linking: gtest_main.cc defines
nothing but main, so the linker leaves its object in the archive unused rather
than reporting a duplicate.

* [Build] Add an ENABLE_SANITIZERS option for ASan and UBSan

Adds -DENABLE_SANITIZERS, off by default, building with AddressSanitizer and
UndefinedBehaviorSanitizer. The bulk of the work is in the three ways the obvious
implementation silently does the wrong thing.

Probe the flags on the link step, not just the compile step. check_cxx_compiler_flag()
only compiles, so every sanitizer flag comes back unsupported, because linking an ASan
object without -fsanitize=address leaves __asan_init undefined. The result is not a
build failure but a build that appears instrumented and is not, which is worse. Use
check_cxx_source_compiles() with CMAKE_REQUIRED_LINK_OPTIONS instead, and treat an
unsupported sanitizer pair as fatal since the cosmetic flags are allowed to degrade.

Opt vendored sources out per source, through a cockatrice_disable_sanitizers_for()
helper rather than a list of set_source_files_properties() calls at the root. The
helper has to exist because CMake resolves a source-file property against the directory
scope its target was declared in: setting the property from the root with absolute paths
is a no-op, silently, which is why the call sites sit next to the add_library and
add_executable that pull the vendored code in. SFMT, minizip, the LZMA SDK and Qxt SMTP
are excluded. oracle/src/qt-json is in the history but not in the tree, removed in
#7214.

Also mark those sources SKIP_PRECOMPILE_HEADERS. The PCH is built with the sanitizer
flags, so it defines __SANITIZE_ADDRESS__; a translation unit compiled with
-fno-sanitize=all does not and GCC then refuses the PCH, which is a fatal
-Werror=invalid-pch in the servatrice and oracle builds.

-fno-sanitize=vptr is not optional. Qt constructs QObject subclasses into raw storage
and writes the vptr afterwards, so the vptr check reports on essentially every QObject
instantiation, and it has no value here because nothing outside moc-generated code
inherits QObject.

Verified on the resulting build: our own translation units carry ASan and UBSan calls,
SFMT.o carries neither, and the full suite of 464 cases passes clean under
ASAN_OPTIONS=detect_leaks=0:abort_on_error=1 with UBSAN_OPTIONS=halt_on_error=0.

The test suite needs two adjustments to make this option usable at all, both of which
land here because they are consequences of COCKATRICE_SANITIZERS existing rather than
independent changes.

DISCOVERY_TIMEOUT rises from 60s to 300s under sanitizers. An instrumented binary takes
2.8s to answer --gtest_list_tests against 0.04s uninstrumented, and discovery runs
several binaries at once behind a -j build.

One target is excluded via a NO_SANITIZE flag, server_developer_role_test. Reaching
google::protobuf::TextFormat::Printer's destructor aborts on an absl assertion inside
system protobuf 33.4 whenever the consumer translation unit is built without NDEBUG,
which a Debug sanitizer build is. This reproduces with no Cockatrice code at all, just
"TextFormat::Printer p; p.PrintToString(msg, &out);" built with -fsanitize=address, so it
is upstream rather than ours. It is a flag and not a fourth label because "skip this in
the sanitizer leg" has to compose with a test's group rather than compete for the single
label slot, and because a flag excludes by not registering the cases at all, which
cannot rot the way a label silently can. The target still builds and still runs on the
legs without sanitizers.

* [CI] Run the suite once under ASan and UBSan

Adds --sanitize to .ci/compile.sh, mirroring --test, and turns it on for one entry of
the existing Linux Docker matrix: Ubuntu 26.04, which already had a test-enabled leg.
That reuses the image, the ccache layer and the existing step shape, so the CI diff is a
matrix key plus the argument plumbing. Debian 13 and Fedora 44 keep covering the
uninstrumented suite, so nothing is lost by instrumenting one leg.

Sanitizers stay off the release and package legs, which build with --release for
packaging: instrumenting those would change the shipped binaries' optimization profile
and build time for no benefit. macOS and Windows stay uninstrumented too, since ASan is
unreliable against the Qt build there and MSVC has no equivalent without /fsanitize=address.

--sanitize does not imply --test, so an instrumented build can be produced locally
without paying for ctest. When both are given, ctest runs with detect_leaks=0 because Qt
and the sanitizer runtimes both leak at exit by design and LeakSanitizer's default buries
real findings under hundreds of process-teardown reports, and with UBSan halt_on_error=0 so
one run collects many findings instead of stopping at the first. ASan keeps
abort_on_error=1, since a bad access leaves the process unusable and continuing only
produces a second, more confusing report from the wreckage.

* [CI] Publish a gcovr line-coverage report from the test run

Sanitizers tell us what a run did wrong; they say nothing about what it never
reached. This adds the other half: an instrumented build whose report says which
of our own lines no test executes.

Add an ENABLE_COVERAGE option that compiles with --coverage -O0 -g, mirroring the
ENABLE_SANITIZERS opt-in including the per-source carve-out that keeps vendored
trees out of the denominator. -O0 is deliberate: at higher optimisation levels GCC
merges and relocates lines, so the report would describe code that no longer exists
in that form. Only GCC is supported, since Clang's intermediate format needs
llvm-cov; asking for coverage under Clang is a hard error rather than a build that
silently produces unreadable .gcno files.

Keep the filters in a committed gcovr.cfg rather than in the invocation. The tests
themselves are excluded because they are compiled with the same instrumentation
and are only ever executed by the run doing the measuring, so leaving them in would
report a healthier project than we have. The process entry points and the client's
window chrome go too: each needs a display, an interactive terminal or a live
database before it does anything a headless test could observe, so they would
otherwise sit at 0% forever and bury real movement.

Give coverage its own matrix leg rather than a second flag on the sanitizer leg.
The two answer different questions, and combining them slows both down without
telling us more. The new variant key gives the leg its own Docker image and ccache
entry so the two legs cannot race on the same tag.

The leg publishes an HTML report with per-file pages, LCOV, Cobertura XML and a
JSON summary. gcovr is a report-time dependency rather than a build dependency, so
a missing one warns and skips instead of failing an otherwise green build.

* [CI] Resolve the coverage source root before the build dir cd

The previous commit computed SRC_ROOT after `cd "$BUILD_DIR"`, under a comment
claiming it ran before. `$0` is relative to the directory the script was invoked
from, so from inside the build dir `dirname "$0"` no longer resolves and the
subshell's `cd` fails. Under `set -e` that is fatal rather than an empty variable,
which took down every leg in the matrix, coverage or not, including the ones this
change does not otherwise touch.

Move it above the cd and guard it on MAKE_COVERAGE, since the coverage report is
the only consumer. Resolving it unconditionally meant a coverage-only detail was
paid for by every build in the fleet, and gave every one of them a way to fail.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-10-11 15:05:28 +02:00
BruebachL
8822ade49d
[Tests] Register each test case with CTest instead of each test binary (#7399)
* [Tests] Register each test case with CTest instead of each test binary

Registering a test cost four edits across a dozen CMakeLists files: add_test,
add_executable, a conditional add_dependencies on gtest, and target_link_libraries.
The conditional branch was the trap -- it is only needed when GTest was fetched and
add_subdirectory'd, and omitting it yields undefined gtest symbols at link time --
so it was easy to leave out and impossible to notice until the link failed.

Fold all four steps into cockatrice_add_test() and migrate every test directory
onto it, with a cockatrice_add_manual_test() wrapper for the three benchmarks that
are built but deliberately kept out of ctest. The calls now read as a declaration
of what a test is rather than a transcript of how to register one.

While rewriting the registrations, replace add_test(NAME x COMMAND x) with
gtest_discover_tests(). Naming the binary as a single CTest entry meant a CI failure
reported only which binary failed, and ctest -R Suite.Case could not select an
individual case. Discovery raises the suite from 35 opaque entries to 469 real ones:
ctest -R ThemeMigrationTest.IsIdempotent now runs exactly that case, and
ctest -L server / -LE bench work off labels applied at registration.

Give every test a 60s TIMEOUT. CTest's implicit default is 1500s, so one hung
socket wait would stall CI for 25 minutes, and single_instance_manager_test already
does real QLocalSocket waits.

Dropping per-case granularity costs wall-clock time, since the suite becomes
process-spawn bound rather than CPU bound: serial ctest went from 3.8s to 30.8s. Run
it with -j in CI to recover it -- 7.6s at -j4, and 3.5s at -j8, both at or below
the old serial time. The binaries are parallel-safe: the only test claiming a fixed
resource scopes its local socket name to its own pid, so concurrent copies cannot
collide.

Also delete dummy_test, which asserted 1 == 1 and only proved the harness was
wired up -- something the other 468 cases already establish.

No test is added, removed, or changed by this commit; the 469 cases are the same
ones the 35 binaries contained.

* [Tests] Make QtTest available to test targets

QtTest was not among the modules test targets could link, so the Qt model and
signal contracts had no way to be verified from a test: QAbstractItemModelTester
and QSignalSpy both live there.

Add Test to _TEST_NEEDED in cmake/FindQtRuntime.cmake. That is the single place the
union of test-only Qt modules is declared, so this is the only file that needs to
change to make the module reachable from every test target.

Nothing uses it yet. This lands the capability ahead of the model-layer tests that
need it, so those can be added as tests alone rather than as test plus build-system
plumbing.

* [Tests] Correct the note on the oracle test compression defines

add_definitions() populates the directory's COMPILE_DEFINITIONS property, which is
consumed at generate time, so it reaches every target in the directory no matter
where it is called relative to add_executable(). The previous note claimed the
flags stayed confined to the benchmark because the find_package block came after
it; oracle_importer_test, created above that block, carries both defines too.

Verified harmless rather than a live bug: HAS_ZLIB and HAS_LZMA gate code only in
oracle/src/pages.cpp and oracle/src/pagetemplates.cpp, and no test target compiles
either. The ordering and the leaked defines are both pre-existing, so this only
corrects the explanation.

* [Tests] Carry one CTest label per test instead of several

gtest_discover_tests() can hand CTest exactly one label. GoogleTestAddTests.cmake
accumulates its generated script with `foreach(arg ${ARGN})`, so a label list reaches
the CTestTestfile as bare `LABELS ci server`, and CTest reads only the first value of a
property and drops the rest without a word.

The `ci` label that is appended to every registered test was therefore absent from every
test that also asked for `server` or `bench`: `ctest -L ci` reported 400 of 469 cases,
and `ctest --print-labels` never listed a second label anywhere in the build.

Reduce each test to exactly one of `ci`, `server`, or `bench`, which is all the
selections need. The three now partition the suite exactly, 400 + 64 + 5 = 469, and
`ctest -LE bench` still means everything but benchmarks. A second label is now a
configure-time FATAL_ERROR rather than a line that quietly means something else.

Anything orthogonal to a test's kind belongs in a helper flag beside the label, so it
composes with the group instead of competing for the single slot.

Also raise DISCOVERY_TIMEOUT off GoogleTestAddTests' 5s default. A plain debug binary
answers --gtest_list_tests in 0.04s, but that budget is thin once several binaries
discover concurrently behind a -j build, and being over it fails at the *link* step with
a bare "Process terminated due to timeout" that points nowhere near the cause.

* [Tests] Link gtest_main on the fetched-gtest path

The downloaded-gtest branch set GTEST_BOTH_LIBRARIES to `gtest` alone, while
FindGTest sets it to both halves. Every test target links that list rather than
picking an entry point, so on this path nothing provides main() and any test
source that does not define its own fails at link with `undefined reference to
main` pointing at crt1.o, naming neither gtest nor the line responsible.

That stayed latent while every test carried its own four-line main(). A test that
omits it now fails, and the error points at the C runtime rather than at gtest,
which is a long way from the cause. Restoring both halves makes the two paths
agree and drops the boilerplate requirement on new test sources.

The 18 sources that still define main() keep linking: gtest_main.cc defines
nothing but main, so the linker leaves its object in the archive unused rather
than reporting a duplicate.

* [Build] Run the test suite under ASan and UBSan (#7400)

* [Build] Add an ENABLE_SANITIZERS option for ASan and UBSan

Adds -DENABLE_SANITIZERS, off by default, building with AddressSanitizer and
UndefinedBehaviorSanitizer. The bulk of the work is in the three ways the obvious
implementation silently does the wrong thing.

Probe the flags on the link step, not just the compile step. check_cxx_compiler_flag()
only compiles, so every sanitizer flag comes back unsupported, because linking an ASan
object without -fsanitize=address leaves __asan_init undefined. The result is not a
build failure but a build that appears instrumented and is not, which is worse. Use
check_cxx_source_compiles() with CMAKE_REQUIRED_LINK_OPTIONS instead, and treat an
unsupported sanitizer pair as fatal since the cosmetic flags are allowed to degrade.

Opt vendored sources out per source, through a cockatrice_disable_sanitizers_for()
helper rather than a list of set_source_files_properties() calls at the root. The
helper has to exist because CMake resolves a source-file property against the directory
scope its target was declared in: setting the property from the root with absolute paths
is a no-op, silently, which is why the call sites sit next to the add_library and
add_executable that pull the vendored code in. SFMT, minizip, the LZMA SDK and Qxt SMTP
are excluded. oracle/src/qt-json is in the history but not in the tree, removed in
#7214.

Also mark those sources SKIP_PRECOMPILE_HEADERS. The PCH is built with the sanitizer
flags, so it defines __SANITIZE_ADDRESS__; a translation unit compiled with
-fno-sanitize=all does not and GCC then refuses the PCH, which is a fatal
-Werror=invalid-pch in the servatrice and oracle builds.

-fno-sanitize=vptr is not optional. Qt constructs QObject subclasses into raw storage
and writes the vptr afterwards, so the vptr check reports on essentially every QObject
instantiation, and it has no value here because nothing outside moc-generated code
inherits QObject.

Verified on the resulting build: our own translation units carry ASan and UBSan calls,
SFMT.o carries neither, and the full suite of 464 cases passes clean under
ASAN_OPTIONS=detect_leaks=0:abort_on_error=1 with UBSAN_OPTIONS=halt_on_error=0.

The test suite needs two adjustments to make this option usable at all, both of which
land here because they are consequences of COCKATRICE_SANITIZERS existing rather than
independent changes.

DISCOVERY_TIMEOUT rises from 60s to 300s under sanitizers. An instrumented binary takes
2.8s to answer --gtest_list_tests against 0.04s uninstrumented, and discovery runs
several binaries at once behind a -j build.

One target is excluded via a NO_SANITIZE flag, server_developer_role_test. Reaching
google::protobuf::TextFormat::Printer's destructor aborts on an absl assertion inside
system protobuf 33.4 whenever the consumer translation unit is built without NDEBUG,
which a Debug sanitizer build is. This reproduces with no Cockatrice code at all, just
"TextFormat::Printer p; p.PrintToString(msg, &out);" built with -fsanitize=address, so it
is upstream rather than ours. It is a flag and not a fourth label because "skip this in
the sanitizer leg" has to compose with a test's group rather than compete for the single
label slot, and because a flag excludes by not registering the cases at all, which
cannot rot the way a label silently can. The target still builds and still runs on the
legs without sanitizers.

* [CI] Run the suite once under ASan and UBSan

Adds --sanitize to .ci/compile.sh, mirroring --test, and turns it on for one entry of
the existing Linux Docker matrix: Ubuntu 26.04, which already had a test-enabled leg.
That reuses the image, the ccache layer and the existing step shape, so the CI diff is a
matrix key plus the argument plumbing. Debian 13 and Fedora 44 keep covering the
uninstrumented suite, so nothing is lost by instrumenting one leg.

Sanitizers stay off the release and package legs, which build with --release for
packaging: instrumenting those would change the shipped binaries' optimization profile
and build time for no benefit. macOS and Windows stay uninstrumented too, since ASan is
unreliable against the Qt build there and MSVC has no equivalent without /fsanitize=address.

--sanitize does not imply --test, so an instrumented build can be produced locally
without paying for ctest. When both are given, ctest runs with detect_leaks=0 because Qt
and the sanitizer runtimes both leak at exit by design and LeakSanitizer's default buries
real findings under hundreds of process-teardown reports, and with UBSan halt_on_error=0 so
one run collects many findings instead of stopping at the first. ASan keeps
abort_on_error=1, since a bad access leaves the process unusable and continuing only
produces a second, more confusing report from the wreckage.

* [Build] Fix sanitizer flag leak into release package and make UBSan fail CI

- compile.sh always passes -DENABLE_SANITIZERS explicitly so the
  release-package reconfigure in the same build dir cannot inherit ON
  from the sanitized debug step (which shipped libasan/libubsan DEB deps).
- Sanitizers.cmake resets COCKATRICE_SANITIZERS to OFF when the option is
  off (CACHE INTERNAL otherwise sticks forever, keeping NO_SANITIZE skips
  and the 300s discovery timeout).
- UBSan now fails the job: -fno-sanitize-recover=undefined at compile time
  plus UBSAN_OPTIONS halt_on_error=1 (recover mode exits 0, so the old
  halt_on_error=0 could never fail).

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-10-11 14:50:09 +02:00
tooomm
7fa6234eee
[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
2026-10-10 23:48:37 +02:00
ebbit1q
efc7e7a079
fix the desktop file, removing the invalid key (#7406)
Some checks failed
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
Build Desktop / Configure (push) Has been cancelled
Build Desktop / Debian 13 (push) Has been cancelled
Build Desktop / Debian 12 (push) Has been cancelled
Build Desktop / Fedora 44 (push) Has been cancelled
Build Desktop / Fedora 43 (push) Has been cancelled
Build Desktop / Servatrice_Debian 12 (push) Has been cancelled
Build Desktop / Ubuntu 26.04 (push) Has been cancelled
Build Desktop / Ubuntu 24.04 (push) Has been cancelled
Build Desktop / Arch (push) Has been cancelled
Build Desktop / macOS 13 Intel (push) Has been cancelled
Build Desktop / macOS 14 (push) Has been cancelled
Build Desktop / macOS 15 (push) Has been cancelled
Build Desktop / macOS 26 Debug (push) Has been cancelled
Build Desktop / Windows 10 (push) Has been cancelled
* fix the desktop file, removing the invalid key

* Update cockatrice.desktop

---------

Co-authored-by: BruebachL <44814898+BruebachL@users.noreply.github.com>
2026-10-10 17:15:10 +02:00
BruebachL
be2c16eca5
[UI] Fix auto-completion acceptance on Space/Tab/Enter (#7466)
* [UI] Fix auto-completion acceptance on Space/Tab/Enter

* [UI] Accept current completion when no item selected in popup

* [UI] Also accept completion on focus out when none selected

* [UI] Keep focus in chat on Tab completion acceptance

* [UI] Prevent Tab from moving focus when no completion popup

* [UI] Refocus on Tab when completing

* [UI] Let focusOut handle Tab completion acceptance

* [UI] Restore Tab behavior to match original (handle in focusOut)

* [UI] Defer refocus on Tab to avoid focus chain issues

* [UI] Auto-select first completion match in popup

* [UI] Auto-select first match when completer popup resets

* Revert "[UI] Auto-select first match when completer popup resets"

This reverts commit c7a3de3595.

* Revert "[UI] Auto-select first completion match in popup"

This reverts commit ae0dfb8622.

* [UI] Ensure completion selection updates on model changes

* Revert "[UI] Ensure completion selection updates on model changes"

This reverts commit 686bbef5b2.

* [UI] Highlight the selected completion in chat popups

The card completer's above-variant moved the current row with
NoUpdate, so the delegate never painted the selection border, and it
used Select which accumulated highlights while navigating. Use
ClearAndSelect so only the current row is highlighted and the old one is
cleared.

The mention popup opened with no current row at all, so nothing was
highlighted and there was no visual hint of what Enter would insert.
Select the row currentCompletion() uses, and install a delegate that
mirrors the active highlight into the inactive group so the selection
stays visible when the transient popup is not the active window.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-10-10 09:45:42 +02:00
BruebachL
b8e8f2c738
[PrintingSelector] Fix crash setting all printings to preferred (#7467)
getPreferredPrinting returns a null PrintingInfo when a deck card has no
database entry or printings. SetProviderIdToPreferred dereferenced
getSet() unconditionally, crashing on custom or unresolved cards.

Guard against a null preferred set and leave the card's cleared printing
data as is.

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-10-10 09:36:26 +02:00
BruebachL
1e1f79db0d
[Windows] Wait for uninstaller, rework .dll bundling, add logging (#7407)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 26 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
* [Windows] Purge every runtime DLL on upgrade instead of a named few

A user updating from 3.0.2 to 3.1 got "abseil_dll.dll is either not designed
to run on Windows or it contains an error" and could not start the client.

abseil_dll.dll comes from vcpkg, not Qt. It rides into the install directory
on the "*.dll" glob in cockatrice/CMakeLists.txt, alongside libprotobuf.dll,
liblzma.dll and z.dll, and none of those were named in the upgrade purge. So
on an upgrade the old uninstaller's RMDir /r skipped whichever of them was
locked, the purge did not look for them, HasStaleRuntimeFiles did not look
for them either, and the installer reported success - having written the new
executables next to the previous version's protobuf runtime. Windows then
refused to load it.

The mask list was also already wrong where it existed: it matches zlib*.dll
while vcpkg ships z.dll.

Delete every top-level DLL instead of naming library families. The payload is
written after the purge, so a DLL the new build still needs is put straight
back; what the purge has to guarantee is only that nothing from the previous
build is left to be loaded next to the new executables. Enumerated families
cannot do that - every transitive dependency that arrives between two
releases is one more name to forget, and the three previous rounds of this
work each reasoned about Qt because that is what the reports named at the
time.

HasStaleRuntimeFiles collapses to the matching *.dll probe, so the abort
fires on any survivor rather than only on the families it was told about.

IsExistingCockatriceInstall stays on Qt6*.dll on purpose: it decides whether a
directory is ours at all, and a folder that merely holds some DLLs of its own
must not be purged. Qt6*.dll is enough even when only the protobuf runtime
survived, because the whole Qt set is mapped by the same process that maps
abseil_dll.dll and so is locked or free with it.

* [CI] Assert the whole Windows runtime, not just the Qt modules

required-qt-runtime.txt listed only the Qt modules COCKATRICE_QT_MODULES
names, so it could not see any other runtime go missing. The three previous
rounds of installer work all reasoned about Qt*.dll because that was all the
build knew how to ask about, and the one runtime that actually broke the
reported upgrade - abseil_dll.dll - was outside the set entirely.

- Rename it to required-runtime.txt and take the third party runtime names
  from the vcpkg bin directory the app-local copies come from, so a
  dependency bump cannot leave the list behind and a versioned name such as
  libcrypto-3-x64.dll becoming libcrypto-4-x64.dll cannot either
- Union the three applications' module lists rather than the client's alone:
  Servatrice contributes Qt6Sql.dll, servatrice.exe is installed next to
  cockatrice.exe and cannot start without it, and nothing asserted it
- Add Core to the client's module list. It is the one module nothing can do
  without and it only ever arrived transitively, so Qt6Core.dll was shipped
  and never checked - the new unknown-runtime check flagged it on the first
  run
- Fail the build on a missing runtime, as before, and warn on a runtime
  nothing requires. Only the test framework is a hard failure there: "no
  shipped executable links gtest" is a fact, whereas a runtime missing from
  the manifest may be one the manifest failed to derive, and a check that has
  never run before should not be able to block a release

The warning is meant to be read. Qt6::Quick pulls in Qml, QmlModels,
QmlWorkerScript, QuickBasic, QuickTemplates2 and OpenGL transitively and none
are named, so the manifest is known to be a partial list; the first Windows
run reports the real one, which is evidence rather than a guess about what Qt
links. Fold that back in when it lands.

* [CI] Stop deriving the required runtime from the vcpkg bin directory

The first Windows run of this check failed, and the seven files it named as
missing are not runtime at all: gmock.dll, gmock_main.dll, legacy.dll,
libprotobuf-lite.dll, libprotoc.dll and pthreadVC3/VCE3/VSE3.dll. None of them
is linked by any shipped executable, so a correct package leaves them out.

vcpkg_installed/x64-windows/bin is everything vcpkg installed, not everything
the applications import. The protoc compiler, the GoogleMock library, a port
tool and three CMake FindThreads probes sit beside the genuine runtimes, and a
directory listing cannot tell them apart. Asserting it fails the build over a
package that is right - which is what happened, and it is the same mistake as
the one that produced this bug report: reasoning about a list of names instead
of about the build.

The real source of truth is the three executables' import tables, which
fixup_bundle already walks to decide what to copy. Until CMake can read that
result, the Qt half is what can be derived honestly, from the same module lists
the targets are built against. So assert that, and report the rest rather than
require it:

- Drop the vcpkg bin glob, its gtest filter and the vcpkg-runtime-known marker.
  All three existed only to support the glob.
- Report top level DLLs the manifest does not name without failing. These are
  expected: protobuf, Abseil, OpenSSL, liblzma, ffmpeg and the Qt debug DLLs all
  arrive on the build output glob because nothing in the build declares them by
  name. The list is a report of what the package carries, not a verdict, and it
  is how the missing Qt6Qml/Quick/OpenGL entries above became visible.
- Report a leaked gtest or gmock instead of failing on it. "No shipped
  executable links this" is a fact, but a check that has never passed on a real
  package has not earned the right to block a release, and the first run of an
  enforcing version is exactly when its own wrong assumptions show up.
- Skip DLLs under a subdirectory, which are platform plugins rather than
  imported runtimes, and the NSIS plugins, which extract to $PLUGINSDIR and so
  reach the comparison looking like payload.

Verified against the paths 7-Zip actually listed in the failing package: the
check passes on it, still fails when Qt6Sql.dll or the qwindows plugin is
removed, and still reports gtest.dll and gmock.dll if either is added.

The purge in cmake/NSIS.template.in covers the runtimes the manifest does not
name. That fix is independent of any list and is what makes the reported update
failure go away.

* [Installer] Fix the /R upgrade aborting before it does anything

WaitForLauncherToExit read the wrong registers. System.dll writes its
.rN outputs to $N and its .RN outputs to $RN, but the wait used
p.r0/i.r1 while testing $R1. $R1 therefore stayed empty, and the "did
not exit" test compared "" with "0" - true every time. Every in-app
update passes /PID=, so every upgrade hit AbortInstall before writing
anything; because /R also sets silent, the override just vanished with
exit code 1 and no message. The bug came in with #7372 and is inherited
by this branch, not introduced by it.

Use the uppercase .RN forms, and treat a null or plugin-failed
OpenProcess as the launcher already being gone - the case the old
${Errors} check never caught, because System.dll does not set the NSIS
error flag from the callee's return value.

Also restore the abort dialog for the in-app updater: AbortInstall and
the two inline warnings gated their message box on ${IfNot} ${Silent},
which is false for a /R upgrade, so any abort on that path was equally
silent. The unattended case is a /S install without /R, not /R, so show
the box for $ReinstallMode = 1 or an interactive install. The
stale-runtime abort now goes through AbortInstall so that decision
lives in one place.

* [Installer] Log every upgrade step to %TEMP%\Cockatrice-installer.log

* [Installer] Actually append to the upgrade log (FileSeek END)

* [Installer] Wait for the old uninstaller before replacing files

An NSIS uninstaller copies itself to %TEMP% and runs from there so that it
can delete its own directory; the process the installer started exits
immediately. A plain ExecWait therefore returns before the uninstall has
run, and the temp copy's "RMDir /r $INSTDIR" then executes concurrently
with the install section, deleting files the payload just wrote. That is
why an upgrade could leave Qt6Core/Gui/Multimedia and the other
alphabetically-first DLLs missing while the tail of the directory survived:
a recursive delete marching from the front while the final listing ran.

Pass _?=$INSTDIR to the old uninstaller so it runs in place and ExecWait
actually blocks until the uninstall completes. _?= must be the last
argument and unquoted, even for paths with spaces. Applied to both the
silent /R path (AutoUninstallIfNeeded) and the interactive path
(componentsPagePre).

* [Installer] Tighten the wording of the upgrade comments

Drop the dash-heavy phrasing from the comments this PR adds, and say plainly
in the _?= comment that ExecWait returns before the old uninstaller has run
and the temp copy then deletes the payload the section is still writing.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-10-09 20:09:34 +02:00
BruebachL
bb61617d42
[Versioning] Bump version number for 3.1.1 beta release. (#7464)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 26 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-10-09 11:35:35 +02:00
BruebachL
a44757bd11
[Client] Improve card database update visibility (#7458)
* [Client] Improve card database update visibility

Addresses Cockatrice/Cockatrice#7431.

Cockatrice previously offered no in-client feedback while the card
database updater ran: the only progress UI lived in the first-run
wizard, stderr/stdout were not parsed in interactive runs, and starting
a second update with Oracle already open or with a hidden background run
active just showed a confusing 'already running' guard - even though
Oracle is launched as a separate process with no lifecycle tracking.

- Add a status-bar widget (CardDatabaseUpdateStatusBar) that appears
  while an update runs and shows the Downloading/Parsing/Importing
  stages with percent progress; it is also used in the quit-confirm
  prompt so users see an unfinished update before cancelling it.
- Parse the updater's PROGRESS lines (new CardUpdateProgressParser,
  unit-tested) in both foreground and background runs and log stage
  changes to the new card_database.update category, throttled to at
  most one line per second; forward Oracle stderr to the logs.
- Show a tray balloon when a background update starts and on finish or
  failure instead of staying silent.
- Request a running Oracle instance to raise itself (QLocalServer
  CockatriceOracleRaise-<user>) when the user starts another update, and
  demote the 'already running' guard to an informative message only when
  no running instance can be found.
- Emit download/scan/import progress unconditionally from Oracle so
  interactive and background runs behave the same.
- Document the new log category in the troubleshooting guide.

* [Client] Address card database update review feedback

* [Client] Add setting to control card database update tray notifications

* [Client] Address remaining card database update review nits

- Rename card_update_progress_parser.{h,cpp} to card_update_progress.{h,cpp}
- Store the CardUpdateProgress struct in the status bar widget

* Lint.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-10-09 11:07:48 +02:00
BruebachL
6bed5d6f67
[Style] Warn about the broken Windows native styles and stop resolving System to them (#7461)
* [Style] Fall back to the built-in windows style instead of windowsvista

The Windows Vista native style is broken the same way as the Windows 11 one,
so the sanitizer now catches both and falls back to the built-in QWindowsStyle
instead of the Vista plugin. The style picker no longer offers either.

* [Style] Keep Windows native styles selectable but warn about them

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-10-09 11:07:22 +02:00
BruebachL
205e1ecebf
[CardDB] Keep parse-time set filtering and bust the cache on enablement changes (#7448)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 26 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
The parsers keep dropping printings for disabled sets so that disabling a
set hides them everywhere, not just in deck editor search. The cached parse
output therefore depends on enablement, which the source hash did not cover:
the first parse froze whatever enablement state existed at that moment and
later toggles hit the stale cache.

- Fold a parser schema version and the sorted enabled set names into the
  binary cache source hash so toggling a set or enabling newly discovered
  sets (including custom ones) forces a re-parse on the next load.
- Harden the legacy set migration to only write keys that exist so missing
  legacy values can no longer clobber cardDatabase.ini and silently disable
  sets on upgrade.
- Make CardDatabase::reloadCardDatabasesAndNotify() bypass the binary cache
  so an explicit reload always re-reads the files on disk, even when their
  size/mtime hash still matches.
- Cover cache invalidation end to end: sandboxed loads assert that disabling
  a set, adding a custom database and enabling a fresh set each rewrite the
  cache, that a no-op load keeps it, and that an explicit reload picks up
  edits the source hash cannot see.

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-10-09 06:09:18 +02:00
BruebachL
aaafa72c0e
[Game] Use native title bar height for ZoneViewWidget close button (#7450)
* [Game] Use native title bar height for ZoneViewWidget close button

Compute title bar height from style (PM_TitleBarHeight) instead of hardcoded 24.0 to align hit box with native close button.

* [Game] Fix ZoneViewWidget title bar height compile errors

Drop the redundant static on the anonymous namespace helper, stop
redeclaring titleBarHeight's parameter and reuse tbh in the fallback
path, and restore the position clamp that still referenced the removed
kTitleBarHeight constant.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-10-09 06:08:15 +02:00
BruebachL
56c456414b
[Game] Use non-blocking popup for zone context menus (#7452)
Replace QMenu::exec with popup() for zone context menus to avoid blocking the scene.

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-10-09 06:07:39 +02:00
BruebachL
95f02ac0a1
[CardDB] Drop the rebalanced-cards setting entirely (#7457)
* [CardDB] Drop rebalanced-card filtering from the v4 parser

Rebalanced cards no longer exist, so the includeRebalancedCards
preference has no data to filter. Always keep printings marked
isRebalanced and remove the now-unused ICardPreferenceProvider
plumbing from CockatriceXml4Parser and CardDatabaseLoader.

The setting itself (CardsDisplaySettings, Manage Sets checkbox,
Oracle attribute mapping) is removed in a follow-up.

* [CardDB] Drop the rebalanced-cards setting entirely

Rebalanced Alchemy cards no longer exist, so remove the
includeRebalancedCards preference surface: the CardsDisplaySettings
getter/setter/signal, the ICardPreferenceProvider and
ICardsDisplaySettingsProvider methods, the Manage Sets checkbox, the
legacy settings migration mapping, the isRebalanced attribute mapping
in the Oracle importer, and the isRebalanced attribute in the v4
schema.

Builds on #7448, which already stopped the v4 parser from filtering
on the preference.

* [CardDB] Drop the unused isOnlineOnly printing property

The Arena-card detection switched from isOnlineOnly to isRebalanced in
#5778, so nothing has read this printing property since; it only
round-trips from the Oracle importer into cards.xml. Remove the
importer mapping, the benchmark fixture field and the v4 schema
attribute so regenerated databases stop carrying it.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-10-09 06:02:47 +02:00
BruebachL
d0fc27e3a8
[Security] Harden network input handling and websocket proxying (#7076)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 26 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
- TCP client connections: reject messages whose declared length is
  negative or exceeds a 1.5MB cap immediately instead of buffering
  indefinitely, preventing unbounded memory growth and the previously
  stuck state caused by negative lengths overflowing the int header.
- ISL connections: apply the same length validation with a 4MB cap,
  disconnecting the peer on an implausible length.
- Websockets: only trust the forwarded client-address header (e.g.
  X-Forwarded-For) when the websocket connection originates from an
  address in security/trusted_sources. Previously any client could
  spoof its address to bypass bans, user limits and rate limiting.

Took 3 minutes

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-10-08 23:18:47 +02:00
BruebachL
514014dc00
[Completer] Stop line edit rewrite on popup highlight (#7447)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 26 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
* [Completer] Stop line edit rewrite on popup highlight

* [Completer] Extract the completion index into a variable

Make the popup selection update readable at a glance.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-10-08 14:38:11 +02:00
BruebachL
870ab23608
[Tooling] Add IWYU Qt6 mapping, generator, and runner script (#7454)
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-10-08 11:17:01 +02:00
BruebachL
d603a2bb6a
[Tooling] Apply include-what-you-use across the tree (#7396)
* [DeckList] Apply include-what-you-use to libcockatrice_deck_list

* [IWYU] Apply include-what-you-use across the tree

Run one whole-tree IWYU pass over the libcockatrice_* libraries, the
Cockatrice client, Oracle, Servatrice and the test suite, so each
translation unit includes what it uses directly and forward-declares
what it does not need.

IWYU only analyses real translation units, never the moc code CMake
generates from Q_OBJECT headers, so it strips includes that moc output
silently relies on. Where a type is passed by value (or nested in a
QList/QMap) in a signal or slot signature, the generated moc code needs
the complete type; those includes are kept with an explanatory
`// IWYU pragma: keep`. The same holds for Q_DECLARE_METATYPE targets
such as QSqlDatabase.

A handful of includes are added by hand where callers brace-initialize a
const-reference parameter, which also requires the complete type.

Applied as a single commit on purpose: stripping includes from the
libcockatrice_* headers is precisely what breaks the client, test and
server translation units, and the fixes for those live in the same
translation units. Splitting by component would therefore leave
intermediate commits that do not compile.

All 426 modified Q_OBJECT/Q_GADGET headers were verified to be
moc-self-contained by compiling each generated moc_*.cpp standalone;
several built only because of moc include ordering in
mocs_compilation.cpp before the keep-includes were added.

* [Tooling] Emit public Qt headers and fix IWYU fallout CI found

Three problems the local build could not see, all because a green local
build only proves the configuration it was run under.

Private Qt headers. IWYU emitted private, lowercase Qt headers, and
qtypes.h does not exist at all before Qt 6.9, so the Debian 12 and
Ubuntu 24.04 jobs on Qt 6.4.2 failed to compile. The generator only
emitted a mapping when the capital header name mirrored the private one
(QAbstractAnimation for qabstractanimation.h), so qtypes.h, which is
wrapped by QtTypes instead, got no mapping and kept the private
spelling. Map it explicitly to <QtTypes>, which is public since Qt 5 and
provides the same typedefs either way, and rewrite the 100 affected
includes.

Do not generalize this to "prefer any Qt* wrapper". The table is
generated from whichever Qt is installed, and the wrappers are not
equally old: QtLogging only appeared in Qt 6.8 and QtMinMax in 6.5, so
that heuristic trades one breakage for another. Every other lowercase
header still in the tree has shipped publicly since Qt 5, so those are
left alone.

Platform-guarded includes. IWYU only sees the preprocessor configuration
it is handed, so it dropped an include that only the macOS branch of
main.cpp needs, and the macOS jobs failed on an undeclared
UrlSchemeEventFilter. Restore the include. Force-compiling every file
that carries a platform guard under Q_OS_MAC, Q_OS_MACOS and __APPLE__
reports nothing else, so the macOS branches are covered rather than
assumed.

shellcheck. Drop a leftover variable and stop assigning the
include-what-you-use lookup, which was only ever an existence check.

* [Tooling] Map Qt 6.5+ split headers to QtGlobal for Qt 6.4 compatibility

IWYU emitted QtTypes, QtPreprocessorSupport, QtVersionChecks, QtAssert,
QtMinMax, QtClassHelperMacros, QtEnvironmentVariables and QtTranslation.
Qt 6.5 split those declarations out of qglobal.h into separate headers, so
neither the Qt* wrapper nor the q*.h private name exists on the oldest Qt we
support (6.4.2), and Debian 12 / Ubuntu 24.04 fail with "No such file or
directory". qtypes.h is worse still: it only appeared in Qt 6.9.

QtGlobal is the one spelling that works across the whole supported range. It
has been public since Qt 5, defines these declarations itself up to Qt 6.4, and
includes the split headers from Qt 6.5 on, so redirect all eight private
headers to it and collapse the affected includes in the tree.

Curated overrides now take precedence over the name-mirroring rule, which
cannot know about Qt version floors because the mapping is generated from
whatever Qt is installed. That rule was mapping qtversionchecks.h to
<QtVersionChecks> even with an override present, because the override was only
consulted as a fallback. A curated key that matches nothing is now a hard
error rather than a silent fallback, and the redirected headers are no longer
advertised as the home of a symbol.

Verified every Qt include in the tree resolves against the real
qt6-base-dev 6.4.2 package: 15 distinct includes, 0 missing.

* [Tooling] Map QOverload to QtGlobal and drop the QMetaMethodArgument include

Two more headers IWYU emitted do not exist on Qt 6.4.2:

QOverload was split out of qglobal.h in Qt 6.5, so on 6.4.2 it is declared in
qglobal.h directly and the standalone header is a hard error. QtGlobal covers
both, so map qoverload.h to it like the other 6.5+ split headers.

QMetaMethodArgument is never referenced anywhere in the tree. It is a bare
forwarder for qobjectdefs.h that our generator advertised as a class symbol,
which is how IWYU came to add the include at all. The symbol mappings are
generated from header names, so they cannot tell a forwarder that happens to be
capitalised from a real class; removing the unused include is the fix, and the
generic case stays a known limitation of the generator.

Extending the audit to every Q* header (an earlier pass only matched headers
starting with "Qt" or lowercase q*.h, so it missed all of them) against the real
qt6-base-dev, qt6-multimedia-dev, qt6-declarative-dev, qt6-svg-dev and
qt6-websockets-dev 6.4.2 packages: 341 distinct Qt includes, 0 missing.

* [Game] Give moc the complete CardItem type in player_actions.h

player_actions.h forward declares CardItem but passes QList<CardItem *> by
value in actPlay/actPlayFacedown/actHide. moc instantiates the metatype for
those parameters, and on Qt 6.4 that expands to sizeof(CardItem), so Ubuntu
24.04 fails with "invalid application of 'sizeof' to incomplete type
'CardItem'". Newer Qt tolerates the incomplete type, which is why only the
6.4.2 jobs failed.

Include the defining header with an IWYU pragma keep, matching how the other
MOC-required includes in this area are handled.

* [Game] Give moc the complete ServerInfo_Card type

player_logic.h, game_scene.h, view_zone.h and view_zone_widget.h forward
declare ServerInfo_Card but pass QList<const ServerInfo_Card *> through signal
and slot signatures. moc instantiates the metatype for those parameters, which
Qt 6.4 expands to sizeof(ServerInfo_Card), so the connect in game_scene.cpp
fails on Debian 12 and Ubuntu 24.04 with "invalid application of 'sizeof' to
incomplete type 'const ServerInfo_Card'". Newer Qt tolerates it, so only the
6.4.2 jobs failed.

Include the generated protobuf header with an IWYU pragma keep, following the
pattern already used for ServerInfo_Player and ServerInfo_User in
game_event_handler.h.

* [Client] Complete the types Qt 6.4 needs for connect and moc

Qt 6.4 expands QMetaType::fromType on the normalised argument list of a
PMF connect and on moc-generated metatypes, which requires sizeof on every
signal and slot parameter. Newer Qt tolerates forward declarations there,
so only the Debian 12 and Ubuntu 24.04 jobs failed, with
"invalid application of 'sizeof' to incomplete type" for QMouseEvent,
QPrinter and GameReplay.

Replace the forward declarations with the real includes:

- QMouseEvent in the nine widget headers that pass it through signals
- QPrinter in abstract_tab_deck_editor.cpp, for the actPrintDeck lambda
- game_replay.pb.h in tab_replays.h. GameReplay is a protobuf message, so
  the project's game/replay.h only forward declares it and cannot supply
  the complete type.

Found by recompiling all 1918 project TUs against the Debian Qt 6.4.2
headers, which reproduces those two CI jobs locally in one pass.

* [Client] Complete Release and QMenu for Qt 6.4 moc and connect

Continuing the Qt 6.4 complete-type sweep:

- window_main.cpp connects ClientUpdateChecker::finishedCheck to
  MainWindow::checkClientUpdatesFinished, whose Release * parameter made
  moc 6.4 instantiate the metatype. GameReplay turned out to be a protobuf
  message, so Release likewise needed release_channel.h in the three
  headers declaring those signals: window_main.h, dlg_update.h and
  client_update_checker.h.
- moc 6.4 also needs sizeof(QMenu) for the QList<QMenu *> carried by
  TabSupervisor::setMenu and MainWindow::updateTabMenu.

Verified by regenerating moc output with the Debian moc 6.4.2 for all 434
Q_OBJECT/Q_GADGET classes and compiling it, plus recompiling all 1612
source TUs, against the Qt 6.4.2 headers: no remaining failures.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-10-08 11:13:50 +02:00
BruebachL
b1f4d1e99d
[UI] Flip tear-off menus default to off (#7449)
Detach menus no longer default to tear-off; users can re-enable in settings.

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-10-08 10:51:03 +02:00
BruebachL
d0991b68f6
[EDHRec] Update tab title when a card is searched (#7408)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 26 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
TabEdhRecMain never emitted tabTextChanged, so TabSupervisor::updateTabText
was never called and the tab kept the title it was created with.

addEdhrecMainTab() creates the tab without a card, so every search left the
title reading "EDHRec: " with no card name for the rest of the tab's life.

addEdhrecTab() sets the card before myAddTab(), which is why the older
TabEdhRec tab was never affected.

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-10-07 14:55:25 +02:00
tooomm
08a608992d
[Docs] Cleanup Doxygen files (#7361)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
* Table layout

* table layout

* table layout

* Delete doc/doxygen/extra-pages/developer_documentation/network/game_event_handler.md

* Delete doc/doxygen/extra-pages/developer_documentation/network/player_event_handler.md

* use default value
2026-10-06 07:23:41 +02:00
tooomm
b545efb929
[Readme] Add hint on building docs (#7389)
Some checks failed
CodeQL / Analyze (cpp) (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
Build Desktop / Configure (push) Has been cancelled
Build Docker / Servatrice (arm) (push) Has been cancelled
Build Docker / Servatrice (x86) (push) Has been cancelled
Build Desktop / Debian 13 (push) Has been cancelled
Build Desktop / Debian 12 (push) Has been cancelled
Build Desktop / Fedora 44 (push) Has been cancelled
Build Desktop / Fedora 43 (push) Has been cancelled
Build Desktop / Servatrice_Debian 12 (push) Has been cancelled
Build Desktop / Ubuntu 26.04 (push) Has been cancelled
Build Desktop / Ubuntu 24.04 (push) Has been cancelled
Build Desktop / Arch (push) Has been cancelled
Build Desktop / macOS 13 Intel (push) Has been cancelled
Build Desktop / macOS 14 (push) Has been cancelled
Build Desktop / macOS 15 (push) Has been cancelled
Build Desktop / macOS 26 Debug (push) Has been cancelled
Build Desktop / Windows 10 (push) Has been cancelled
Build Docker / Publish multi-platform Servatrice image (push) Has been cancelled
* add hint on building docs

* Update README.md

* Update README.md

* Expand doxygen README
2026-10-04 21:48:10 +02:00
tooomm
a02523ee9e
Add CONTRIBUTING file to doc links (#7395) 2026-10-04 21:46:56 +02:00
github-actions[bot]
a8e9eb3c1b
Update translation source strings (#7397) 2026-10-04 21:08:47 +02:00
tooomm
327a6bfd8a
[CI] Fix permissions for automations around translations (#7394)
* Update translations-push.yml

* Update translations-pull.yml
2026-10-04 20:20:28 +02:00
BruebachL
86039d7eb6
Change release name for 3.1.0 release. (#7388)
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-10-04 20:15:31 +02:00
Michael Hogg
0a9d6b2226
Include git metadata in docker build context (#7393)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
cmake/getversion.cmake reads the commit date and release tag from git, but .dockerignore excluded .git/, so docker builds had no git info and Servatrice reported its version as "3.1.0 ()". Keep .git/ in the build context; only /usr/local is copied into the final image, so its size is unaffected.

Co-authored-by: Michael Hogg <3495076+jedinerd@users.noreply.github.com>
2026-10-04 03:48:15 +02:00
Michael Hogg
885b50de00
Allow manually publishing a release image in docker workflow (#7392)
Add a workflow_dispatch trigger that builds an existing release tag with the current workflow and publishes it to GHCR under that tag name (optionally also as latest). This allows republishing a release whose image failed to publish, since re-running the original run reuses the workflow file from the release tag.

Dispatched runs check out the given tag, publish like stable releases, and use their own concurrency group so pushes to master can't cancel them.

Co-authored-by: Michael Hogg <3495076+jedinerd@users.noreply.github.com>
2026-10-04 02:44:54 +02:00
Michael Hogg
76bcc4842b
Fix digest artifact names in docker release workflow (#7391)
With archive: false, actions/upload-artifact ignores the name input and names the artifact after the uploaded file, so the digest artifacts were named after the raw digest instead of digest-<label>. The index job's download-artifact pattern digest-* then matched nothing, leaving no digests to build the image index from. Drop archive: false so the digest-<label> names apply.

Co-authored-by: Michael Hogg <3495076+jedinerd@users.noreply.github.com>
2026-10-04 02:35:24 +02:00
Michael Hogg
b858d684cb
Fix docker metadata tags input in release workflow (#7390)
The inline comment after type=ref,event=tag was inside the tags block scalar, so it was passed to docker/metadata-action as part of the tag spec, failing with "Invalid event". Move the comment above the block.

Co-authored-by: Michael Hogg <3495076+jedinerd@users.noreply.github.com>
2026-10-04 02:35:06 +02:00
tooomm
5dc5390880
Fix and rework client updater logic (#7038)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 26 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
* first version

* combined logic + better regex

* Simplify, we only support 64bit, installer prevents wrong installs

* lint + fix Linux versions (missing declaration)

* update regex

* improve/add comments

* comments

* ReleaseChannel: parse non-numeric OS versions on Windows Server

QSysInfo::productVersion() returns "Server 2022" on Windows Server, so
splitting on '.' and calling toInt() made the version unparseable and
disabled the in-client updater there. Extract the first numeric token
instead; Server 2022 becomes 2022 and matches the Win10 assets.

* ReleaseChannel: don't offer 64-bit assets on 32-bit Windows

The old downloadMatchesCurrentOS() had an explicit 32-bit branch that
only accepted "32bit" assets. Restore that guard: 32-bit Windows builds
return no match instead of being offered the 64-bit Win10 installer that
won't run.

* ReleaseChannel: only query the OS version on updater platforms

The in-client updater only ships assets for macOS and Windows, but the
host system version was still being resolved on every platform, including
Linux where it is never used. Compute it only under the Q_OS_MACOS /
Q_OS_WIN guards.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-10-03 17:57:03 +02:00
BruebachL
0934380a5c
[Translations] Load Qt's own translations and build ours with the binary (#7384)
* [Translations] Load Qt's own translations and build ours with the binary

Three separate bugs kept translations from working.

installNewTranslator() loaded the application translation into
qtTranslator. QTranslator discards its previous contents on every load, so
that wiped the qt_<lang>.qm loaded one line earlier and installed an empty
translator instead. Qt's own strings were never translated on any platform.
Give each file its own translator.

QLibraryInfo::path(TranslationsPath) was the only directory searched for
Qt's translations. A relocated Qt can report a path that does not exist: the
KDE Flatpak runtime reports /usr/translations while its files live in
/usr/share/qt6/translations. Probe the usual install layouts and only hand
back the ones that exist.

The .qm files were attached to the default build target only, so building
the executable on its own left it without any translations and the language
selector empty. Attach them to the target as well.

Move the shared lookup into TranslationLoader so client and Oracle cannot
drift again, and have it report a missing translation at debug level. An
unset language is the default for English, which has no file to begin with,
and warning about it only added noise to every fresh debug log.

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>

* [Tests] Define main() in translation_loader_test

Without a GTest installation the fallback in tests/CMakeLists.txt links
only gtest, not gtest_main, so the test has to bring its own main(). Every
other test already does; only this one relied on the distribution package
and failed to link on CI.

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>

* [Tests] Put the translation loader fixture next to the test executable

A multi-config generator (MSBuild, Xcode) writes the executable to a
per-config subdirectory while the .qm stays in the current binary
directory, so applicationDirPath() never saw the fixture and
ABuildTreeFindsTheTranslationsNextToTheExecutable failed on Windows.

* [Translations] Depend on Qt's lrelease target and skip the loader test without lrelease

Attaching the .qm custom commands to a second custom target races with
Qt's own <Project>_lrelease target on MSBuild/Makefiles and is rejected
by Xcode, so depend on Qt's target instead. Only build the translation
loader test when Qt6::lrelease is available.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
Co-authored-by: ZeldaZach <zahalpern+github@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 15:04:30 +02:00
BruebachL
3aba1ef929
[Settings] Back up the settings folder before migrating (#7386)
* [Settings] Back up the settings folder before migrating

The per-file settings migration only preserved global.ini, as
global.ini.old. Everything else the migrations write was overwritten in
place with no way back: the legacy migration and the 17 per-domain
migrateXxxSettings functions all write straight into the per-domain .ini
files, so any of them that got a key mapping wrong silently replaced a
working file. The re-migration path is the sharpest case, because
migrateSettingsFromGlobalIni() runs again whenever an older build wrote
non-sentinel keys back into global.ini, and those values then land on
top of per-domain files the user had already edited.

Snapshot the whole settings folder before either migration runs, into a
timestamped sibling settings-backup-<yyyyMMdd-HHmmss>. A sibling rather
than a child, so a backup is never copied into the next backup and the
"Open Settings Folder" action keeps showing only live files. Keep every
snapshot; settings files are kilobytes, so growth is not a concern.

SettingsMigration::isMigrationPending() gates the copy so a normal start
creates nothing. It reuses the same condition migrateSettingsFromGlobalIni()
uses to decide whether it has work, factored into a shared helper so the
two cannot drift, and it ignores the legacy migration on an empty profile
where that migration only writes its sentinel. The backup itself skips a
folder whose files are all empty, which covers a fresh profile whose
global.ini was created but never written.

A failed copy logs a warning and returns an empty path rather than
aborting the migration, since a full disk or an unwritable data folder
must not leave the client unable to start. global.ini.old still works as
the single-file escape hatch.

Document the folder layout, the automatic backups, and the fact that
migrations only run forwards, so users know to copy the settings folder
before downgrading to a version that cannot read the per-group files.

* [Client] Match the desktop file name case for the portal app ID

QGuiApplication::setDesktopFileName() was given "Cockatrice"/"Oracle"
while the installed desktop entries are cockatrice.desktop and
oracle.desktop. Linux paths are case-sensitive, so xdg-desktop-portal
failed the app-info lookup for "Cockatrice" and Qt warned on every
QDesktopServices call that it could not register with the host portal.
The same string becomes the xdg-shell appId, so the window icon lookup
failed on Wayland for the same reason.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-10-03 14:59:58 +02:00
BruebachL
b8b81170bf
[Client] Show card names in the game log and [card] tags in the chosen card language (#7387)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 26 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
The game log translates its sentences, zone names and counter names, but every
card name was rendered as the raw canonical name straight from the server.
The card board and the card info text already went through
CardLocalization::displayName(), so the log was the odd one out.

cardLink() used one string for both the card:// href and the visible label,
which left no room for a localized label. Split the two: the href keeps the
canonical name because ChatView::mousePressEvent feeds it straight to
showCardInfoPopup for lookup, while the label goes through the display
language. Same split for [card] chat tags in ChatView::appendCardTag.

Sanitize the name while building the link. logUndoDraw already did this by
hand and had drifted into its own non-italic copy of cardLink(); route it back
through the helper.

Add CardLocalization::displayNameFor() for callers that only hold a canonical
name and need no CardInfoPtr of their own.

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-10-03 13:47:07 +02:00
BruebachL
014983b5bc
[Theme] Carry Default theme directory customizations over to System (#7385)
* [Theme] Carry Default theme directory customizations over to System

The Default -> System rename rewrote the stored theme name but left the
user profile's themes/Default directory behind, so anyone who had
customized zone graphics, stylesheets or palettes there silently lost them.

Migrate the directory alongside the name. Rename it when System does not
exist yet, otherwise merge file by file without overwriting what System
already has, then drop the legacy directory. Users who already ran the
rename get their customizations back on the next start, and a hand-made
Default folder of a user on another theme is left untouched.

* [Tests] Give theme_migration_test an explicit main and gate it on WITH_CLIENT

The build machines that cannot find GTest fall back to the downloaded
googletest, which links the bare gtest library without gtest_main. Every
other test defines its own main() for that reason; this one did not, so
Debian 13, Fedora 44 and Ubuntu 26.04 failed to link it.

The test compiles client sources, so -DTEST=1 -DWITH_CLIENT=0 has no way
to build it. Gate the subdirectory on WITH_CLIENT like the other
client-source test.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-10-03 13:43:25 +02:00
BruebachL
add65caae5
[Client] Hand off single-instance payloads outside the readyRead signal (#7373)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 26 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
* [Client] Hand off single-instance payloads outside the readyRead signal

Opening a cockatrice:// link from outside a running client showed the
"Join game ...?" confirmation and then crashed on both answers. The
join reached the server first, so the other players saw the user join
and immediately disconnect.

SingleInstanceManager::handleNewConnection() emitted filesReceived from
inside the forwarding socket's readyRead lambda and only disconnected
afterwards. A joingame link makes the handler show a modal QMessageBox,
which runs a nested event loop. The sending instance exits as soon as
it has the acknowledgment, so the socket was disconnected and
deleteLater'd inside that loop: it died in the middle of its own
readyRead signal, and both this lambda and Qt's signal dispatch kept
using the freed memory.

Disconnect right after the acknowledgment and emit filesReceived
through a queued call, so no handler and no nested event loop ever runs
inside the socket's signal. Guarding the disconnect call alone is not
enough, since Qt still touches the socket after the lambda returns.

Add a regression test that reproduces the nested-event-loop hand-off.

* [Tests] Apply cmake-format to the single-instance test wiring

* [Client] Defer the single-instance socket teardown past the readyRead frame

The forwarded payload is emitted synchronously from inside the forwarding
socket's readyRead, which is what lets the modal "Join game?" confirmation
appear. A handler can spin a nested event loop, and the sending instance
exits as soon as it has the ACK, so the socket is disconnected and
deleteLater'd while that loop runs. Control then returns to this frame with
the socket already destroyed, and the inline disconnectFromServer() touched
freed memory.

Queue the teardown instead and guard it with a QPointer taken before the
handlers run -- building the guard afterwards is itself a use-after-free,
since QPointer dereferences the pointer it is constructed from. The payload
still reaches the handlers synchronously, so the confirmation is unchanged.

The regression test covers both halves: the handlers must run while the
socket is still connected, and the socket must not be torn down until the
readyRead frame has returned.

* Lint.

* [Client] Only delete the single-instance socket outside the readyRead frame

The auto-delete on `disconnected` was the remaining use-after-free. A
handler may spin a nested event loop (the modal "Join game?" box), the
sending instance exits as soon as it has the ACK, and so the peer closes
while that loop runs. deleteLater() issued from inside such a loop is not
deferred to the outer loop: Qt frees the object as soon as the nested loop
unwinds, which is still inside this readyRead emission, so Qt's own signal
dispatch and the handler lambda both keep using a freed socket. When the
notification happens to arrive late the bug hides, which is why this only
reproduced on some platforms.

Drop the auto-delete and let the queued teardown own the socket's lifetime:
it disconnects and then deletes, once the frame has unwound. The QPointer
guard was necessary but not sufficient -- it stopped us dereferencing a
dead pointer, not the socket being freed underneath the frame.

The test waits on the socket's destroyed signal instead of polling, so the
teardown assertion does not depend on platform event timing.

* [Client] Do not queue the single-instance socket teardown

The previous commit moved the deleteLater() off the disconnected signal
and into a queued invocation, believing that "queued" meant "after the
readyRead frame returns". It does not. A queued call is delivered by
whichever event loop is running, and the modal "Join game?" box runs a
nested one -- so the teardown was still executed inside the readyRead
emission, which is exactly the use-after-free it claimed to prevent.

Queueing was never necessary. Because nothing connects deleted/close to
the socket's disconnected signal any more, no nested loop can free it
while the handlers run, and emit filesReceived() is synchronous: once it
returns, the frame has unwound and the socket can be closed and deleted
inline. The QPointer stays, covering the one case that remains -- the
primary shutting down while the confirmation box is open, since
QLocalServer destroys its pending connections.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-10-02 21:19:02 +02:00
BruebachL
4ce2e8c3d3
[UserList] Fix join/spectate from the profile hover card doing nothing (#7383)
The games list in the profile hover card offered "Join game" and
"Spectate", but UserInfoPopup::joinGameRequested was only forwarded to an
identical UserListWidget::joinGameRequested signal that nothing connected
to. Clicking either entry sent no command, opened no tab and showed no
error.

Route the request to TabSupervisor, which owns the join flow. The popup's
games list spans every room the user is in, so the target room may not be
open yet; IntentJoinServerGame already joins the room first and retries,
which is why this goes through the intent rather than straight to
GameSelector.

Extract the intent construction that joinReportGame already had into a
shared joinGameByIntent helper instead of duplicating it.

Also stop disabling "Join game" for games that are full or already
running. GameSelector::joinGame() offers to join as a spectator in that
case, so a greyed-out entry read as a dead button; GameSelector's own
context menu never disables it either.

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-10-02 18:57:07 +02:00
BruebachL
750d5b3c21
[Client] Add option to use game time in game log timestamps (#7265)
* [Client] Add option to use game time in game log timestamps (#2202)

Game log timestamps showed the local clock time, which is meaningless in
a replay or in a log read after the fact — nobody can tell when a play
happened relative to the game.

Add a "Use game time instead of local time in game logs" setting. When
enabled, the message log stamps entries with the elapsed game time
(HH:MM:SS since the game started); regular chat views keep the local
clock.

* [Game] Move formatElapsedTime helper out of public slots (#2202)

The message log needs the HH:MM:SS formatter used by the game timer, but
it lives in GameState's public slots, tying a pure formatting helper to
the signal-slot machinery.

Move formatElapsedTime into a public section of GameState so the log can
call it directly, and add the missing trailing newline to game_state.cpp.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-10-02 17:30:48 +02:00
RickyRister
ffcb9094ca
[Replay] Fix cards accumulating in zone view when rewinding (#7357)
* [Replay] Fix cards accumulating in zone view when rewinding

* rename method
2026-10-02 17:17:34 +02:00
BruebachL
ecd955bc60
[Installer] Wait on the launching process so auto-updates cannot write over a mixed Qt runtime (#7372)
* [Installer] Abort instead of installing over leftover runtime files

The silent upgrade could leave a mix of old and new Qt DLLs next to the new
executables, which is what makes the next start fail with "The procedure entry
point X could not be located in the dynamic link library ...Qt6QuickWidgets.dll"
or "The code execution cannot proceed because Qt6Multimedia.dll was not found".

The check that was supposed to catch this probed only Qt6Core.dll and
Qt6Network.dll, so it was blind to the two files the reports name. It also only
warned and then installed anyway, so a caught problem was still compounded.

- Scan every file the purge targets with FindFirst, which takes the same
  wildcards the Delete/RMDir calls use, instead of two hardcoded names
- Widen IsExistingCockatriceInstall from Qt6Core.dll to Qt6*.dll so a directory
  left holding only some Qt modules is still recognised as an install
- Gate the scan on that, so a first-time install into a directory that happens to
  hold unrelated Qt5 DLLs is not turned into an abort
- Abort with a non-zero exit code instead of warning, so the new payload is
  never written next to files of the previous version
- Skip the message box on an unattended /S install, which would block on it
  forever, matching the two existing abort sites

* [Installer] Cancel a silent upgrade when the running-instance guard fails

The running instance guard is what stands between a locked install directory
and the mixed Qt DLL set that breaks the next start, but it failed open in two
places and let the install carry on anyway:

- powershell.exe could not be started: the check warned and returned
- the process lookup answered "could not determine": WaitForAppToClose returned
  on $R0 != 1, so "unknown" was treated exactly like "not running"

On a silent upgrade there is nobody at the keyboard to close the applications in
response to that warning, which is precisely the state in which the launcher was
still holding its Qt DLLs mapped.

- Treat "could not determine" as its own state rather than folding it into "not
  running", in all three places WaitForAppToClose inspects the result
- Cancel a silent upgrade in both cases; keep an interactive install on the
  existing warn-and-continue path, since refusing there would leave an installer
  that can never complete and the stale-runtime check still applies
- Route every abort through one AbortInstall so the dialog-vs-silent decision
  lives in a single place, and stop the two pre-existing warning dialogs that a
  /S install without /R would have blocked on forever

* [Installer] Wait on the process that launched the update installer

The in-app updater starts the installer and only then quits itself, so the
installer has to decide whether the launching process is still alive while it is
running. It did that by polling powershell.exe for an image name under the
install directory, which is a proxy for the real question - is the process that
started me still holding its Qt DLLs mapped - and a proxy that can answer "not
running" or "could not determine" while the process is very much alive. Either
answer let the install carry on into the mixed Qt DLL set.

Take /PID= from the command line and wait on that process handle directly:

- OpenProcess with PROCESS_SYNCHRONIZE only, which is all that is needed to wait
  and the least likely to be refused for an unrelated reason
- WaitForSingleObject for the same 60 s the name based check allows, so a client
  slow to flush its card database is not squeezed
- Treat an unopenable handle as "already exited", which is what it means for a
  dead process
- Cancel the upgrade if the launcher has not exited, rather than replacing files
  it is still using

Run it before EnsureAppsNotRunning, not after: it needs no powershell.exe, so it
is the one check that still works on a machine where the process lookup cannot
run, and it is what lets that case keep upgrading instead of being cancelled by
what was only ever a proxy for it. Keep an older client, which passes no /PID=,
on the existing path.

An older installer ignores /PID=, and /D= stays the last parameter either way.

* [Client] Pass our process id to the update installer

The updater starts the installer and only then quits itself, so the installer
begins replacing files while this process still has every Qt runtime DLL mapped,
and a mapped DLL cannot be deleted. It has to work out that it must wait, and
until now all it had was its own image name to go on.

Pass /PID= so it can wait on this process directly instead of guessing, and
cancel the update rather than replace files this process is still using. /D=
stays the last parameter, which NSIS requires.

* [CI] Fail the build when the installer is missing a Qt runtime DLL

The Windows payload is assembled by globbing every *.dll out of the build output
directories, so nothing but luck decides whether a module the client links
statically - Qt6Multimedia.dll, Qt6QuickWidgets.dll - reaches the installer at
all. A module that is left out is invisible until a user installs an update and
the client then refuses to start, with "The code execution cannot proceed because
Qt6Multimedia.dll was not found" or a missing procedure entry point in a
Qt6QuickWidgets.dll carried over from the previous version.

Derive the required list from SERVATRICE_QT_MODULES, COCKATRICE_QT_MODULES and
ORACLE_QT_MODULES, the same module lists the targets are actually built against,
so it cannot drift from what the binaries need, and write it next to the CMake
cache as required-qt-runtime.txt. Add the platform plugin, without which the
client cannot start at all.

Assert it in the existing 7-Zip pass that already rejects build-tree artifacts,
comparing exact relative paths so a same-named file in the wrong directory does
not satisfy a requirement, and normalizing the archive's own separators so it
does not matter which one NSIS chose. Missing manifest, missing entry or missing
7-Zip-independent failures all stop the build rather than warn, which is the
whole point: the previous check already failed hard for artifacts, and a warning
here is what let a broken package ship.

* [Client] Point the Windows qt.conf translations path at the real one

The Windows qt.conf carried Translations = Resources/translations, copied from
the macOS bundle block, but the Windows install has no Resources directory: the
.qm files are installed to COCKATRICE_WIN32_QM_INSTALL_DIR, which is
translations, and main.cpp looks for them at applicationDirPath() +
"/translations". Leave the macOS value alone, which is correct for it.

* [Installer] Close the /? help string so the installer compiles again

The last line of the continued MessageBox string ended in $\n\" instead of $\n".
NSIS reads \" as an escaped quote, so the string never terminated and makensis
rejected the script, which took the CPack packaging step down with it. The
Windows build failed on "Problem running NSIS command" before ever reaching the
new required-runtime check.

* [Installer] Give FindFirst the two arguments it takes

FindFirst takes the pattern and the output variable and leaves the search
handle in $0. There is no handle argument, and FindNext taking only a variable
is the giveaway: the handle has to live somewhere implicit, and it is $0.

The call passed three arguments, so makensis rejected the script and the CPack
packaging step failed with "Problem running NSIS command" before ever reaching
the required-runtime check.

This was not only a compile error. The handle variable was never populated, so
the stale-runtime check that commit 844a1a9b added could never see a match: it
probed $StaleFindHandle, which stayed empty, and the widened wildcard scan over
the purge set was a no-op. Pass the pattern, test $0, and close $0. No FindNext
is needed, because $R0 is a yes/no flag rather than a list.

Drops the now unused StaleFindHandle Var.

* [CI] Dump the NSIS compile log when packaging fails

CPack reports a failed makensis run as "Problem running NSIS command" plus a
path to NSISOutput.log, and that log exists only on the runner, so the actual
compile error never reaches the workflow output. An installer script that does
not compile is therefore undiagnosable from CI alone, which is exactly how a
three-argument FindFirst cost two build cycles here.

* [CI] Run the NSIS log dump under bash

The dump step used the default pwsh shell, where `find` resolves to Windows'
text-search find.exe rather than a file finder, so it died with "FIND:
Parameter format not correct" instead of printing anything. Pin shell: bash.

* [Installer] Test the error flag with LogicLib's ${Errors} condition

makensis rejected the script with "macro _If requires 4 parameter(s), passed
2" at the ClearErrors/OpenProcess pair. A ${If} condition cannot be a bare
$Var: LogicLib needs either a comparison or one of its flag macros, and a lone
$Errors splits into too few arguments for _If. ${Errors} is the documented flag
condition and is the same mechanism as the ${Silent} test already used here.

The dump step earned its keep - this took one run to locate rather than another
round of guessing.

* [Installer] Give FindFirst its handle back and stop blocking silent aborts

makensis: "FindFirst expects 3 parameters, got 2. Usage: FindFirst
$(user_var: handle output) $(user_var: filename output) filespec".

The previous commit was wrong about this. FindFirst does take the handle
explicitly, and FindNext finds it in $0 as a side effect of the FindFirst that
set it, which is why FindNext takes only a variable. The three-argument form the
stale-runtime check was written with was correct all along, and the two-argument
form broke it. Restored, along with Var StaleFindHandle.

That same commit inverted the interactive check in both abort paths.
`${If} $ReinstallMode = 1` with `${OrIfNot} ${Silent}` shows the message box
exactly when the install is the silent /R upgrade - the one case the comment
says must never block, since nothing will dismiss it and the installer hangs
until it is killed. Replaced with `${IfNot} ${Silent}`, so the box appears only
for an interactive install and a silent abort goes straight to SetErrorLevel.

Audited every construct added by this branch against master, which compiles:
all 9 LogicLib condition shapes have a master precedent, FindFirst is the only
FindFirst and is back to 3 arguments, Function/Section and block depth balance,
and the 12 lines with an odd number of quotes are the same pre-existing string
continuations master has.

* [CI] Strip the CR 7-Zip leaves on its paths before comparing them

The installer check reported all 14 required files missing, including
Qt6Core-era basics an 82 MB package obviously contains, so the payload was fine
and the comparison was broken. 7-Zip writes CRLF on Windows, sed 's/^Path = //p'
kept the \r on every line, and grep -qxF matches whole lines - so every entry
carried a trailing \r and nothing matched. The build-tree artifact check next to
it uses an unanchored grep -E and was unaffected, which is what made the two
checks disagree.

Reproduced locally against a 7z archive with CRLF injected: the old pipeline
matches nothing, the new one finds the present files and reports only the
genuinely absent one.

Also match case-insensitively. That is how the installed tree behaves - the
uninstaller clears both $INSTDIR\plugins and $INSTDIR\Plugins - so a
Plugins vs plugins difference in the archive is not a missing file.

* [CI] Print both sides of the installer path comparison, and strip its prefixes

The check still reported all 14 files missing after the CR fix, so the CR was
not the cause and the real difference between 7-Zip's output and the manifest
is still unknown. Inferring it again is what produced two wrong fixes in a row,
so on failure the check now prints the required list and the first 40 paths
7-Zip lists. A bare "missing" line cannot distinguish a missing file from a
misread listing, and twice it was the latter.

Also strip $INSTDIR/, $OUTDIR/, $PLUGINSDIR/ and ./ prefixes from the listed
paths. Written as separate -e expressions rather than one alternation: with |
as the sed delimiter an escaped \| is a literal pipe, so the alternation
version silently matched nothing at all.

Verified against a 7z archive fed through seven path shapes - plain, Windows
backslashes with CRLF, each prefix, lowercase plugins, and all of them combined
- each reporting only the file genuinely absent from the archive.

* [CI] Strip CR from the manifest side of the installer path check

The dump added last run settled it: 7-Zip listed 209 paths including Qt6Gui.dll,
Qt6Multimedia.dll and Plugins/platforms/qwindows.dll verbatim, as plain relative
paths with no prefix - so the archive side was already clean and the previous
fix was right to strip CR there but not sufficient.

The trailing CR was on the other side. CMake writes required-qt-runtime.txt
with the platform line ending, so on Windows $entry arrived as
"Qt6Gui.dll\r", which cannot whole-line match a clean "Qt6Gui.dll". Every
entry then read as missing while the listing held it, which is precisely what
the two sides of the dump showed.

Reproduced locally with a CRLF manifest against a 7z archive: without the strip
every entry is reported missing, with it only the genuinely absent file is.
Checked across plain paths, Windows backslashes with CRLF, $INSTDIR, $OUTDIR,
$PLUGINSDIR, ./ and / prefixes.

The literal $INSTDIR-style prefixes are written as [$] bracket expressions so
shellcheck does not read them as unexpanded variables; that clears the SC2016
that failed the format job, without a disable directive.

* [CI] Reword the bracket-expression comment so the linter stops parsing it

A comment whose first word is shellcheck is read as a directive, so
"# shellcheck does not read these as unexpanded shell variables" was parsed as
a directive named "does" and failed with SC1073 and SC1072 - which cascaded into
the SC1009 on the else clause above it. The SC2016 it was meant to document is
already gone now that the prefixes are written as [$] bracket expressions, so
the note only had to stop looking like a directive.

* [CI] Indent the diagnostic path lists without sed, and undo comment drift

SC2001 asks for parameter expansion instead of sed 's/^/  /'. Echoing
${manifest//$'\n'/$'\n  '} prepends the two spaces directly and keeps the
diagnostic block readable. Both occurrences are converted; only the one reading
from a here-string was reported, but the piped one has the same shape.

Also restore master's wording of the build-tree artifact comment. An earlier
edit inserted "in " ("not the install)" became "not in the install)"). It has no
functional effect, but it was an unintended change to a line this branch has no
reason to touch, and .ci/compile.sh now differs from master only by additions.

* [CI] Ship the Qt module runtimes the Windows package check requires

The installer check reports Qt6Concurrent.dll and Qt6ShaderTools.dll missing
from the Windows package. Both are genuinely absent, not another comparison
artifact - the manifest now matches 12 of 14 entries and the dual-list
diagnostic shows the other two are not in the archive at all.

Nothing deploys them. fixup_bundle copies only the DLLs it finds in
Cockatrice.exe's import table, and the *.dll glob only sees what was already
copied next to the executable. Qt6::Concurrent is linked but never imported,
because QtConcurrent::run and QFutureWatcher are header-only in Qt 6, so the
linker drops the import; Qt6::ShaderTools is skipped the same way. The package
has shipped without them and the client still starts, but that is luck rather
than intent, and required-qt-runtime.txt asks for both.

Install the runtime of every linked module explicitly so the installer contains
what the check demands. An unused DLL costs a few hundred kilobytes; a missing
one is what stops the client starting after an update, and stays invisible until
then.

Derive both the manifest and the install list from the imported target's TYPE,
so a statically linked Qt module - which ships no runtime at all - is skipped by
each and the two can never disagree. Scope the manifest to the client rather
than the union across server, client and Oracle: only the client is packaged on
Windows, and the other two module lists are subsets of the client's, so this
changes nothing today.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-10-02 15:15:29 +02:00
tooomm
61e01990c4
[CI] Use Doxygen v1.18.0 (#7374)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
* Bump doxygen version to v1.18.0

* Update Doxyfile config to v1.18.0

* bump batch size

* bump more
2026-10-02 03:41:29 +02:00
dependabot[bot]
6816c580cf
Bump doc/doxygen/theme from d52eafe to 46483f1 (#7371)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
2026-10-01 20:59:20 +02:00
BruebachL
1365f3cb49
[Avatar] Cap user avatar upload resolution at 1024x1024 (#7353)
Some checks failed
CodeQL / Analyze (cpp) (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
Build Desktop / Configure (push) Has been cancelled
Build Docker / Servatrice (arm) (push) Has been cancelled
Build Docker / Servatrice (x86) (push) Has been cancelled
Build Desktop / Debian 13 (push) Has been cancelled
Build Desktop / Debian 12 (push) Has been cancelled
Build Desktop / Fedora 44 (push) Has been cancelled
Build Desktop / Fedora 43 (push) Has been cancelled
Build Desktop / Servatrice_Debian 12 (push) Has been cancelled
Build Desktop / Ubuntu 26.04 (push) Has been cancelled
Build Desktop / Ubuntu 24.04 (push) Has been cancelled
Build Desktop / Arch (push) Has been cancelled
Build Desktop / macOS 13 Intel (push) Has been cancelled
Build Desktop / macOS 14 (push) Has been cancelled
Build Desktop / macOS 15 (push) Has been cancelled
Build Desktop / macOS 26 Debug (push) Has been cancelled
Build Desktop / Windows 10 (push) Has been cancelled
Build Docker / Publish multi-platform Servatrice image (push) Has been cancelled
* [Avatar] Cap user avatar upload resolution at 1024x1024

* Remove stylesheet.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-27 21:38:50 +02:00
BruebachL
2632f8a040
[Client/Server] Keep non-deck files out of the deck storage (#7352)
The local deck and replay browsers list every file in the folder, so a
stray file (e.g. a PNG screenshot of a deck) can be parsed as a garbage
plaintext deck, opened in the editor, and uploaded. The server also
accepts oversized deck payloads with only silent truncation.

Hide files that are not in a supported deck/replay format in the two
QFileSystemModel views (directories stay visible), skip them when opening,
reject them when uploading, and make the server reject deck uploads larger
than MAX_FILE_LENGTH instead of truncating them.

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-27 21:32:49 +02:00
BruebachL
fa8220df3c
[Client] Exit deterministically when launching the update installer (#7309)
* [Client] Exit deterministically when launching the update installer

* [Client] Only quit when the main window close is accepted

Wait for MainWindow::close() to be accepted before quitting after the
update installer is launched. When the close is vetoed (a running card DB
update, open games, or an unsaved deck), keep running and tell the user
the installer is already waiting, instead of exiting over their answer.

Also fix the comment so it does not claim settings are saved on the vetoed
path.

* [Client] Only quit on update when the shutdown actually ran

MainWindow::closeEvent has a static re-entrancy guard that returns early on a
second close event, leaving it in its default accepted state. DlgUpdate reached
from a nested event loop while a shutdown prompt was up could then get close()
== true with no shutdown work done (no settings flush, no tab shutdown) and tear
down the process behind an unanswered prompt.

Add MainWindow::closeForUpdate(), which reports false when a close is already in
progress or was vetoed, and gate the update-exit on it.

* [Client] Use QCoreApplication::exit() to leave on update

QCoreApplication::quit() only asks the application to quit and can be
interrupted if a top-level window refuses its close event. On the update path
the process must actually leave so its Qt DLLs are unlocked before the installer
starts replacing files - apply the exit(0) variant both on the accepted-close
path and the fallback where no MainWindow was ever closed.

* [Client] Warn that the update installer force-closes within a minute

Telling the user Cockatrice just 'stays open for now' is misleading: the
installer launched by #7308 waits only about 60 s for a graceful close before it
force-terminates the process, which would lose unsaved work. State the deadline
and tell the user to save and close before then.

* [Client] Warn that a vetoed update is cancelled, not forced

The warning on the veto path promised the opposite of what the installer
does: WaitForAppToClose in #7308 never force-closes cockatrice.exe
($AllowForceClose is 0), it aborts the update after 60 s so no unsaved data
is lost. A user who is told Cockatrice will be terminated in a minute hurries
to close it, only to find the update already given up.

State the real outcome instead: the installer waits about a minute for a
close, and the update is cancelled if that does not happen.

* [Client] Warn about a waiting installer without blocking it

CloseMatchingApps in #7308 sends the WM_CLOSE once and the wait loop only
polls IsAppRunning afterwards, but QMessageBox::warning is application modal
and QGuiApplicationPrivate::processCloseEvent drops spontaneous close events
for windows blocked by a modal widget. The veto path therefore swallowed the
installer's one and only request, so even a user who resolved the blocker and
closed the app still ended in the 60 s timeout.

Move the warning into warnInstallerIsWaiting() and show it modeless, so the
main window stays able to answer the installer while the message is up.

* [Client] Close the parent on the no-MainWindow update fallback

The fallback branch exits the process without closing anything, so it skips
the settings flush and tab shutdown this change exists to get in front of the
installer - the opposite of the fix if a call site ever stops passing a
MainWindow.

Close the parent widget first, like the code before this change did, and only
then leave unconditionally: the parent is the one that knows how to shut down
gracefully, and the deterministic exit is what the installer needs.

* Add override.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-27 20:05:41 +02:00
BruebachL
28d684f8aa
[Windows] Close running instances and purge stale runtime DLLs during update (#7308)
* [Windows] Close running instances and purge stale runtime DLLs during update

* [Windows] Scope running-instance handling to install dir and fix NSIS build

- Drop !include nsExec.nsh: nsExec is a plugin DLL, not a header, so
  makensis aborts before NSIS can build the installer.
- Match processes by image name and executable path under $INSTDIR via
  PowerShell, then close (WM_CLOSE) and force-stop only those PIDs, so an
  unrelated oracle.exe (Oracle DB) is never killed on a silent /R update.
- Gate the stale-runtime-DLL purge on $INSTDIR\cockatrice.exe existing, so
  a first-time install can't recursively delete an unrelated Plugins
  directory.

* [Windows] Make process detection rely on an explicit PowerShell exit code

* [Windows] Fix exit-code detection in the uninstaller process check

* [Windows] Pass the install dir to PowerShell without quoting pitfalls

* [Windows] Fail closed when a process path is unreadable

* [Windows] Only pre-check running instances for /R updates

* [Windows] Wait on a wall-clock 60 s deadline instead of a loop counter

* [Windows] Verify the force close and abort if the process survived

* [Windows] Defer silent updates instead of force-killing the client

* [Windows] Purge OpenSSL 3 DLLs on portable upgrades and broaden the stale-DLL guard

* [Windows] Replace the GetTickCount macro with a direct System::Call

* [Windows] Only close running instances whose path is readable

CloseMatchingApps reused the fail-closed detection filter, which counts a
process whose Path cannot be read (Access denied) as a match. The installer
runs elevated, so CloseMainWindow() would then be delivered to an unrelated
oracle.exe / servatrice.exe belonging to some other program, silently closing
a third party application during a Cockatrice update.

The set of processes we act on now has to be the strict one: a readable path
under $INSTDIR. Unreadable paths are simply not ours.

* [Windows] Match running instances by a readable path under the install dir

The Path read in the Where-Object filter raises "Access is denied" for
processes the installer cannot open, and both branches of the try/catch
turned that into a match. Together with mapping every non-zero nsExec result
to "running" this made the guard a hard stop rather than a fail-closed one: a
machine with an Oracle database service (or a servatrice service) running as
SYSTEM always has a process named oracle / servatrice whose path cannot be
read, so every silent update aborted after 60 s and every interactive install
re-asked for Retry indefinitely.

A process whose path cannot be read is not one of ours - the updater is
launched by the application it updates and therefore runs in the same user
session, and an interactive install is elevated and can read the paths of the
user's own processes. Both the detection and the close path now require a
readable path under $INSTDIR, which is what the comment above the helpers
already claimed.

* [Windows] Distinguish "could not determine" from "definitely running"

Mapping every non-zero nsExec result to "running" folds nsExec's "error"
return (powershell.exe could not be started at all - removed, blocked by
policy, refused by antivirus) into "Cockatrice is running". That state can
never clear: closing applications does not help, so silent installs aborted
after the full 60 s wait and interactive installs could not get past the
Retry/Cancel prompt.

IsAppRunning now reports three states (0 not running, 1 running, 2 could not
determine) and a new IsProcessLookupAvailable preflight checks that
powershell.exe can run at all. A broken PowerShell is reported to the user
once - with a message box, since a silent install would otherwise show
nothing - and the install continues instead of being blocked forever. Only a
definite "running" is now waited for.

* [Windows] Force-stop the console applications after the grace period

Servatrice and Oracle are console applications, so CloseMainWindow()
returns false and does nothing for them. Every silent update therefore
waited the full 60 s and then aborted, and every interactive install looped
on the Retry prompt, for any user running Servatrice out of the install
directory.

They now get a second shutdown path: once the grace period is over they are
force-stopped by PID with the same strict install-dir filter, since they hold
no unsaved user data. Cockatrice keeps the graceful-only treatment - a
force-kill there could destroy an unsaved deck - and still aborts instead.

* [Windows] Tell the user when a silent install or uninstall is aborted

On the /R path the installer runs with SetSilent and SetAutoClose, so the
DetailPrint before Abort went nowhere: the user clicks Help -> Check for
Client Updates, the download finishes, Cockatrice closes its window, and
nothing else happens - no update and no error, because the client launches the
installer detached and never looks at its exit code.

Every silent abort path now shows a message box telling the user which
application is still open and that they should close it and run the
installer again. The same applies to the silent uninstall, where the only
output is a log file nobody reads.

* [Windows] Treat leftover runtime files as an install marker for the purge

The guard on the stale-DLL purge only looked for the three executables and
uninstall.exe, so it skipped precisely the case the purge exists for: a
previous failed update whose uninstaller removed what it could and silently
skipped the Qt DLLs that a running instance had locked. None of the four
markers is left in that directory, and the stale DLLs then survive into the
new install.

A leftover Qt6Core.dll (what we ship today) or the qt.conf written by a
portable install now count as an install marker as well.

* [Windows] Run the section instance check before any file is deleted

Call EnsureAppsNotRunning sat after the portable-upgrade block, which had
already deleted the old cockatrice.exe, Qt*.dll and plugins directory. An
interactive install ("Portable mode" pointing at an existing portable folder)
has $ReinstallMode = 0, so the .onInit guard is skipped and locked files were
silently skipped - exactly the mixed-Qt-DLL state this change is about.

The check now runs right after SetOutPath, before the first Delete/RMDir in
the section.

* [Windows] Guard the old install before the old uninstaller deletes files

The interactive upgrade path removes the previous installation long before the
install section: componentsPagePre runs the old UninstallString on the
components page, i.e. before the directory page and before any call of ours
can check anything. Every uninstaller shipped before this change has no
un.EnsureAppsNotRunning, so for exactly the versions affected by #1576 the old
files were deleted while cockatrice.exe was still running and the new
installer had no say in it.

componentsPagePre now runs the same check before both ExecWait calls. At that
point $INSTDIR is still only the default, not the directory the old install
lives in, so the new EnsureOldInstallNotRunning guards the location recorded
in the registry instead. The helpers now work on a $GuardDir the caller
picks; the normal entry points keep seeding it with $INSTDIR.

* [Windows] Do not abort the section check past the point of no return

"nothing has been replaced yet, so aborting just defers the update" only holds
for the guard in .onInit. On the /R path AutoUninstallIfNeeded has already run
the old uninstaller, which RMDir /r's the whole install directory, and the
purge below it deletes the runtime files - so an Abort from the section would
leave the user with the old version uninstalled and the new one not installed
at all, which is strictly worse than the bug this change fixes.

The ordering now guarantees the check already passed: .onInit guards the /R
path before the uninstall, so the section only repeats it where that is not
already true, i.e. for interactive installs, where nothing of this installer
has been deleted yet and an Abort is the correct outcome.

As a safety net for whatever the purge could not remove (a still locked file,
or a check that could not run at all), HasStaleRuntimeFiles reports surviving
runtime files with a message box - also on a silent update, where there is no
other way to let the user know the install came out incomplete.

* [Windows] Only block the in-app updater with a message box

The message box on the silent abort would also fire for a plain /S install,
which is exactly the unattended case: a package manager (or a CI job) running
the installer with a leftover Cockatrice process would now hang forever on a
dialog nobody can see instead of aborting.

The box is therefore limited to /R, the path the in-app updater takes, where
the user just clicked Help -> Check for Client Updates, the client launches
the installer detached and never inspects its exit code, and the window is
about to disappear - without a message, the update would silently not happen.
Every silent abort also sets an error level now, so an unattended caller can
see that the run failed instead of having to read the log.

The silent uninstaller reports through log and exit code for the same reason:
it is run unattended by the /R updater itself (QuietUninstallString) and by
package managers. The installer that invoked it tells the user about the
incomplete result itself.

* [Windows] Treat an unset guard directory as undeterminable

$GuardDir is set by the three entry points into the check, but it is now what
decides whether a process counts as one of ours, and an empty string would
match every process that merely shares an image name - the same false positive
the strict path filter exists to avoid. Report it as "could not determine"
instead of running the check, so a future caller that forgets to set it warns
the user rather than closing or killing a third party process.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-27 20:05:41 +02:00
patricktsullivan
7c9dc303f8
[Game] Make 'Put top cards on stack until...' history case-sensitive (#7358)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 26 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
2026-09-27 09:50:05 +02:00
BruebachL
26a74c96c5
[Oracle] Stop linking the full client network stack into the oracle binary (#7354)
Some checks failed
CodeQL / Analyze (cpp) (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
Build Desktop / Configure (push) Has been cancelled
Build Docker / Servatrice (arm) (push) Has been cancelled
Build Docker / Servatrice (x86) (push) Has been cancelled
Build Desktop / Debian 13 (push) Has been cancelled
Build Desktop / Debian 12 (push) Has been cancelled
Build Desktop / Fedora 44 (push) Has been cancelled
Build Desktop / Fedora 43 (push) Has been cancelled
Build Desktop / Servatrice_Debian 12 (push) Has been cancelled
Build Desktop / Ubuntu 26.04 (push) Has been cancelled
Build Desktop / Ubuntu 24.04 (push) Has been cancelled
Build Desktop / Arch (push) Has been cancelled
Build Desktop / macOS 13 Intel (push) Has been cancelled
Build Desktop / macOS 14 (push) Has been cancelled
Build Desktop / macOS 15 (push) Has been cancelled
Build Desktop / macOS 26 Debug (push) Has been cancelled
Build Desktop / Windows 10 (push) Has been cancelled
Build Docker / Publish multi-platform Servatrice image (push) Has been cancelled
Co-authored-by: user.email <Bruebach.Lukas@bdosecurity.de>
2026-09-25 08:32:54 +02:00
BruebachL
e9cd9453ea
[Startup] Run the onboarding wizard before the first-run set dialog (#7349)
On a clean install, checkUnknownSets() fired before the onboarding wizard,
so the legacy 'all the sets have been enabled' welcome and the Manage Sets
dialog appeared first - with no card data there are no sets to manage yet.
Defer the set check until after the wizard closes, suppress its dialogs
while onboarding runs, and enable newly downloaded sets silently instead.

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-25 08:32:03 +02:00
BruebachL
bc26e19564
[DeckEditor] Highlight card name in VDE quick search after adding (#7350)
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-25 08:31:38 +02:00
BruebachL
329a6ba94c
[Installer] Restore cockatrice:// and .cod registration in the Windows NSIS installer (#7355)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 26 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
Co-authored-by: user.email <Bruebach.Lukas@bdosecurity.de>
2026-09-24 20:50:34 +02:00
BruebachL
0955388d50
[Client] Release room-join join markers when a command times out (#7348)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 26 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
RemoteClient's ping sweep deleted timed-out pending commands without
emitting finished, so TabServer's in-flight room-join markers were never
released: the room stayed silently unjoinable for the rest of the session
even though the connection stayed alive. Answer swept commands with
RespNotConnected instead of dropping them, matching the disconnect
cleanup, so every awaiter gets a deterministic terminal response.

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-24 18:40:37 +02:00
BruebachL
a9b38ec0e1
[UserList] Sort developers below judges (#7347)
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-24 18:40:24 +02:00
BruebachL
712ca61a9e
[Themes] Encode home backgrounds as JPEG to slim the repo (#7346)
The home background PNGs shipped since #7280 total ~67 MB and bloat
every clone and pull for other devs. Encode them as JPEG at quality 95
(matching resolution, visually lossless at 40-48 dB PSNR since the art
is noise-heavy renders PNG cannot compress) for ~79% size reduction.

The scheme-variant and theme asset resolution already probe .jpg, so
only the qrc entries needed updating.

Refs #7327

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-24 18:40:04 +02:00
BruebachL
eae9591a17
[Server/Client] Unify report categories (#7345)
* [Server/Client] Unify report categories into one shared source of truth

* Lont.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-24 18:37:54 +02:00
BruebachL
a1d8ce6165
[Accessibility] Bind card size sliders to Ctrl + Scroll (#7338)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 26 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
Ctrl+scroll over a card display now resizes the cards everywhere a card
size slider is shown, matching the standard way of resizing content.

CardSizeWidget learns to forward Ctrl+wheel events onto its slider via an
event filter that is installed on the display container and, when present,
on the scroll area's content widget so the resize intercepts the wheel
event before the view scrolls. The existing slider valueChanged wiring
then rescales the displayed cards.

Applied to the visual deck editor (per card group, covering flat and
overlapped layouts), visual database display, printing selector, sample
hand, visual and public deck storage, Archidekt previews and EDHRec card
displays.

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-23 21:03:34 +02:00
BruebachL
71ef36374d
[DeckEditor] Show card counts in Visual Deck Editor banners (#7339)
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-23 19:19:30 +02:00
BruebachL
7a41dfe175
[PictureLoader] Fix custom-folder pictures in subdirectories not loading (#7340)
Some checks failed
CodeQL / Analyze (cpp) (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
Build Desktop / Configure (push) Has been cancelled
Build Docker / Servatrice (arm) (push) Has been cancelled
Build Docker / Servatrice (x86) (push) Has been cancelled
Build Desktop / Debian 13 (push) Has been cancelled
Build Desktop / Debian 12 (push) Has been cancelled
Build Desktop / Fedora 44 (push) Has been cancelled
Build Desktop / Fedora 43 (push) Has been cancelled
Build Desktop / Servatrice_Debian 12 (push) Has been cancelled
Build Desktop / Ubuntu 26.04 (push) Has been cancelled
Build Desktop / Ubuntu 24.04 (push) Has been cancelled
Build Desktop / Arch (push) Has been cancelled
Build Desktop / macOS 13 Intel (push) Has been cancelled
Build Desktop / macOS 14 (push) Has been cancelled
Build Desktop / macOS 15 (push) Has been cancelled
Build Desktop / macOS 26 Debug (push) Has been cancelled
Build Desktop / Windows 10 (push) Has been cancelled
Build Docker / Publish multi-platform Servatrice image (push) Has been cancelled
CardPictureLoaderLocal compared the full file name (including extension)
of the custom-folder candidate against the extension-stripped base name of
each directory entry, so a CUSTOM subfolder image like
pics/CUSTOM/poker/1 of Hearts.png never matched and the card fell through
to the network. Compare the complete base names instead.

Add matcher tests covering CUSTOM subfolder resolution.

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-21 21:48:34 +02:00
BruebachL
e97ef5a617
[Style] Do not offer broken windows11 (#7341)
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-21 21:45:11 +02:00
BruebachL
fe83966087
[Theme] Revert default light palette and only include AppColors (#7342)
* [Theme] Revert default light palette and only include AppColors

* [Style] Also remove dark mode palette

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-21 21:45:05 +02:00
BruebachL
e01912d3c8
[Tests] Add manual picture loader benchmark against the real card hosts (#7288)
* tests: add manual picture loader benchmark against the real card hosts

(cherry picked from commit 588d71d3b9a8278ab98b847802908f322c1035b7)

* tests: address review on picture loader benchmark

- Sandbox via unique app/org names plus Linux-only XDG redirection, derive
  the warm-cache probe and data path from SettingsCache, and bail out when the
  temporary sandbox cannot be created.
- Run each pass with a fresh worker and shut workers down before reading the
  429 counters, so the redirect cache is persisted and no worker thread can
  outlive the stack-local counters or the installed message handler.
- Make s_activeCounters atomic and always forward log output when the previous
  handler is the built-in (nullptr) one.
- Validate --timeout-min, scale the cached-pass budget with --count, honour the
  first --url as the stress template, and add the missing trailing newlines.
- Zero-initialise SettingsCache members so the benchmark mock cannot
  dereference an indeterminate pointer.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-21 18:30:04 +02:00
BruebachL
9f66a098ac
[PictureLoader] Fix worker thread shutdown and cross-thread cache clearing (#7292)
* [PictureLoader] Fix worker thread shutdown and cross-thread cache clearing

clearNetworkCache() ran directly on the UI thread while the worker thread
owned the disk cache and redirect cache, racing cache reads/writes. Make it
a worker-thread slot invoked via a blocking queued call when the thread is
running, so the 'Cached card pictures have been reset.' message is truthful.

The worker thread was also never quit()/wait()ed: both destructors only
deleteLater'd their objects, so Qt warned 'QThread: Destroyed while thread
is still running' and leaked a running loop at exit. Wire the worker's
finished() signal to its own deleteLater() (canonical worker-object
pattern), add shutdownThread() to stop the loop, and let CardPictureLoader
destroy the QThread only after wait() has returned.

* [PictureLoader] Guard cache teardown and stop blocking the UI thread

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-21 18:30:03 +02:00
BruebachL
14acf3bf64
[PictureLoader] Add user-configurable per-host request caps (#7287)
* [PictureLoader] Add user-configurable per-host request caps

Picture downloads were throttled to a uniform 10 requests/second per host
with no way to tune a specific server. A rate-limited API host (Scryfall
caps at 10 req/s) can trip 429s during bursts, and CDN hosts with no rate
limit were throttled needlessly.

Introduce developer-owned per-host caps that users can only ever lower,
never raise, exposed in the download settings page:
- DownloadSettings::DEVELOPER_HOST_CAPS sets the ceiling per host
  (api.scryfall.com 9, cards.scryfall.io unlimited, others 10).
- A new hostRequestLimits setting stores user overrides in downloads.ini;
  clampHostRequestLimit() bounds them to [1, devCap] so a user can reduce
  api.scryfall.com to 5 but never raise it above 9.
- The picture worker seeds, halves on 429, and recovers its sustained
  per-host allowance against the effective ceiling instead of the global
  maximum, and skips per-host accounting entirely for unlocked hosts
  (cards.scryfall.io) while global pacing and 429 backoff still apply.
- The deck editor settings page gains one spinbox per known host, each
  clamped to its developer cap.

* [PictureLoader] Let unlocked hosts skip dispatch pacing; adjust limits per URL

Two refinements to the per-host request caps:

- Unlocked hosts (UNLIMITED_HOST_QUOTA, e.g. cards.scryfall.io) no longer
  wait on the 100ms dispatch pacing or consume the global per-second quota.
  dispatchQueuedRequest fires their queued requests back-to-back, bounded
  only by their 429 backoff window and Qt's per-host connection pool, so
  an unthrottled CDN is not artificially slowed.
- The deck editor download settings page replaces the static grid of one
  spinbox per known host with an "Adjust Rate Limit" toolbar action on the
  URL list. It picks the host out of the selected URL and clamps the entry
  against the developer cap table (including for user-added URLs).

Also fixes a review finding: resetRequestQuota could write the
UNLIMITED_HOST_QUOTA sentinel (-1) into the sustained per-host quota when a
host became unlocked mid-run, permanently poisoning its allowance. Stale
entries for unlocked hosts are now dropped, and the per-second seed is
clamped against the effective ceiling so a lowered limit applies immediately.

* [PictureLoader] Cap unlocked host bursts and adapt them to 429s

* [PictureLoader] Store per-host limits readably and show them per URL

* [PictureLoader] Make dispatch and rate-limit bookkeeping key on the real host

Addresses ZeldaZach's round-4 review nits:

- Dispatch now resolves the cached-redirect chain before the in-flight gate,
  so a redirect learned after a URL was queued can no longer bypass the
  MAX_IN_FLIGHT_PER_HOST cap and drain the whole queue onto the redirect
  target, which may carry its own developer cap. processSingleRequest does
  the same so the allowance math keys on the host that is actually hit.
- The per-host in-flight slot is released when the reply is destroyed (with
  the worker as the connection context) rather than on a 'finished'
  connection bound to the work object, so an aborted reply or a work object
  deleted while a reply is pending can never permanently shrink the fast
  path's concurrency.
- storeSettings only prunes limits for hosts with neither a URL nor a
  developer cap, so throttles on redirect targets (api.scryfall.com ->
  cards.scryfall.io) survive URL removal.
- Unlocked hosts are offered 0..UNLOCKED_HOST_LIMIT_MAX (50) in the rate
  limit dialog, matching clampHostRequestLimit() and the documented
  hand-editable range, so values written into downloads.ini are no longer
  silently rewritten on the next edit.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-21 18:30:03 +02:00
BruebachL
3d5eb84d81
[PictureLoader] Seed per-host allowances on demand and skip hosts in 429 backoff (#7286)
* [PictureLoader] Seed per-host allowances on demand and skip hosts in 429 backoff

The quota reset re-filled every host's remaining allowance to a full
MAX_REQUESTS_PER_SEC as soon as the queue had a request for it, so a server
that was just rate limited could be hammered again at full speed immediately
after (or even during) recovery.

Only seed a host's allowance the first time it is dispatched in the current
second, seeded from its reduced sustained quota, and skip hosts still inside
their 429 backoff window entirely, handing the entry back to its worker so it
can wait the backoff out or fall through to another source instead of parking
in the queue with no reply pending. Deferrals wait on the host that is
actually blocking the request (cached-redirect targets and the reply host of
a 429) rather than the current card URL's host.

Rebased onto network-requests/request-pacing, which absorbed the earlier
pacing and dispatch-guard commits, and reuses its updateTimerState idle-429
recovery plumbing.

* [PictureLoader] Hand backed-off entries back across the whole dispatch tick

Review fixes on the 429-backoff skip:

- Hand a backed-off queue entry back to its worker via
  scheduleDeferredRetry(host) instead of startNextPicDownload(), which was
  looping on the pre-redirect host every 100ms tick whenever the queued URL
  was a cached-redirect target whose redirect host was the one in backoff.
- Keep scanning the queue after a hand-back (--i; continue) so a backed-off
  host at the head no longer consumes the entire dispatch tick, stalling
  every healthy host further down.
- Emit imageRequestSucceeded(url) before makeRequest()'s cached-redirect
  backoff return so the status bar reclaims the deferred URL's widget instead
  of inflating the progress bar forever.
- Only spend a per-second allowance when makeRequest() actually issues a
  request: it returns nullptr when the redirect target is backed off and the
  work is handed back, so the slot would otherwise be wasted on a no-op.
- Gate the dispatch-time backoff skip on requestTouchesNetwork() so entries
  served straight from the disk cache (e.g. with downloads disabled) are not
  bounced into a 30-60s deferred retry for a host that 429'd earlier.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-21 18:30:03 +02:00
BruebachL
334a743953
[PictureLoader] Pace requests and run the throttle timers on the worker thread (#7285)
* [PictureLoader] Pace requests and run the throttle timers on the worker thread

Previously the whole backed-up queue was drained in a burst as soon as a
request was enqueued, sending up to 10 requests back-to-back and then
immediately re-filling the quota one second later. That hard-bursts a
rate-limited API like Scryfall's (10 requests/second) into a 30 second
lockout.

Introduce a pacing timer that dispatches a single queue entry every
100 ms, so the per-second allowance is used smoothly instead of in spikes,
and keep the quota timer at 1 second. Also fix both timers' thread
affinity: they are QTimer value members and so are not QObject children,
meaning moveToThread() on the worker left them on the main thread while
the slot code started them from the picture thread, which was a no-op that
also warned. They are moved to the worker thread explicitly and started
lazily from there.

* [PictureLoader] Guard dispatch timer restarts and drop dead request quota

* [PictureLoader] Reconcile quota-timer lifecycle with idle 429 recovery

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-21 18:30:02 +02:00
BruebachL
e2a4556546
[Filter] Restore exact, case-insensitive set code search (#7336)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 26 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
* [Filter] Restore exact, case-insensitive set code search (#7332, #7333)

* [Filter] Group set query suffix modes into a subrule

Address review feedback: keep the 'e'/'set' prefix in one place and move
the three suffix modes (exact, negated, release-date comparison) into a
dedicated choice-like rule so sv.choice() still dispatches on them.

Add coverage for the release-date comparison mode.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-21 14:22:05 +02:00
BruebachL
7a2492ac67
[Chat] Render room chat history usernames as live user tags (#7269)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 26 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
* Render room chat history usernames as live user tags (#1595)

Room chat history carries no user-level data, so history usernames were
rendered as fixed, zero-level tags: the moderation context menu was missing the
buddy/ignore and promote/demote entries and the stored name casing was never
corrected.

Resolve each history author against the online user list and, when found, build
the user tag with the real user level and name so the entry behaves exactly like
a live chat tag. Offline users keep the plain fallback.

- chat_view: look up history authors via getOnlineUser for the real level/name

* Fix offline history usernames getting a leading underscore

The offline fallback used "_" as the level placeholder, producing an
href of user://__NAME. The hover handler splits at the first underscore,
so interactions targeted a nonexistent "_NAME" user. Use level 0 so
offline history entries render as zero-level tags like before.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-21 09:13:55 +02:00
BruebachL
ef68a7bdcc
[Card] Add a setting for the language used in card search (#7314)
* [Card] Add a setting for the language used in card search

Localized card names and texts can now be searched too, controlled by a
'Language used in card search' toggle (English, selected card language, or
both) on the general settings page. Untranslated cards always keep matching
in English.

Removed the redundant local copy of the URL templates list in the localized
picture loader while here.

* [Card] Bind search language per FilterString instance

The peg parser rules are set up once per process, so the GenericQuery and
OracleQuery rule actions could not capture per-instance state. Instead of
storing the search language in a process-global that FilterString instance
methods mutate, hand it to the rule actions through a thread-local parse
context and copy it into the filter closures they produce. Card evaluation
in FilterString::check no longer reads any process-global state, and each
instance keeps the language it was built with; constructing one instance no
longer changes what unrelated instances (deck filter, drop-to-hand, zone
views) match against.

The card database display model stores the raw query and rebuilds the
FilterString when the search language changes, since the language is now
bound at parse time.

Add tests for the English/Selected/Both search modes, the English fallback
for untranslated cards, and per-instance language independence.

* [Card] Pass the card search language to deck and zone card searches

Wire the two remaining FilterString consumers to the configured card search
language so card-name matches respect it everywhere:

- DeckFilterString now takes the search language and mode, exposes them to its
  [[card name]] rule action via a thread-local parse context (same pattern as
  FilterString), and the engine's card database uses them for content search.
- ZoneViewZone reads the card language from CardsDisplaySettings when applying
  its search filter, and the reveal-zone widget re-applies the active search
  when the language setting changes.
- The deck-storage search re-runs its filter against the current card language
  setting, including live re-application when the setting changes.

Game-action targeting (DlgMoveTopCardsUntil) intentionally keeps evaluating
against English card names.

* [Card] Rename CardSearchLanguage to SearchLanguageMode

* [Card] Restore displaced namespace doc in card_localization.h

* [Filters] Pass CardSearchLanguage as a single struct

* [CardSearchModel] Match English and localized names in Both mode

Card names are stored in both English and localized forms, so search for
matches in both during the 'Both' search mode instead of checking only
the localized name.

* [CreateTokenDialog] Fetch cardsDisplay settings inside the apply lambda

Avoid capturing the raw settings pointer in the lambda: resolve the card
language and card search language from the settings cache at call time so
the values are always current when the search language is re-applied.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-21 08:50:48 +02:00
BruebachL
12299abcc8
[Doxygen] More picture docs (#7220)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 26 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
* [Doxygen] More picture docs

Took 12 minutes

Took 8 minutes

* [Doxygen] Move custom card pictures page into card_pictures subfolder

The actual cyclic dependency fix is converting the mutual @subpage
reference from custom_card_pictures to fixing_card_pictures into a
plain @ref, so the page hierarchy no longer loops back on itself.

* [Doxygen] Correct card-picture docs per review

* fix table layout

* [Doxygen] Deduplicate placeholder table and document image overrides

- Make custom_card_pictures.md the canonical home of the URL reference-point
  table; loading_card_pictures.md cross-references it through @ref instead of
  maintaining a second copy (unaddressed review comment).
- Switch the remaining @subpage custom_card_pictures to @ref in
  fixing_card_pictures.md so the page keeps its single parent under
  user_reference.
- Document the Image Overrides feature added in #7311/#7312 on the user page,
  loading_card_pictures.md and fixing_card_pictures.md: local override storage,
  the downloadedPics root lookup, exact file-name matching, and the set-folder
  vs flat export naming schemes.

* Update doc/doxygen/extra-pages/user_documentation/card_pictures/custom_card_pictures.md

Co-authored-by: tooomm <tooomm@users.noreply.github.com>

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
Co-authored-by: tooomm <tooomm@users.noreply.github.com>
2026-09-20 23:30:28 +02:00
BruebachL
6823d54c1e
[DeckShare] Browse and open public decks with loading, error and accessibility states (#7245)
* [DeckShare] Browse and open public decks with loading, error and accessibility states

Add a public-decks tab that lists decks published by other users using the
server's deck visibility feature, previewing each deck's banner card, color
identity, tags and upload time without downloading the deck list until the
user opens it.

- Add a public-decks tab with a shared-settings widget and a remote model
  that fetches the target user's decks and refreshes both automatically and
  on user request, with a loading indicator and a server-error message
  instead of a blank tab when the fetch fails or the connection drops
- Render each deck as a focusable preview tile whose banner, color identity,
  tags and upload time follow the existing Preview settings, with the deck
  name announced as the tile's accessible name and Space/Enter opening the
  deck, mirroring the shared-deck preview tile
- Show a message box when opening a public deck fails or arrives corrupted
- Publish and unpublish decks from the server storage toolbar and context
  menu, toggling the deck's own visibility bit (what the server persists)
  rather than the inherited effective state, and batch the visibility
  refresh until the last in-flight change is acknowledged
- Add the Show Upload Time setting so the tile's upload stamp can be hidden
  like the other preview details
- Update the retranslateUi wiring for the new public-decks tab and rename
  the share action tooltip from "Deck share" to "Share link"

* [DeckShare] Adapt deck upload to the server-derived banner and tag protocol

The server now derives the banner card and tags from the uploaded deck
list itself, so Command_DeckUpload only carries the client-computed color
identity. Drop the reserved banner/tag setters from the editor and storage
uploads, send the color identity on remote saves, and read tags from the
now-repeated ServerInfo_DeckStorage_TreeItem field.

* [DeckStorage] Refresh the visibility column with a guarded timer instead of a latch counter

A dropped visibility reply used to leave the pendingVisibilityChanges
counter permanently positive, so the Public/Private column never refreshed
again and nothing reset it on disconnect. A restartable single-shot timer
with a boolean guard re-reads the tree whenever publishes quiet down and is
stopped on disconnect, so a lost reply costs one stale refresh instead of
killing the column for the session.

* [DeckStorage] Summarize batch publish failures when the batch drains

Each rejected node stacked its own modal dialog, so publishing a ten-deck
selection against a rejecting server made the user dismiss ten dialogs one
at a time. Failures are now collected while the batch is in flight and shown
as a single summary when the visibility refresh timer fires; a reply that
lands outside an active batch still reports right away.

* [PublicDecks] Time out the loading state so a dropped reply cannot wedge the tab

loading only cleared in decksReceived, but the ping sweep can drop a pending
command without ever emitting finished, leaving the tab stuck on 'Loading
public decks...' and the refresh button permanently inert. A single-shot
timer started per refresh clears the latch and reports a timeout; the latch
also clears when the client disconnects.

* [PublicDecks] Escape remote-crafted text in tooltips and the tab title

Deck names and usernames come from other users' records and Qt renders
QLabel tooltips as AutoText, so a name like '<h1><table>...' parsed as
markup. Escape and bound the deck-name tooltip and escape the username
interpolated into the title label.

* [DeckStorage] Distinguish an inherited public state in the visibility column

The column reported the effective state while publishing toggles the node's
own bit, so a private deck inside a public folder already read 'Public' and
toggling appeared to do nothing (and toggling again silently unpublished
it). The cell now shows 'Public (inherited)' for that case and the tooltip
explains why.

* [PublicDecks] Run retranslateUi at construction and name the refresh button

retranslateUi was never called from the constructor, so the tooltips set
there were absent until a language change. Call it before the first refresh,
and give the icon-only refresh button an accessible name for screen readers.

* [PublicDecks] Keep the empty and status variants correct across language changes

retranslateUi unconditionally rewrote the empty label to the 'nothing
published' variant, stomping the 'no decks match your filters' choice
rebuildGrid had made, and a visible loading message stayed in the old
language. Let retranslateUi pick the same variant rebuildGrid does and
re-show the status so it retranslates.

* [VDS] Share one color-identity match rule between the two deck grids

The remote public decks model verbatim-copied updateColorMatches' switch,
down to the ExactMatch normalization and the fact that Includes/Excludes do
not normalize case. Extract colorIdentityMatches() next to the FilterMode
enum and call it from both so the subtle rule cannot drift.

* [DeckStorage] Drop the unused tree widget model accessor

The accessor handed the model out past the wrapper methods that exist to
keep it encapsulated, and nothing in the stack called it.

* [DeckShare] End the public-decks files with a trailing newline

keeps the final line's diff clean and stops clang-format CI from flagging
the files.

* [VDS] Reuse the shared quick settings widget for the public decks tab

PublicDecksQuickSettingsWidget was VisualDeckStorageQuickSettingsWidget
minus the folders, banner and tooltip controls, with identical wiring for
the shared keys and a version of the near-identical file to keep in sync by
hand. Fold the Show Upload Time checkbox into the shared widget, give it a
setPublicDecksMode() that hides the controls that do not apply, and delete
the duplicate.

* [PublicDecks] Drop stale deck-list replies after the loading timeout

A reply that lands after its own loading timeout (the reverse of the ping
sweep dropping the command) could stop the newer request's timeout timer
and repaint the grid with out-of-date data. Each refresh now captures a
monotonically increasing request id, and only the newest request's reply
updates the grid.

* [PublicDecks] Re-show a displayed failure message on language changes

The status label carries both the loading and the failure message, and
retranslateUi hid it whenever the model was not loading, so a language
change while a server-error or timeout message was on screen swapped it
for the (empty) grid. The tab now keeps the last failure text and
re-shows it when not loading, clearing it once a new refresh starts.

* [DeckStorage] Keep the visibility refresh armed until replies land

The single-shot drain was armed with the 500 ms delay at send time, so a
round trip slower than that drained before the server applied the change,
re-read the old state and never re-armed, leaving the column stale until
a manual refresh. The timer is now armed with the full network timeout at
send time (a lost reply still costs one stale refresh) and re-armed with
the short delay every time a reply lands.

* [DeckShare] Close public decks tabs when the client disconnects

TabSupervisor::stop() built tabsToDelete from the room and game tabs
only, so a public decks tab survived a disconnect, sitting with stale
contents and a refresh button that kept hitting the dead client. Its
values are now folded into the same cleanup.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-20 20:22:17 +02:00
BruebachL
8ca749c07d
[DeckShare] Open shared decks via links with a gated preview flow (#7244)
* [DeckShare] Open shared decks via links with a gated preview flow

- Serialized url-chain dispatcher in IntentUrlParser; queue-drained
  urlChainFinished(bool) drives the startup auto-connect fallback
- Open-shared-deck intent with sequential download state machine,
  15s per-item timeout, partial-success offer, livable Cancel via
  ApplicationModal dlg_login_prompt interactive fallback
- Preview dialog: download progress label, share vocab sweep,
  palette-highlight selection frame, Space/Enter keyboard toggle,
  NoFocus checkbox, double-click tile opens immediately
- Confirm-before-server-migration with one-shot restore to the
  previous server on failed/cancelled chains (statusChanged settle
  deferral), hostname-only identity comparisons
- Skip credential link when already connected; arrow-key navigation
  in FlowWidget; card glows use palette highlight
- Address code-review M1-M4 and UI/UX QA blockers 1-2

* [DeckShare] End the open-shared-deck files with a trailing newline

* [DeckShare] Forward a dependency's cancellation as the owner's own

* [DeckShare] Let intent chains opt into the link sign-in dialog

* [DeckShare] Track link-intent chains per-run so each can restore its own session

* [Settings] Match a server on the exact host and port when adding it

* [DeckShare] Confirm the share link's target server before opening a deck

* [DeckShare] Reformat the link sign-in intent constructor

* [DeckShare] Time the share-list round trip and backstop silently-destroyed intent chains

* [Client] Drain a single-instance payload before its handlers read the socket again

* [Client] Treat a busy single-instance primary as alive instead of stealing its socket

* [DeckShare] Keep arrow-key navigation between flow items inside a scroll area

* [Client] Skip the startup connection when a macOS URL launch owns the connection

* [Client] Redact share secrets from activation URL logs

* [Client] Make the link-connection gates port-aware and keyboard-safe

Second-pass review notes for the shared-deck link flow (Cockatrice#7244):

- FlowWidget arrow-key navigation is opt-in via addNavigableWidget, so
  combo/spin controls on the analytics flows keep their own arrow keys
- isConnectedTo and the open-deck/join-game preconditions compare the
  configured server port alongside the host, so a same-host/different-port
  link cannot resolve its share token or game id on the wrong instance
- the link sign-in dialog reuses an existing server entry's saved name
  instead of renaming it to the raw hostname
- skipStartupAutoConnect is cleared once the launch chain connects, so a
  later mid-session declined link cannot fire the startup fallback
- the plain-launch path of SingleInstanceManager no longer blocks on the
  primary's ACK
- link- and server-supplied text is html-escaped in the confirm prompts and
  shared-deck preview so markup cannot spoof the shown messages

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-20 20:22:17 +02:00
BruebachL
ba2900dcb9
[DeckShare] Create temporary share links for local and server decks (#7243)
* [DeckShare] Create temporary share links for local and server decks

* [DeckShare] Address review findings and harden the share flows

Gate every share entry point on login, de-duplicate the share-link and
color-identity logic behind DeckShareUtils and an injected querier, and
replace the silent tray/status-bar notices with always-visible dialogs.

- abstract_tab_deck_editor: explain that sharing requires a connection
  instead of silently doing nothing when logged out
- tab_deck_storage: disable the share action on disconnect, reject
  folder/deck mixes and the root folder with clear warnings, re-enable
  Create on every entry/response so a dropped connection cannot leave
  the button disabled
- tab_deck_storage_visual: same login gate for the context-menu entry,
  visible success/error dialogs, and a symmetric in-flight guard
- getDeckColorIdentity now takes a CardDatabaseQuerier, dropping the
  CardDatabaseManager singleton access and enabling unit tests

* [DeckShare] Fix share-link expiry build on the minimum-supported Qt

QTimeZone::UTC (the Initialization enum) only exists since Qt 6.7, so
Debian 12 and Ubuntu 24.04 (Qt 6.4) fail to compile the share-link expiry
handling in the share dialog and the two deck-storage tabs. Mirror the
existing games_model guard and fall back to Qt::UTC on older Qt.

* [DeckShare] Use the stable server client for the visual deck storage tab

* [DeckShare] Extract the share-creation response handling into DeckShareUtils

* [DeckShare] Drop includes left unused by the share-response extraction

* [DeckShare] Format share expiry with the locale-aware short format

* [DeckShare] Build share links with QUrl and QUrlQuery for percent-encoding

* [DeckShare] Replace the duplicate computeColorIdentity with the shared getDeckColorIdentity

* [DeckShare] Recover the share controls when the server never answers

* [DeckShare] Provide the full share hint in each plural form

* [DeckShare] Join the selected-count label with a non-translatable separator

* [DeckShare] Retranslate the share button tooltip with the storage widget

* [DeckShare] Forward retranslateUi to the visual deck storage widget

* [DeckShare] Let the share bar owners supply the hint text

* [DeckShare] End the share-related headers and sources with a trailing newline

* [DeckShare] Keep the settings include in the project include block

* [DeckShare] Include the network settings header used by the share timeout

* [DeckShare] Resolve the share theme icon through themePixmap

QPixmap("theme:icons/share") has no file extension, so ThemeManager::assetPath()
is bypassed and the pixmap is always null. Use themePixmap(QStringLiteral("icons/share"))
like every other toolbar action, so the .svg (and dark/light variants) resolves.

* [DeckShare] Keep the share selection consistent with the visible decks

Filtered-out previews are hidden but kept alive, so selectedFilePaths() counted
them in the share and the selection highlight. Only decks the user can see are
now shared, and a deck that stops matching the filters is deselectd as the deck
pass runs, keeping the %n count and the highlight in sync with the screen.

* [DeckShare] Abandon an in-flight tree share on cancel

Leaving share mode never stopped the timeout timer, and a late response still
ran shareFromTreeFinished, copying the link and announcing success for a share
the user backed out of. Stopping the timer and tracking the outstanding request
by sequence number means a stale reply (or a timed-out one) after cancel is
ignored, and cancelling + re-entering share mode can no longer confuse the two
requests.

* [DeckShare] Abandon an in-flight tile share on cancel

exitShareMode() left shareTimeoutTimer running and did not abandon the pending
Command_DeckShareCreate, so a timer pop or a late success still reported the
share after the user cancelled. Stop the timer and ignore stale responses via a
sequence number, mirroring the tree tab.

* [DeckShare] Wire the status-changed handler after shareBar exists

handleConnectionChanged() dereferences shareBar->isVisible(), but the connection
was set up before shareBar was constructed and shareBar had no in-class
initializer. On any status change delivered before construction the slot read an
indeterminate pointer. Seed the connection (and the initial share availability)
after shareBar exists and give shareBar a = nullptr initializer.

* [DeckShare] Explain why a blank deck cannot be shared

A blank deck exited the share flow silently. The menu only disables the entry
via setSaveStatus(), a different predicate, so the path is reachable (e.g. add a
card and remove it again). Mirror the not-logged-in branch with a short
information dialog.

* [DeckShare] Restore the banner-text doc comment

Re-add the doc block above refreshBannerCardText() that was removed as part of
the share-selection work; it documents the coupling to refreshBannerCardToolTip.

* [DeckShare] Resolve the stable server client in the deck editor gate

actShareDeck went through tabSupervisor->getClient(), which hands back a
LocalClient while an offline game is running. LocalClient never sets its status,
so a logged-in user could not share from the deck editor during a local game,
and got a misleading "You must be connected" message. Expose the supervisor's
stable remote client and use it for the gate and the dialog, matching the other
share tabs.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-20 20:22:16 +02:00
BruebachL
a289d61765
[VDS] Decouple tag filter and fix reordered-chips crash (#7242)
* [VDS] Decouple tag filter and fix reordered-chips crash

* [VDS] Address review: dead code, chip reparenting, filter signal and sort fast-path

* [VDS] Address second round of review nits

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-20 20:22:16 +02:00
BruebachL
073ec29c4d
[Server] Add deck share links and public deck visibility (#7241)
* [Server] Add deck share links and public deck visibility

* Address server review comments for deck share links

* Document transaction teardown in deck share rollback paths

* Address second round of deck share review comments

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-20 20:22:16 +02:00
BruebachL
a5e94d8a4f
[Build] Keep Windows installs free of build-tree artifacts (#7316)
* [Build] Keep Windows installs free of build-tree artifacts

Several Windows packaging gaps could leak Visual Studio CMake build
output into the installed application or the NSIS installer:

- The per-app DLL sweep used
  ${CMAKE_BINARY_DIR}/${PROJECT_NAME}/${CMAKE_BUILD_TYPE}, which is
  empty on multi-config generators, collapsing the recursive
  DIRECTORY install into the whole build tree (containing *.dir,
  *_autogen, .qt, .qsb, x64, ...). Point it at the real per-config
  output with $<TARGET_FILE_DIR:...> and exclude build artifacts.
- install(FILES ${OPENSSL_INCLUDE_DIRS} ...) tried to install OpenSSL
  include directories as files. CMake refuses this
  ("install FILES given directory"); it only slipped through CI
  because the vcpkg OpenSSL config leaves the variable empty. Remove it;
  fixup_bundle already ships the OpenSSL runtime DLLs.
- The NSIS uninstaller only deleted *.exe/*.dll and a few known files,
  so build-tree leftovers survived an uninstall/reinstall cycle. Wipe
  the whole directory tree instead.
- Add a Windows CI gate that lists the packaged installer with 7-Zip
  and fails the build if any build-tree artifact path is found.

* Update .ci/compile.sh

Co-authored-by: tooomm <tooomm@users.noreply.github.com>

* [Build] Rework Windows installer artifact exclusions per review

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
Co-authored-by: tooomm <tooomm@users.noreply.github.com>
2026-09-20 20:21:52 +02:00
tooomm
b3c426cd43
Alphabetical ordering of Qt modules/packages (#7334)
* ordering

* Update docker-release.yml

* Revert "Update docker-release.yml"

This reverts commit e908d92184.
2026-09-20 19:24:55 +02:00
tooomm
c97e1c4149
Add back dir location (#7335) 2026-09-20 17:52:56 +02:00
tooomm
ec41c103d1
[CI] Utilize version resolution in install-qt-action + cache with full version key (#6993)
* Direct wildcard resolution in action + cache with version key

* add back space

* Delete .ci/resolve_latest_aqt_qt_version.sh

* Disable Qt slimming and manual caching (use build-in fat caching)

* cleanup

* Re-add resolve_latest_aqt_qt_version.sh
2026-09-20 16:53:10 +02:00
tooomm
d9cd2d1750
[CI] Only save caches from master (#7186)
* Save caches only from master

* Save cache only from master

* Update desktop-build.yml
2026-09-20 16:15:24 +02:00
tooomm
1405952f1b
Space quantity + unit (#7328) 2026-09-20 06:44:31 +02:00
BruebachL
db2e159dca
[Game] Allow judges to enter any game regardless of restrictions (#7315)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 26 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-19 10:39:52 +02:00
BruebachL
cb19922e55
[DeckList] Extract deck metadata element readers (#7325)
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-19 10:35:05 +02:00
BruebachL
ec39ec611b
[DeckList] Extract deck root seeking and body reading in XML load (#7324)
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-19 10:35:05 +02:00
BruebachL
662f1b79cc
[DeckList] Extract board-zone pruning in node deletion (#7323)
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-19 10:35:05 +02:00
BruebachL
eb1e34c5a6
[DeckList] Extract recursive card traversal helpers (#7322)
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-19 10:35:04 +02:00
BruebachL
dce9efcaa3
[DeckList] Extract deck-hash encoding helpers (#7321)
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-19 10:35:04 +02:00
BruebachL
c45cb8ac32
[DeckList] Extract deck-node sort helpers (#7320)
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-19 10:35:03 +02:00
BruebachL
1a6d9d7749
[DeckList] Extract card parsing from zone XML reader (#7319)
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-19 10:35:03 +02:00
BruebachL
faffb5a837
[DeckList] Extract sideboard-plan move parsing into a helper (#7318)
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-19 10:35:03 +02:00
BruebachL
9acb9739b2
[Client] Fix spurious server room join error (#7259)
* [Client] Fix spurious server room join error

The server replies RespContextError when a join command is received for a
room that connection is already registered in. The client was sending such
duplicate joins in benign situations - double-clicking to join a room, or
clicking a room the selector was already auto-joining - and answered them
with a modal telling users to restart the client.

Joins for the same room are now deduplicated while one is in flight, and a
remaining RespContextError is healed by leaving and rejoining the room so
the tab appears without a client restart. Error dialogs are only shown for
user-initiated joins, so failed auto-joins no longer spam critical popups.

* [Client] Bound stale-membership room join heal to one attempt

The RespContextError heal (leave + rejoin) previously recurred
unconditionally, so a server that kept returning RespContextError for a
reason other than stale membership would loop forever. Track room ids
that already received a heal and surface the error dialog after one
attempt instead of retrying indefinitely.

* [Client] Scope room-join heal guard to one join attempt

* [Client] Hoist room-join heal guard lookup out of response switch

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-19 09:56:09 +02:00
BruebachL
59dd052143
[DeckEditor] Restore auto-scroll when adding cards (#7317)
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-19 09:55:30 +02:00
RickyRister
51365960fe
[Client] Create custom pawn for dev role (#7295)
* [Client] Create new pawn for dev role

* lighten color
2026-09-18 10:58:10 -07:00
BruebachL
7b39cf98d5
[Themes] Add identity default palettes for the image themes (#7280)
* [Themes] Add identity default palettes for the image themes

The Fabric, Leather, Plasma and VelvetMarble themes shipped only zone art and no palette, so their chrome fell back to the bare OS palette. Each theme now ships light and dark curated defaults written in the palette editor's own conventions, with [AppColors] so the home-tab buttons keep the theme's identity over static backgrounds.

- Add palette-default-light.toml / palette-default-dark.toml for the four themes; scheme resolution follows the OS since the themes declare no color scheme.
- Tint the window/base/button chrome and bevels towards each theme's identity: navy cloth (Fabric), black-brown with brass accents (Leather), electric violet with cyan sparks (Plasma), charcoal velvet with slate marble (VelvetMarble).
- Fill [AppColors] AccentStrong / AccentSoft per theme so the home-tab button gradient matches instead of falling back to the default greens.
- Derive Disabled and Inactive groups with the same conventions as PaletteGenerator::fromAccent.

* [Themes] Add home backgrounds for the image themes

* [Themes] Ship Fusion style + scheme backgrounds for the image themes

Address PR review:
- Add theme.cfg ([Style] Name = Fusion, ColorScheme = System) to Fabric,
  Leather, Plasma and VelvetMarble so their curated palettes actually apply.
  Without it the native style paints button chrome from the OS (windowsvista
  on Windows has no dark mode), making the dark palettes' light ButtonText
  unreadable on light buttons.
- Leverage the scheme-variant asset resolution: register home-dark.png and
  home-light.png in resources and add them to the qrc so the built-in home
  background also flips with the palette instead of staying static.
- Fix Leather [Palette.Inactive] Accent, which copied the Active Highlight
  color instead of the theme accent (dark #4a5f8f -> #c9995a, light
  #34508c -> #a5712f) in both palette files.

* [Themes] Align light plasma home background with the dark variant

Recolored the Plasma light home background to mirror the dark one:
brighter violet/cyan blooms, vivid azure spark arcs, and higher accent
saturation against the same pale-lavender key. Brightness is unchanged
so it still reads as a light scheme.

* [Home] Add option to disable the home tab background dim

Adds a 'Dim the home tab background' checkbox to the Home tab settings
page (Appearance). When unchecked, HomeWidget skips the translucent
black overlay it paints over the whole background. Default is on,
preserving current behavior; the home tab repaints live on change.

* Update leather backgrounds

* Update velvet marble backgrounds

* Update light plasma background

* Update light fabric background

* WIP [UI] Theme-aware onboarding banner with frosted light mode

Banner colours now derive from palette tokens at ~60fps (tick-driven,
equality-guarded setters) so scheme switches and live accent-picker
previews apply instantly:

- dark stages: byte-for-byte the original treatment (near-black stage
  from window hue, Highlight accent, white centre halo, vignette 0.62)
- light stages: pastel accent-hue wash instead of a neutral grey copy,
  brightness-lifted accent for additive glow legibility, deep-Highlight
  halo (uGlowColor) instead of white blowout, gentler vignette
  (uVignetteMin 0.88) so corners don't go muddy
- black logo silhouette variant selected on light stages
- theme picker preseeded with brand green (brand_colors.h single source)

WIP notes for next session:
- real-pixel wizard screenshot check still pending (headless capture
  exists: Xvfb :77 + isolated XDG_DATA_HOME; shader vs fallback pixel
  analysis not finished)
- user plans separately: promote Fusion to default theme, Default -> system

* [Themes] Align Fusion accent tokens with the SVG brand gradient

Align AccentStrong (#139740) and AccentSoft (#c9fd62) and the linked
Link/Accent roles with cockatrice.svg's linearGradient4265-7-8 stops so the
identity gradient used by the logo emulation matches the static art the icon
shipped.

* [Onboarding] Draw the banner logo as a static gradient plate

Replace the black/white logo tint switch with a ShaderEffect plate that
repaints the SVG's brand gradient (light AccentSoft -> dark AccentStrong
along the baked-in userSpaceOnUse axis) clipped to the full-color logo's
alpha silhouette, with the white highlight path overlaid on top — matching
the home widget's QPainter composite. The plate is static: no glow or
breathing. Brand colors flow from BannerShaderConfig's new brandStrong/
brandSoft pair instead of the removed logoDark flag, and the background
motifs get a touch more accent so the mark keeps its coloured surround.

* [Home] Draw the featured logo as a theme gradient composite

Repaint cockatrice.svg in Qt instead of showing the baked-in static art:
fill the full-color logo's alpha silhouette with the same brand gradient the
banner plate uses (light AccentSoft grading to dark AccentStrong along the
SVG's userSpaceOnUse axis), then overlay the white highlight path. Renders
an explicit QPixmap so the mark stays crisp at the 200px display size, and
re-seeds it on theme/palette/appearance changes so it never goes stale.

* [Resources] Drop the unused black logo asset

No consumer remains after the banner's logoDark toggle was replaced by the
static gradient plate (unit-tested in d7529e0c6a), so remove
cockatrice-logo-black.svg and its qrc entry.

* [Onboarding] Seed the theme picker from the theme's identity accent

Replace the hardcoded brand-green preseed with the shipped theme's own
AccentStrong (Plasma seeds violet, Fusion green), resolved from the default
palette so auto/user-generated palettes can't mask it, and re-seed whenever
the theme changes so the swatch never goes stale. Also consult the shipped
palette in maybeAutoGeneratePalette so scheme flips don't regenerate a fresh
palette over curated theme colors.

* Regenerate zone assets for Fabric, Leather, VelvetMarble themes

Align zone textures with each theme's palette identity:
- Fabric: linen weave in navy/steel tones (was generic green/blue/red/gray)
- Leather: grain texture in warm brown/amber tones
- VelvetMarble: smooth charcoal marble veining (replaces .jpg with .png)

All zones now have dark + light scheme variants for OS color scheme
adaptation. Plasma zones deferred to separate iteration.

* Regenerate Plasma zone assets: horizontal-hand layout, seamless tiling

Bake each zone's phase from its true world position in the horizontal-hand
layout (player 0, stack 172, table 280, hand 172+406) so the diagonal sheen
continues continuously across zone boundaries. All zones share one
mathematically tileable (1,1) diagonal at frequency 3; playerzone gets a
distinct blue-violet identity and the light scheme gets extra contrast.

* Regenerate VelvetMarble zones: classic Perlin marble, seamless tiling

* Regenerate VelvetMarble zones: domain-warped fractal veins, stone grain

* Promote Fusion to default theme, rename Default to System

Fresh installs and new profiles now default to the Fusion (dark) theme
instead of the platform-native theme. The old "Default" theme is
renamed "System" to better describe its purpose — using the OS-native
Qt style (windowsvista, macOS, etc.).

Existing users who had "Default" selected are automatically migrated
to "System" so they keep their platform-native styling. Users with
an empty or invalid theme name now fall back to Fusion.

* Move checkbox.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-18 15:16:07 +02:00
BruebachL
6c1d1c7b58
[Client] Add [AppColors] application palette roles (#7279)
* [Client] Add [AppColors] application palette roles

The home-tab buttons' gradient over the static theme background was hardcoded, and accent-derived fallbacks could not be themed or edited: QPalette's role set is closed, so any application-specific color has to live in Cockatrice's own palette layer.

- Add an AppColor::Role enum (AccentStrong / AccentSoft) stored on PaletteConfig and round-tripped from palette-<scheme>.toml under a new [AppColors] section.
- Cache the applied app colors in ThemeManager and expose appColor(Role) with a palette-accent-derived fallback; emit paletteChanged() from applyStyleAndPalette so previews, scheme switches and OS dark mode repaint palette-driven widgets.
- Fill both app roles in PaletteGenerator::fromAccent and surface them as a dedicated section in the palette editor.
- Drive the home-tab buttons from appColor() whenever the background source is the theme (any theme, not just built-ins).
- Ship AccentStrong / AccentSoft values in the Fusion and Default default palettes so the static home-tab buttons keep their classic greens.

# Conflicts:
#	cockatrice/src/interface/widgets/general/home_widget.cpp

* [AppColors] Address review comments

- PaletteEditorDialog::onSave(): compare whole PaletteConfig (colors and
  appColors) so a change to only AccentStrong/AccentSoft writes the file;
  add PaletteConfig::operator==.
- appColor(): derive both roles from QPalette::Highlight unconditionally.
  The Fusion palettes pin Accent to near-Window values, and QPalette::Accent
  only exists on Qt 6.6+, so keying on it made identical themes render very
  differently across Qt versions.
- themeChangedSlot(): merge the theme default's [AppColors] into a custom
  palette that predates the section instead of all-or-nothing per file;
  hasPalette() now counts an appColors-only file as a palette.
- Add Default/palette-default-light.toml so the Default theme's Light scheme
  keeps the classic greens instead of falling back to the OS accent.
- home_widget: restore the isBuiltInTheme() half of the Automatic condition;
  non-built-in themes extract button colors from their own background art.
- palette_grid_widget: use appEnum.value(i) for the role cast (3 sites),
  append appHeader to headerLabels, fix the 'Lighted' typo.

* [Themes] Route theme writes to the user themes directory

setColorScheme()/setStyleName() and the palette editor wrote directly to
the resolved theme directory, which for built-in themes is the read-only
system (install) location. Changes therefore landed in the install dir and
were lost on upgrade.

Add ThemeManager::writableThemeDir(), which always resolves to the user
themes directory, and route all theme writes through it. The palette editor
reuses the same helper, dropping its private writability probe.

* [Home] Replace 'Automatic' button color with explicit theme colors default

The Automatic option gated on isBuiltInTheme(): built-in themes used the
theme's accent colors, while non-built-in themes extracted colors from
their own background art. That made the result depend on the theme's
origin rather than what the user actually sees.

Remove Automatic and expose two explicit choices: 'From theme colors'
(always the theme's identity accents, now the default) and 'Extract from
background' (always sample the painted background). Drop the now-unused
isBuiltInTheme() helper.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-18 15:16:06 +02:00
BruebachL
456db56058
[PrintingSelector] Add Image Overrides submenu with hover preview (#7312)
* [PrintingSelector] Add Image Overrides submenu with hover preview

* [PrintingSelector] Address review comments

- Move QAction/QMenu forward declarations after the includes
- Use the renamed installPrintingOverride API and deleteAllLocalOverrides statically
- Drop the flavorName usage; Cockatrice does not use that field anywhere yet
- Extract the Load Custom Image handler into loadCustomImage()
- Make the preview size/offset constexpr and drop the redundant pixmap copy
- Extract the preview placement into a previewPositionNear() QPoint helper

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-18 15:03:09 +02:00
BruebachL
cca4af0ec7
[PictureLoader] Add local override storage and resolution with matcher tests (#7311)
* [PictureLoader] Add local override storage and resolution with matcher tests

* [PictureLoader] Address review comments

- Make deleteAllLocalOverrides static; it does not touch instance state
- Drop the now-unused hasCustomArt dead code
- Rename the override install methods to installPrintingOverride /
  installPrintingOverrideOnLoad

* [Tests] Give loader matcher tests a writable HOME in CI

Under GitHub's docker runner the process uid has no passwd entry, so HOME
resolves to '/' and the test-mode qttest data dir cannot be created.
SettingsCache's QSettings then drops every write, getPicsPath() comes back
empty, and the loader searches a blank path. Point HOME at a QTemporaryDir
for the duration of the run.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-18 15:03:08 +02:00
BruebachL
e11c915a0c
[Servatrice] Detect MySQL strict mode on startup and exit early (#7251)
* [Servatrice] Detect MySQL strict mode on startup and exit early

* Update servatrice/src/servatrice_database_interface.cpp

Co-authored-by: tooomm <tooomm@users.noreply.github.com>

* [Servatrice] Treat failed strict-mode check as boot error

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
Co-authored-by: tooomm <tooomm@users.noreply.github.com>
2026-09-18 15:02:45 +02:00
BruebachL
1c93309952
[Client] Show localized card names, texts and pictures (#7294)
* [Client] Show localized card names, texts and pictures

Localization wiring now runs end to end: the oracle importer collects
foreignData for the configured language and the client renders it.

- [Oracle] Import localized names and rules texts for the selected cardLang
  - single-face cards store their foreignData name and full text
  - multi-face (split/adventure/aftermath/prepare) cards collect the joined
    name once and join each face's translated text with the same separator
    as the English merge; an incomplete translation falls back to English;
    the joined text follows the same highest-priority-set policy as the
    single-face path and is only collected when localization is enabled
  - the wizard switching languages re-imports the card database

- [Client] Display localized card info throughout the client
  - card info text/picture widgets and the game board re-render on language
    change
  - pictures resolve cardLang art through Scryfall's named endpoint using the
    localized name, falling back to id-based art when no match exists
  - deck editor keeps canonical English names as card identity (EditRole)
    while showing localized names (DisplayRole), so decks and wire names
    stay stable

- [Card] Add CardLocalization-backed name/text lookup and cards.xml v4
  localization elements with a bounded-size translation cache

- [Tests] Cover oracle foreignData import (incl. multi-face joins, priority
  and fallback paths), XML v4 localization parsing, deck model localized
  display and the language-aware settings default

Existing installations need to re-run Oracle to see translations: localized
data only lands in cards.xml when the Oracle app is started with the
preferred language selected — launch the separate "Oracle" program that
ships with Cockatrice, pick the language in the wizard and let it re-import
the card database.

The client's database cache (cards.xml.cache) is invalidated by the cache
format bump and the source-hash checks, but a cache written before the
re-import can still hold English-only entries (the hash uses file size and
mtime, so a same-size/same-timestamp rewrite may be served as-is); delete
cards.xml.cache and relaunch if no localized names/texts show up after
re-importing.

* [Card] Pass localized card names and texts into CardInfo construction

Address review: instead of constructing the card and then calling
setLocalizedName/setLocalizedText (which emit a cardInfoChanged signal per
language), both constructors, both newInstance overloads and their callers
(cards.xml v4 parser and the binary cache reader) now pass the localized maps
as constructor arguments.

* [Client] Rename LocalizedCard:: helpers namespace to CardLocalization

The namespace now matches its header file name, as the review pointed out;
LocalizedCard reads more like a class or struct. Callers (card info text
widget, board card name rendering) are updated to match.

* [Client] Drop unused info member from the card info text widget

The CardInfoPtr member was only ever initialized to nullptr and never read;
remove it together with its initializer.

* [PictureLoader] Add the localized picture URL explicitly, not implicitly

Address review: silently prepending the Scryfall named-picture URL to the
download list whenever a non-English card language was active was surprising,
consumed quota per card when it failed, and could grab the wrong (canon) art on
name collisions, with no way to turn it off.

The insert is now opt-in and user-controlled: changing the card language adds
the template to the top of the download URLs once (persisted, documented in the
re-import prompt, and editable/removable in the deck editor settings), while the
picture loader no longer injects it at request time.

* [Card] Show card languages in the same native (English) format as the UI

Address review: the card text & images language dropdown listed bare native
names, some in inconsistent lowercase (e.g. "čeština", "español de España"),
which makes the languages easy to mix up for users that do not read the script
(e.g. 日本語 vs 한국어). It now mirrors the UI language dropdown and always pairs
the native name with its English name (e.g. "Deutsch (German)",
"日本語 (Japanese)"), using the same fixed casing.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-18 12:03:07 +02:00
BruebachL
5ace88c111
[DeckList] Extract deck metadata XML serialization (#7306)
* [DeckList] Extract deck metadata XML serialization

DeckList still serialized its metadata inline: a ten-branch
readElement dispatch and a static writeMetadata that duplicated the
tree plumbing. The metadata arms (name, comments, format, timestamp,
banner card, playmat, tags) move to DeckListMetadataXml free
functions over the Metadata struct, leaving DeckList::readElement a
thin dispatcher between metadata, zones and sideboard plans. The
playmat clamping helper moves along with the element that uses it.

* [DeckList] Make deck metadata XML serialization instance methods

* [DeckList] Inline deck metadata XML serialization

Fold DeckList::Metadata::readElement and write back into deck_list.cpp
alongside isEmpty(), and drop the separate deck_list_metadata_xml
translation unit. The metadata arms are instance methods of the nested
Metadata struct, so keeping them in the same file as its other method
keeps the class from being scattered across two .cpp files; the rest of
the refactor (readElement as a thin dispatcher, element-wise reads,
clamped playmat params) is unchanged.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-18 11:58:31 +02:00
BruebachL
69d32ed853
[DeckList] Extract plain-text deck parser into own file (#7305)
* [DeckList] Extract plain-text deck parser into own file

DeckList::loadFromStream_Plain was a 160-line god-method mixing
deck clearing, name/comment detection, sideboard heuristics, set
and multiplier extraction and normalization. The parsing logic
moves verbatim into DeckListPlainText::parse() so it lives in a
dedicated, testable unit; DeckList keeps a thin delegating wrapper
and still refreshes the deck hash exactly as before (also on the
empty-input path, to match cleanList's original behavior). The
*F* foil suffix handling is relocated unchanged.

* [DeckList] Harden plain-text parser regexes and move metadata clearing up

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-18 11:58:30 +02:00
BruebachL
f4d5fc181d
[DeckList] Drop const from zone lookup that creates nodes (#7304)
DecklistNodeTree::getZoneObjFromName creates a new zone node when
the name is unknown, so declaring it const was a lie that let a
const DecklistNodeTree mutate its tree. It is only called from
mutating paths (addCard, readZoneElement), so the const qualifier
is removed.

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-18 11:58:30 +02:00
BruebachL
733deac0bb
[DeckList] Collapse repeated playmat parameter clamping (#7303)
The playmat read path clamped margin, offset and zoom with four
nearly identical qBound + fallback blocks. A single
parseClampedParam helper now owns that logic; behavior is unchanged
(parse whose string is well-formed clamps, unparseable text uses
the documented fallback).

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-18 11:58:30 +02:00
BruebachL
2a3a8982a6
[DeckList] Deduplicate undo/redo state switching (#7302)
undo() and redo() were mirror images that differed only in
which stack was the source. Both now delegate to a single
restoreAndSwap(source, target, deck) helper, so the save-current-
state, apply-memento and signal-emission logic lives in one place.

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-18 11:58:29 +02:00
BruebachL
87443d58f7
[DeckList] Remove dead card XML readElement (#7301)
* [DeckList] Remove no-op card XML readElement

AbstractDecklistCardNode::readElement only advanced the XML
reader to </card> and always returned 0; a card's attributes were
already parsed by the parent InnerDecklistNode::readElement. The
containing zone loop skips the card's end tag itself, so the
method was dead weight and is dropped from the node interface
along with the pure virtual it existed to satisfy.

* [DeckList] Document writeElement as the only serialization method

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-18 11:58:29 +02:00
BruebachL
61c1215a30
[DeckList] Remove obsolete Qt5 qHash compatibility shim (#7300)
The codebase is Qt6-only since #7071 dropped Qt5, so the
#if QT_VERSION < 0x050600 branch can never compile. Removing it
deletes a dead qHash overload that only existed to support
QRegularExpression in QSet on old Qt versions.

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-18 11:58:28 +02:00
BruebachL
fd82b140a8
[PictureLoader] Serve cached pictures from the disk cache instead of re-fetching them (#7284)
Some checks failed
CodeQL / Analyze (cpp) (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
Build Desktop / Configure (push) Has been cancelled
Build Docker / Servatrice (arm) (push) Has been cancelled
Build Docker / Servatrice (x86) (push) Has been cancelled
Build Desktop / Debian 13 (push) Has been cancelled
Build Desktop / Debian 12 (push) Has been cancelled
Build Desktop / Fedora 44 (push) Has been cancelled
Build Desktop / Fedora 43 (push) Has been cancelled
Build Desktop / Servatrice_Debian 12 (push) Has been cancelled
Build Desktop / Ubuntu 26.04 (push) Has been cancelled
Build Desktop / Ubuntu 24.04 (push) Has been cancelled
Build Desktop / Arch (push) Has been cancelled
Build Desktop / macOS 13 Intel (push) Has been cancelled
Build Desktop / macOS 14 (push) Has been cancelled
Build Desktop / macOS 15 (push) Has been cancelled
Build Desktop / macOS 26 Debug (push) Has been cancelled
Build Desktop / Windows 10 (push) Has been cancelled
Build Docker / Publish multi-platform Servatrice image (push) Has been cancelled
With picture downloads enabled, requests were issued with AlwaysNetwork
cache control, which per Qt never consults the disk cache. A picture that
had already been downloaded was therefore fetched from the network again
on every session start, with the queue bypass letting those re-fetches
skip the rate limit entirely.

Treat the network cache as the intent of the 'Network Cache' storage
method suggests: if the URL is already cached, serve it with AlwaysCache
(no network, no quota); only a genuine miss goes to the network, and only
when downloads are enabled. Cache hits skip the queue for free since they
never consume the per-second request allowance.

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-13 03:36:29 +02:00
tooomm
5d025ca0bd
Use capitalized app names (#7255)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 26 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
* use capitalized app name

* update urls

* app description

* Update main.cpp
2026-09-12 17:30:46 +02:00
BruebachL
a85203e457
Migrate theme asset loads to scheme-variant resolution (#7209-2) (#7276)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 26 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
Replace remaining QPixmap("theme:...") call sites with themePixmap() so icons, replay controls, card backs, and other images resolve to -dark/-light variants under theme schemes. Stem-exact 1:1 migration; behavior unchanged for non-variant themes.

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-11 17:59:57 +02:00
BruebachL
e9bf1e6e46
[Theme] Add scheme-variant asset resolution (#7275)
* Add scheme-variant theme asset resolution (#7209-1)

ThemeManager::assetPath() and schemeVariantPath() resolve a theme asset to its scheme-variant file (prefix-light/dark.png) with fallback to the plain asset, and themePixmap()/loadBrush()/loadExtraBrush() use them. CSS files load style-dark.css or style-light.css when present. Home widget re-resolves its background on theme change.

Link pixel_map_generator.cpp into the oracle target, which needs Qt6::Xml for QDomDocument.

* Fix clang-format wrap of theme format probe lists

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-11 17:59:57 +02:00
BruebachL
202a5ac958
[Client/Server/Protocol] Surface live metrics in the Developer tab (#7212)
* [Server] Instrument command processing, game starts, and event loops

Add a lock-free MetricsRegistry that accumulates per-command processing
times in preallocated histogram slots (one per protobuf command type,
bucketed at 1/5/10/25/50/100/250/500/1000/2500/5000 ms +Inf). The
hot-path observeCommand() uses only relaxed atomic adds — no locks,
no allocations, no cache-line ping-pong beyond the unavoidable counter
updates.

Wire the registry into AbstractServerSocketInterface::processCommandContainer()
so every processed command is attributed with its container's wall-clock
time. When a container exceeds metrics/slow_command_ms (default 500),
a warning is logged including the connected username.

Add an EventLoopWatchdog heartbeat that runs on every socket pool thread.
If a heartbeat overshoots metrics/stall_warn_ms (default 2000 ms), the
overshoot is recorded in atomic counters and a warning is logged. Both
thresholds are configurable in servatrice.ini; setting stall_warn_ms to 0
disables the watchdogs entirely.

Track game-start durations via a separate histogram in MetricsRegistry.
Server_Game::startGameNow() measures the time from zone creation through
player materialization and reports it via Server::observeGameStartDurationMs().

Add a live card-count gauge: Server_Game exposes getCardsInGame() and
Servatrice::getCardsInGamesTotal() sums across all running games under
the appropriate read locks.

Include a standalone metrics_registry_test (Google Test) that validates
empty registries, single/multi-sample histograms, kind encoding,
overflow-slot collapse, negative-duration clamping, gauge rendering,
and the game-start histogram separation.

Took 10 minutes

* [Client/Server/Protocol] Surface live metrics in the Developer tab

Extend Response_GetServerStats with live counters from the in-process
MetricsRegistry: cards in games, event loop stall totals/worst,
total commands processed, average command time, active command types,
and game-start count/duration. Add a repeated CommandStats message
carrying per-command breakdowns (kind, extension number, resolved
protobuf name, count, total ms) for every type that has seen at
least one sample.

Server-side cmdGetServerStats() populates all new fields after the
existing DB uptime snapshot query, resolving protobuf extension names
via the descriptor pool for human-readable labels like
session/Command_Ping.

Expand TabDeveloper with two tables: an overview section (existing
DB stats plus the new live metrics) and a per-command breakdown table
(Command / Count / Total ms / Avg ms) sorted by total_ms descending
so the hottest commands surface first.

Took 55 minutes

Took 47 seconds

* [Server] Drop dead Prometheus histogram, add developer command metrics, fix watchdog init order

- metrics_registry: remove toPrometheusText/appendCumulativeBuckets and the time-bucket histogram that nothing in production ever emitted (the future /metrics exporter can bring it back); keep counts/totals read by the Developer tab
- Fix +Inf bucket routing that never incremented, and its test that locked the bug in
- Instrument developer_command container (kind 6) in processCommandContainer and stats label resolution
- Read metrics/{slow_command_ms,stall_warn_ms} at the top of initServer() so stall_warn_ms=0 disables the watchdogs before pool threads start
- Shrink KindStride to 1280 (largest extension in use is 1206) with a static_assert; document scrape cost of getCardsInGamesTotal; note slow_command logging has no rate limit in servatrice.ini.example

* [Tests] Give metrics_registry_test an explicit main

* [Server] Record only the dispatched command family; drop unused totals

processCommandContainer recorded every family in a container even though
the base if/else-if dispatch processes at most one. An unauthenticated
client could batch a session command (login) with fabricated developer,
moderator, and admin entries and forge genuine-looking samples that were
never executed or authorized. Mirror the base's selection, skip when the
handler was already deleted, and skip entries whose extension number is
-1 (which would otherwise wrap into the previous kind's id range).

[Server] Drop dead process-lifetime byte/uptime counters

txBytesTotal/rxBytesTotal added an atomic RMW to every socket write and
read for counters nothing consumes (cmdGetServerStats fills tx_bytes,
rx_bytes, and uptime_secs from the DB snapshot). Remove the two atomics
and the getTxBytesTotal/getRxBytesTotal/getUptimeSeconds getters; the
incTxBytes/incRxBytes slots and mutexes remain for the ISL legacy
counters.

[Protocol] Document kind 5 as developer in CommandStats

NumKinds is 6 and the server emits kind_index = 5 for developer
commands; the comment stopped at 4.

* [Client] Togglable auto-refresh for Developer stats tab

* [Oracle] Fix clang-format alignment of card type priority list

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-11 17:18:56 +02:00
BruebachL
d5d99e4dfb
[Server/Client/Protocol] Add developer staff role (#7211)
* [Server/Client/Protocol] Add developer staff role

Introduce a Developer staff level (proto flag 32, DB admin bit 8) that
sits between admin and moderator: no kick/ban/warn/report/admin powers,
but gets server log access via a new developer command container family
(GET_SERVER_STATS, VIEWLOG_HISTORY) and an idle-timeout exemption.

- Protocol: IsDeveloper flag, developer_commands.proto envelope,
  Command_GetServerStats/Command_GetLogHistory, Response_GetServerStats,
  Command_AdjustMod.should_be_developer
- Servatrice: fail-closed developer dispatcher, uptime snapshot handler,
  shared log history handler reuse, bit-8 DB mapping
- Client: burgundy pawn/badge/labels/sort order, prepareDeveloperCommand,
  minimal Developer stats tab, log tab access, promote/demote actions

Took 24 minutes

Took 18 seconds

* [Server/Client/Protocol] Address developer role review feedback

Address ZeizaZach's review of the developer staff role:

- Nudge the developer log query to exclude private chat and sender IPs
  (the ModeratorCommand path still sees everything).
- Deduplicate Command_GetLogHistory into Command_ViewLogHistory, which now
  extends both ModeratorCommand (ext) and DeveloperCommand (dev_ext); the
  client picks the DeveloperCommand-scoped extension by extendee, and the
  server reads it via the extension number.
- Pull the uptime snapshot SQL into Servatrice_DatabaseInterface as
  getLatestUptimeSnapshot() and widen the reported counters to 64-bit.
- Document the admin bitfield (1 admin, 2 moderator, 4 judge, 8 developer)
  and add a server-side test for the developer command path.

* Add missing trailing newline to user_context_menu.cpp

* Remove stale includes of deleted command_get_log_history proto

The Command_GetLogHistory message was folded into Command_ViewLogHistory,
which deleted command_get_log_history.proto, but serversocketinterface
still #included its generated header. Fresh CI builds fail on the missing
file; local builds masked it by reusing a previously generated header.

* [Server] Exclude chat rows when private-chat filter is bypassable

A developer who omits log_location entirely — or sends only "chat" —
leaves chatType, gameType, roomType all false, so getMessageLogHistory
skips the target_type clause and returns every row, private messages
included. When !allowPrivateChat the server now forces game+room when
no surviving location was requested, guaranteeing the query always
carries a target_type restriction.

[Client] Demote mod+dev to moderator path in log-tab dispatch

The developer command family is strictly weaker than the moderator one
(no private chat, no sender_ip, ip filter ignored), so granting the
developer bit to an existing moderator must not silently strip their
capabilities. useDeveloperCommands is now true only when the user holds
the developer bit and not the moderator bit.

[Client] Hide the IP-address filter for developer log tab users

The developer path ignores the ip_address query field server-side.
Showing the field lets a developer type an IP and get results that are
silently unfiltered by it rather than an empty result set — reads as a
broken filter. Hide labelFindIPAddress/findIPAddress alongside the
privateChat checkbox.

* Developer pawn is silver.

* [Client] Fix indentation of merged Card Art Rules / Developer tabs

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-11 17:18:56 +02:00
BruebachL
7d867b9745
[Oracle/Client] Report card database download progress (#7253)
Some checks failed
CodeQL / Analyze (cpp) (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
Build Desktop / Configure (push) Has been cancelled
Build Docker / Servatrice (arm) (push) Has been cancelled
Build Docker / Servatrice (x86) (push) Has been cancelled
Build Desktop / Debian 13 (push) Has been cancelled
Build Desktop / Debian 12 (push) Has been cancelled
Build Desktop / Fedora 44 (push) Has been cancelled
Build Desktop / Fedora 43 (push) Has been cancelled
Build Desktop / Servatrice_Debian 12 (push) Has been cancelled
Build Desktop / Ubuntu 26.04 (push) Has been cancelled
Build Desktop / Ubuntu 24.04 (push) Has been cancelled
Build Desktop / Arch (push) Has been cancelled
Build Desktop / macOS 13 Intel (push) Has been cancelled
Build Desktop / macOS 14 (push) Has been cancelled
Build Desktop / macOS 15 (push) Has been cancelled
Build Desktop / macOS 26 Debug (push) Has been cancelled
Build Desktop / Windows 10 (push) Has been cancelled
Build Docker / Publish multi-platform Servatrice image (push) Has been cancelled
* [Oracle/Client] Report card database download progress

Card database updates ran invisibly: MTGJSON parsing spun an indeterminate
bar on the UI thread and the set import blocked the window, while the
onboarding wizard spawned `oracle -b` with no progress to show at all.

- Add byte-level scan progress to `RawJson::scanSetRanges` via an optional
  callback, throttled to ~100 reports per scan.
- Emit `OracleImporter::dataReadProgress` during the scan and import sets on a
  worker thread, driving the wizard's progress bar per set.
- With `-b`, write machine-readable `PROGRESS <stage> <done> <total>` lines to
  stdout for the download/scan/import stages; stderr keeps the log output.
- Parse the oracle stdout in `MainWindow` and forward it to the onboarding
  wizard, giving the card database step a determinate bar with stage-specific
  status text.
- Guard the async workers against the wizard being closed mid-run.
- Add Google Test coverage for scan progress reporting.

* [Oracle/Client] Harden oracle progress workers and quit prompt

Address review feedback on the download-progress change: decompress and read
sets files off the UI thread, cancel the load/import workers before the
wizard can tear down the importer, and show an 'Extracting file...' status
plus a clean 100% tail so the poll never looks stuck. Quitting Cockatrice
while a card database update runs now asks for confirmation.

* Show 100% for 500ms on complete.

* Disable buttons on set import until done.

* Clean up progress bar.

* Drop wrapper around lambda

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-09 22:27:16 +02:00
BruebachL
69e8f80fa1
[Oracle] Prefer higher-priority maintype when merging split cards (#7272)
Some checks failed
CodeQL / Analyze (cpp) (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
Build Desktop / Configure (push) Has been cancelled
Build Docker / Servatrice (arm) (push) Has been cancelled
Build Docker / Servatrice (x86) (push) Has been cancelled
Build Desktop / Debian 13 (push) Has been cancelled
Build Desktop / Debian 12 (push) Has been cancelled
Build Desktop / Fedora 44 (push) Has been cancelled
Build Desktop / Fedora 43 (push) Has been cancelled
Build Desktop / Servatrice_Debian 12 (push) Has been cancelled
Build Desktop / Ubuntu 26.04 (push) Has been cancelled
Build Desktop / Ubuntu 24.04 (push) Has been cancelled
Build Desktop / Arch (push) Has been cancelled
Build Desktop / macOS 13 Intel (push) Has been cancelled
Build Desktop / macOS 14 (push) Has been cancelled
Build Desktop / macOS 15 (push) Has been cancelled
Build Desktop / macOS 26 Debug (push) Has been cancelled
Build Desktop / Windows 10 (push) Has been cancelled
Build Docker / Publish multi-platform Servatrice image (push) Has been cancelled
* [Oracle] Prefer higher-priority maintype when merging split cards

Adventure cards (e.g. Bonecrusher Giant, Virtue of Knowledge) are stored as
split cards in MTGJSON, with a Creature/permanent face and an Instant/Sorcery
adventure face. When the two faces are merged into a single card, the code
previously discarded the second face's maintype entirely, keeping whichever
face was processed first.

If the Instant/Sorcery adventure face appeared first, the merged card got
maintype 'Instant' with tableRow 3. At runtime this made double-clicking the
card on the stack send it to the graveyard instead of the table
(PlayerActions::playCard).

Fix the merge to follow the same priority order used by getMainCardType()
(Planeswalker > Creature > Land > Sorcery > Instant > Artifact > Enchantment),
so the permanent Creature type wins for adventure cards regardless of face
order, matching the physical card and the reported expectations.

Closes #4394

* Extract helper.

* Simplify const

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-07 21:22:53 +02:00
BruebachL
df3defa890
[PictureLoader] Leave failed pixmap null so solid color is shown instead. (#7274)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 26 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-07 13:37:19 +02:00
BruebachL
030e4f5871
[Game] Stop duplicate attach log entries (#7273)
Fix duplicate attach messages in the game log caused by a redundant
child arrow in CardItem::drawAttachArrow.

Unlike the sibling drawArrow, drawAttachArrow omitted the ``card ==
this`` guard when iterating selectedItems(). Because right-clicking a
card to open the attach menu selects that card, it was always present
in selectedItems(), producing a second arrow for the same source card.

On release both arrows sent an identical Command_AttachCard, so the
server broadcast two Event_AttachCard messages and the log rendered
"attaches to" twice. Mirror the drawArrow skip condition so the active
card is excluded and only one attach command is sent.

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-07 13:37:13 +02:00
RickyRister
6e5c58069b
[Settings] Make chat settings page scrollable (#7271)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 26 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
2026-09-07 00:52:06 -07:00
BruebachL
ebeae48652
[Game] Preserve hand card order when restoring connection (#7266)
When a player restores connection to a game, the client rebuilds each
zone from the cards the server sends in the game state. Non-coordinate
zones (hand, piles, stack) report x == 0 on every card, so inserting
each rebuilt card at that coordinate reversed the received order one
card at a time.

Append rebuilt cards in the order they arrive for zones without
coordinates; coordinate-based zones (table) keep using x/y.

Fixes #2759

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-07 08:21:44 +02:00
BruebachL
36f998e466
[Client] Fix pawn avatar cache key collision for players without a custom avatar (#4086) (#7264)
Player pawns are looked up in QPixmapCache under a key built from the
rendered size, user level, and the avatar pixmap's cacheKey(). A null
pixmap reports cacheKey() 0, so all players without a custom avatar
collided: the first pawn rendered for a given size and user level was
reused for the next one, showing the wrong player's pawn.

Extend the key with the rendered height, the lowercased privlevel
(matching UserLevelPixmapGenerator), and both pawn colors so that every
visually distinct pawn gets its own cache entry.

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-07 08:21:26 +02:00
BruebachL
760fe88fa3
[Client] Add setting to ignore all private messages (#7260)
* [Client] Add setting to ignore all private messages (#1250)

* Early return

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-07 08:20:46 +02:00
BruebachL
fff506dbbe
[Client] Show confirmation when adding a user to the ignore list (#1875) (#7261)
Nothing in the UI confirmed that a user had been added to the ignore
list, so the action felt ambiguous and could be repeated by accident.

Show an information dialog when the server acknowledges the
add-to-ignore command.

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-07 08:14:39 +02:00
BruebachL
8e0bdafb14
[Client] Prevent contacting users on the ignore list (#7262)
* [Client] Prevent contacting users on the ignore list (#1249)

The ignore list silences incoming messages, but a user on it was still
reachable: the context menu's chat item stayed enabled, private messages
could be sent, and a chat tab could be opened for an ignored user.

Make ignored users uncontactable: disable the chat item for them, refuse
to deliver messages typed in an open PM tab with one, and refuse to open
a new private chat tab with an ignored user (with a hint on how to undo
the ignore).

* Update cockatrice/src/interface/widgets/tabs/tab_message.cpp

Co-authored-by: RickyRister <42636155+RickyRister@users.noreply.github.com>

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
Co-authored-by: RickyRister <42636155+RickyRister@users.noreply.github.com>
2026-09-07 08:14:15 +02:00
BruebachL
9b0d62c152
Close all zone views of a leaving player to avoid crashing (#4298) (#7270)
When a player left, GameScene::removePlayer iterated zoneViews while close()
synchronously removed the current view from that list. A judge with several open
views of the departing player (e.g. library and hand) only had the first one
closed; the remaining views were skipped and left pointing at a player that was
about to be deleted, crashing the client on the next access.

- GameScene::removePlayer: iterate over a copy of zoneViews
- GameScene::toggleZoneView: same fix for the identical iteration bug

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-07 02:51:38 +02:00
BruebachL
6cdeb0c428
[Client] Explain the buddy and ignore lists in the user tab (#2072) (#7258)
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-07 02:29:17 +02:00
BruebachL
745e94f332
[Replay] Skip damage animations when skipping backward in replays (#7249)
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-07 02:26:52 +02:00
BruebachL
b01e107908
[Client] Show horizontal art for plane and siege cards in the profile banner (#7118) (#7250)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 26 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
Use the landscape orientation flag to rotate sideways-layout card art
upright before cropping, so planes/sieges show their horizontal art as
the server profile banner card instead of a rotated full card.

- Curve cropCardArt around the card's landscapeOrientation flag with
  landscape-specific art margins (mirrors CardInfoPictureWidget)
- Add shared CardArtUtils::rotateSidewaysLayoutArt helper and apply it
  to the playmat (game render and settings preview) and card info widget,
  replacing three duplicate 90-degree rotation blocks

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-07 02:20:54 +02:00
BruebachL
8d30ac54f0
[Game] Prevent spectator duplication when replaying joined events (#7248)
* [Game] Prevent spectator duplication when replaying joined events

The spectator branch of eventJoin emitted spectatorJoined unconditionally
even when the spectator was already present (e.g. replayed during a rewind).
Guard it like the player branch and eventGameStateChanged, and make
PlayerListWidget::addPlayer idempotent as defense in depth.

* In resetChatAndPhase() (the rewound() handler), also clear all spectators from both PlayerManager and PlayerListWidget before the replay rebuilds from event 0. The forward replay then re-adds exactly the spectators whose join events fall within the new time range via eventGameStateChanged/eventJoin.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-07 01:57:35 +02:00
tooomm
fb482f037e
550 MB --> 600 MB (#7257) 2026-09-06 23:13:34 +02:00
tooomm
0f0e46a177
[Oracle] Have space between size value and unit (#7254) 2026-09-06 22:19:13 +02:00
tooomm
2fe59d6326
fix table formatting (#7256) 2026-09-06 22:18:36 +02:00
tooomm
048fe247f4
Add ccache eviction to debug builds as well (#7247)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 26 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
2026-09-06 14:05:47 +02:00
RickyRister
0f003eabf9
[Game] Implement total toughness tally (#7252) 2026-09-06 01:46:37 -07:00
BruebachL
ada774f5cc
[Game] Render custom deck zones in the deck view (#7207)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 26 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
* [Game] Render custom deck zones in the deck view

The in-game deck view now walks custom zones like the standard
boards, so cards filed under a user-created zone show up in their
zone's card pile instead of disappearing from the view.

* [Game] Collect deck-view cards via DeckList::getCardNodes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-05 22:00:22 +02:00
BruebachL
b0e566ed54
[Client] Show custom zones in the card display widgets (#7206)
* [Client] Show custom zones in the card display widgets

Card group displays and deck zone displays learn to render custom
zones alongside the standard boards.

- Group display widgets treat custom-zone nodes like other group
  headers, keeping counts and layout consistent.
- Zone display widgets resolve their title through visibleNameFromName
  so custom zones show their user-chosen names localized like the
  standard zones.

* [DeckEditor] Apply sort criteria inside custom zones and align display order with the model

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-05 22:00:21 +02:00
BruebachL
0d09e633e3
[Client] Expose custom zone management in the deck editor (#7205)
* [Client] Expose custom zone management in the deck editor

Wires the state layer into every editor surface that shows deck zones.

- Deck dock: context menu on zones gains New/Rename/Delete/Change
  board actions, with per-zone submenus for adding cards.
- Card database dock and visual database display gain an add-to-zone
  submenu listing custom zones per board plus a create-zone entry.
- All prompt call sites pass validateNewZoneName so duplicates and
  reserved names are rejected inline before Ok unlocks.
- Rename reuses the same dialog in name-only mode, keeping one
  validation contract for every zone-name entry point.
- Change board marks the current board instead of offering a no-op,
  and the state layer refuses moves onto boards holding a same-named
  zone from imported decks.

* [DeckEditor] Address custom-zone menu and export review feedback

* [DeckLoader] Keep the sideboard marker and block ordering when exporting nested zones

- saveToStream_DeckZone threads the owning board zone name down to the card
  writer, so cards in a custom zone under the sideboard keep their SB:
  prefix instead of being re-imported into the maindeck
- nested sub-zones are collected during the loop and written after the
  parent zone's own header and cards, so they no longer read as part of the
  zone printed before them

* [DeckEditor] Fix move-to-zone menu use-after-free and per-zone enabled state

- resolve the card name/provider/collector number before createNewCustomZone
  rebuilds the model tree, then re-find the refreshed index via findCard and
  move it (mirrors the decrementCard re-find pattern)
- the enabled test now compares the card's own zone (nearest custom-zone
  ancestor, else its board), matching moveCardToZone's lookup, so moving a
  card out of a custom zone back to the board root is offered and the card's
  own zone is disabled

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-05 22:00:21 +02:00
BruebachL
9677fad342
[Client] Add zone management to the deck state manager (#7204)
* [Client] Add zone management to the deck state manager

State-layer operations for custom deck zones, plus the shared prompt
dialog that later editor menus will call into.

- moveCardToZone relocates every copy of a card row into any zone,
  refusing non-card rows and tokens so miswired selections can never
  shred a group or turn tokens into deck cards. The current zone is
  found by walking ancestors, which also handles legacy top-level
  zones.
- createCustomZone, renameCustomZone, moveCustomZone and
  removeCustomZone wrap the tree API with memento history, model
  rebuilds and deck hash refreshes via modifyTree.
- Same-board zone moves return success without minting a history
  entry, keeping the undo log honest.
- promptForNewZone asks for a name and the parent zone, keeps Ok
  disabled until the trimmed name passes a caller-supplied validator
  (shown inline as an error), and reports its own translation context.

Took 14 minutes

# Commit time for manual adjustment:
# Took 6 minutes

# Commit time for manual adjustment:
# Took 33 seconds

* [DeckEditor] Address zone-management review feedback

- Expose DecklistNodeTree::hasZoneName and use it in validateNewZoneName
  so the uniqueness scan covers custom zones on every board, not just the
  standard ones.
- Hide the board selector in the rename dialog path where it is not used.
- Emit deckHashChanged after refreshDeckHash so the deck hash label stays
  current after zone create/rename/move/remove.

* [DeckEditor] Notify card set changes after zone edits and drop the board scan

- modifyTree emits cardNodesChanged alongside deckHashChanged so the
  banner-card combo and printing in-deck counts refresh after removing a
  zone that still holds cards
- DecklistNodeTree::findCustomZoneByName is public and moveCustomZone uses
  it, locating zones under non-standard boards (e.g. tokens) instead of
  scanning only main/side/maybeboard

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-05 22:00:21 +02:00
BruebachL
e8ec28572f
[Models] Mirror custom deck zones in the deck list model (#7203)
* [Models] Mirror custom deck zones in the deck list model

DeckListModel now surfaces the custom zones from the deck tree so
views can render and edit them alongside criteria groups.

The custom-zone bookkeeping that made the model unwieldy is extracted
into DeckListModelCustomZones (deck_list_model_custom_zones.h/.cpp), a
single self-contained unit owning every "what is / where is a custom
zone" decision for the model's shadow tree:

- rebuildTree mirrors each custom zone as a DecklistModelSubZoneNode
  under its board zone, cards flat inside (no further grouping).
- The freshly built shadow tree is sorted while the model reset is
  still open, so views never observe unsorted intermediate order and
  proxies cannot desync.
- Custom zones always sort after criteria groups within a board,
  regardless of their names. One shared sortWithCustomZonesLast backs
  both the live sortHelper (which remaps persistent indexes from the
  movement mapping) and the silent reset-time sortShadowTree.
- addCard inserts flat into a custom zone by name and keeps grouping
  by active criteria for board zones. findCardNode resolves cards in
  both layouts, legacy top-level zones unchanged.
- New IsCustomZoneRole lets views tell zones apart from groups.
- Empty custom zones survive row removal. Zone rows themselves are
  only mutable through the deck tree API.

A new deck_list_model_custom_zones_test suite locks the extracted
shadow-tree logic (type testing, mirroring, name lookup, and the
sort-with-custom-zones-last mapping).

No behavior change.

* [Models] Route group lookups around mirrored custom zones

Group lookups (createNodeIfNeeded, findCardNode) must not resolve a
mirrored custom zone that shares the group name. Introduce
findGroupChild to search only non-custom children, and make addCard
consult the deck tree before falling back to creating a top-level zone
so cards added to an un-mirrored custom zone land inside it.

mirrorCustomZones now flattens cards nested at any depth into the
mirrored zone so no card is left without a model row.

Add model behaviour tests (addCard routing, same-name group/zone
collision, removeRows guard, empty-zone survival, findCard inside a
custom zone) and fix the missing main() in the unit test binaries.

* [Models] Fix addCard routing for card-named zones and nested custom zones

- hasDeckZone no longer matches board cards that merely share the zone
  name, which previously caused infinite addCard/rebuildTree recursion
- Adding to a custom zone whose deck side holds nested sub-zones appends
  to the deck tree instead of writing past its direct children

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-05 22:00:20 +02:00
Magnus Groß
0c725f9a03
Allow to filter sets by release date (#7239)
This compares the release dates of sets, which enables users to filter
for sets in a certain range, for example to filter for all commanders
with an old card frame, `t:legendary set<8ED` can be used, which will
only include cards appearing before 8th edition.

This acts as a more powerful superset of the "Filter to X most recent
sets" feature.

Fixes #7238
2026-09-05 11:58:37 -07:00
BruebachL
c011ea7ceb
[Oracle] Parse sets lazily to slash importer peak memory (#7217)
* [Oracle] Parse sets lazily to slash importer peak memory

- Add a raw JSON scanner that splits the document into per-set byte ranges
  without materializing the JSON tree
- Keep only the raw document bytes and parse one set at a time in startImport
- Take readSetsFromByteArray by value so the wizard's buffer is moved, not copied
- Clear the retained raw data in releaseSetData()/clear()
- Cover the scanner and lazy parsing with tests

Took 2 minutes

* [Oracle] Fix nesting-depth cap, tolerate unescaped control chars, lazy-parse review fixes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-05 20:35:28 +02:00
BruebachL
1dc54617ba
[Oracle] Add RAM usage benchmarks for the oracle importer (#7216)
* [Oracle] Add RAM usage benchmarks for the oracle importer

- Measure process peak/current RSS via procfs (Linux) or getrusage (macOS)
- Add a synthetic-scale RAM benchmark and an opt-in real AllPrintings
  run gated by COCKATRICE_ORACLE_RAM_BENCHMARK=1
- Mirror the wizard's magic-byte handling to decompress .xz/.zip payloads
- Wire optional ZLIB/LibLZMA into the benchmark target and raise its timeout

Took 2 minutes

* [Oracle/Tests] Measure parse against post-fixture baseline; assert release empties sets

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-05 20:35:27 +02:00
BruebachL
aa96d81e4b
[Build] Enable ccache by default when it is installed (#7236)
* [Build] Enable ccache by default when it is installed

ccache is a near free win for both clean and incremental rebuilds and
has no effect on systems where it is not installed (find_program
guards the whole block). Aligns the CMake default with the documented
behavior; users can still arch with -DUSE_CCACHE=OFF.

* [Build] Disable ccache auto-engage on Windows (MSVC)

* [Build] Report ccache skip on Windows explicitly

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-05 19:40:37 +02:00
BruebachL
14ecfff700
[Build] Add precompiled headers for Qt-backed executables (#7235)
* [Build] Add precompiled headers for Qt-backed executables

Reparsing QtCore/QtGui/QtWidgets/QtNetwork in ~460 client translation
units is the dominant compilation cost. Precompile the two common layers:
- qtcore_pch.h (Qt Core only; safe even for headless Servatrice)
- qtwidgets_pch.h (adds Gui/Widgets/Network; used by Cockatrice and Oracle)

target_precompile_headers() requires CMake 3.16, now the project minimum.
Estimated 30-50% faster client rebuilds.

* [Build] Format qtwidgets precompiled header

clang-format include regrouping and a missing trailing newline.

* [Build] Add PCH-aware ccache sloppiness config; format cmake/pch headers

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-05 19:40:36 +02:00
BruebachL
61e6a9913e
[Oracle] Add oracle importer tests and fix set parsing details (#7215)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 26 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
* [Oracle] Add oracle importer tests and fix set parsing details

- Add oracle_importer_test and oracle_importer_benchmark_test targets
- Preserve the first printing's legalities when an existing card is reused
- Concatenate split-card coloridentity and sort/dedupe card colors
- Use a raw string for the Basic Land format regex
- Pre-allocate the card hash and micro-optimize string handling

Took 2 minutes

* [Oracle/Tests] Pin cmc coercion in CI run; scope the reserve pass

The #7214 coercion assertion lived only in oracle_importer_benchmark_test,
which gets no add_test and so never runs under ctest. Add NumericManaValueCoercedToCmc
and LegacyConvertedManaCostCoercedToCmc to oracle_importer_test (a CI-ran
binary): manaValue/convertedManaCost are JSON numbers in AllPrintings, and
QJsonValue::toString() would drop them to an empty cmc without the
#7214 coercion fix.

Wrap the distinct-name reserve pass in a bare block so the ~35k name
QStrings are handed back before the memory-heavy import loop starts.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-04 22:14:16 +02:00
BruebachL
4d4ddd4278
[Oracle] Replace vendored qt-json with native QJson for set import (#7214)
* [Oracle] Replace vendored QtJson with native QJson for set import

- Drop the vendored oracle/src/qt-json/json.{h,cpp} implementation
- Switch SetToDownload and importCardsFromSet from QList<QVariant> to
  native QJsonArray/QJsonObject
- Release set JSON data after import in the save sets page

Took 20 minutes

* [Oracle] Restore property coercion and legality merge in native JSON import

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-04 22:14:16 +02:00
BruebachL
d6fbfb32a1
[Security] Use a CSPRNG for salts, tokens, and RNG seeding (#7192)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 26 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
* [Security] Use a CSPRNG for salts, tokens, and RNG seeding

Password salts and activation tokens were generated with the global SFMT
RNG, which was seeded from a 32-bit timestamp, making registration
salts and activation tokens predictable. The game RNG used the same
timestamp seed across restarts.

Add CryptoUtil backed by OpenSSL RAND_bytes and use it for salt/token
generation and to seed RNG_SFMT with a 64-bit CSPRNG value in both the
client and server. Link libcockatrice_utility against OpenSSL::Crypto.

Took 30 seconds

Took 25 minutes

* Lint.

Took 4 minutes

Took 36 seconds

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-04 13:49:18 +02:00
BruebachL
3ec62df3e7
[Protocol] Remove duplicate event_game_state_changed.proto entry (#7234)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 26 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
The .proto file appeared twice in the PROTO_FILES list, causing protoc to
process it twice on every build. Keep a single entry.

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-04 05:20:53 +02:00
BruebachL
fcfb14cf56
[Build] Use pipes for GCC/Clang compilation (#7233)
Pass -pipe so GCC/Clang transfer intermediate representation between
compiler stages over pipes instead of temporary files, reducing build I/O.

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-04 05:20:53 +02:00
BruebachL
35ebae8d7f
[Build] Bump cmake_minimum_required from 3.10 to 3.16 (#7232)
3.16 is already required by Qt6 (and enforced at find_package time).
This unlocks native target_precompile_headers(), better AUTOMOC/AUTORCC
handling, and qt6_finalize_project() without a version guard.

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-04 05:20:52 +02:00
BruebachL
4e9d148163
[TabSupervisor] Initialize all tabs (#7231)
Some checks failed
CodeQL / Analyze (cpp) (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
Build Desktop / Configure (push) Has been cancelled
Build Docker / Servatrice (arm) (push) Has been cancelled
Build Docker / Servatrice (x86) (push) Has been cancelled
Build Desktop / Debian 13 (push) Has been cancelled
Build Desktop / Debian 12 (push) Has been cancelled
Build Desktop / Fedora 44 (push) Has been cancelled
Build Desktop / Fedora 43 (push) Has been cancelled
Build Desktop / Servatrice_Debian 12 (push) Has been cancelled
Build Desktop / Ubuntu 26.04 (push) Has been cancelled
Build Desktop / Ubuntu 24.04 (push) Has been cancelled
Build Desktop / Arch (push) Has been cancelled
Build Desktop / macOS 13 Intel (push) Has been cancelled
Build Desktop / macOS 14 (push) Has been cancelled
Build Desktop / macOS 15 (push) Has been cancelled
Build Desktop / macOS 26 Debug (push) Has been cancelled
Build Desktop / Windows 10 (push) Has been cancelled
Build Docker / Publish multi-platform Servatrice image (push) Has been cancelled
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-01 16:11:02 +02:00
RickyRister
425b16ea0d
[Game] Allow dropping cards at bottom of stack zone (#7230)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 26 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
2026-09-01 12:11:31 +02:00
BruebachL
45c7ff6f87
[Mods] Properly close card art rules tab on disconnect (#7227)
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-01 11:31:49 +02:00
BruebachL
d974501277
[GameScene] Sever connections properly. (#7191)
* [GameScene] Sever connections properly.

Took 2 minutes


Took 54 minutes

* [GameScene] Sever animated item destroy connections at teardown

Fix crash when a replay's board is closed (GameScene teardown abort).

The old QObject::disconnect(nullptr, nullptr, this, nullptr) is invalid per
Qt docs (the sender must never be nullptr), so it never severed the PMF
destroyed -> GameScene::removeAnimatedItem connections that fire when
QGraphicsScene::~QGraphicsScene -> clear() destroys the remaining items.

Store the QMetaObject::Connection handle for each animated item and
disconnect them all in ~GameScene via the connection-handle overload.
Dedup connections on the connection map rather than animatedItems, since
the animation timer clears animatedItems on completion, which let a
re-registered item (e.g. a life counter flashed repeatedly) accumulate
orphaned duplicate destroyed connections that survived teardown.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-01 11:31:11 +02:00
RickyRister
9bf2202739
[Game] Fix dragged card always placed on bottom of stack (#7228)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 26 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
2026-08-31 07:31:02 -07:00
RickyRister
03de1af678
[VDS] Fix search filter not being applied on refresh (#7229)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 26 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
2026-08-31 10:28:50 +02:00
RickyRister
3dc9dba67a
[SettingsPage] Refactor: Clean up order of variables (#7184)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 26 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
* [SettingsPage] Refactor: Clean up order of variables

* fixes
2026-08-30 14:43:23 -07:00
BruebachL
68e4fa054d
[UserList] Add invite button to hover popup (#7144)
Some checks failed
CodeQL / Analyze (cpp) (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
Build Desktop / Configure (push) Has been cancelled
Build Docker / Servatrice (arm) (push) Has been cancelled
Build Docker / Servatrice (x86) (push) Has been cancelled
Build Desktop / Debian 13 (push) Has been cancelled
Build Desktop / Debian 12 (push) Has been cancelled
Build Desktop / Fedora 44 (push) Has been cancelled
Build Desktop / Fedora 43 (push) Has been cancelled
Build Desktop / Servatrice_Debian 12 (push) Has been cancelled
Build Desktop / Ubuntu 26.04 (push) Has been cancelled
Build Desktop / Ubuntu 24.04 (push) Has been cancelled
Build Desktop / Arch (push) Has been cancelled
Build Desktop / macOS 13 Intel (push) Has been cancelled
Build Desktop / macOS 14 (push) Has been cancelled
Build Desktop / macOS 15 (push) Has been cancelled
Build Desktop / macOS 26 Debug (push) Has been cancelled
Build Desktop / Windows 10 (push) Has been cancelled
Build Docker / Publish multi-platform Servatrice image (push) Has been cancelled
* [Client] Send game invites from the user context menu via a private message

The user context menu gains an "Invite to Game" submenu listing the
inviteable games in the room (the inviter's own games, honoring the
buddy-only setting). Picking one opens a private message to the target
user with a cockatrice://joingame link naming the game, so the target
gets a clickable invite instead of a raw URL. Multi-game rooms offer a
picker; a single inviteable game sends directly. Sending a message to
an offline user no longer swallows the draft — it reports that the
user is offline and keeps the typed text.

Took 30 seconds

Took 1 minute

* [Client] Open the invite dialog taller by default without enforcing a minimum size

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-29 21:35:28 +02:00
BruebachL
6f86c45ea8
[Refactor] Extract shared deck conversion prompt helper (#7107)
Move the deck-to-.cod conversion prompt logic (format check, saved
preference handling, overwrite confirmation, dialog) out of
DeckPreviewWidget into dlg_convert_deck_to_cod_format so the deck
editor can reuse it without duplicating it.

Took 4 minutes

Took 4 minutes

Took 1 minute

# Commit time for manual adjustment:
# Took 3 minutes

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-29 21:12:11 +02:00
dependabot[bot]
dade7ae78a
Bump actions/checkout from 6 to 7 (#7210)
Bumps [actions/checkout](https://github.com/actions/checkout) from 6 to 7.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-29 18:23:45 +02:00
dependabot[bot]
8f52223322
Bump actions/download-artifact from 7 to 8 (#7209)
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 7 to 8.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v7...v8)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: '8'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-29 18:11:11 +02:00
tooomm
704611fc1f
Correct ccache default setting (#7190)
* gate ccache correctly

* Revert "gate ccache correctly"

This reverts commit 0af93629de.

* Do not enable ccache by default
2026-08-29 15:21:38 +02:00
ebbit1q
4b7b785452
add a limit to the size of deckfiles cockatrice can load (#7163)
* add a limit to the size of deckfiles cockatrice can load

the limit is 99999 or 100k -1 right now, which is kind of the limit of
what looks acceptable in the player

* format

* up limit to 100k because that's what the tests do
2026-08-29 15:21:04 +02:00
ebbit1q
cf8e5858ab
increase the timeout for the hashing performance test (#7166)
it seems like there is too much variance across platforms for how long this test takes
probably fixes #7152
2026-08-29 15:20:49 +02:00
RickyRister
83833f4684
[UserList] Refactor: split dialog code to separate file (#7189)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 26 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
2026-08-28 22:50:06 -07:00
BruebachL
0a09884c78
[DeckList] Add custom deck zones to the deck tree (#7176)
Some checks failed
CodeQL / Analyze (cpp) (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
Build Desktop / Configure (push) Has been cancelled
Build Docker / Servatrice (arm) (push) Has been cancelled
Build Docker / Servatrice (x86) (push) Has been cancelled
Build Desktop / Debian 13 (push) Has been cancelled
Build Desktop / Debian 12 (push) Has been cancelled
Build Desktop / Fedora 44 (push) Has been cancelled
Build Desktop / Fedora 43 (push) Has been cancelled
Build Desktop / Servatrice_Debian 12 (push) Has been cancelled
Build Desktop / Ubuntu 26.04 (push) Has been cancelled
Build Desktop / Ubuntu 24.04 (push) Has been cancelled
Build Desktop / Arch (push) Has been cancelled
Build Desktop / macOS 13 Intel (push) Has been cancelled
Build Desktop / macOS 14 (push) Has been cancelled
Build Desktop / macOS 15 (push) Has been cancelled
Build Desktop / macOS 26 Debug (push) Has been cancelled
Build Desktop / Windows 10 (push) Has been cancelled
Build Docker / Publish multi-platform Servatrice image (push) Has been cancelled
* [DeckList] Add custom deck zones to the deck tree

Introduce user-definable zones nested under a board zone (main, side
or maybeboard) so players can organize cards inside a board without
changing board semantics.

- addCustomZone, renameCustomZone, moveCustomZone and removeCustomZone
  manage zones. Names are unique across the whole deck and the standard
  zone names (main/side/maybeboard/tokens) stay reserved.
- Board zones are created lazily on first use.
- getZoneObjFromName resolves custom names to their nested node so
  addCard and XML loading route cards into them. Unknown names keep
  creating legacy top-level zones.
- deleteNode keeps empty custom zones alive and only prunes empty
  board zones.
- New deck_list_zones test suite locks hash parity with flat decks,
  sideboard size accounting, maybeboard exclusion from plain export
  and native-format round-trips.

Took 17 minutes


Took 11 minutes

* Extract to function

Took 4 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-26 23:15:16 +02:00
BruebachL
e12293bb28
[GameScene] Don't just sever self connections, sever them all. (#7188)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 26 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
Took 21 minutes

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-26 02:22:57 +02:00
RickyRister
dba7cc73a4
[HomeTab] Introduce button color source setting (#7181)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 26 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
2026-08-25 19:27:07 +02:00
BruebachL
b3e126f904
[VDS] Drive folder and preview widgets from the model (#7106)
* [VDS] Drive folder and preview widgets from the model (MVC views)

Took 16 minutes

Took 8 minutes

Took 3 minutes


Took 11 minutes

* Rebase whoopsie

Took 4 minutes

* Hide widgets instead of destroying, go back to signals, rename for consistency.

Took 13 minutes

Took 4 seconds

Took 26 minutes

Took 5 seconds

# Commit time for manual adjustment:
# Took 9 minutes

* Make VDS startup smooth: batch deck loads, guard preview resizes

- Move color identity computation into the background load task and apply
  finished deck loads in bounded batches per event loop turn, so finishing
  hundreds of loads at once cannot stall the UI thread
- Skip redundant resize work in DeckPreviewWidget when the banner width did
  not change, and collect the clamped children once instead of searching the
  widget tree on every layout pass

Took 19 minutes

# Commit time for manual adjustment:
# Took 3 minutes

* [VDS] Expose filter matches as a proxy role instead of dropping rows

The folder display scanned source-model rows and probed acceptance with
mapFromSource(...).isValid(), reaching into both models for one answer.

The proxy now keeps every row and exposes each row's search/tag/color
filter result through FilterMatchRole. The folder display and the tag
filter read everything off proxy indexes, and hidden previews keep their
sorted position in the flow layout instead of being appended at the end.

Took 11 minutes

* [VDS] Bound pending-load drain by time and make row lookups O(1)

The fixed DECK_LOADS_PER_TURN = 24 cap had no measured basis. It was guessed and existed
because every applied load emitted dataChanged into each DeckPreviewWidget,
whose handler resolved its own row with an O(n) linear scan per widget.

The model now maintains a file path -> row hash kept in sync across scans,
renames and deletions, so rowForFilePath is O(1) and the fan-out cost is
gone at its source. The drain applies finished loads until a small time
budget per event loop turn runs out, so throughput self-tunes instead of
relying on an arbitrary count.

* Actual minimal fix for resize squishing

Took 20 minutes

* Fix color widget sizing

Took 16 minutes

* [BannerWidget] Also set a max height

Took 4 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-25 19:23:16 +02:00
BruebachL
e589429bd9
[Client] Pin user list header length to the viewport width (#7158)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 26 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
* [Client] Pin user list header length to the viewport width

The header stretch mode kept a resize section property, so after any
column grew past the viewport the list carried an invisible horizontal
pan range that scrolled rows sideways without visual feedback

Drop the leftover property so displayed length always equals viewport
width and horizontal panning is impossible

* Show columns 1 and 2

Took 12 minutes

Took 2 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-25 06:33:57 +02:00
BruebachL
2b7b4e8168
[App] Add onboarding wizard (#7064)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 26 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
* [App] Add onboarding wizard

Took 10 minutes

Took 3 minutes

Took 7 minutes

Took 9 minutes

Took 2 minutes

Took 1 minute


Took 7 minutes

* Adjust CI

Took 14 minutes

Took 56 seconds

Took 2 seconds

Took 3 seconds

* Adjust CI again

Took 14 minutes

Took 2 seconds

* Comments and fixes

Took 9 seconds


Took 1 minute

* Rebase.

Took 5 minutes

Took 50 seconds

Took 15 seconds

* Comments.

Took 7 minutes

* CI lol

Took 3 minutes

* CI again lol

Took 4 minutes

* Drop some settings, add some new ones.

Took 19 minutes

* Resize when expanding section

Took 4 minutes

Took 3 minutes

Took 7 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-24 23:05:58 +02:00
BruebachL
815c5987b4
[Doxygen] Add troubleshooting for card pictures and logs (#7125)
* [Doxygen] Add troubleshooting for card pictures and logs

Took 3 minutes

* Apply suggestion from @tooomm

Co-authored-by: tooomm <tooomm@users.noreply.github.com>

* Apply suggestion from @tooomm

Co-authored-by: tooomm <tooomm@users.noreply.github.com>

* [Doxygen] Address review feedback on troubleshooting docs

- enabling_debug_logs.md: use shell code fence for terminal commands,
  add export alternative for macOS
- fixing_card_pictures.md: split log section into 'Check Logs' and
  'Enable Picture Loader Debug Logs', remove hardcoded URL list
  (defaults may drift), remove redundant Scryfall/Gatherer note
  (covered in Provider Accuracy section)

Took 1 minute

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
Co-authored-by: tooomm <tooomm@users.noreply.github.com>
2026-08-24 21:38:04 +02:00
BruebachL
24d8d8be3b
[VDS] Cache mana symbol renders and skip redundant resizes (#7167)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 26 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
* [VDS] Cache mana symbol renders and skip redundant resizes

- Render each mana symbol once at a bounded master size and derive every
  requested size from the cached master, avoiding repeated full-size SVG
  rasterization on the GUI thread
- Share scaled results through a process-wide cache keyed by symbol and
  size, so repeated widget creation and rescales don't redo the work
- Skip redundant resize work in ColorIdentityWidget and ManaSymbolWidget
  when sizes did not change

Took 8 minutes


Took 50 seconds

* Move to pixmap generator

Took 8 minutes

Took 4 seconds

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-24 19:26:41 +02:00
BruebachL
b13c682a7a
[DeckList] Make deck tree card traversal recursive (#7175)
* [DeckList] Make deck tree card traversal recursive

getCardNodes and forEachCard now descend into nested zones instead of
assuming a flat main/side/token layout. For today's flat trees this is
behavior-preserving; it also removes two latent crashes (unchecked
dynamic_cast dereference, null card nodes passed to forEachCard
callers). Nested zones are introduced by later custom-zones units.

Took 3 minutes


Took 7 seconds

Took 9 seconds

* Fix rebase mistake.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-24 18:44:05 +02:00
BruebachL
22b0f69706
[UserList] Request banner card art for visible rows and hovered popup (#7172)
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-24 18:25:14 +02:00
BruebachL
fecbbab983
[Client] Replace playmat crop spinboxes with a direct manipulation preview (#7161)
* [Client] Replace playmat crop spinboxes with a direct manipulation preview

The numeric fields exposed raw parameters with no relation to what the
game draws, accepted values the renderer clamps away, and no way to see
the result before committing

The preview now renders through the exact game pipeline into a viewport
shaped like a fresh board stack plus table area, with dimmed strips
marking where a developed table crops further. Drag pans, wheel zooms,
arrow keys nudge, plus and minus zoom, Backspace or Esc restores the
crop as of focus gain and lets Esc close the dialog when unchanged.
Focus ring and accessible name and description cover keyboard and screen
reader users, new paints use palette roles so themes recolor them

Took 25 minutes

Took 3 minutes


Took 54 seconds

* Remove stale constant

Took 3 minutes


Took 26 seconds

* Rebase

Took 2 minutes


Took 3 seconds

* Add editor spinboxes again

Took 4 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-24 18:22:48 +02:00
BruebachL
0b0ec64fe7
[DeckList] Add maybeboard zone constant and skip it in exports (#7174)
Introduce DECK_ZONE_MAYBEBOARD and its visible name, and treat the
maybeboard as editor-only scratch space: plain-text export, DeckStats
and TappedOut uploads now skip cards living there. Zones of this name
are created by later custom-zones units; until then the skips are inert.

Took 20 minutes

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-24 18:04:22 +02:00
BruebachL
0d0488cb5b
[Client] Edit banner crop by direct manipulation in the card art dialog (#7162)
The offset and zoom fields exposed raw numbers with no relation to the
strip, and the stored printing reset to the first local printing on
every open

The preview now paints through UserListPainter::drawCardArt itself, so
what you see is exactly what the user list renders. Drag pans the art
vertically at output scale, wheel zooms, arrow keys nudge, plus and
minus zoom, Backspace or Esc restores the crop as of focus gain and lets
Esc close the dialog when unchanged. Margins stay explicit spinboxes
since they trim the strip sides with no natural drag mapping. Legacy
stored zoom below the gesture floor is normalized once on open, focus
ring and accessible name and description cover keyboard and screen
reader users, all strings set in retranslateUi

Took 7 minutes

# Commit time for manual adjustment:
# Took 55 seconds


Took 41 seconds

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-24 18:01:59 +02:00
RickyRister
c3599be89b
[HomeTab] Fix incorrect handling of invalid backgroundSource setting (#7180)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 26 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
2026-08-23 23:58:43 -07:00
BruebachL
1459b3869d
[Server] Add abstract match game factory for future tournament modes (#7132)
Took 5 minutes

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-24 07:54:47 +02:00
BruebachL
a571a9aa04
[UserList] Restore accent-tinted rows for regular users in light mode (#7171)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 26 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-23 23:00:08 +02:00
BruebachL
648b472bfb
[UserList] Prevent failed loads from poisoning the banner card art cache (#7170)
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-23 22:46:07 +02:00
BruebachL
daa0dcb2ea
[UserList] Keep banner art when a params-less user copy arrives (#7173)
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-23 22:45:49 +02:00
BruebachL
21633eb0ec
[UserList] Raise banner card art cache to 1024 entries (#7169)
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-23 22:33:26 +02:00
tooomm
b80e6994ab
[CI] Use modern macos-26 across Mac builds (#7035)
* use macos26 across

* fix xcode versions

* fix cross-arch compilation test

* No cross-compile

* reduce cache churn and race conditions between matrix runs better

* Increase timeout to 300s

* Update CMakeDMGSetup.script

* switch intel target to faster `macos-15-intel` runner

* Newest Xcode on macOS 15 runners is 26.3

* Revert timeout change

* Qt bump, cache key updates, cleanup

* more cleanup

* more cleanup + separation

* cache key
2026-08-23 20:40:28 +02:00
BruebachL
03229db0bd
Fix cn and ss flags losing stars and clamp svg render sizes (#7168)
cn.svg and ss.svg defined their star polygons with coordinates around
plus/minus 5e5 compensated by tiny scale transforms. Qt drops shapes whose
device space bounds exceed its rasterizer coordinate limit, so both flags
silently lost their stars when rendered wider than roughly 76px. That
threshold was always exceeded because loadSvg with expandOnly renders at
the declared 640x480 native size before scaling down to the icon size.

Fold the scale transforms into the polygon coordinates so the geometry is
unchanged while bounds stay small at every render size.

Also cap expandOnly and usericon render canvases at four times the
requested size to bound memory use and keep pathological theme svgs away
from the rasterizer limit.

Took 8 minutes

Took 57 seconds


Took 3 minutes

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-23 20:13:14 +02:00
BruebachL
e2eb36f19f
[Server] Add match result strategy hook (#7131)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
* [Server] Add match result strategy hook

Took 7 minutes

Took 18 minutes

* Rebase.

Took 2 minutes

Took 13 seconds

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-23 18:37:15 +02:00
BruebachL
25a9e37ff8
[Client] Restore stored banner printing when the art dialog opens (#7157)
Two gaps made opening and confirming the banner dialog silently switch
the banner to the default art of the first local printing. The caller
dropped the card provider id when constructing the initial params, and
the constructor left the printing combo wherever onCardNameChanged put
it, which is always the first printing

Pass the provider id through, restore it in the combo when it resolves
locally, and keep it verbatim when it does not

Took 2 minutes

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-23 17:28:37 +02:00
BruebachL
bb0a96984d
[Game] Derive playmat sampling window from shared clamped helpers (#7159)
* [Game] Derive playmat sampling window from shared clamped helpers

The crop formula computed the sampled window inline with an unclamped
zoom floor, so stored vertical offsets below half of travel were dead
and extreme zooms could sample outside the art

Remap verticalOffset to place the window top edge within its travel,
floor zoom at visible width over min card side with a 4.0 ceiling,
clamp pan along the margin sum constant segment, and expose
playmatClampedZoom, playmatWindowSide and aspectFitRect so the game
renderer and any editor share one geometry model

Zoom 1 rendering is bit identical to before

Took 7 seconds

* Comments.

Took 29 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-23 17:26:15 +02:00
tooomm
976546fbe0
Docker: Build ARM image natively (#7046)
* native builds + merge

* naming and ordering

* use ninja and cmake build

* add ccache and cache mounts

* formatting

* Update servatrice.cpp

* Revert "Update servatrice.cpp"

This reverts commit 3acc684135c6db9baa17d00deaceecf8f7079721.

* remove ccache again

cache mounts are not part of GHA caches from docker action

* comments and cleanup

Use buildx provided in runner, see https://github.com/actions/runner-images/blob/main/images/ubuntu-slim/ubuntu-slim-Readme.md

* more explicit

* comments, first pass

* ${{ runner.temp }}

* $(printf "$GHCR_IMAGE@sha256:%s " *)

* follow docker docs for latest and extract short semver from our tags

* not so pretty, but allows the easy inspect at the end

* add Servatrice name

* add links to runner images

* comments, second pass

* cleanup
2026-08-23 15:51:33 +02:00
tooomm
f425dcc93e
Update & add arch/platform labels in release template (#7149)
* Update & add arch/platform labels

* Update release_template.md
2026-08-23 12:56:00 +02:00
BruebachL
c42fb6691d
[Client] Fix user list banner art rendering under display scaling (#7160)
Cached card art pixmaps carry the screen device pixel ratio, so both
banner painters did their crop math on scaled pixels and blitted the
result at raw over logical size, clipping art into its top left quadrant
on any display above 100 percent

Normalize a local copy to DPR 1 before crop math in UserListPainter and
the popup header, clamp srcX and srcY bounds against stored zoom below 1,
keep shared cache entries untouched

Took 15 minutes

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-23 11:44:59 +02:00
tooomm
63a970045a
[CI] Minimal GitHub Actions permissions (#7165)
* add permission block

* add permissions block

* add permission block

* add permissions block

* switch order
2026-08-23 11:42:47 +02:00
BruebachL
da924c2bd6
[Server] Add game lifecycle strategy hook (#7130)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
Took 30 minutes

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-23 10:26:14 +02:00
BruebachL
6b5105eecc
[Network] Don't allow Event_ListGames to leak info and bloat bandwith (#7164)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
Took 20 minutes

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-23 02:59:46 +02:00
BruebachL
8a5723c0b5
[Client] Show latency in status bar and server tab indicator (#7154)
* [Client] Show live connection latency in the status bar

Add a permanent status-bar label fed by ConnectionController's
pingStatsUpdated: shows the latest round-trip time, hides while
disconnected or without samples, and carries a tooltip with
Last/Median/95th percentile/Maximum over the rolling sample window
(mirrored into the accessible description). The server tab gets the
same stats as its tooltip.

Took 2 minutes

Took 2 minutes

Took 1 minute

Took 48 seconds

Took 25 seconds

Took 4 minutes

* [Client] Graph connection latency history in the status bar

Add LatencyGraphWidget, a size-agnostic bar sparkline over the rolling
sample window: heights scale to the window's own range while colors map
onto an absolute quality ramp, so a steady good ping stays green. Embed
it in the new LatencyStatusWidget together with the textual ping
readout and feed both through ConnectionController's forwarded signals;
the whole area hides while disconnected or without samples.

Took 9 minutes

Took 14 seconds

* [Client] Show latency details when clicking the ping display

Clicking the status bar ping area opens a popup with a larger instance
of the latency graph plus the numeric statistics, selectable and
mirrored into the accessible name. Qt::Popup closes it on any outside
click; contents refresh live while open.


Took 33 seconds

* Fixup from core commit

Took 6 minutes

Took 5 seconds

Took 5 minutes

* Lint.

Took 12 minutes

* Consolidate.

Took 6 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-23 02:25:04 +02:00
BruebachL
fc0199d3db
[GamesModel] Clear games types before merging (#7156)
Took 7 minutes

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-23 01:47:48 +02:00
BruebachL
88aa036f7e
[Client] Detect main-thread event loop stalls (#7155)
* [Client] Detect main-thread event loop stalls

LagMonitor ticks the GUI event loop every 500 ms and records gaps
beyond 2 s as stalls, warning with their duration and keeping a
bounded ring of recent records for diagnostics. Measurement uses a
monotonic QElapsedTimer so wall-clock steps and suspend do not
fabricate stalls. Recorded timestamps stay in wall time for
correlating with user reports.

Took 1 minute

Took 13 minutes


Took 2 minutes

* [Client] Rename LagMonitor constants to SCREAMING_SNAKE_CASE

Took 15 minutes

* [Client] Discard suspend-spanning gaps in LagMonitor

Windows counts sleep time in its monotonic clock, so a suspend would
fabricate one bogus stall per resume. Reset the clock on application
state changes and drop implausibly huge gaps; extract recordGap() for
testability.

Took 3 minutes

* [Client] Unit test LagMonitor stall recording

Drives recordGap() directly to cover the threshold, plausibility cap,
trim, and clear behavior without timing-dependent waits.

Took 36 seconds

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-23 00:53:40 +02:00
BruebachL
b91e872f5f
[Network] Measure real server round-trip times (#7153)
* [Network] Measure real server round-trip times

Time each command container from send to response with QElapsedTimer,
aggregate samples in a fixed-size ring buffer (last/median/p95/max),
and emit aggregated pingStatsUpdated at most once per second so the
hot path stays free of signal traffic. Stats are cleared on
disconnect. Forward the signal through ConnectionController for UI
consumers. Unit-tested in latency_tracker_test.

Took 37 minutes

Took 4 minutes

# Commit time for manual adjustment:
# Took 8 minutes

* Move params to struct, more informative debug

Took 56 seconds

Took 53 seconds

Took 2 minutes

Took 33 seconds

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-23 00:03:52 +02:00
tooomm
157e7022cd
CI: Add CodeQL workflow (#6958)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
* Create codeql.yml

* remove a few deps

* swap order

* fix query name + add comments

* concurrency group

* update trigger
2026-08-22 16:55:51 +02:00
BruebachL
5ba543f333
[Game] Give a graphics_item_type.h to arrow_item.h (#7150)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
Took 23 minutes

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-21 19:22:44 +02:00
BruebachL
ed4eb1cb31
[Server/Client/Protocol] Reporting users + moderation queue functionality (#7091)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
* [Server/Client/Protocol] Reporting users + moderation queue functionality

Took 6 minutes

Took 3 minutes

Took 8 seconds

Took 11 minutes

Took 12 minutes

Took 7 minutes

Took 15 seconds

Took 2 minutes

Took 1 minute


Took 30 seconds

Took 16 seconds

* CI Fix

Took 6 minutes

* CI Fix

Took 6 minutes

* [Protocol] Add moderation investigation commands

Adds the protocol layer for the moderation investigation suite:
- Command_GetUserSessions/GetUserAlts/GetModeratorLastLogins/ResetUserPassword/RemoveUserAvatar (1013-1017)
- Response extensions 1215-1219 with ServerInfo messages for sessions, alts, and staff logins
- last_login on Response_ReportUserInfo and warning_il on Response_WarnList

Took 2 minutes

* [Utility] Add warning categories parser with infraction levels

Parses the server's 'officialwarnings' setting (comma-separated, optional
'|IL' suffix) into WarningCategory structs so the client can display the
infraction level of each warning category. Includes GTest coverage.

* [Server] Add moderation investigation tools

Implements the server side of the moderation suite:
- getUserSessions/getUserAlts/getModeratorLastLogins/removeUserAvatar DB methods
- Handlers for all five new commands with audit records (PASSWORD_RESET,
  REMOVE_USER_AVATAR); password resets return a generated temporary password
- cmdGetWarnList now reports per-category infraction levels from the
  officialwarnings setting; cmdReportUserInfo reports last_login
- Update servatrice.ini.example with the warning taxonomy
- Password/avatar mutations report RespNameNotFound when the user does not exist

* [Client] Add moderation tab with investigate, password reset, and avatar removal

- New Moderation tab: search a user to show account info, alternate
  accounts, login sessions, and staff last logins; actions to reset the
  user's password (shows the generated temporary password) and remove the
  user's avatar
- 'Investigate user' entry in the user context menu opens the tab pre-loaded
  for that user
- Warning dialog shows the infraction level of each warning category
- Tab wired into TabSupervisor with a moderator-gated menu action, shortcut,
  and tabs.ini persistence (default closed)

* [Server/Client/Protocol] Address PR #7091 review: security, bug, and perf fixes

Security:
- Promote RESET_USER_PASSWORD to admin-only dispatch (was moderator-accessible)
- Reject password reset on users with equal/higher privilege than caller
- Notify affected user via Event_NotifyUser::CUSTOM when password is reset
- Add server-side category whitelist for reports
- Drop reporter name fallback in comment/details authorization (ID-only)
- Force password change: new DB column + login enforcement + client disconnect

Bugs:
- XSS via QTextEdit::append() → insertPlainText() in report tab and utils
- allNotified initialized to true even with empty recipients list
- Warning combo box: use currentData() instead of baked-in display text
- Report resolution now records who resolved (resolved_by column + audit)

Performance:
- IP-correlation subquery: add 6-month window + LIMIT 200
- getUserSessions: clamp limit to 500

Non-blocking:
- Palette-aware colors in report_utils.cpp (dark/light mode)
- Report list pagination: offset/limit fields + total_count in response
- SessionCommand enum gap comment for reserved values 1201-1203

Schema: 36→37 (force_password_change), 37→38 (resolved_by)

Took 12 minutes


Took 16 seconds

* Fix macOs pedantry

Took 5 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-21 15:00:13 +02:00
BruebachL
9eafd90a91
[Game] Playmats (#7101)
* [Game] Playmats

Took 19 seconds

Took 1 minute

* [Playmats] Add fixed override and configurable fallbacks to settings.

Took 29 minutes

Took 43 seconds

* Add main to test.

Took 1 minute

Took 29 seconds

* Move settings to own group

Took 11 minutes

* Some attempts to refresh macOS compositor

Took 2 minutes

* Try something else

Took 17 minutes

* Don't manipulate live list

Took 11 minutes

* Change things about resolution, address comments.

Took 45 minutes

Took 12 minutes

* Comments.

Took 14 minutes

Took 8 seconds

* Re-order settings menu location

Took 2 minutes

* Rename PlaymatResolution to Info and add enums

Took 8 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-21 10:40:49 +02:00
BruebachL
74a454552a
[Cards] Artist attribution (#7092)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
* [Cards] Artist attribution

Took 7 minutes

Took 4 minutes

* Nudge attribution pill on home screen to align

Took 3 minutes


Took 28 seconds

Took 38 seconds

* Lint.

Took 3 minutes

* Lint.

Took 2 minutes

* Fix rebase whoopsie

Took 5 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-21 02:54:36 +02:00
tooomm
18b4ffefa8
[CI] macos-14 GHA runner is deprecated (#7127)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
2026-08-20 06:57:39 +02:00
BruebachL
919b9d92ac
Fix a bug with a symlink dir not resolving correctly. (#7148)
Some checks failed
Build Docker Image / amd64 & arm64 (push) Has been cancelled
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-18 21:50:12 +02:00
tooomm
06762ea7b4
Update peglib to v1.16.0 (#7134)
Some checks failed
Build Docker Image / amd64 & arm64 (push) Waiting to run
Build Desktop / Configure (push) Has been cancelled
Build Desktop / Debian 13 (push) Has been cancelled
Build Desktop / Debian 12 (push) Has been cancelled
Build Desktop / Fedora 44 (push) Has been cancelled
Build Desktop / Fedora 43 (push) Has been cancelled
Build Desktop / Servatrice_Debian 12 (push) Has been cancelled
Build Desktop / Ubuntu 26.04 (push) Has been cancelled
Build Desktop / Ubuntu 24.04 (push) Has been cancelled
Build Desktop / Arch (push) Has been cancelled
Build Desktop / macOS 14 (push) Has been cancelled
Build Desktop / macOS 15 (push) Has been cancelled
Build Desktop / macOS 13 Intel (push) Has been cancelled
Build Desktop / macOS 15 Debug (push) Has been cancelled
Build Desktop / Windows 10 (push) Has been cancelled
2026-08-17 22:52:30 +02:00
BruebachL
7c550ee505
[Game] Add an invite button to non-started and not full games (#7143)
* [Client] Send game invites from the user context menu via a private message

The user context menu gains an "Invite to Game" submenu listing the
inviteable games in the room (the inviter's own games, honoring the
buddy-only setting). Picking one opens a private message to the target
user with a cockatrice://joingame link naming the game, so the target
gets a clickable invite instead of a raw URL. Multi-game rooms offer a
picker; a single inviteable game sends directly. Sending a message to
an offline user no longer swallows the draft — it reports that the
user is offline and keeps the typed text.

Took 1 minute

* [Client] Add invite-to-game dialog to the game window

Took 15 seconds

* [Client] Open the invite dialog taller by default without enforcing a minimum size

* Move button to bottom

Took 3 minutes

* Address comments.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-17 21:37:15 +02:00
BruebachL
08d6b51db9
[Server] Add deck validation strategy interface (#7129)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-17 11:21:25 +02:00
BruebachL
a8bacc5296
[VDS] Add async-scanned model and sort/filter proxy (#7105)
* [VDS] Add async-scanned model and sort/filter proxy (model layer)


Took 1 minute

Took 27 seconds

* Address comments

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-17 11:20:26 +02:00
BruebachL
765ebf8fb1
[UserList] Context menu invite (#7138)
* [Client] Send game invites from the user context menu via a private message

The user context menu gains an "Invite to Game" submenu listing the
inviteable games in the room (the inviter's own games, honoring the
buddy-only setting). Picking one opens a private message to the target
user with a cockatrice://joingame link naming the game, so the target
gets a clickable invite instead of a raw URL. Multi-game rooms offer a
picker; a single inviteable game sends directly. Sending a message to
an offline user no longer swallows the draft — it reports that the
user is offline and keeps the typed text.

Took 50 seconds


Took 3 minutes

* [Client] Extract sendPrivateMessage() to fix invite message draft overwrite

sendInviteMessage() was calling sayEdit->setText(text) then sendMessage(),
which overwrites any text the user had typed.  Extract the command-building
and sending logic into a new sendPrivateMessage(const QString &text) method
that takes the text directly.  sendMessage() now calls it after its guards
and clears sayEdit; sendInviteMessage() calls it directly without touching
the input field at all.

Took 33 minutes

* Rename method, address comments.

Took 5 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-17 10:49:19 +02:00
BruebachL
776f917ffc
[Client] Confirm before joining a game opened from a game link (#7137)
* [Client] Confirm before joining a game opened from a game link

Joining a game from a cockatrice://joingame link is a navigation
decision, so restate what will be joined and ask before acting:
the confirm names the game description when the link carries one
(falling back to the room name and numeric id for older links), and
reports the host:port so links that point at a different server are
obvious. The intent chain is only started after confirmation.


Took 3 minutes

* [Client] Extract the join-game confirm message into a helper

Took 3 minutes

# Commit time for manual adjustment:
# Took 6 seconds

* Proper fwd declare.

Took 3 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-17 09:37:51 +02:00
BruebachL
b2cdf44bbd
[UserList] Show amount of online buddies (#7126)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
* [UserList] Show amount of online buddies

Took 11 minutes

* [UserList] Replace early return with if-else in updateSectionDivider

RickyRister nit: the code is easier to follow with a standard if-else
branch instead of an early return for the Buddy section.

Took 1 minute

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-17 03:58:41 +02:00
BruebachL
60ee81cfbe
[Client] Route cockatrice:// link clicks from chat to the intent chain (#7136)
* [Client] Route cockatrice:// link clicks from chat to the intent chain

A cockatrice:// link clicked in chat is currently handed to the OS (or
does nothing in-process). Clicks now emit a cockatriceLinkActivated signal
that travels ChatView -> Tab -> TabSupervisor -> MainWindow, which feeds
the URL through the same IntentUrlParser the OS activation path uses, so
the join runs entirely in-process. card/user schemes and all other links
behave as before.

* [Client] Route cockatrice:// link clicks from the in-game chat to the intent chain

* [Client] Reuse one IntentUrlParser instance for cockatrice:// links

Took 59 seconds

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-17 01:03:37 +02:00
BruebachL
3de7882f0c
[UserList] Fix context menu crash by correctly parenting (#7145)
Took 5 minutes

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-17 00:53:43 +02:00
BruebachL
fe53f9c3eb
[Chat] Render game link buttons (#7135)
* [Chat] Render cockatrice://joingame links in chat as clickable buttons

Words starting with cockatrice:// become button-style anchors labelled with
the game description, id and server (falling back to id + server for links
built without a description). The description is spliced via the multi-arg
arg() overloads so a title containing "%…" cannot corrupt the label.

Keyboard link access is enabled so the anchors are reachable without a mouse.

* [Chat] Fix percent-encoding and scheme gating in game-link chat labels

Game descriptions containing '%' were rendered as '%25' in the chat
button label because QUrlQuery's default decode leaves %25 untouched.
Use QUrl::FullyDecoded for the description item, and restrict the
invite-button treatment to cockatrice://joingame links; any other
cockatrice:// scheme now falls through to plain text.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-17 00:26:33 +02:00
BruebachL
f466a25893
[Client] Keep the message draft and notify when the recipient is offline (#7142)
* [Client] Keep the message draft and notify when the recipient is offline

* Don't blindly assume a user is online.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-17 00:16:55 +02:00
BruebachL
6c8fcf7d19
[Client] Confirm before joining a full game as a spectator (#7140)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-16 23:20:47 +02:00
BruebachL
94943f7ff3
[Client] Add copy-game-link action to the game menu (#7139)
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-16 23:20:36 +02:00
BruebachL
078e67c56f
[Client] Name the game in the join-game password prompt (#7141)
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-16 23:03:29 +02:00
BruebachL
d36865518e
[Game] Extract makeGameJoinLink helper for cockatrice://joingame links (#7133)
The inline URL building in GameSelector's copy-link action moves into a
shared helper so every invite/copy site produces the same link format.
The helper embeds the game description as an extra "game" query item
(percent-encoded); links without it stay valid — the receiving parser
ignores unknown query items.

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-16 22:14:49 +02:00
BruebachL
7c134efd29
[Server] Consolidate Server_Game construction parameters into a GameConfig struct (#7128)
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-16 19:13:03 +02:00
BruebachL
d99798111e
[UserList] Unify friends/online/ignored list with section dividers and add search bar. (#7119)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
* [UserList] Unify friends/online/ignored list with section dividers and add search bar.

Took 31 minutes

Took 7 seconds

* [UserList] Add a light mode theme

Took 12 minutes

Took 11 seconds


Took 5 minutes

Took 2 minutes

* [UserList] Re-sort when a user's online state changes

setUserOnline() flipped the online flag but never re-sorted, so a buddy
who went offline kept the position they had while online and stayed at
the top of the list. Re-sort (and re-apply the filter) whenever the flag
actually changes, mirroring processUserInfo().

Took 10 minutes

* [UserList] Show users in every section they belong to

The sectioned list used one row per user with a priority rule
(ignored > buddy > online), so an online buddy only appeared under
"Buddies" and never in the "Online" list. Sections are now pure
membership views: a user gets one row per section they belong to, so an
online buddy appears under both "Online" and "Buddies".

- Track rows per (section, user) in sectionUsers instead of reparenting
  a single row; the name->primary-row map is kept for external lookups.
- Rebuild, presence and buddy/ignore mutations create/drop rows per
  section instead of moving a single row between sections.
- Dropping one membership no longer removes the user from the other
  sections.

* [UserList] Keyboard navigation for section dividers, popup on selection

Section dividers were not selectable, so arrow-key navigation skipped
them entirely, and the user popup only appeared on hover or click. Now:

- Dividers are selectable, so Up/Down navigation lands on them; they act
  as collapsible headers once focused (Enter/Space toggle, Left/Right
  collapse/expand per tree convention), with a focus indicator drawn by
  the existing delegate.
- The popup follows keyboard selection via currentItemChanged, exactly
  like mouse hover, and closes when the selection moves to a divider or
  leaves the list.
- The popup anchors on the hovered/selected row instead of a user-name
  lookup, so with duplicate rows (online + buddy) it stays attached to
  the row under the mouse/cursor.
- Left-arrow now actually collapses an expanded section divider: the
  collapse branch hardcoded the target expansion state to 'expanded',
  making the key a no-op.
- The user popup no longer flashes through a fade when hopping between
  users (hover or arrow-key navigation): a content swap keeps it opaque,
  and pending show/hide timers are cancelled so an armed hover timer
  cannot override a keyboard-selected row or a pending hide kill the
  newly shown popup.
- Bulk rebuild defers per-row divider-count updates to endBulkLoad(),
  removing the quadratic recount during large online-list loads.
- handleOnlineChangeLeft/handleListRemove skip the sort+filter+repaint
  when nothing actually changed.

* [UserList] Tune the role row gradient colors (dark parity, light mode)

Dark mode is byte-for-byte the pre-branch painter profile, with the
original saturated-left to navy-right fade restored verbatim. Light mode
uses the same language at high tint strength: role rows get colored
fades (0.75/0.65 left to 0.18/0.10 right), and regular users get flat
warm paper cards (AlternateBase) instead of the grey slate.

* [UserList] Deselect the list and close the popup on outside clicks

Clicking anywhere outside the tree, the popup or an open menu now clears
the selection and hides the popup, so a pinned popup does not stay open
when the list loses focus.

- The application-wide event filter watches every mouse press and treats
  a press as inside the list UI only when its target is the tree, the
  popup or an open menu (parent-chain walk), so a click on another list,
  a tab or the window background deselects.
- A hover popup now also closes when the cursor leaves the hovered row.
  The hide timer previously checked whether the cursor was over the
  tree, which is always true over empty list space and section dividers,
  so the popup stayed open after moving off the user.
- Deselection keeps the current item so keyboard navigation is not
  disturbed, and the pinned flag is dropped before hiding so the
  selection-changed handler does not hide twice.

Took 15 minutes

* [UserList] Use an enum for the list sections

The section identifiers were stringly-typed: eleven hardcoded
QStringLiteral comparisons scattered through user_list_widget.cpp, and
the display path (sectionTitle) maps every id through tr() anyway, so
the raw strings were never shown. A typo compiled fine and silently
broke a section.

- enum class Section { Buddy, Online, Ignore } replaces the section
  strings across the sectioned-list API (setSectioned, getSectionIds,
  setSectionExpanded, the sectionExpanded signal and all membership
  helpers), giving compile-time checks at every call site.
- sectionTitle becomes a switch over the enum and the dead raw-string
  fallback is gone.
- The expanded-section state persists the same stable keys via the
  panel widget boundary, so existing settings files survive unchanged.
- The divider reverse lookup in handleSectionExpansion no longer relies
  on an empty-string sentinel from QMap::key; it scans the three
  dividers and bails when the item is not one of them.

Took 12 minutes

# Commit time for manual adjustment:
# Took 2 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-15 22:53:13 +02:00
BruebachL
16b6132701
[Tabs] Add a setting to define startup tab on application launch (#7121)
* [Tabs] Add a setting to define startup tab on application launch.

Took 29 minutes

* Naming and sizing

Took 4 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-15 22:16:37 +02:00
BruebachL
fbe5c4ade0
[VDE] Add a new setting to determine initial tab (Context/Deck/Database) (#7122)
* [VDE] Add a new setting to determine initial tab (Context/Deck/Database)

Took 16 minutes

Took 4 seconds

* Adjust tooltip

Took 4 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-15 21:55:39 +02:00
BruebachL
6ee1fd58e6
[Arrows] More fixes and assurances for drag arrows (#7117)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-14 19:22:30 +02:00
BruebachL
daa896866f
[VDD] Defer heavy construction until after the tab paints (#7115)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
* [VDD] Defer heavy construction until after the tab paints

* Use singleshot QTimer instead of member variable.

Took 27 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-14 11:39:10 +02:00
BruebachL
404b0cdf28
[Game] Animate Arrows (#7099)
* Add an arrow draw animation setting

- New arrowDrawAnimation cards-display setting, default on
- The arrow draw animation checkbox joins the animation settings group
- Visual Deck Storage selection animation checkbox moves next to the
  other animation checkboxes, and the enable/disable-all buttons now
  cover it and the arrow animation

Took 2 minutes

Took 21 minutes

Took 7 minutes


Took 11 minutes

Took 20 seconds

* Animate arrows drawing from start to target

- The arrow stroke reveals itself along the arc with an eased timing,
  followed by a short light sheen that sweeps down the shaft
- The arrow head pops in once the reveal reaches it, then the whole
  arrow fades from its initial glow
- Decay is driven by GameScene's shared animation timer through the
  IAnimatedItem interface (QElapsedTimer based), respecting the
  arrowDrawAnimation setting
- GameScene adds the arrow item to the scene before starting its
  animation so the item is registered against a valid scene

Took 6 minutes


Took 1 minute

* Defer animation start so arrows don't start halfway materialized

Took 13 minutes

* Don't draw tip/shaft outline

Took 12 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-14 10:44:17 +02:00
BruebachL
1eca89d75a
[Refactor] Batch CardDatabaseModel enabled-sets rebuild into one model reset (#7113)
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-14 09:03:24 +02:00
BruebachL
d93f63050c
[Server] Fix unauthenticated crash via replay submit code (#7072)
cmdReplaySubmitCode dereferenced userInfo without an authentication
guard, allowing an unauthenticated connection with a valid replay code
to segfault the server. Add the same authState != PasswordRight guard
used by all other replay handlers, and gate session command dispatch on
a pre-auth whitelist so future handlers cannot be reached before login.

Took 2 minutes

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-14 08:56:54 +02:00
BruebachL
7971ebfe94
[VDD] Parent filter toolbar layouts to their group boxes (#7116)
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-14 07:30:11 +02:00
BruebachL
a40969003c
[CardDB] Cache CardDatabaseQuerier count maps (#7114)
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-14 07:19:34 +02:00
BruebachL
2eca362e2b
[DeckEditor] Reuse the inherited CardDatabaseModel in the visual editor (#7112)
Took 6 minutes

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-14 07:11:26 +02:00
BruebachL
813a3ea034
[ThemeManager] Better default style guard (#7110)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
Took 21 minutes

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-14 06:51:46 +02:00
BruebachL
2086deff5c
[CI] No need to capture a variable in a lambda. (#7109)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
Took 32 minutes

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-13 20:51:23 +02:00
tooomm
3f22089af7
CI: Integrate vcpkg with GH dependency graph (#7032) 2026-08-13 20:44:49 +02:00
BruebachL
9d26e07165
[Game] Animate life counter manipulation (#7100)
* Add animations toggles for the life counter and battlefield

Per-effect toggles plus Enable/Disable-all buttons: a life-counter
flash on loss and a crimson battlefield shimmer on damage.


Took 24 seconds

* Animate life changes with a counter flash and battlefield shimmer

- Life loss/gain pulses the player life counter with a brief flash
- The battlefield table zone shimmers crimson on damage
- Respects the per-effect animation toggles

Both effects drive their decay from GameScene's shared animation timer
through the IAnimatedItem interface (QElapsedTimer based), instead of
owning per-item QTimers.

Took 8 minutes

* Revert unintentional cherry picks

Took 2 minutes

* Lambda to re-use path calculation.

Took 8 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-13 19:12:12 +02:00
BruebachL
12a5b34e42
[Refactor] Decouple DeckFilterString from DeckPreviewWidget (#7104)
* [Refactor] Decouple DeckFilterString from DeckPreviewWidget

Took 19 minutes

* Use designated initializer

Took 24 seconds

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-13 19:04:16 +02:00
BruebachL
26fb8622e3
[GamesModel] Rename 'Creator' column to 'Host' (#7083)
* [GamesModel] Rename 'Creator' column to 'Host' (#2108)


Took 5 minutes

* Actually re-broadcast host change

Took 8 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-13 18:58:21 +02:00
BruebachL
48776cfeba
[Game] Generic Animation Interface (#7098)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
* Introduce generic IAnimatedItem interface for scene animations

GameScene's shared 10ms animation timer previously only knew about
CardItems (cardsToAnimate). Generalize it so any scene item can tick on
the shared timer instead of owning its own QTimer:

- New IAnimatedItem interface with a single animationEvent() tick.
- GameScene tracks animated items in a QHash keyed by QObject and
  auto-unregisters items when they are destroyed, so an item deleted
  mid-animation (concede, removePlayer, deleteLater) can never leave a
  dangling pointer in the set.
- GameScene::~GameScene disconnects incoming connections before the
  animation timer is deleted; all timer stops are null-guarded so
  destruction ordering no longer matters.
- AbstractCardItem implements IAnimatedItem with a no-op tick so the
  existing tap-animation registration path keeps working; CardItem
  overrides it with the real rotate animation.

Took 5 minutes

* Add Enable/Disable all animations buttons to settings

The animation settings group gets two push buttons that toggle every
per-effect animation checkbox at once. The base branch carries the
buttons and the shared slots; per-effect toggles (life counter,
battlefield, arrow draw) are added by the feature branches on top.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-12 20:36:33 +02:00
BruebachL
ce2c31424c
[Chat] Scroll chat view to bottom when loading chat history (#7079)
* [Chat] Scroll chat view to bottom when loading chat history (#2725)

Took 4 minutes


Took 38 seconds

* Harden scrolling to bottom

Took 11 minutes

* Implement stick-to-bottom flag

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-12 15:52:49 +02:00
BruebachL
ef3929356b
[Server/Client] Fix open_decklists not showing for already-loaded decks on join (#7097)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
Took 6 minutes

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-12 12:17:03 +02:00
kongwu
83920a4ae9
[Servatrice] Notify newly logged-in users of pending server shutdown (#6976)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
2026-08-11 20:33:41 +02:00
BruebachL
83fd65b34b
[CardSearch] Add card completion popups to chats and search fields (#7089)
* Add card completion popups to chats and search fields

Completes @mention and [[card]] in chat, and card names in the deck
editor, EDHREC, Archidekt, card art rules, and user card settings
searches. Pops up a styled list with mana pips and a card image
preview, flipping the list order when the popup opens above the text
field.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-11 18:50:30 +02:00
RickyRister
a0e76607b5
[CardInfo] Encapsulate lazy properties loading into new class (#7088)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
* [CardInfo] Encapsulate lazy properties loading into new class

* check if new value was inserted
2026-08-10 22:43:21 -07:00
DawnFire42
a5f43c08fa
Fix share decklists checkbox to reflect actual game setting in game info dialog (#7094)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
2026-08-10 23:31:58 +02:00
BruebachL
bfdb9b0f1d
[UserList] Bulk load to prevent hang on connect, fix multi-monitor positioning (#7087)
* [UserList] Bulk load to prevent hang on connect, fix multi-monitor positioning

Took 48 minutes

* Extract slot to method

Took 10 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-10 22:11:14 +02:00
Galaxy
42f890ea7c
[TabRoom] Swapped wording in server filtering to match quick filters. (#7090)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
2026-08-10 06:19:47 +02:00
RickyRister
c08dc780a7
[Replay] Implement option to skip empty sections (#7069)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
* add settings

* [Replay] Implement option to skip empty sections

* correct starting offset
2026-08-08 23:12:10 -07:00
Galaxy
46bdb6df32
Included an Exclude button (ironic) on the Visual Deck Storage. (#7086)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
2026-08-09 03:20:47 +02:00
BruebachL
e65fcbfa7d
[TabServer] ensure "isOpen" settings value is always persisted/kepy in sync (#7085)
Took 23 minutes

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-09 01:44:22 +02:00
BruebachL
827e44a4d3
[DeckEditor] Use CommanderSpellbook.com to estimate bracket if format is 'commander' (#6415)
* [DeckEditor] Use CommanderSpellbook.com to estimate bracket if format is 'commander'

Took 2 minutes

Took 16 minutes

* Convert json data holder to structs, rename variables, extract widget

- Extract bracket estimation UI from DeckEditorDeckDockWidget into a
  new CommanderBracketWidget
- Move CommanderSpellbook integration settings from CardsDisplaySettings
  to DeckEditorSettings (matching the settings refactor on master)
- Rename CommanderSpellbook integration variables to drop the redundant
  'DeckEditor' prefix

Took 4 minutes

# Commit time for manual adjustment:
# Took 6 minutes

# Commit time for manual adjustment:
# Took 8 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-09 00:46:17 +02:00
BruebachL
e9eab328a4
[PictureLoader] Schedule exponential backoff on 429 - Too many request handler and call failed on fail (#7053)
Took 16 minutes

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-08 22:55:27 +02:00
BruebachL
adf574e038
[Settings] Shuffle some settings around (#7084)
* [Settings] Shuffle some settings around

Took 21 minutes


Took 1 hour 25 minutes

* [Settings] Camel case everything

* Revert debug schema change

* Add new classes

* Fix card counters writing to global

* Fix CI tests

* Fix Windows CI

* interface() is a protected keyword for MSVC

Took 5 minutes

Took 5 seconds

* [Settings] Keep menu settings on the appearance settings page

Leave the 'Menu settings' group box on the appearance settings page for
now; relocating it to the user interface settings page will be done in a
separate PR.

Took 6 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-08 22:27:41 +02:00
BruebachL
bf6b2a90bc
[Settings][Dialog] Implement search for settings by text, description, tooltip, etc. (#7065)
* [Settings] Implement search

Took 38 minutes

Took 8 seconds

Took 9 minutes

Took 5 seconds

Took 44 seconds


Took 25 seconds

* Comments

Took 23 minutes


Took 15 seconds

* Comments

Took 1 hour 14 minutes

* Minor fixes to search

Took 13 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-08 22:19:21 +02:00
BruebachL
ba203440af
[Chat] Don't tag cards that have no match in the database (#7078)
* [Chat] Don't tag cards that have no match in the database (#2217)

Took 6 minutes


Took 4 minutes

* Highlight not found but attempted tag

Took 6 minutes

* Change color to not be server message color

Took 3 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-08 22:18:11 +02:00
BruebachL
c4316c40e9
[CI] Remove Qt5 (#7071)
* [CI] Remove Qt5

Took 10 minutes

Took 9 minutes

* Revert CI failure and fix up comments

Took 4 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-08 22:04:50 +02:00
BruebachL
59d90db3c7
Define Cockatrice as an editor/handler for .cod files and cockatrice:// protocol on all platforms (#6775)
* [Application] Add single instance guard and mime types.

Took 2 hours 39 minutes

Took 18 minutes

Took 5 minutes

Took 12 seconds


Took 11 seconds

* Rework

Took 30 minutes


Took 50 seconds

* Only enforce single instance if launched with arguments.

Took 5 minutes

* Prototype intents

Took 53 minutes

Took 6 seconds

* Connect/disconnect and join game/room intents.

Took 3 hours 14 minutes

Took 2 seconds

Took 15 seconds

* Fix include.

Took 1 minute


Took 23 seconds

Took 2 seconds

* Mac handling.

Took 10 minutes

Took 12 seconds

Took 3 minutes

* Lint.

Took 3 minutes

* Rebase.

Took 3 minutes

Took 17 seconds

* Implement UrlSchemeEventFilter

Took 10 minutes

Took 7 seconds

* Qt Moc

Took 3 minutes

* Modern PList.

Took 21 minutes

Took 1 minute

* Debug output.

Took 6 minutes

Took 19 minutes

* Watch file:// prefix.

Took 15 minutes

Took 7 seconds

* Better handler.

Took 6 minutes

* Don't store reference in member

Took 5 minutes

* Move impl to cpp, fix lifetime issues.

Took 11 minutes

Took 2 minutes

* Better single-instance handoff, url intent harded

copy game link context-menu
Polish for installers

Took 35 minutes

Took 8 seconds

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-08 19:02:19 +02:00
BruebachL
27fb5e51de
[Security] Redact sensitive user data from client-visible responses (#7077)
The getUserInfo command for a user that is not currently online returned
the full database record, including the account id, the email address
and the stored client id, to any logged-in requester. Mirror the
redaction already applied to online users via copyUserInfo(): the id and
email are only ever exposed to the account owner, and the client id only
to moderators.

The buddy/ignore add-to-list event likewise returned the target user's
email address and client id to the requester. The list entry only needs
the public profile fields, so strip the email and client id from it as
well.

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-08 17:32:39 +02:00
RickyRister
0d14fb77a0
[CardInfo] Refactor: remove properties setters (#7070) 2026-08-07 11:50:58 -07:00
tooomm
32619f4d55
Add --x-abi-tools-use-exact-version to vcpkg invocation (#7068) 2026-08-05 23:14:29 +02:00
tooomm
2bc921321b
Update vcpkg submodule to 2026.07.29 release (+ add baseline) (#7067) 2026-08-05 22:00:06 +02:00
BruebachL
10b99a7415
[Dialogs] Focus login after selecting server, center update message box (#7080)
* [ConnectDialog] Focus login field after selecting a server (#3346)

Took 12 minutes

* [UpdateDialog] Center update message boxes over the main window (#3538)

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-05 20:09:22 +02:00
BruebachL
c7ab75c332
[UpdateDialog] Close dialog when declining an update (#2517) (#7081)
Some checks failed
Build Desktop / Configure (push) Has been cancelled
Build Docker Image / amd64 & arm64 (push) Has been cancelled
Build Desktop / Debian 13 (push) Has been cancelled
Build Desktop / Debian 12 (push) Has been cancelled
Build Desktop / Fedora 44 (push) Has been cancelled
Build Desktop / Fedora 43 (push) Has been cancelled
Build Desktop / Servatrice_Debian 12 (push) Has been cancelled
Build Desktop / Ubuntu 26.04 (push) Has been cancelled
Build Desktop / Ubuntu 24.04 (push) Has been cancelled
Build Desktop / Arch (push) Has been cancelled
Build Desktop / macOS 14 (push) Has been cancelled
Build Desktop / macOS 15 (push) Has been cancelled
Build Desktop / macOS 13 Intel (push) Has been cancelled
Build Desktop / macOS 15 Debug (push) Has been cancelled
Build Desktop / Windows 10 (push) Has been cancelled
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-05 12:27:46 +02:00
RickyRister
1ed9823b56
[CardInfo] use QString QHash instead of QVariantHash for properties (#7063)
Some checks failed
Build Desktop / Configure (push) Has been cancelled
Build Docker Image / amd64 & arm64 (push) Has been cancelled
Build Desktop / Debian 13 (push) Has been cancelled
Build Desktop / Debian 12 (push) Has been cancelled
Build Desktop / Fedora 44 (push) Has been cancelled
Build Desktop / Fedora 43 (push) Has been cancelled
Build Desktop / Servatrice_Debian 12 (push) Has been cancelled
Build Desktop / Ubuntu 26.04 (push) Has been cancelled
Build Desktop / Ubuntu 24.04 (push) Has been cancelled
Build Desktop / Arch (push) Has been cancelled
Build Desktop / macOS 14 (push) Has been cancelled
Build Desktop / macOS 15 (push) Has been cancelled
Build Desktop / macOS 13 Intel (push) Has been cancelled
Build Desktop / macOS 15 Debug (push) Has been cancelled
Build Desktop / Windows 10 (push) Has been cancelled
* [CardInfo] use QString QHash instead of QVariantHash for properties

* bump CACHE_VERSION

* cleanups
2026-08-02 20:02:37 -07:00
RickyRister
b44dcf5951
[Replay] Refactor: extract replay playback logic into single class (#7060)
* [Replay] Refactor: consolidate replay logic into single class

* fixes
2026-08-02 19:22:33 -07:00
RickyRister
ca1c063687
[Game] Implement total power tally (#7057)
* [Game] Implement total power tally

* PR comments
2026-08-02 19:20:47 -07:00
RickyRister
a4557454a7
[Game] Remove tally requiring two cards selected (#7058) 2026-08-02 18:51:10 -07:00
tooomm
486b8e8407
Capitalize label (#7066) 2026-08-02 21:17:20 -04:00
RickyRister
6766b0af89
[SettingsButtonWidget] Fix memory leak from unparented popup (#7055)
Some checks failed
Build Desktop / Configure (push) Has been cancelled
Build Docker Image / amd64 & arm64 (push) Has been cancelled
Build Desktop / Debian 13 (push) Has been cancelled
Build Desktop / Debian 12 (push) Has been cancelled
Build Desktop / Fedora 44 (push) Has been cancelled
Build Desktop / Fedora 43 (push) Has been cancelled
Build Desktop / Servatrice_Debian 12 (push) Has been cancelled
Build Desktop / Ubuntu 26.04 (push) Has been cancelled
Build Desktop / Ubuntu 24.04 (push) Has been cancelled
Build Desktop / Arch (push) Has been cancelled
Build Desktop / macOS 14 (push) Has been cancelled
Build Desktop / macOS 15 (push) Has been cancelled
Build Desktop / macOS 13 Intel (push) Has been cancelled
Build Desktop / macOS 15 Debug (push) Has been cancelled
Build Desktop / Windows 10 (push) Has been cancelled
2026-07-28 16:12:41 -07:00
RickyRister
be6a783c12
[Replay] Refactor: rename ReplayManager to ReplayWidget (#7059)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
2026-07-27 19:40:43 -07:00
BruebachL
6260bd1ed1
[GameScene] Arrow Graphics destruction safety (#7062)
Took 13 minutes

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-07-28 02:47:23 +02:00
BruebachL
749223c2dc
[Card Database] Improve loading times through binary cache (#7051)
* [Card Database] Improve loading times through binary cache

Took 10 minutes


Took 9 minutes

Took 16 seconds

* [Card Database] Remove lib qt include

Took 18 minutes


Took 14 seconds

* Downgrade to 6.3 datastream

Took 5 minutes

* go up to 6.4 datastream

Took 1 minute

* Address comments

* Small bug fixes

Took 20 minutes


Took 10 seconds

* More fixes.

Took 4 minutes


Took 4 seconds

* Even more fixes.

Took 11 minutes


Took 4 seconds

* More fixes.

Took 6 minutes

Took 26 seconds

Took 8 minutes

* Namespace instead of class

Took 6 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-07-27 23:12:53 +02:00
DawnFire42
4bb8831531
Vertical stacking behaviour fix (#7054)
The overflow function covers the necessary edge case, the minimized branch of it was an artefact of earlier builds
2026-07-27 08:00:28 -07:00
BruebachL
688c5d2a12
Fix up a small rebase/merge conflict (#7061)
Took 2 minutes


Took 5 minutes

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-07-27 03:59:57 -07:00
BruebachL
61b9d7abf6
[Networking] Doxygen (#7030)
* [Networking] Doxygen

* Lint cause my autolinter is broken lol

* Update.

* Update Doxyfile

Co-authored-by: tooomm <tooomm@users.noreply.github.com>

* Alphabetical ordering

Co-authored-by: tooomm <tooomm@users.noreply.github.com>

* Add new card art rule comment

* move filter program into doxygen folder

* [Networking] Doxygen


Took 49 seconds

* [Networking] Doxygen

Took 2 hours 0 minutes

Took 58 minutes

Took 8 seconds

Took 29 seconds

Took 4 minutes

Took 1 minute

Took 4 minutes

* [Networking] Fix Doxyfile

Took 2 hours 0 minutes

Took 58 minutes

Took 8 seconds

Took 29 seconds

Took 4 minutes

Took 1 minute

Took 6 minutes

* [Networking] Fix Doxyfile

Took 2 hours 0 minutes

Took 58 minutes

Took 8 seconds

Took 29 seconds

Took 4 minutes

Took 1 minute

Took 3 minutes

* [Networking] Fix Doxyfile again

Took 2 hours 0 minutes

Took 58 minutes

Took 8 seconds

Took 29 seconds

Took 4 minutes

Took 1 minute

Took 2 minutes

---------

Co-authored-by: Lukas Brübach <lukas.bruebach@bdosecurity.de>
Co-authored-by: tooomm <tooomm@users.noreply.github.com>
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-07-27 11:27:00 +02:00
BruebachL
12f0f59453
[Settings] Split cache_settings monolith into multiple SettingsManager sub-classes (#7050)
* [Settings] Split cache_settings into multiple files

Took 9 minutes

Took 4 minutes

* [Settings] Fwd declare settings classes in cache_settings

Took 15 minutes

* Fix oracle includes.

Took 8 minutes

* Address comments, fix windows CI

Took 8 minutes

* fix copy constructor visibility

Took 3 minutes

* lint

Took 2 minutes

* Fix native format tests.

Took 5 minutes

* Remove test header guard

Took 4 seconds

* Remove tests invalid in CI environ

Took 24 seconds

* Adjust to rebase.

Took 11 minutes

* Change settings file name.

Took 8 minutes

---------

Co-authored-by: Lukas Brübach <lukas.bruebach@bdosecurity.de>
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-07-27 11:25:39 +02:00
BruebachL
cf0b453e75
[PaletteEditor] Ensure directory is writeable, don't fall back to alternate palette (#7048)
* [PaletteEditor] Ensure directory is writeable, don't fall back to alternate palette.

Took 17 minutes

* [ThemeManager] Extract system theme dir path resolution to helper, use it in default palette fetching

Took 8 minutes

* [ThemeManager] Expose styling again

Took 3 minutes

* [ThemeManager] Capture app palette before theme is applied for reverting

* [ThemeManager] Simply delete custom palette instead of reverting to default

* [ThemeManager] Generate and apply immediately on change.

* [PaletteEditor] Block grid signals during initial loading.

Took 5 minutes

* Address comments

Took 8 minutes

* Seed accent from scheme on reset and revert

Took 2 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-07-27 11:23:41 +02:00
RickyRister
3f9dbdb33b
[Game] Use in-game menu for tally setting (#7036)
* [Game] Use in-game menu for tally setting

* pluralize name

* fix includes

* clean up TallyMenu

* update settings

* fix guard
2026-07-26 15:26:02 -07:00
RickyRister
b70f770633
[Replay] Make fast forward speed configurable (#7037)
* [Replay] Make fast forward speed configurable

* fix settings widget

* clamp interval
2026-07-26 15:18:40 -07:00
ebbit1q
ae37762ee1
fix crash when pressing Back to results (#7043)
Some checks failed
Build Desktop / Configure (push) Has been cancelled
Build Docker Image / amd64 & arm64 (push) Has been cancelled
Build Desktop / Debian 13 (push) Has been cancelled
Build Desktop / Debian 12 (push) Has been cancelled
Build Desktop / Fedora 44 (push) Has been cancelled
Build Desktop / Fedora 43 (push) Has been cancelled
Build Desktop / Servatrice_Debian 12 (push) Has been cancelled
Build Desktop / Ubuntu 26.04 (push) Has been cancelled
Build Desktop / Ubuntu 24.04 (push) Has been cancelled
Build Desktop / Arch (push) Has been cancelled
Build Desktop / macOS 14 (push) Has been cancelled
Build Desktop / macOS 15 (push) Has been cancelled
Build Desktop / macOS 13 Intel (push) Has been cancelled
Build Desktop / macOS 15 Debug (push) Has been cancelled
Build Desktop / Windows 10 (push) Has been cancelled
2026-07-16 11:06:18 +02:00
github-actions[bot]
6fe46542f7
Update translation files (#7044) 2026-07-16 09:07:33 +02:00
DawnFire42
5bb6073422
Fix card position when added to the stack (#7041)
Some checks failed
Build Desktop / Configure (push) Has been cancelled
Build Docker Image / amd64 & arm64 (push) Has been cancelled
Build Desktop / Debian 13 (push) Has been cancelled
Build Desktop / Debian 12 (push) Has been cancelled
Build Desktop / Fedora 44 (push) Has been cancelled
Build Desktop / Fedora 43 (push) Has been cancelled
Build Desktop / Servatrice_Debian 12 (push) Has been cancelled
Build Desktop / Ubuntu 26.04 (push) Has been cancelled
Build Desktop / Ubuntu 24.04 (push) Has been cancelled
Build Desktop / Arch (push) Has been cancelled
Build Desktop / macOS 14 (push) Has been cancelled
Build Desktop / macOS 15 (push) Has been cancelled
Build Desktop / macOS 13 Intel (push) Has been cancelled
Build Desktop / macOS 15 Debug (push) Has been cancelled
Build Desktop / Windows 10 (push) Has been cancelled
2026-07-12 08:33:26 +02:00
tooomm
6c960fdf31
Use default values (#7017)
Some checks failed
Build Desktop / Configure (push) Has been cancelled
Build Docker Image / amd64 & arm64 (push) Has been cancelled
Build Desktop / Debian 13 (push) Has been cancelled
Build Desktop / Debian 12 (push) Has been cancelled
Build Desktop / Fedora 44 (push) Has been cancelled
Build Desktop / Fedora 43 (push) Has been cancelled
Build Desktop / Servatrice_Debian 12 (push) Has been cancelled
Build Desktop / Ubuntu 26.04 (push) Has been cancelled
Build Desktop / Ubuntu 24.04 (push) Has been cancelled
Build Desktop / Arch (push) Has been cancelled
Build Desktop / macOS 14 (push) Has been cancelled
Build Desktop / macOS 15 (push) Has been cancelled
Build Desktop / macOS 13 Intel (push) Has been cancelled
Build Desktop / macOS 15 Debug (push) Has been cancelled
Build Desktop / Windows 10 (push) Has been cancelled
2026-07-06 11:24:11 +02:00
tooomm
f3b41432cb
CI: Fix login+push for publishing Servatrice Docker images (#7023) 2026-07-06 07:01:12 +02:00
tooomm
808d991489
Delete .codacy.yml (#6957) 2026-07-06 07:00:03 +02:00
RickyRister
c5b37cdffe
[Game] Refactor subtype tally code to be more generic (#7021)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
* [Game] Refactor subtype tally code to be more generic

* remove default case

* consolidate empty check

* fix include guard

* backwards compatibility with qt5
2026-07-05 19:11:08 -07:00
RickyRister
430fc117b4
[UserContextMenu] Refactor: Consolidate actions (#7022) 2026-07-05 18:24:52 -07:00
github-actions[bot]
baddbfae14
Update translation source strings (#7028)
Some checks failed
Build Desktop / Configure (push) Has been cancelled
Build Docker Image / amd64 & arm64 (push) Has been cancelled
Build Desktop / Debian 13 (push) Has been cancelled
Build Desktop / Debian 12 (push) Has been cancelled
Build Desktop / Fedora 44 (push) Has been cancelled
Build Desktop / Fedora 43 (push) Has been cancelled
Build Desktop / Servatrice_Debian 12 (push) Has been cancelled
Build Desktop / Ubuntu 26.04 (push) Has been cancelled
Build Desktop / Ubuntu 24.04 (push) Has been cancelled
Build Desktop / Arch (push) Has been cancelled
Build Desktop / macOS 14 (push) Has been cancelled
Build Desktop / macOS 15 (push) Has been cancelled
Build Desktop / macOS 13 Intel (push) Has been cancelled
Build Desktop / macOS 15 Debug (push) Has been cancelled
Build Desktop / Windows 10 (push) Has been cancelled
Co-authored-by: github-actions <github-actions@github.com>
2026-07-01 16:25:10 +02:00
Skagra42
9b0348240d
Search by date. (#7027)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
2026-06-30 23:46:37 -07:00
DawnFire42
18b23b19a7
Split trice_limits.h into dedicated headers (#7025)
Some checks failed
Build Desktop / Configure (push) Has been cancelled
Build Docker Image / amd64 & arm64 (push) Has been cancelled
Build Desktop / Debian 13 (push) Has been cancelled
Build Desktop / Debian 12 (push) Has been cancelled
Build Desktop / Fedora 44 (push) Has been cancelled
Build Desktop / Fedora 43 (push) Has been cancelled
Build Desktop / Servatrice_Debian 12 (push) Has been cancelled
Build Desktop / Ubuntu 26.04 (push) Has been cancelled
Build Desktop / Ubuntu 24.04 (push) Has been cancelled
Build Desktop / Arch (push) Has been cancelled
Build Desktop / macOS 14 (push) Has been cancelled
Build Desktop / macOS 15 (push) Has been cancelled
Build Desktop / macOS 13 Intel (push) Has been cancelled
Build Desktop / macOS 15 Debug (push) Has been cancelled
Build Desktop / Windows 10 (push) Has been cancelled
* Split trice_limits.h into dedicated headers

* Updated docstrings
2026-06-29 14:37:52 -07:00
DawnFire42
4a384f2a75
fix theme manager return type warning (#7026)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
2026-06-29 08:03:30 -07:00
DawnFire42
05ae6f47a6
Unify counter clamp arithmetic into shared addClamped() helper (#7009)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
* Unify counter clamp arithmetic into shared addClamped() helper

- Add addClamped() in new header clamped_arithmetic.h; uses a 64-bit
  intermediate so the addition cannot overflow int.
- Use it in Server_Card::incrementCounter() (clamps [0, MAX_COUNTERS_ON_CARD])
  and Server_Counter::incrementCount() (clamps [INT_MIN, INT_MAX]), removing
  the duplicated overflow-safe logic and its keep-in-sync TODO.
- Inline incrementCount() into server_counter.h; server_counter.cpp now holds
  only the constructor and getInfo().
- Clarify the card-counter bounds comment in trice_limits.h.

* Rename MAX_COUNTERS_ON_CARD to MAX_COUNTER_VALUE

The constant caps the counter's value, not how many counters can be on the card

* Add direct unit tests for addClamped() helper

* Harden offsetCardCounter() against signed-int overflow

Replace the raw oldValue + offset sum with addClamped(), clamping to [0, MAX_COUNTER_VALUE] without overflow.

* Comment update

* Remove class names from addClamped() docstring
2026-06-28 16:10:57 -07:00
RickyRister
fcac7493ad
[Card] Add facedown property to CardRelation (#6997)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
* [Card] Add facedown property to CardRelation

* trailing newline

* fix comments

* update schema
2026-06-28 02:03:07 -07:00
DawnFire42
055ba9a16f
Add subtype breakdown counter for card selection (#6923)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
* Add subtype breakdown counter for card selection

  Display a categorized count of creature subtypes (and other card type
  subtypes) when multiple cards are selected. The breakdown appears above
  the total selection counter in the bottom-right corner.

  Subtypes are grouped by main card type and sorted by frequency, with
  the most common subtypes positioned adjacent to the total count for
  quick reference. The feature can be toggled via a new checkbox in
  Settings > User Interface.

* Alignment fix

* Computation logic moved to helper funtction in separate file

* Rename SubtypeCounter to SubtypeTally

* Fix subtype tally alignment by using grid layout instead of character padding

* Rename count to tally in the subtype breakdown feature

* partial rename

* list position fixed

* Clean up code and documentation

* Rename subtypeCountLabelStyle to subtypeTallyLabelStyle and fix include ordering

* Fix include path for selection_subtype_tally.h after file relocation

* fixed count to tally rename inconsistencies
2026-06-27 15:53:21 -07:00
BruebachL
ad4922537d
[UserListDelegate] Supply providerid in cmd, position popup correctly (#7020)
* [UserListDelegate] Transmit providerId in cmd when setting user banner card

* [UserListDelegate] Position popup correctly

* Lint.

---------

Co-authored-by: Lukas Brübach <lukas.bruebach@bdosecurity.de>
2026-06-27 14:44:24 -04:00
dependabot[bot]
4cbc00b9c4
Bump actions/cache from 5 to 6 (#7019)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
2026-06-27 17:25:30 +02:00
BruebachL
6dc974a05d
[UserListDelegate] Consider providerId (#7018)
Co-authored-by: Lukas Brübach <lukas.bruebach@bdosecurity.de>
2026-06-27 11:23:55 -04:00
BruebachL
2914874720
[Room][UserList] Introduce style delegate (#6981)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
* [Room] Additionally show a tab for friends and ignored users instead of just all online users.

Took 21 minutes


Took 12 minutes

* [Room][UserList] Introduce style delegate for user list
- Allow users to set a card name and parameters as their background banner
- Allow mods to white/blacklist cards
- Allow toggling back to the old display style

Took 7 minutes

Took 28 seconds

Took 2 minutes

Took 2 minutes

* Right checkstate.

Took 14 minutes


Took 2 minutes

* Utility for test.

Took 9 minutes

Took 8 seconds

Took 2 seconds

* Lint.

Took 10 minutes

* Algorithm for sql schema migration

Took 13 minutes

* Use {prefix}, bound card name, return errors.

Took 27 seconds

* Convert queue to while loop.

Took 19 seconds

* Hover popup.

Took 36 minutes

Took 1 minute

* More granular signals, popup for user info.

Took 25 minutes

Took 8 seconds

Took 16 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-06-26 20:52:24 -04:00
tooomm
c9ebdb451f
CI: Avoid failing tx step if secret can not be accessed (#7012)
Some checks are pending
Build Docker Image / amd64 & arm64 (push) Waiting to run
2026-06-26 16:39:33 +02:00
Zach H
80426d77bc
Add Discord account registration monitor (#7013)
Some checks failed
Build Desktop / Configure (push) Has been cancelled
Build Docker Image / amd64 & arm64 (push) Has been cancelled
Build Desktop / Debian 13 (push) Has been cancelled
Build Desktop / Debian 12 (push) Has been cancelled
Build Desktop / Fedora 44 (push) Has been cancelled
Build Desktop / Fedora 43 (push) Has been cancelled
Build Desktop / Servatrice_Debian 12 (push) Has been cancelled
Build Desktop / Ubuntu 26.04 (push) Has been cancelled
Build Desktop / Ubuntu 24.04 (push) Has been cancelled
Build Desktop / Arch (push) Has been cancelled
Build Desktop / macOS 14 (push) Has been cancelled
Build Desktop / macOS 15 (push) Has been cancelled
Build Desktop / macOS 13 Intel (push) Has been cancelled
Build Desktop / macOS 15 Debug (push) Has been cancelled
Build Desktop / Windows 10 (push) Has been cancelled
A read-only cron script that posts new Servatrice account registrations to a
Discord channel via webhook. Dedups by an auto-increment id high-water-mark
(single-integer state, no duplicates, nothing missed across downtime). Reads DB
credentials and the webhook from a servatrice-style ini via --config.
2026-06-21 01:09:57 -04:00
Phred Lane
e99a55ccab
CI: Simplified build workflow (#6995)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
* simplified build workflow

* cleaned up translation workflows

* fixed typo

* updated format based on code review

Co-authored-by: tooomm <tooomm@users.noreply.github.com>

* restored quotes around `group` string

---------

Co-authored-by: tooomm <tooomm@users.noreply.github.com>
2026-06-20 22:44:37 +02:00
dependabot[bot]
2a3c4a2455
Bump actions/checkout from 6 to 7 (#7011) 2026-06-20 19:11:10 +02:00
Phred Lane
687e6644bc
replaced dynamically injected variables with environment variables (#6996)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
This prevents some injection attacks and makes all the scripts valid Bash.
2026-06-19 16:42:15 +02:00
transifex-integration[bot]
e28f31c93e
Translate oracle/oracle_en@source.ts in es (#7006)
Some checks failed
Build Desktop / Configure (push) Has been cancelled
Build Docker Image / amd64 & arm64 (push) Has been cancelled
Build Desktop / Debian 13 (push) Has been cancelled
Build Desktop / Debian 12 (push) Has been cancelled
Build Desktop / Fedora 44 (push) Has been cancelled
Build Desktop / Fedora 43 (push) Has been cancelled
Build Desktop / Servatrice_Debian 12 (push) Has been cancelled
Build Desktop / Ubuntu 26.04 (push) Has been cancelled
Build Desktop / Ubuntu 24.04 (push) Has been cancelled
Build Desktop / Arch (push) Has been cancelled
Build Desktop / macOS 14 (push) Has been cancelled
Build Desktop / macOS 15 (push) Has been cancelled
Build Desktop / macOS 13 Intel (push) Has been cancelled
Build Desktop / macOS 15 Debug (push) Has been cancelled
Build Desktop / Windows 10 (push) Has been cancelled
2026-06-17 22:16:26 +02:00
DawnFire42
0c140d866f
[Card] Respect disabled sets when loading v3 card databases (#6882) (#7004)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
2026-06-17 14:09:05 +02:00
RickyRister
45d0cedb5c
[Game] Setting to restore old chat autofocus behavior (#6992)
Some checks failed
Build Desktop / Configure (push) Has been cancelled
Build Docker Image / amd64 & arm64 (push) Has been cancelled
Build Desktop / Debian 13 (push) Has been cancelled
Build Desktop / Debian 12 (push) Has been cancelled
Build Desktop / Fedora 44 (push) Has been cancelled
Build Desktop / Fedora 43 (push) Has been cancelled
Build Desktop / Servatrice_Debian 12 (push) Has been cancelled
Build Desktop / Ubuntu 26.04 (push) Has been cancelled
Build Desktop / Ubuntu 24.04 (push) Has been cancelled
Build Desktop / Arch (push) Has been cancelled
Build Desktop / macOS 14 (push) Has been cancelled
Build Desktop / macOS 15 (push) Has been cancelled
Build Desktop / macOS 13 Intel (push) Has been cancelled
Build Desktop / macOS 15 Debug (push) Has been cancelled
Build Desktop / Windows 10 (push) Has been cancelled
* [Game] Setting to restore old chat autofocus behavior

* fixes
2026-06-15 17:04:30 +02:00
BruebachL
309e4730a3
[Lobby][DeckView] Always at minimum create main and side deck zone containers. (#7001)
Took 12 minutes

Took 3 seconds

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-06-15 15:50:04 +02:00
BruebachL
0c4cc3f824
[DeckView][DeckEditor] Implement shortcut for load deck from website (default: Ctrl+Shift+O) (#7002)
Took 12 minutes

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-06-15 15:49:47 +02:00
BruebachL
dfbe944c31
[App][Windows][NSIS] Use QProcess::setNativeArguments on Windows, properly order portable detection (#6989)
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-06-15 15:23:18 +02:00
RickyRister
0f3e6fbe26
[CardDatabase] Pass CardInfoPtr by const ref (#6998)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
* [CardDatabase] Pass CardInfoPtr by const ref

* trailing newline
2026-06-14 04:46:18 -07:00
RickyRister
5ffe344779
[Game] Fix facedown predefined tokens leaking tablerow (#7000) 2026-06-14 03:21:17 -07:00
kongwu
f28ede7ae3
[UserContextMenu] Add confirmation dialog before kicking a player (#6987)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
2026-06-13 02:42:55 -07:00
BruebachL
7aaacbf347
[Update][NSIS] Use single string shell invocation (#6986)
Some checks failed
Build Desktop / Configure (push) Has been cancelled
Build Docker Image / amd64 & arm64 (push) Has been cancelled
Build Desktop / Debian 13 (push) Has been cancelled
Build Desktop / Debian 12 (push) Has been cancelled
Build Desktop / Fedora 44 (push) Has been cancelled
Build Desktop / Fedora 43 (push) Has been cancelled
Build Desktop / Servatrice_Debian 12 (push) Has been cancelled
Build Desktop / Ubuntu 26.04 (push) Has been cancelled
Build Desktop / Ubuntu 24.04 (push) Has been cancelled
Build Desktop / Arch (push) Has been cancelled
Build Desktop / macOS 14 (push) Has been cancelled
Build Desktop / macOS 15 (push) Has been cancelled
Build Desktop / macOS 13 Intel (push) Has been cancelled
Build Desktop / macOS 15 Debug (push) Has been cancelled
Build Desktop / Windows 10 (push) Has been cancelled
Took 18 minutes


Took 2 minutes

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-06-11 21:17:28 +02:00
tooomm
694adc9e64
Use Visual Studio 2026 (#6985)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
2026-06-11 10:45:17 +02:00
BruebachL
b17d879da8
[Game][Graphics][Player] Add named zone lookup-map to player graphics. (#6984)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
Took 16 minutes

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-06-10 10:58:28 +02:00
ebbit1q
6be9cec6e2
do not save a const reference to the user data in the info dialog (#6974)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
* do not save a const reference to the user data in the info dialog

* cmake format
2026-06-09 23:35:00 -07:00
kongwu
6d0a423dcf
[Messages] Add option to ignore private messages from non-buddy users (#6966)
* [Messages] Add option to ignore private messages from non-buddy users

* [Messages] Exclude Moderator/Admin from non-buddy ignore filter

Moderator and Admin messages should not be filtered out when the
'Ignore private messages from non-buddies' setting is enabled, to
ensure that important warnings from server staff reach users.
2026-06-09 20:49:29 -07:00
kongwu
f72c82d0f9
[DeckEditor] Replace mainboard/sideboard with tokensboard for tokens (#6971)
* [DeckEditor] Replace mainboard/sideboard with tokensboard for token cards (#6546)

* [PrintingSelector] Replace std::tuple with ZoneCounts struct for readability (#6546)
2026-06-09 20:46:43 -07:00
BruebachL
da4ba222c0
[Game] Move graphics out of game and into game_graphics (#6928)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
* [Game][Player] Pull out graphics_items out of player_logic

Took 25 seconds


Took 9 minutes

* [Game] Move graphics files into game_graphics

Took 1 minute

Took 2 minutes

Took 23 seconds

Took 1 minute

Took 2 seconds

* Include.

Took 4 minutes

Took 3 minutes

Took 4 minutes

Took 1 minute

Took 3 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-06-09 09:51:13 +02:00
BruebachL
cbfd286908
[Game][Player] Move dialog creation out of player_actions and into player_dialogs (#6946)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
* [Game][Player] Split Player into PlayerLogic/PlayerGraphicsItem

Took 4 minutes

Took 48 seconds

Took 2 minutes

* Drop early return.

Took 1 hour 13 minutes


Took 2 minutes

Took 1 minute

Took 24 seconds

* [Game][Player] Split Player into PlayerLogic/PlayerGraphicsItem

Took 4 minutes

Took 58 seconds

* [Game][Menus] Make Menus accept PlayerGraphicsItem instead of PlayerLogic

Took 7 minutes

Took 4 minutes

Took 9 seconds

Took 2 minutes


Took 5 minutes

Took 58 seconds

* [Game][Player] Split Player into PlayerLogic/PlayerGraphicsItem

Took 4 minutes

Took 2 minutes

* [Game][Menus] Make Menus accept PlayerGraphicsItem instead of PlayerLogic

Took 7 minutes


Took 1 minute

Took 57 seconds

* [Game][Player] Move dialog creation out of player_actions and into player_dialogs

Took 3 minutes

Took 1 second

* Fix typo.

Took 5 minutes

* Addressed comments.

Took 16 minutes

Took 11 seconds

* Reintroduce clearCardsToDelete check.

Took 3 minutes

* Capture cards before semaphore.

Took 1 minute

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-06-09 08:22:59 +02:00
BruebachL
487bb84b6f
[Game][Menus] Make Menus accept PlayerGraphicsItem instead of PlayerLogic (#6945)
* [Game][Player] Split Player into PlayerLogic/PlayerGraphicsItem

Took 4 minutes

Took 58 seconds


Took 2 minutes

* [Game][Menus] Make Menus accept PlayerGraphicsItem instead of PlayerLogic

Took 7 minutes

Took 4 minutes

Took 9 seconds

Took 2 minutes


Took 5 minutes

Took 58 seconds

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-06-09 08:07:06 +02:00
BruebachL
9e03f82616
[Game][Player] Split Player into PlayerLogic/PlayerGraphicsItem (#6944)
* [Game][Player] Split Player into PlayerLogic/PlayerGraphicsItem

Took 4 minutes

Took 48 seconds

* Drop early return.

Took 1 hour 13 minutes


Took 2 minutes

Took 1 minute

* Delete player view.

Took 37 seconds

* Restore card counter color in menu.

Took 5 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-06-09 08:05:39 +02:00
Christo
e674a39b87
Fix #6952: prevent deck loss when saving to a full disk (#6978)
* Fix #6952: prevent deck loss when saving to a full disk

DeckLoader::saveToFile() opened the target with QFile in WriteOnly mode,
which truncates the existing file to 0 bytes the moment it is opened. The
serializers (DeckList::saveToFile_Native/_Plain) always return true and the
result of flush() was ignored, so a write that failed part-way -- e.g.
because the disk was full -- left a 0-byte file behind yet was still
reported (and logged) as a successful save. The same truncate-then-write
pattern in updateLastLoadedTimestamp() could destroy a deck on load.

Switch both paths to QSaveFile, which writes to a temporary file and only
atomically replaces the target if commit() succeeds. On any write or flush
failure commit() returns false, the original deck is left untouched, and
the failure is logged instead of being reported as success.

* Use QSaveFile in convertToCockatriceFormat() too

convertToCockatriceFormat() had the same data-loss pattern: QFile WriteOnly
truncated the .cod, saveToFile_Native() always returns true, and the original
file was then removed unconditionally -- so a full disk during conversion wrote
a 0-byte .cod and then deleted the source deck.

Switch to QSaveFile (write + atomic commit), remove the original only after a
successful commit, and move the format check ahead of the file open so an
already-Cockatrice or unsupported deck never truncates or deletes anything.

Raised in review by ZeldaZach.
2026-06-09 07:54:01 +02:00
tooomm
1efc382c05
CI: Cleanup (#6959)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
* Label & variables

* fix bracket

* other workflows

* fix trailing whitespace

* fixes
2026-06-08 19:37:50 +02:00
tooomm
dc152e89f7
CI: Print colored diff for lint check (#6975)
* print colored diff in gha

* use spaces
2026-06-08 19:19:28 +02:00
RickyRister
20cdcdb382
[ReplayManager] Refactor to send replayed events through signal (#6979)
* [ReplayManager] Refactor to send replayed events through signal

* remove blank

* pass by const auto ref
2026-06-07 19:39:31 -07:00
BruebachL
23da49ee5b
[Game] [Arrows] Use arrowData/registry and generate unique server-side ids (#6973)
* [Game] [Arrows] Track creatorId, use arrowData in arrowItem, use registry, generate unique arrow id's on server side and delete-on-exist inserts.

Took 2 minutes

Took 1 minute

* Fix emitting slot instead of signal.

Took 15 minutes

* Clear arrows locally in special circumstances i.e. teardown.

Took 28 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-06-07 21:11:02 +02:00
RickyRister
c14a008080
[TabDeckEditor] Refactor card database view into own class (#6967)
* rename method

* [TabDeckEditor] Refactor card database view into own class

* fix include guard

* directly get key signals for eventFilter

* fix includes
2026-06-05 10:20:46 -07:00
RickyRister
0da2ac4087
[TabDeckEditor] Refactor: pass nullable deck model into filter widget (#6969) 2026-06-05 09:21:28 -07:00
RickyRister
29cc622ce3
[TabDeckEditor] Refactor: Create shared CardDatabaseModel for tab (#6968) 2026-06-05 09:21:13 -07:00
RickyRister
86256602ff
[TabDeckEditor] Refactor to use signal instead of calling tab (#6965)
* [TabDeckEditor] Refactor to use signal instead of calling tab

* update docs

* fix cardInfoRequest
2026-06-03 10:41:55 -07:00
RickyRister
f37c418865
[TabDeckEditor] Refactor: Remove cardDatabase field from analysis interfaces (#6963)
* [TabDeckEditor] Refactor: Remove cardDatabase field from analysis interfaces

* update includes
2026-06-03 10:08:57 -07:00
RickyRister
46d3b820db
[TabDeckEditor] Refactor: pull up showPrintingSelector (#6964)
* [TabDeckEditor] Refactor: pull up showPrintingSelector

* trailing newline
2026-06-03 09:35:48 -07:00
RickyRister
e0cbb7f06c
[TabDeckEditor] Refactor: pass ExactCard in signal instead of widget (#6962)
* [TabDeckEditor] Refactor: pass ExactCard in signal instead of widget

* address comments
2026-06-02 21:22:06 -07:00
RickyRister
f52dc6dda8
[TabDeckEditor] Refactor: consolidate add/decrement card signals (#6961) 2026-06-02 20:13:39 -07:00
RickyRister
3fa377a11c
[TabDeckEditor] Refactor check ctrl to be on click (#6956) 2026-05-31 03:44:40 -07:00
RickyRister
c5372a9e92
[DeckEditor] Refactor: clean up addCardHelper (#6939)
* [DeckEditor] Refactor: clean up addCardHelper

* remove setSaveStatus
2026-05-31 03:14:21 -07:00
RickyRister
6de55e9096
[Game][Arrow] Correctly call clear all arrows for player (#6951) 2026-05-28 23:51:12 -07:00
RickyRister
43c3bf5966
[Game][Arrow] Refactor: Rename arrow methods in GameScene (#6949)
* [Game][Arrow] Rename methods in GameScene

* move stuff around and docs
2026-05-28 02:32:40 -07:00
RickyRister
c4f4cece01
[Game] Show counter color icons in game log (#6948) 2026-05-28 11:15:30 +02:00
RickyRister
0d7047a728
[Game] Show color icons in counters menu (#6947) 2026-05-28 11:05:26 +02:00
RickyRister
7f30728f87
[CardDatabaseModel] Pass CardInfoPtr by const ref (#6940) 2026-05-26 18:53:20 -07:00
RickyRister
1d5d3f2d38
Run formatter on all our files (#6942) 2026-05-26 15:11:38 -07:00
BruebachL
b3c89167c5
[Game][Arrows] Hook up to the state zone change properly. (#6937)
Took 17 minutes

Took 3 seconds

Took 2 minutes

Took 10 minutes

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-05-25 08:19:27 +02:00
BruebachL
90ab663212
[Server][Game][Arrows] Properly notify clients when deleting arrows on card move and transform into (#6936)
* [Server][Game][Arrows] Properly notify clients when deleting arrows on card move and transform into

Took 15 minutes

* Observe "not found" response

Took 18 minutes

Took 4 seconds

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-05-25 08:19:17 +02:00
BruebachL
98c00c55ed
[Game][Arrows] ArrowItem should not send deletion game commands by itself (#6932)
Took 13 minutes

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-05-23 02:20:41 +02:00
BruebachL
d2164c3f08
[Oracle] Change basic lands exception to regex to deal with snow basics. (#6931)
* [Oracle] Change basic lands exception to regex to deal with snow basics.

Took 45 minutes

* Update oracle/src/oracleimporter.cpp

Co-authored-by: ebbit1q <ebbit1q@gmail.com>

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
Co-authored-by: ebbit1q <ebbit1q@gmail.com>
2026-05-22 22:36:53 +02:00
RickyRister
8004d4f2d4
[VDE] Disable filter dock widget (#6924) 2026-05-22 03:26:52 -07:00
BruebachL
8751f0605d
[Game][Arrows] Don't request deletion on inbound arrow deletion signal again but react to it locally (#6927)
* [Game][Arrows] Deleting arrows has no acknowledgement command so we have to delete locally as well.

Took 22 minutes

* Fix properly.

Took 15 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-05-22 10:25:13 +02:00
RickyRister
09d817770e
[Game] Refactor: move proliferate action code to PlayerActions (#6926) 2026-05-22 10:25:00 +02:00
DawnFire42
81a2712b92
Fix card stacking overflow when vertical zone has many cards (#6925)
Previously, minOffset (10px) was enforced unconditionally, causing card
  tops to overflow zone bounds when many cards were stacked. For example,
  50 cards in a 200px zone would place the last card's top at y=490.

  Now offsets compress below minOffset when necessary to keep all card
  tops visible. The constraint is guarded by !allowBottomOverflow to
  preserve future clipping zone semantics.
2026-05-21 22:56:24 -07:00
DawnFire42
8dca14933c
Centralize counter API with server-side bounds and no-op filtering (#6879)
* Refactor server counter API to own overflow protection and filter no-op events

  Counter modifications now clamp to int bounds server-side and return change
  status, allowing command handlers to skip network broadcasts when values
  don't actually change.

* Centralize MAX_COUNTERS_ON_CARD and enforce [0, 999] bounds on server

  - Move MAX_COUNTERS_ON_CARD to trice_limits.h
  - Server clamps values in setCounter() and incrementCounter()
  - Client uses clamped comparison to allow recovery from invalid states
  - Add tests for clamping behavior

* move incrementCount() implementation from header to cpp
2026-05-21 20:39:35 -07:00
tooomm
74102aa1ec
Update external c libs: SFMT & peglib (#6901) 2026-05-22 03:31:59 +02:00
ebbit1q
a9003be30f
update version to 3.1.0 for subsequent beta releases (#6921) 2026-05-22 01:35:01 +02:00
github-actions[bot]
6faa0d54e3
Update translation files (#6920)
Co-authored-by: github-actions <github-actions@github.com>
2026-05-21 19:14:58 -04:00
DawnFire42
33e0f8699b
Standardize Doxygen documentation (#6885) 2026-05-21 22:58:07 +02:00
tooomm
03d54265fe
escape ampersand (#6900) 2026-05-21 22:02:09 +02:00
BruebachL
491d1c9187
[Game][Arrows] Split Arrows into ArrowData and ArrowItem (#6918)
* [Game][Arrows] Split Arrows into ArrowData and ArrowItem

Took 13 minutes

Took 5 seconds

Took 1 minute

Took 26 seconds

* Address comments.

Took 17 minutes

Took 9 seconds


Took 1 minute

* Change check.

Took 3 minutes

* Pass by const reference.

Took 10 minutes

* Remove extra method

Took 2 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-05-21 20:31:14 +02:00
BruebachL
bddf9bd818
[Game][Counters] Split counters into AbstractCounter (graphics) and CounterState (logic) (#6917)
* [Counters] Split counters into graphics and logic states

Took 22 minutes

* Don't have widget hold pointer to state -> Copy what we need and subscribe to changes.

Took 12 minutes

Took 5 seconds

* Sync value too.

Took 3 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-05-21 20:16:28 +02:00
RickyRister
0549892092
[Settings] Regroup card layout settings (#6914) 2026-05-20 14:00:47 -07:00
RickyRister
10b9a65f17
[Server][Game] Make undo draw failure visible in chat (#6889)
* [Server][Game] Make undo draw failure visible in chat

* genericize the proto
2026-05-20 02:23:02 -07:00
BruebachL
5219cffa6b
[Player] Rename player to player logic (#6913)
Took 13 minutes

Took 6 seconds

Took 2 minutes

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-05-19 12:36:31 +02:00
RickyRister
71790d8e10
[Settings] Split printing settings into own group (#6906) 2026-05-19 03:34:42 -07:00
RickyRister
fe31a49f86
[Settings] Split appearance home tab settings into own group (#6905) 2026-05-19 03:33:25 -07:00
RickyRister
55c84ca860
[Settings] Clean up groupings in general settings (#6907) 2026-05-19 03:32:45 -07:00
ebbit1q
40b947c1e7
fix cards attaching/transforming to the wrong card across players (#6909)
* fix cards attaching/transforming to the wrong card across players

* move comment
2026-05-19 12:26:17 +02:00
ebbit1q
40cef0e436
fix resizing the visual card database loading more pages forever (#6884)
* fix resizing the vde loading more pages forever

* make near end of page const
2026-05-19 12:25:35 +02:00
BruebachL
9f1c225b7a
[Player] Stop reaching into graphics_item and emit signals instead for conceded and zoneId (#6912)
* [Player] Stop reaching into graphics_item and emit signals instead for conceded and zoneId

Took 7 minutes

Took 3 seconds

* Add sameValue check.

Took 3 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-05-19 12:22:40 +02:00
BruebachL
af2f888293
[Player] Pull handVisible out of player_info and let graphics_item just determine this on its own. (#6911)
Took 12 minutes


Took 15 seconds

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-05-19 12:10:19 +02:00
BruebachL
7a5b2e9f0e
[Game] Move state fields out of CardItem (#6904)
* [Game] Move state fields out of CardItem

Took 1 hour 2 minutes

* Move stuff into .cpp

Took 14 minutes

* Signals pass changed values as params

Took 2 minutes

* Comments.

Took 23 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-05-18 20:21:57 +02:00
RickyRister
021a9f8383
[DlgSettings] Refactor: split SettingsPage into separate files (#6899) 2026-05-18 01:59:02 -07:00
BruebachL
cba9ce2b2b
[Game/Zones] Simple move refactor to differentiate between logic and graphics for zones (#6903)
* [Game/Zones] Simple move refactor to differentiate between logic and graphics for zones

Took 21 minutes

* Clean up game/zones/logic folder.

Took 6 minutes

* Adjust tests.

Took 3 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-05-18 06:36:18 +02:00
SlightlyCircuitous
bb1a5b33a1
Add generic colorless mana symbol (#6873)
* Add generic colorless mana symbol

* Add generic colorless mana symbol
2026-05-17 19:48:48 +02:00
BruebachL
6ac340026f
Update CMakeLists.txt (#6898) 2026-05-17 16:15:15 +02:00
BruebachL
059eeebe89
[Settings] Fix typo for schemeComboLabel (#6897)
Took 6 minutes

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-05-17 10:12:34 +02:00
BruebachL
117ea543c5
[App/Theme] Palette Editor (#6877)
* [App/Theme] Palette Editor

Took 1 minute

Took 1 hour 47 minutes

Took 6 seconds

Took 3 minutes

Took 5 minutes


Took 3 minutes

* Add oracle, add palette files and configs.

Took 10 minutes

* Fix a stupid include mistake, thanks IDE

Took 3 minutes

Took 20 seconds

* Includes.

Took 4 minutes

* Fix ampersand not displaying correctly.

Took 14 minutes

* Longer variable names.

Took 10 minutes

Took 5 seconds

* Change ampersand everywhere

Took 23 seconds

* Doxygen properly.

Took 1 minute

* Remove namespace, fold I/O into structs.

Took 12 minutes

* Remove namespace, fold I/O into structs.

Took 33 seconds

* Alphabetize.

Took 35 seconds

* Lint.

Took 49 seconds

* Add a combo box to quick switch settings.

Took 19 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-05-17 10:08:00 +02:00
tooomm
989a5be23b
Enable RemoveSemicolon & update format.sh (#6896)
* Update .clang-format

* Add libs that got added in the meantime

* Use libcockatrice_*
2026-05-16 20:26:37 +02:00
BruebachL
f8ce5c2e39
[PictureLoader] Allow saving downloaded images to local storage and not just the QNetworkManager cache (#6620)
* [PictureLoader] Allow saving downloaded images to local storage and not just the QNetworkManager cache.

Took 1 hour 11 minutes

Took 4 seconds


Took 25 seconds

* Give people options from a dropdown.

Took 1 hour 6 minutes

Took 3 seconds

* Simplify directory removal code.

Took 5 minutes

Took 8 seconds

* Merge pull request #8

* Create new category for new settings

* Split off storage settings

Took 47 minutes

Took 4 seconds

* Allow toggling between caching methods.

Took 1 hour 30 minutes

Took 9 seconds

* Adjust settings dialog.

Took 5 minutes

Took 59 seconds

Took 22 seconds

Took 6 seconds

* tr() strings

Took 1 minute

Took 6 seconds

* Readjust layout, default naming scheme.

Took 5 minutes

* Add stretch.

Took 9 minutes

* Make scrollable.

Took 2 minutes

* Add icon.

Took 7 minutes

* Change naming to be uniform.

Took 3 minutes

Took 3 seconds

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
Co-authored-by: RickyRister <42636155+RickyRister@users.noreply.github.com>
2026-05-16 20:15:10 +02:00
ebbit1q
20cd7ce73d
fix arch compiling (#6895) 2026-05-16 19:39:34 +02:00
DawnFire42
aadee34238
style: Add braces to all control flow statements (#6887)
* style: Add braces to all control flow statements

  Standardize code style by adding explicit braces to all single-statement
  control flow blocks (if, else, for, while) across the entire codebase.

  Also documents the InsertBraces clang-format option (requires v15+) for
  future automated enforcement.

* InsertBraces-check-enabled
2026-05-16 19:19:53 +02:00
BruebachL
7153f7d4c1
[VDD] Fix minimum size by adding a compact mode to quickSettingsButtons (#6890)
* [VDD] Fix minimum size by adding a compact mode to quickSettingsButtons

Took 17 minutes

Took 5 seconds

* Fix and use FlowWidget/FlowLayout

Took 35 minutes

Took 4 seconds

* Set spacings.

Took 12 minutes

* Make VDE tools flow

Took 1 hour 23 minutes

Took 5 seconds

* Squeeze and flow even more.

Took 11 minutes

* Make pushbutton compact.

Took 54 minutes

Took 7 seconds

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-05-14 19:07:15 +02:00
RickyRister
762e742be0
[Game] Implement action to automatically take damage from creatures (#6869)
* [Game] Implement action to automatically take damage from creatures

* cleanup
2026-05-13 15:03:41 -07:00
RickyRister
67f6ab66f0
[DeckLoader] Don't modify text deck files on load (#6888)
* [DeckLoader] Don't modify text deck files on load

* formatter
2026-05-13 11:32:05 -07:00
tooomm
7507103bb2
CI: Update trigger for doc builds (#6871)
* Update trigger

* use `published` trigger

* narrow deploy step filter

* re-add deployments on manual runs
2026-05-13 14:54:47 +02:00
RickyRister
fe12f4cbb9
[VisualDeckEditor] Highlight searchEdit after add card (#6876) 2026-05-11 20:23:44 -07:00
RickyRister
d18f3bce47
[VisualDatabaseDisplayWidget] Refactor: Make all fields private (#6875) 2026-05-11 20:23:29 -07:00
RickyRister
b66743c83c
[DeckEditor] Refactor searchEdit highlighting after add card (#6874)
* [DeckEditor] Refactor searchEdit highlighting after add card

* make searchEdit private
2026-05-11 20:23:12 -07:00
DawnFire42
1a62f82aee
Refactor vertical card stacking with clip containers for variable zone sizes (#6774)
* Refactor vertical card stacking with opt-in overflow for variable zone sizes

Introduce a shared vertical stacking layout system in SelectZone that replaces the old divideCardSpaceInZone() free function with structured layout computation (StackLayoutParams, ZoneLayout, computeZoneLayout).

By default, cards are guaranteed to fit within zone bounds (no overflow). Zones can opt-in to bottom overflow via allowBottomOverflow flag, with sqrt-scaled compression for smooth visual transitions. A clip container mechanism is available for future zones that need visual clipping.

  Key changes:
  - SelectZone: new layout engine with allowBottomOverflow opt-in; clip container infrastructure for future zones needing visual clipping
  - StackZone: uses new layout (no overflow); adds setHeight() for dynamic resizing capabilities
  - HandZone: vertical layout delegates to SelectZone's shared stacking
  - AbstractCardItem: preserves hover z-value during layout passes; invalidates scene rect on hover exit for proper sibling repainting
  - CardZone::onCardAdded made virtual for clip container reparenting
  - Zone widths updated to CardDimensions::WIDTH_F * 1.5

* Changed anonymous namespace for static and braced functions

* CI tests re-run
2026-05-10 19:10:14 -07:00
tooomm
5735a44a9a
Update Dockerfile to 26.04 (#6861) 2026-05-10 14:53:59 +02:00
BruebachL
dbaf5f2e05
[GameSelector] Don't conditionally initialize gamesListProxyModel (#6870)
Took 44 seconds

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-05-10 11:35:17 +02:00
RickyRister
9c53dad4b8
[Game] Refactor: add selectedCards method to GameScene (#6859) 2026-05-09 17:03:48 -07:00
RickyRister
7814204fe2
[Game] Refactor: explicitly pass params from card counter actions (#6780) 2026-05-09 13:20:33 -07:00
RickyRister
cdb171f201
[Game] Refactor: move setCardAttrHelper to PlayerEventHandler (#6772) 2026-05-09 13:10:56 -07:00
RickyRister
48e21aad38
[Game] Refactor: move parsePT to a static method (#6860) 2026-05-09 12:55:11 -07:00
tooomm
f223ff387e
bump vcpkg to 2026.04.27 release (#6855)
* bump vcpkg to 2026.04.27

* remove hint
2026-05-09 14:49:58 +02:00
RickyRister
8845a75627
[GameSelector] Fix bug with hideNotBuddyCreatedGames checkbox (#6858) 2026-05-09 04:08:30 -07:00
BruebachL
caf2bb9ded
Pull client networking out of window_main and into remote_connection_controller (#6796)
* Pull client networking out of window_main and into remote_connection_controller

Took 2 minutes

* Things.

Took 13 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-05-09 12:50:53 +02:00
RickyRister
985936a917
[GameModel] Refactor game filters options into struct (#6856) 2026-05-09 02:45:33 -07:00
ebbit1q
2c51054e77
update version number after update (#6853) 2026-05-08 20:40:39 -04:00
SlightlyCircuitous
f7eeaeddcb
Drop builds for Debian 11 and Ubuntu 22.04 (#6643)
* Delete .ci/Debian11 directory

* Delete .ci/Servatrice_Debian11 directory

* Delete .ci/Ubuntu22.04 directory

* Update desktop-build.yml

* Update release_template.md

* Add servatrice debian 12

* Update desktop-build.yml
2026-05-09 00:28:03 +02:00
SlightlyCircuitous
6cace2a8e6
Bump minimum required cmake to 3.10 in gtest (#6851)
* Bump minimum_required to 3.5 and GoogleTest to 1.12

GoogleTest 1.12 is the oldest version that sets cmake_minimum_required() to 3.5 in the CMakeLists files it provides

* code style changes

* Use 1.17.0

* Set minimum to 3.10 to make top-level CMakeLists

* New hash

Co-authored-by: tooomm <tooomm@users.noreply.github.com>

* Update cmake/gtest-CMakeLists.txt.in

Co-authored-by: tooomm <tooomm@users.noreply.github.com>

---------

Co-authored-by: tooomm <tooomm@users.noreply.github.com>
Co-authored-by: ebbit1q <ebbit1q@gmail.com>
2026-05-09 00:05:10 +02:00
tooomm
6b5f341e10
Fix toc link (#6854) 2026-05-09 00:04:16 +02:00
ZeldaZach
63143f9416
Fix triplet source for macOS13 Intel 2026-05-08 11:02:50 -04:00
ZeldaZach
efe52b5412
Fix windows copying over CMakeFiles to themes 2026-05-08 10:28:14 -04:00
ZeldaZach
0e014c0e5c
Use dash instead of underscore 2026-05-08 10:13:40 -04:00
ZeldaZach
511ccae738
Replace spaces with underscore for release name file names 2026-05-08 10:10:39 -04:00
Zach H
0672603755
Support spaces in release name (#6849) 2026-05-08 09:53:33 -04:00
Zach H
f5f326f65b
Update Repo URL for Webatrice 2026-05-08 09:22:17 -04:00
Jeremy Letto
c5702cc8b6
Remove webclient (extracted to Webatrice repo) (#6848)
The webclient has been extracted to https://github.com/seavor/Webatrice
and the Playwright e2e suite has moved to Sockatrice. Cockatrice keeps
no copy.

Deleted:
- webclient/ (entire tree, 349 files)
- .github/workflows/web-build.yml, web-lint.yml
- .husky/pre-commit (the only tracked husky hook; managed entirely
  from webclient's package.json which no longer exists)

Edited:
- .tx/config: dropped the webclient resource block; Webatrice/.tx/config
  now manages its own translations
- .github/workflows/translations-pull.yml: removed webclient locales
  from add-paths
- .github/workflows/desktop-build.yml, desktop-lint.yml: removed dead
  '!webclient/**' and '!.husky/**' path-filter exclusions
- .github/dependabot.yml: removed commented-out npm/webclient block
- Doxyfile: removed webclient/ from EXCLUDE list
- .ci/release_template.md: dropped Webatrice section (Webatrice now
  cuts its own releases)
- README.md: dropped 'first work on a webclient' line, added Webatrice
  to Related Repositories, updated translation paragraph and build
  badges

History preserved: every webclient commit remains recoverable via
git log on master before this commit.
2026-05-08 09:14:53 -04:00
BruebachL
4f2f942121
Use Qt 6.11.0 (#6846)
* Use Qt 6.10.*

Took 3 hours 1 minute

* Remove workaround.

Took 10 minutes

* Pin to 6.11.0 for now.

Took 3 minutes

* Revert "Remove workaround."

This reverts commit 71584d1e50.


Took 4 seconds

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-05-08 15:10:17 +02:00
BruebachL
a4c2b1411f
Utility method to check if a theme is supposed to be in dark or light mode. (#6785)
* Utility method to check if a theme is supposed to be in dark or light mode.

Took 22 minutes

Took 4 seconds

* Method is public.

Took 3 minutes

* Add a utility method to check if we're using a built-in theme

Took 3 minutes

Took 3 seconds

* Use built-in theme detection for home screen.

Took 6 minutes

* Re-polish on theme change

Took 2 minutes

* Fetch background on theme change.

Took 4 minutes

Took 6 seconds

* No need to double polish.

Took 4 minutes

* No need to repaint.

Took 32 seconds

* Only repolish visible widgets.

Took 5 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-05-08 13:47:14 +02:00
DawnFire42
43bee2316e
Fix Fedora 44 build: suppress -Werror=sfinae-incomplete for GCC 16+ (#6843)
Qt MOC and protobuf forward declarations trigger this warning in GCC 16.
  Re-enable Fedora 44 CI build now that it compiles successfully.
2026-05-06 01:00:13 +02:00
RickyRister
19dbb17fb9
[SettingsManager] Properly handle multithreaded access again (#6844)
* [SettingsManager] Properly handle multithreaded access again

* Add comment

* Add batch write function

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-05-05 21:03:01 +02:00
BruebachL
7c9fbe2be0
Don't queue connection (#6842)
Took 5 minutes

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-05-04 15:44:48 +02:00
BruebachL
d30690236a
Reload card db and notify enabled sets change on "Manage Sets" dialog save (#6837)
* Reload card db and notify enabled sets change on "Manage Sets" dialog save

Took 1 hour 18 minutes

Took 6 seconds

* Extract to method, also notify on "Reload db" and "new sets found"

Took 3 minutes

Took 4 seconds

* Add an "always enable new sets" fuse to "new sets found" dialog

Took 11 minutes

* Always debounce modelDirty() with dirty() timer.

Took 29 minutes

Took 3 minutes

* Performance improvements for settings by not constructing a new settings object on every single set() call (this forced a sync to/from fs but it seems fine to just rely on Qts own periodic sync?)

Took 23 minutes

Took 3 seconds

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-05-03 03:11:10 +02:00
tooomm
ac2e995f15
add annotations level (#6840) 2026-04-30 23:51:53 +02:00
SlightlyCircuitous
dac611f0f1
Add Fedora 44 build and drop Fedora 42 build (#6836)
* Add Fedora 44; Drop Fedora 42

* Delete .ci/Fedora42 directory

* Create Dockerfile

* Update release_template.md

* Skip debug
2026-04-30 21:09:49 +02:00
ebbit1q
45ab2602c6
ensure judges are omniscient when not spectating (#6831) 2026-04-30 14:31:28 +02:00
tooomm
5101cc3d74
CI: Ensure Docker runs + publishes images only on stable releases (#6839)
* Update docker-release.yml

* Update docker-release.yml

* Update docker-release.yml

* Update docker-release.yml

* Update docker-release.yml
2026-04-30 14:30:51 +02:00
BruebachL
9ac9a0c73a
[Game] Add a coinflip shortcut. (#6829)
Took 18 minutes

Took 6 seconds

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-04-29 22:09:30 +02:00
BruebachL
314a577807
Do not allow users to remove VDE functionality by closing tabs (#6838)
* Do not allow users to remove VDE functionality by closing tabs.

Took 6 minutes

* Set filter toolbar visible on delayed initialization.

Took 5 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-04-29 12:37:47 +02:00
tooomm
c5ace60f26
Bump Doxygen to 1.16.1 (#6752)
* Bump Doxygen version to 1.16.1

* Update Doxyfile

* Enable parallel processing
2026-04-29 09:40:18 +02:00
transifex-integration[bot]
8953ae3c67
Updates for project Cockatrice and language de (#6833)
* Translate cockatrice/cockatrice_en@source.ts in de

100% translated source file: 'cockatrice/cockatrice_en@source.ts'
on 'de'.

* Translate cockatrice/cockatrice_en@source.ts in de

100% translated source file: 'cockatrice/cockatrice_en@source.ts'
on 'de'.

---------

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2026-04-27 21:16:10 +02:00
ebbit1q
b1fe4c85d3
fix sending decks to tappedout (#6832)
Some checks failed
Build Desktop / Configure (push) Has been cancelled
Build Docker Image / amd64 & arm64 (push) Has been cancelled
Build Desktop / Debian 11 (push) Has been cancelled
Build Desktop / Debian 13 (push) Has been cancelled
Build Desktop / Debian 12 (push) Has been cancelled
Build Desktop / Fedora 43 (push) Has been cancelled
Build Desktop / Fedora 42 (push) Has been cancelled
Build Desktop / Servatrice_Debian 11 (push) Has been cancelled
Build Desktop / Ubuntu 24.04 (push) Has been cancelled
Build Desktop / Ubuntu 26.04 (push) Has been cancelled
Build Desktop / Ubuntu 22.04 (push) Has been cancelled
Build Desktop / Arch (push) Has been cancelled
Build Desktop / macOS 14 (push) Has been cancelled
Build Desktop / macOS 15 (push) Has been cancelled
Build Desktop / macOS 13 Intel (push) Has been cancelled
Build Desktop / macOS 15 Debug (push) Has been cancelled
Build Desktop / Windows 10 (push) Has been cancelled
2026-04-23 02:28:12 +02:00
Vorliz
a20f3c0fb4
Fix #6659: Correct logging for bottom-of-library card moves (#6764)
Some checks failed
Build Desktop / Configure (push) Has been cancelled
Build Docker Image / amd64 & arm64 (push) Has been cancelled
Build Desktop / Debian 11 (push) Has been cancelled
Build Desktop / Debian 13 (push) Has been cancelled
Build Desktop / Debian 12 (push) Has been cancelled
Build Desktop / Fedora 43 (push) Has been cancelled
Build Desktop / Fedora 42 (push) Has been cancelled
Build Desktop / Servatrice_Debian 11 (push) Has been cancelled
Build Desktop / Ubuntu 24.04 (push) Has been cancelled
Build Desktop / Ubuntu 26.04 (push) Has been cancelled
Build Desktop / Ubuntu 22.04 (push) Has been cancelled
Build Desktop / Arch (push) Has been cancelled
Build Desktop / macOS 14 (push) Has been cancelled
Build Desktop / macOS 15 (push) Has been cancelled
Build Desktop / macOS 13 Intel (push) Has been cancelled
Build Desktop / macOS 15 Debug (push) Has been cancelled
Build Desktop / Windows 10 (push) Has been cancelled
* Fix #6659:  Correct logging for bottom-of-library card moves

Cause:
- This issue happens due to logic of moving the card from the top of the
  deck being reused when moving from the bottom of the deck, in a way
  that makes it impossible to check if the card came from the bottom.

Resolution:
- Updated the logging logic in the client for card moves.
- Added a gRPC parameter ('is_from_bottom') for card moves.
- Updates the server logic to reverse the order of the card move if the
'is_from_bottom' parameter is true.
- Added a test to show the expected behaviour of the fix.

NOTE: While the changes in this patch seem big, this is due to changing
the loop in the moveCard function to a helper function, in order to make
the bug fix change. The only change to the loop was to pass a
variable attribution to the moveCard function because it was redundant
to be in the loop.

* chore: run format on test

* refactor: new way to check if a move is from the bottom of the deck

* refactor: change isFromBottom check to static function

* update comments

Co-authored-by: ebbit1q <ebbit1q@gmail.com>

---------

Co-authored-by: ebbit1q <ebbit1q@gmail.com>
2026-04-21 19:05:31 +02:00
BruebachL
9226bc9ddd
[TabArchidekt] Place sideboard categories into the sideboard. (#6824)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 11 (push) Blocked by required conditions
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Fedora 42 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 11 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 22.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
Took 35 minutes

Took 3 seconds

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-04-21 08:09:39 +02:00
ebbit1q
501c4b96d4
clear all players when closing game tab (#6828)
this prevents issues caused by items in play when using the player
destructor in the wrong order
2026-04-21 08:09:20 +02:00
ebbit1q
6ab947418c
add an isEmpty method to printing info (#6805)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 11 (push) Blocked by required conditions
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Fedora 42 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 11 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 22.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
no reason, I just find it easier to understand
2026-04-21 01:26:08 +02:00
BruebachL
6765831b92
Change button colors to be palette aware. (#6821)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 11 (push) Blocked by required conditions
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Fedora 42 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 11 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 22.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
* Change button colors to be palette aware.

Took 13 minutes


Took 41 seconds

Took 15 seconds

* Change button style.

Took 24 minutes

Took 4 seconds

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-04-20 17:47:15 +02:00
BruebachL
98c4e829f8
[GameSelector/Filters] Properly sync toolbar and dialog. (#6822)
Took 59 minutes

Took 2 minutes

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-04-20 13:19:29 +02:00
BruebachL
ccb9901b28
[DeckAnalytics] Add a checkbox to include/exclude sideboard for calculation (#6823)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 11 (push) Blocked by required conditions
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Fedora 42 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 11 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 22.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
* [DeckAnalytics] Add a checkbox to include/exclude sideboard for calculation

Took 15 minutes

* default to false, actually

Took 2 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-04-19 23:08:56 +02:00
tooomm
58a8c7d3df
CI: Use ubuntu-slim for simple jobs (#6798)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 11 (push) Blocked by required conditions
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Fedora 42 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 11 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 22.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
2026-04-19 02:47:14 +02:00
ebbit1q
655a8e52a1 update release name 2026-04-19 01:15:58 +02:00
ebbit1q
db235ecae8 update version number to 3.0.0 2026-04-19 01:11:00 +02:00
BruebachL
682ac4ed0c
Add -R option in Windows NSIS script for silent upgrade (#6818)
* Add -R option in Windows NSIS script

Took 23 minutes

* Small fix.

Took 3 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-04-19 01:07:38 +02:00
ebbit1q
77978c7178
stop prepending the adventure side (#6819)
the adventure side of adventure cards used to be the first entry in
mtgjson, at some point this changed to the second entry, better
reflecting its secondary nature, however cockatrice has hardcoded the
treatment of the card to assume the second part was the "main" part,
when implementing the treatment of prepare layout cards (#6792) I
copied the behavior over which was then already incorrect, this pr
removes the special treatment of this card layout bringing the result
back in line with expectation: the main part of the card provides the
main type used in cockatrice for sorting and behavior
2026-04-19 01:03:49 +02:00
ebbit1q
e918856fa4
clear player before removing it from the scene (#6817)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 11 (push) Blocked by required conditions
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Fedora 42 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 11 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 22.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
revert #6740
2026-04-18 12:50:53 +02:00
github-actions[bot]
87c4216e80
Update translation files (#6813)
Some checks failed
Build Desktop / Configure (push) Has been cancelled
Build Docker Image / amd64 & arm64 (push) Has been cancelled
Build Desktop / Debian 11 (push) Has been cancelled
Build Desktop / Debian 13 (push) Has been cancelled
Build Desktop / Debian 12 (push) Has been cancelled
Build Desktop / Fedora 43 (push) Has been cancelled
Build Desktop / Fedora 42 (push) Has been cancelled
Build Desktop / Servatrice_Debian 11 (push) Has been cancelled
Build Desktop / Ubuntu 24.04 (push) Has been cancelled
Build Desktop / Ubuntu 26.04 (push) Has been cancelled
Build Desktop / Ubuntu 22.04 (push) Has been cancelled
Build Desktop / Arch (push) Has been cancelled
Build Desktop / macOS 14 (push) Has been cancelled
Build Desktop / macOS 15 (push) Has been cancelled
Build Desktop / macOS 13 Intel (push) Has been cancelled
Build Desktop / macOS 15 Debug (push) Has been cancelled
Build Desktop / Windows 10 (push) Has been cancelled
Co-authored-by: github-actions <github-actions@github.com>
2026-04-15 20:41:45 +02:00
BruebachL
832f70496a
[EDHRec] Fix unintentional navigation on card right click (#6814)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 11 (push) Blocked by required conditions
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Fedora 42 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 11 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 22.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
Don't double emit cardClicked and also pass along the mouseEvent so we can check if it's a left or right click.

Took 26 minutes

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-04-15 14:14:30 +02:00
ebbit1q
f97864b72b
set the minimum qt6 version to 6.4 (#6811)
Some checks failed
Build Desktop / Configure (push) Has been cancelled
Build Docker Image / amd64 & arm64 (push) Has been cancelled
Build Desktop / Debian 11 (push) Has been cancelled
Build Desktop / Debian 13 (push) Has been cancelled
Build Desktop / Debian 12 (push) Has been cancelled
Build Desktop / Fedora 43 (push) Has been cancelled
Build Desktop / Fedora 42 (push) Has been cancelled
Build Desktop / Servatrice_Debian 11 (push) Has been cancelled
Build Desktop / Ubuntu 24.04 (push) Has been cancelled
Build Desktop / Ubuntu 26.04 (push) Has been cancelled
Build Desktop / Ubuntu 22.04 (push) Has been cancelled
Build Desktop / Arch (push) Has been cancelled
Build Desktop / macOS 14 (push) Has been cancelled
Build Desktop / macOS 15 (push) Has been cancelled
Build Desktop / macOS 13 Intel (push) Has been cancelled
Build Desktop / macOS 15 Debug (push) Has been cancelled
Build Desktop / Windows 10 (push) Has been cancelled
* set the minimum qt6 version to 6.4

this will be separate from the qt5 removal after release

* add include for optional
2026-04-14 00:59:46 +02:00
ebbit1q
338c56678a
add workaround for windows11 theme (#6810)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 11 (push) Blocked by required conditions
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Fedora 42 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 11 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 22.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
2026-04-13 14:49:31 +02:00
Jeremy Letto
8cc65b8967
Initial implementation completion and refactor (#6806)
Some checks failed
Build Desktop / Configure (push) Has been cancelled
Build Docker Image / amd64 & arm64 (push) Has been cancelled
Build Web / React (Node 16) (push) Has been cancelled
Build Web / React (Node lts/*) (push) Has been cancelled
Build Desktop / Debian 11 (push) Has been cancelled
Build Desktop / Debian 13 (push) Has been cancelled
Build Desktop / Debian 12 (push) Has been cancelled
Build Desktop / Fedora 43 (push) Has been cancelled
Build Desktop / Fedora 42 (push) Has been cancelled
Build Desktop / Servatrice_Debian 11 (push) Has been cancelled
Build Desktop / Ubuntu 24.04 (push) Has been cancelled
Build Desktop / Ubuntu 26.04 (push) Has been cancelled
Build Desktop / Ubuntu 22.04 (push) Has been cancelled
Build Desktop / Arch (push) Has been cancelled
Build Desktop / macOS 14 (push) Has been cancelled
Build Desktop / macOS 15 (push) Has been cancelled
Build Desktop / macOS 13 Intel (push) Has been cancelled
Build Desktop / macOS 15 Debug (push) Has been cancelled
Build Desktop / Windows 10 (push) Has been cancelled
2026-04-11 23:51:10 -04:00
ebbit1q
2e10b2f5d5
make general settings scrollable (#6800) 2026-04-12 02:38:04 +02:00
ebbit1q
92fe406c22
remove break statement when closing views (#6801)
it's possible that multiple views need to be closed
this is an oversight of #4570
2026-04-12 02:37:43 +02:00
tooomm
d677e2bb70
Print success msg if cache deletion did succeed (#6802) 2026-04-12 02:37:30 +02:00
ebbit1q
e977f123ce
add ccache eviction for files older than 7 days (#6795)
* add ccache eviction for files older than 7 days

also clean up some of the scripts to be more internally consistent

* move name build into the docker container again

* remove one extra empty line [skip ci]

* allow canceling concurrent builds on master for both desktop and docker

* add ccache eviction age to macos as well
2026-04-11 19:18:12 +02:00
tooomm
f56b672307
CI: Allow failing of ccache deletion step (#6799)
* Don't fail cache delete step

* Use `continue-on-error`

* remove leftover
2026-04-11 18:20:35 +02:00
tooomm
29c1d7f3e4
add timeout to job matrixes (#6797) 2026-04-11 18:19:11 +02:00
ebbit1q
36aba81b1b
adds eternal to prioritisation list (#6793)
Some checks failed
Build Desktop / Configure (push) Has been cancelled
Build Docker Image / amd64 & arm64 (push) Has been cancelled
Build Desktop / Debian 11 (push) Has been cancelled
Build Desktop / Debian 13 (push) Has been cancelled
Build Desktop / Debian 12 (push) Has been cancelled
Build Desktop / Fedora 43 (push) Has been cancelled
Build Desktop / Fedora 42 (push) Has been cancelled
Build Desktop / Servatrice_Debian 11 (push) Has been cancelled
Build Desktop / Ubuntu 24.04 (push) Has been cancelled
Build Desktop / Ubuntu 26.04 (push) Has been cancelled
Build Desktop / Ubuntu 22.04 (push) Has been cancelled
Build Desktop / Arch (push) Has been cancelled
Build Desktop / macOS 14 (push) Has been cancelled
Build Desktop / macOS 15 (push) Has been cancelled
Build Desktop / macOS 13 Intel (push) Has been cancelled
Build Desktop / macOS 15 Debug (push) Has been cancelled
Build Desktop / Windows 10 (push) Has been cancelled
2026-04-10 18:18:08 +02:00
ebbit1q
f9fb03b26b
update all runners to use qt6.11 (#6794)
* update all runners to use qt6.11

* use aqt version directly from repo
2026-04-10 18:17:43 +02:00
ebbit1q
d2732ac742
fix prepare cards (#6792)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 11 (push) Blocked by required conditions
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Fedora 42 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 11 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 22.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
2026-04-10 13:42:05 +02:00
transifex-integration[bot]
9aa5702e14
Translate cockatrice_en@source.ts in en_US (#6783)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 11 (push) Blocked by required conditions
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Fedora 42 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 11 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 22.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
100% translated source file: 'cockatrice_en@source.ts'
on 'en_US'.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2026-04-09 21:27:14 +02:00
ebbit1q
2d412bfe52
fix cardloading on qt5 (#6790) 2026-04-09 21:16:38 +02:00
RickyRister
ac06fb9d1c
[Game] Fix crash by properly parenting QObjects (#6788)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 11 (push) Blocked by required conditions
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Fedora 42 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 11 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 22.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
2026-04-09 15:02:00 +02:00
BruebachL
d7b31f2f9d
Set OracleWizard style to "Modern" instead of "Aero" (#6778)
Some checks failed
Build Desktop / Configure (push) Has been cancelled
Build Docker Image / amd64 & arm64 (push) Has been cancelled
Build Desktop / Debian 11 (push) Has been cancelled
Build Desktop / Debian 13 (push) Has been cancelled
Build Desktop / Debian 12 (push) Has been cancelled
Build Desktop / Fedora 43 (push) Has been cancelled
Build Desktop / Fedora 42 (push) Has been cancelled
Build Desktop / Servatrice_Debian 11 (push) Has been cancelled
Build Desktop / Ubuntu 24.04 (push) Has been cancelled
Build Desktop / Ubuntu 26.04 (push) Has been cancelled
Build Desktop / Ubuntu 22.04 (push) Has been cancelled
Build Desktop / Arch (push) Has been cancelled
Build Desktop / macOS 14 (push) Has been cancelled
Build Desktop / macOS 15 (push) Has been cancelled
Build Desktop / macOS 13 Intel (push) Has been cancelled
Build Desktop / macOS 15 Debug (push) Has been cancelled
Build Desktop / Windows 10 (push) Has been cancelled
* Use fusions own palette.

Took 6 minutes

* Start from default palette always.

Took 4 minutes

* Add modern style.

Took 24 seconds

* Scope this fix to Windows.

Took 4 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-04-07 15:58:42 +02:00
BruebachL
635fae101d
Use palette colors for resizable panel stylesheets. (#6782)
Took 7 minutes

Took 5 seconds

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-04-07 15:57:03 +02:00
BruebachL
335022c4aa
Include themes (#6781)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 11 (push) Blocked by required conditions
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Fedora 42 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 11 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 22.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
* Properly reset default theme.

Took 9 minutes

* Descend in preference on windows.

Took 9 minutes

* Also reset style for custom themes.

Took 3 minutes

* Try things

Took 9 minutes

* Add modern windows style.

Took 8 minutes

* Update theme_manager.cpp

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-04-07 15:02:00 +02:00
Bruno Alexandre Rosa
dbed6890da
feat: support expressions when setting card counters (#6753)
* feat: enable expressions in card counters

* fix includes

* fix multiple selection

* cleanup useless conversions

* const ref where possible

* do not use const, be consistent with local patterns in the file
2026-04-06 22:36:45 -07:00
ebbit1q
3ec9ae9772
fix compiling on arch (#6768)
Some checks failed
Build Desktop / Configure (push) Has been cancelled
Build Docker Image / amd64 & arm64 (push) Has been cancelled
Build Desktop / Debian 11 (push) Has been cancelled
Build Desktop / Debian 13 (push) Has been cancelled
Build Desktop / Debian 12 (push) Has been cancelled
Build Desktop / Fedora 43 (push) Has been cancelled
Build Desktop / Fedora 42 (push) Has been cancelled
Build Desktop / Servatrice_Debian 11 (push) Has been cancelled
Build Desktop / Ubuntu 24.04 (push) Has been cancelled
Build Desktop / Ubuntu 26.04 (push) Has been cancelled
Build Desktop / Ubuntu 22.04 (push) Has been cancelled
Build Desktop / Arch (push) Has been cancelled
Build Desktop / macOS 14 (push) Has been cancelled
Build Desktop / macOS 15 (push) Has been cancelled
Build Desktop / macOS 13 Intel (push) Has been cancelled
Build Desktop / macOS 15 Debug (push) Has been cancelled
Build Desktop / Windows 10 (push) Has been cancelled
* fix compiling on arch

* redo all the logging in affected files
2026-04-05 21:52:46 +02:00
RickyRister
a46ab5cd68
[Game] Allow uppercase X in expressions (#6767)
Some checks failed
Build Desktop / Configure (push) Has been cancelled
Build Docker Image / amd64 & arm64 (push) Has been cancelled
Build Desktop / Debian 11 (push) Has been cancelled
Build Desktop / Debian 13 (push) Has been cancelled
Build Desktop / Debian 12 (push) Has been cancelled
Build Desktop / Fedora 43 (push) Has been cancelled
Build Desktop / Fedora 42 (push) Has been cancelled
Build Desktop / Servatrice_Debian 11 (push) Has been cancelled
Build Desktop / Ubuntu 24.04 (push) Has been cancelled
Build Desktop / Ubuntu 26.04 (push) Has been cancelled
Build Desktop / Ubuntu 22.04 (push) Has been cancelled
Build Desktop / Arch (push) Has been cancelled
Build Desktop / macOS 14 (push) Has been cancelled
Build Desktop / macOS 15 (push) Has been cancelled
Build Desktop / macOS 13 Intel (push) Has been cancelled
Build Desktop / macOS 15 Debug (push) Has been cancelled
Build Desktop / Windows 10 (push) Has been cancelled
2026-04-04 11:40:38 +02:00
SlightlyCircuitous
690a00aa6c
Add Ubuntu 26.04 "Resolute Racoon" build (#6766)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 11 (push) Blocked by required conditions
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Fedora 42 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 11 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 22.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
* Create 26.04 Dockerfile

* Update desktop-build.yml

* Update release_template.md

* Add ca-certificates package to build
2026-04-03 20:40:42 +02:00
tooomm
ba786a0289
Update dependabot.yml (#6756)
Some checks failed
Build Docker Image / amd64 & arm64 (push) Has been cancelled
2026-04-01 14:54:27 +02:00
github-actions[bot]
9dfac77ba2
Update translation source strings (#6762)
Some checks failed
Build Docker Image / amd64 & arm64 (push) Waiting to run
Build Desktop / Configure (push) Has been cancelled
Build Desktop / Debian 11 (push) Has been cancelled
Build Desktop / Debian 13 (push) Has been cancelled
Build Desktop / Debian 12 (push) Has been cancelled
Build Desktop / Fedora 43 (push) Has been cancelled
Build Desktop / Fedora 42 (push) Has been cancelled
Build Desktop / Servatrice_Debian 11 (push) Has been cancelled
Build Desktop / Ubuntu 22.04 (push) Has been cancelled
Build Desktop / Ubuntu 24.04 (push) Has been cancelled
Build Desktop / Arch (push) Has been cancelled
Build Desktop / macOS 14 (push) Has been cancelled
Build Desktop / macOS 15 (push) Has been cancelled
Build Desktop / macOS 13 Intel (push) Has been cancelled
Build Desktop / macOS 15 Debug (push) Has been cancelled
Build Desktop / Windows 10 (push) Has been cancelled
2026-04-01 05:49:17 +02:00
dependabot[bot]
f3370a4f52
Bump doc/doxygen/theme from 1f36200 to d52eafe (#6751)
Some checks failed
Build Desktop / Configure (push) Has been cancelled
Build Docker Image / amd64 & arm64 (push) Has been cancelled
Build Desktop / Debian 11 (push) Has been cancelled
Build Desktop / Debian 13 (push) Has been cancelled
Build Desktop / Debian 12 (push) Has been cancelled
Build Desktop / Fedora 43 (push) Has been cancelled
Build Desktop / Fedora 42 (push) Has been cancelled
Build Desktop / Servatrice_Debian 11 (push) Has been cancelled
Build Desktop / Ubuntu 22.04 (push) Has been cancelled
Build Desktop / Ubuntu 24.04 (push) Has been cancelled
Build Desktop / Arch (push) Has been cancelled
Build Desktop / macOS 14 (push) Has been cancelled
Build Desktop / macOS 15 (push) Has been cancelled
Build Desktop / macOS 13 Intel (push) Has been cancelled
Build Desktop / macOS 15 Debug (push) Has been cancelled
Build Desktop / Windows 10 (push) Has been cancelled
Bumps [doc/doxygen/theme](https://github.com/jothepro/doxygen-awesome-css) from `1f36200` to `d52eafe`.
- [Release notes](https://github.com/jothepro/doxygen-awesome-css/releases)
- [Commits](1f3620084f...d52eafe3e9)

---
updated-dependencies:
- dependency-name: doc/doxygen/theme
  dependency-version: d52eafe3e9303399fda15661f3d7bb8fe3d7eabc
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-28 07:05:24 +01:00
Bruno Alexandre Rosa
7caa88bc58
fix: solve deprecated literal operator error by updating peglib (#6745)
* fix: solve deprecated literal operator error

* update peglib

dcabc63cf4
2026-03-27 18:37:30 +01:00
RickyRister
34a5b8b9ce
[SettingsManager] Make setting getters const (#6748)
* [SettingsManager] Make setting getters const

* remove hashGameType from header
2026-03-27 18:13:25 +01:00
tooomm
d8e3807ec5
Dependabot: Enable git submodules tracking (#6727)
* Enable gitsubmodules

* update comment
2026-03-27 18:11:56 +01:00
ebbit1q
42bd8164a0
remove hardcoded white in vde banner widget (#6684)
* remove hardcoded white in vde banner widget

* set text color to white on higher opacities
2026-03-27 18:10:29 +01:00
ebbit1q
abf6e72ad1
add a nulcheck in the card item animation timer (#6740) 2026-03-27 18:08:51 +01:00
RickyRister
74cce5ccb2
[SettingsManager] Properly handle multithreaded access (#6747) 2026-03-27 17:12:49 +01:00
DawnFire42
dd053c76df
[Game] Improve context menus and fix face-down play from stack (#6739)
Reorganize card context menus across table, stack, and graveyard/exile zones for better consistency: promote Draw Arrow and Clone actions, move related card entries to the bottom, add Play/Play Face Down to the stack menu, and flatten if/else blocks with early returns. Also fix playCard() ignoring the faceDown flag when routing instants/sorceries from the stack, which sent them to the graveyard instead of the table.
2026-03-25 15:03:59 -07:00
scotland0208
5ef428b9d0
Add visual indicator to toggle untap button (#6737)
* Add visual indicator to toggle untap button

* Rename button to match tooltip

* Change name of string in shortcut settings
2026-03-25 14:15:08 -07:00
DawnFire42
94ea574c76
Add moveToTable context menu action and extract tableRowToGridY helper (#6738)
Adds a Table option to the Move menu, allowing cards to be moved directly to the battlefield from any zone. Extracts the repeated tableRow-to-grid-Y conversion logic into TableZone::tableRowToGridY(), consolidating five call sites and fixing a latent bug where cards with tableRow > 2 could land on the wrong row.
2026-03-24 13:45:52 -07:00
DawnFire42
70b41c2095
refactor: extract AbstractPlayerComponent interface for polymorphic player component management. (#6696)
Non-QObject polymorphic interface with setShortcutsActive(), setShortcutsInactive(), and retranslateUi(). Uses regular multiple inheritance to avoid diamond inheritance with Qt's MOC.

All zone menus, SayMenu, and AbstractCounter implement this interface. PlayerMenu manages them via a managedComponents list with two template helpers (addManagedMenu/registerManagedComponent), replacing individual if-guarded lifecycle calls with a single polymorphic loop.

SayMenu now owns its shortcut and translation lifecycle instead of having PlayerMenu manage its title and shortcuts externally.
Counters are iterated via Player::getCounters() rather than managedComponents to avoid duplicating the authoritative owner's map.
2026-03-24 12:31:34 -07:00
ebbit1q
aa85a39d6a
expand local game life total limits (#6730) 2026-03-24 00:16:48 +01:00
tooomm
51c684251f
Add Docker to release template (#6732) 2026-03-24 00:16:26 +01:00
dependabot[bot]
fa2934373c
Bump microsoft/setup-msbuild from 2 to 3 (#6735) 2026-03-23 21:44:31 +01:00
dependabot[bot]
414567f8b6
Bump docker/login-action from 3 to 4 (#6736) 2026-03-23 21:43:42 +01:00
RickyRister
bc219191db
[Game] Refactor options in DlgMoveTopCardsUntil into struct (#6718)
Some checks failed
Build Desktop / Configure (push) Has been cancelled
Build Docker Image / amd64 & arm64 (push) Has been cancelled
Build Desktop / Debian 11 (push) Has been cancelled
Build Desktop / Debian 13 (push) Has been cancelled
Build Desktop / Debian 12 (push) Has been cancelled
Build Desktop / Fedora 43 (push) Has been cancelled
Build Desktop / Fedora 42 (push) Has been cancelled
Build Desktop / Servatrice_Debian 11 (push) Has been cancelled
Build Desktop / Ubuntu 22.04 (push) Has been cancelled
Build Desktop / Ubuntu 24.04 (push) Has been cancelled
Build Desktop / Arch (push) Has been cancelled
Build Desktop / macOS 14 (push) Has been cancelled
Build Desktop / macOS 15 (push) Has been cancelled
Build Desktop / macOS 13 Intel (push) Has been cancelled
Build Desktop / macOS 15 Debug (push) Has been cancelled
Build Desktop / Windows 10 (push) Has been cancelled
2026-03-22 12:32:42 +01:00
tooomm
652c8464a7
Docker compose: Link to our GHCR hosted servatrice image (#6728)
* Point to our own image

* Point to our own image

* Readd build

* Readd build
2026-03-22 10:28:24 +01:00
transifex-integration[bot]
067fe9b534
Translate cockatrice/cockatrice_en@source.ts in it (#6724)
100% translated source file: 'cockatrice/cockatrice_en@source.ts'
on 'it'.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2026-03-21 10:13:43 +01:00
RickyRister
38c85e6db1
[Dialog] Reduce spacing in create local game dialog (#6719)
Some checks failed
Build Desktop / Configure (push) Has been cancelled
Build Docker Image / amd64 & arm64 (push) Has been cancelled
Build Desktop / Debian 11 (push) Has been cancelled
Build Desktop / Debian 13 (push) Has been cancelled
Build Desktop / Debian 12 (push) Has been cancelled
Build Desktop / Fedora 43 (push) Has been cancelled
Build Desktop / Fedora 42 (push) Has been cancelled
Build Desktop / Servatrice_Debian 11 (push) Has been cancelled
Build Desktop / Ubuntu 22.04 (push) Has been cancelled
Build Desktop / Ubuntu 24.04 (push) Has been cancelled
Build Desktop / Arch (push) Has been cancelled
Build Desktop / macOS 14 (push) Has been cancelled
Build Desktop / macOS 15 (push) Has been cancelled
Build Desktop / macOS 13 Intel (push) Has been cancelled
Build Desktop / macOS 15 Debug (push) Has been cancelled
Build Desktop / Windows 10 (push) Has been cancelled
2026-03-18 10:24:34 -07:00
RickyRister
c5cd7d8700
[ShortcutsSettings] Fix duplicate aPlay shortcut; change play shortcut's group (#6716)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 11 (push) Blocked by required conditions
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Fedora 42 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 11 (push) Blocked by required conditions
Build Desktop / Ubuntu 22.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
* [ShortcutsSettings] Fix duplicate aPlay shortcut; change play shortcut's group

* update description
2026-03-18 10:13:36 +01:00
DawnFire42
fc453c68a7
Add card selection counter (#6685)
* feat(game): add drag selection counter overlay
   Display count of selected cards inside the lasso during drag selection.
   Count appears near cursor, repositioning to stay within selection bounds.

   Includes SelectionRubberBand subclass to allow label to appear above band.
   QRubberBand calls raise() in showEvent/changeEvent to stay on top - this
   subclass suppresses that behavior so dragCountLabel can be visible.

   Adds user setting to enable/disable the drag count overlay.

* feat(game): add persistent selection counter overlay.
Display total count of selected cards in bottom-right corner when multiple cards are selected.
Updates on selection changes and window resize.
The counter connects to QGraphicsScene::selectionChanged to stay up-to-date without requiring manual refresh.
Adds user setting to enable/disable the total count overlay.

---------

Co-authored-by: RickyRister <42636155+RickyRister@users.noreply.github.com>
2026-03-16 23:44:29 +01:00
ebbit1q
69c046cca4
remove all separate storing of widget sizes, rely on geometry (#6712) 2026-03-16 23:38:47 +01:00
tooomm
dd8164611b
revert env name (#6711) 2026-03-16 23:38:04 +01:00
dependabot[bot]
b4a5c863d7
Bump docker/metadata-action from 5 to 6 (#6713)
Bumps [docker/metadata-action](https://github.com/docker/metadata-action) from 5 to 6.
- [Release notes](https://github.com/docker/metadata-action/releases)
- [Commits](https://github.com/docker/metadata-action/compare/v5...v6)

---
updated-dependencies:
- dependency-name: docker/metadata-action
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-16 21:11:32 +01:00
dependabot[bot]
4a3d79d00b
Bump docker/build-push-action from 6 to 7 (#6714)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6 to 7.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v6...v7)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-16 21:10:22 +01:00
tooomm
96f436b65e
CI: Resolve to latest Qt version in range for Windows as well (#6700)
* CI: Resolve to latest Qt version in range for Windows as well

* install aqt

* Check dedicated win version
2026-03-15 17:47:48 +01:00
tooomm
cf01dc770b
CI: Adjustments to ccache usage (#6703)
* Increase ccache size

* Fix Arch ccache usage

* more cleanup

* harmonize names
2026-03-15 17:41:17 +01:00
DawnFire42
ce652de272
Fix/cmake test only build (#6709)
* fix(cmake): guard filter_string_test behind WITH_ORACLE or WITH_CLIENT. filter_string_test links against libcockatrice_filters, which is only built when WITH_ORACLE or WITH_CLIENT is enabled. Without this guard, test-only builds fail at configure time because the target doesn't exist. The guard condition mirrors the one in the root CMakeLists.txt that controls whether libcockatrice_filters is built.

* fix(cmake): centralize TEST_QT_MODULES in FindQtRuntime.cmake  Each test CMakeLists.txt was independently defining TEST_QT_MODULES with its own subset of Qt modules. This duplicated knowledge that already lives in FindQtRuntime.cmake (which handles module discovery for all other targets: SERVATRICE, COCKATRICE, ORACLE). Consolidate into a single definition using the union of all test requirements (Concurrent Network Svg Widgets), matching the existing pattern for application-target modules. This ensures test-only builds (-DTEST=ON without application targets) discover all necessary Qt components.

* fix(cmake): guard libcockatrice_network behind application targets. libcockatrice_network is only needed by the client, server, and oracle targets. Other application-specific libraries (settings, models, filters) already have similar guards. This was an oversight that caused test-only builds to fail when network dependencies weren't available.
2026-03-15 17:24:50 +01:00
DawnFire42
9bb399606c
refactor: extract shared card insertion algorithm from hand/stack zones (#6701)
Hand and stack zones had near-identical addCardImpl() implementations, differing only in whether resetState() preserves annotations.
Extract the shared pattern into a template function (CardZoneAlgorithms::addCardToList) to eliminate duplication and enable isolated testing without Qt dependencies.
Pile, table, and zone-view logic are intentionally excluded — their post-add behavior (signals, coordinate placement, hidden cards) is materially different.
2026-03-15 00:39:44 -07:00
tooomm
8180d2e3b0
CI: Update compiler cache on hit (#6691)
* update ccache

* Update desktop-build.yml

* Update desktop-build.yml
2026-03-14 15:12:10 +01:00
RickyRister
33d5721490
[Game] Fix not using zone-specific card menu for opponent's cards (#6695) 2026-03-14 11:41:38 +01:00
transifex-integration[bot]
2b2a6db081
Translate oracle/oracle_en@source.ts in it (#6692)
100% translated source file: 'oracle/oracle_en@source.ts'
on 'it'.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2026-03-13 21:22:48 +01:00
DawnFire42
aa4592dc9e
Add local game options (#6669)
* Add local game options dialog. Introduces LocalGameOptions struct and DlgLocalGameOptions dialog to replace the previous QInputDialog for starting local games. Encapsulates game configuration with a simple interface that prevents parameter explosion as options are added. The dialog provides UI with settings persistence via SettingsCache

* integrate local game options into main window. Replaces QInputDialog with DlgLocalGameOptions in actSinglePlayer(). The startLocalGame() function now accepts LocalGameOptions, enabling configuration of starting life total and spectator visibility in addition to player count. Also adds user documentation for the local game options flow.

* Removed superfluous documentation file

* removed spectator option and moved structure definition

* Now remember settings separately and & shortcuts removed

* re-run checks
2026-03-12 14:30:01 -07:00
RickyRister
20ad9af989
[CacheSettings] Refactor country list creation (#6687) 2026-03-12 11:00:32 -07:00
DawnFire42
9e2276a59f
Refactor zone names (#6686)
* Add ZoneNames constants for protocol zone identifiers. Introduce a centralized ZoneNames namespace providing constexpr constants for zone identifiers used in the client-server protocol. This establishes a single source of truth for zone names like TABLE, GRAVE, EXILE, HAND, DECK, SIDEBOARD, and STACK. The protocol values remain unchanged (e.g., EXILE maps to rfg for backwards compatibility) while providing meaningful constant names.

* refactor(server): use ZoneNames constants in server game logic

 Replace hardcoded zone name strings with ZoneNames:: constants in:
 - server_player.cpp: zone setup, draw, shuffle, mulligan operations
 - server_abstract_player.cpp: card movement and token destruction
 - server_game.cpp: returning cards when players leave

 No functional changes - purely mechanical string literal replacement.

* refactor(client): use ZoneNames constants in core player/zone logic

 Update the foundational player and zone classes to use ZoneNames::
 constants instead of string literals. Changes include:
 - player.h/cpp: zone initialization and builtinZones set
 - card_zone_logic.cpp: zone name translation for UI display
 - table_zone.cpp: table zone operations

 No functional changes - purely mechanical string literal replacement.

* refactor(client): use ZoneNames constants in player actions and events

 Replace zone name strings with ZoneNames:: constants in the player
 action and event handling code. player_actions.cpp contains the most
 extensive changes (~90+ replacements) covering all card movement
 commands.

 No functional changes - purely mechanical string literal replacement.

* refactor(client): use ZoneNames constants in zone menu handlers

 Update all zone-specific menu files to use ZoneNames:: constants
 for QAction data values and zone targeting. This covers context menus
 for cards, graveyard, hand, and exile (RFG) zones.

 No functional changes - purely mechanical string literal replacement.

* refactor(client): use ZoneNames constants in game scene components

 Update remaining game scene components to use ZoneNames:: constants:
 - arrow_item.cpp: arrow drawing between cards
 - game_scene.cpp: zone view positioning
 - message_log_widget.cpp: removes duplicate local static constants
   that were previously defining zone names redundantly
 - phases_toolbar.cpp: phase actions (untap all)

 Notable: message_log_widget.cpp previously had its own local constants
 (TABLE_ZONE_NAME, GRAVE_ZONE_NAME, etc.) which are now removed in favor
 of the centralized ZoneNames:: constants.

* formatting fix
2026-03-12 00:34:05 +01:00
tooomm
42cec10457
CI: Cleanup (#6677)
* Cleanup vcpkg matrix

* Add filename with extension as output

* fix name -> fullname, cleanup
2026-03-09 21:32:47 +01:00
dependabot[bot]
95d7e027fc
Bump docker/setup-buildx-action from 3 to 4 (#6682)
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 3 to 4.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-09 21:30:47 +01:00
dependabot[bot]
8268311fab
Bump docker/setup-qemu-action from 3 to 4 (#6683)
Bumps [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) from 3 to 4.
- [Release notes](https://github.com/docker/setup-qemu-action/releases)
- [Commits](https://github.com/docker/setup-qemu-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: docker/setup-qemu-action
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-09 21:22:26 +01:00
tooomm
413b4b637b
sign+notarize only releases (#6678) 2026-03-09 21:20:38 +01:00
RickyRister
e79bbc67b9
[DeckEditor] Fix undo/redo clearing legality (#6675) 2026-03-08 15:57:39 -07:00
RickyRister
15a1d5440b
[DeckEditor] Fix undo/redo resetting deck sorting (#6673) 2026-03-08 23:50:54 +01:00
tooomm
fd293444c5
tweaks (#6674) 2026-03-08 23:50:33 +01:00
tooomm
852429f248
remove unused zip command (#6676) 2026-03-08 23:48:30 +01:00
tooomm
4606fcdbd5
Add description for code docs (#6679) 2026-03-08 23:27:15 +01:00
tooomm
c375cdbb1a
CI: Upload artifact files directly (#6654)
* Upload artifact files directly

* match pdbs name

* Update name variable
2026-03-08 18:03:01 +01:00
tooomm
f15b70e4ae
Use new attest action (#6671) 2026-03-08 17:53:16 +01:00
RickyRister
2f10634ca2
[DeckList] Fix double-faced cards not importing correctly (#6665)
* [DeckList] Fix double-faced cards not importing correctly

* make tests compile
2026-03-06 11:48:17 -08:00
RickyRister
dead993639
[DeckList] Refactor load from plaintext to take normalizer as param (#6664)
* [DeckList] Refactor load from plaintext to take normalizer as param

* update usages

* weaken unit test

* weaken unit test more

* revert unit test

* move CardNameNormalizer to libcockatrice_card

* update unit test

* formatting
2026-03-06 10:39:04 -08:00
DawnFire42
bd5cbb89d4
refactor: extract CARD_HEIGHT to shared CardDimensions header (#6668)
* refactor: extract CARD_HEIGHT to shared CardDimensions header

  Move duplicated CARD_WIDTH/CARD_HEIGHT constants to card_dimensions.h.
  Fixed documentation in z_value_layer_manager.h.

* WIDTH_F used directly instead of casting

* Improved consistency and added missing newlines at end of files
2026-03-05 19:13:58 -08:00
tooomm
14f1925edc
Add icon to exe (#6655) 2026-03-06 01:50:15 +01:00
ebbit1q
e39bbd2b31
take default theme name from startup instead of using empty (#6663) 2026-03-06 00:19:10 +01:00
DawnFire42
04f06206b7
Refactor/z value constants (#6651)
* feat(z-values): add centralized Z-value constants infrastructure

Magic numbers scattered across the codebase make Z-value layering
hard to understand and maintain. Centralizing them provides:
- Self-documenting layer hierarchy
- Validation utilities for development
- Single source of truth for Z-value ranges

Two-tier header design:
- z_value_layer_manager.h: Foundation with constants and validation
- z_values.h: User-facing namespace with semantic constants

* refactor(z-values): replace magic Z-value numbers with ZValues constants

Magic numbers like 2000000007 are impossible to understand without
context. Named constants (ZValues::DRAG_ITEM) are self-documenting.

This intentionally renumbers overlay Z-values to use a cleaner offset
sequence. The relative stacking order is preserved, which is what
matters for correct rendering.

Each consumer now includes z_values.h and uses semantic constants
instead of magic numbers.

* refactor(z-values): removed redundant inline with contexpr, Updated doc, magic numbers removed from TableZone.
2026-03-05 11:47:14 -08:00
RickyRister
1bcea27a44
[Game] Add face down versions of move cards from library actions (#6661)
* implement actions

* add new actions to menu

* update shortcuts
2026-03-04 18:19:41 -08:00
RickyRister
e7a3ad86eb
[Game] Refactor move cards from library actions (#6658)
* refactor move top/bottom cards actions

* minor cleanup

* translate zone display names
2026-03-04 15:00:18 +01:00
ebbit1q
b36ab66583
nullcheck printing's set in home tab background art crop (#6646)
* nullcheck printing's set in home tab background art crop

* set warning and properly set timer

* fix merge
2026-03-04 00:49:50 +01:00
ebbit1q
566c876bdc
fix scaling for background images on home tab (#6656) 2026-03-04 00:16:45 +01:00
ebbit1q
43acac5f5d
empty card info when switching back to theme background (#6657) 2026-03-04 00:16:19 +01:00
RickyRister
846ecb7e8d
[Client] Support face-down cards in all public zones (#6602)
* [Server] Support face-down cards in all public zones

* add null check

* Check using zone names instead

* add comment

* Rename properties and only pass forceFaceDown

* [Game] Refactor CardDragItem faceDown logic

* revert refactor

* leave face_down unset unless forced

* [Client] Support face-down cards in all public zones

* leave face_down unset unless forced

* log face down

* update remaining logs
2026-03-03 23:19:26 +01:00
RickyRister
2fba5dcd20
[Game] Refactor CardDragItem faceDown logic (#6552)
* Rename properties and only pass forceFaceDown

* [Game] Refactor CardDragItem faceDown logic

* revert refactor

* leave face_down unset unless forced
2026-03-03 23:18:21 +01:00
RickyRister
2828854d32
[Server] Support face-down cards in all public zones (#6539)
* [Server] Support face-down cards in all public zones

* add null check

* Check using zone names instead

* add comment
2026-03-03 23:17:35 +01:00
dependabot[bot]
9794893b63
Bump actions/attest-build-provenance from 3 to 4 (#6653)
Bumps [actions/attest-build-provenance](https://github.com/actions/attest-build-provenance) from 3 to 4.
- [Release notes](https://github.com/actions/attest-build-provenance/releases)
- [Changelog](https://github.com/actions/attest-build-provenance/blob/main/RELEASE.md)
- [Commits](https://github.com/actions/attest-build-provenance/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/attest-build-provenance
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-02 23:06:35 +01:00
ebbit1q
e57ee8e9c9
fix set sorting (#6630) 2026-03-02 23:06:05 +01:00
dependabot[bot]
f978407a19
Bump actions/upload-artifact from 6 to 7 (#6652)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 6 to 7.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-02 23:04:15 +01:00
tooomm
59ce70afc5
Add Code Docs link to issue template (#6649) 2026-03-02 01:59:18 +01:00
DawnFire42
208ccc3a1a
fix(docs): correct typos in README (#6640)
Fix 'projet' -> 'project' and 'invovled' -> 'involved'.
2026-02-26 21:53:39 -08:00
tooomm
6f8f9f016a
CI: windows-2025 runner (#6632)
* `windows-2025` runner

* Install NSIS

* fix naming
2026-02-26 22:31:18 +01:00
Rob Blanckaert
7ad2481e3d
Update UnescapedStringListPart to include parentheses (#6631)
* Update UnescapedStringListPart to include parentheses

* also update deck_filter_string

* add unit test

---------

Co-authored-by: RickyRister <ricky.rister.wang@gmail.com>
2026-02-25 23:55:34 -08:00
RickyRister
12c667afd7
[Search] Fix OR usage in examples (#6628) 2026-02-24 01:34:23 +01:00
RickyRister
2cb16c9fd0
[CardDatabaseDisplay] Reduce width by using icons (#6603)
* [CardDatabaseDisplay] Reduce width by using icons

* use public domain filter svg icon
2026-02-22 20:54:58 -08:00
tooomm
9f00c6f955
Update vcpkg submodule (#6627)
* Update vcpkg

* Target 2025.10.17
2026-02-23 01:44:14 +01:00
RickyRister
a90997353b
[TabGame] Fix concede removing player without waiting for server (#6622) 2026-02-23 01:33:08 +01:00
tooomm
c6dc7eee64
CI: Remove Windows 7 build (#6625)
* Update release_template.md

* Remove Win7
2026-02-22 22:11:58 +01:00
RickyRister
0f2899b5c7
[DeckEditor] Alternate row colors in history list (#6626) 2026-02-22 22:11:10 +01:00
Bruno Alexandre Rosa
71cf3fabbf
build: use qt 6.10 on macOS and win10 (#6491)
* build: use qt 6.8.* LTS in Windows 10 and macOS

* bump msvc

* bump to 6.10

* reset style to the default instead of the first key

---------

Co-authored-by: ebbit1q <ebbit1q@gmail.com>
2026-02-22 18:52:07 +01:00
RickyRister
f0da3cff40
[PictureLoader] Add hash of new gatherer card back image to blacklist (#6621) 2026-02-22 12:12:28 +01:00
ebbit1q
485e4d56aa
Update CONTRIBUTING.md (#6615)
I keep forgetting this so I'm making the warning bigger
2026-02-21 21:38:05 +01:00
tooomm
99424e460b
CI: Fix artifact digest sha for attestation (#6614)
* Fix artifact digest sha for attestation

* linux, too

* Update desktop-build.yml
2026-02-21 15:39:48 +01:00
tooomm
006abf79b1
Add oracle to win pdb's (#6611) 2026-02-21 15:37:26 +01:00
RickyRister
5c3c3bfdba
[ChatView] Fix extra blank line at beginning (#6613) 2026-02-21 15:36:35 +01:00
RickyRister
189f3a7bbc
[ChatView] Fix game log first line incorrect background color (#6612) 2026-02-21 04:40:47 -08:00
ebbit1q
6ab558dd58
reset style to the default instead of the first key (#6596) 2026-02-19 08:36:35 +01:00
RickyRister
a7bb5254a3
[VDE] Fix Qt warnings in log (#6605)
* [VDE] Fix Qt warnings in log

* fix button size
2026-02-19 08:32:56 +01:00
BruebachL
ef87b54b43
[PictureLoader] Set accept header so we don't get CloudFlare'd (#6607)
Took 34 minutes

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-02-19 08:32:38 +01:00
RickyRister
88d0ebb12d
[TabDeckEditor] Fix printingSelector dock close button not working (#6604) 2026-02-19 00:50:11 +01:00
RickyRister
bdb42bbbbd
[VDE] Separate layout settings for visual deck editor (#6595) 2026-02-08 13:37:56 -08:00
RickyRister
ac7ff3a0e9
[LayoutSettings] Refactor how widgetSize settings are managed (#6594) 2026-02-08 05:07:53 -08:00
RickyRister
1eb6027443
clean up freeDocksSize in tabs with dockWidgets (#6593) 2026-02-08 05:07:40 -08:00
RickyRister
edc8691731
[LayoutSettings] Don't return by const value (#6592)
* [LayoutSettings] Don't return by const value

* fix compile failure
2026-02-07 20:42:23 -08:00
RickyRister
804a60f1ea
[LayoutSettings] Move over layout settings in global.ini (#6587)
* [LayoutSettings] Move over some settings from general

* remove unused setting
2026-02-07 19:51:12 -08:00
transifex-integration[bot]
a80a0531a6
Translate oracle/oracle_en@source.ts in fr (#6544)
100% translated source file: 'oracle/oracle_en@source.ts'
on 'fr'.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2026-02-07 21:48:00 -05:00
RickyRister
24bc713ba8
[DeckListModel] Fix deck hash not updating on card node add/remove (#6584) 2026-02-07 21:47:50 -05:00
dependabot[bot]
4884640070
Bump ssciwr/doxygen-install from 1 to 2 (#6585)
Bumps [ssciwr/doxygen-install](https://github.com/ssciwr/doxygen-install) from 1 to 2.
- [Release notes](https://github.com/ssciwr/doxygen-install/releases)
- [Commits](https://github.com/ssciwr/doxygen-install/compare/v1...v2)

---
updated-dependencies:
- dependency-name: ssciwr/doxygen-install
  dependency-version: '2'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-07 21:47:41 -05:00
RickyRister
8d7535c039
[LayoutSettings] Reorganize hierarchy in settings file (#6586)
* [LayoutSettings] Reorganize hierarchy in settings file

* rename stuff since we're moving settings later
2026-02-07 21:45:33 -05:00
RickyRister
32aa60bb14
[Oracle] Move oracle settings to separate file (#6588) 2026-02-07 21:44:49 -05:00
dependabot[bot]
3ada27eae1
Bump webpack from 5.99.9 to 5.105.0 in /webclient (#6590)
Bumps [webpack](https://github.com/webpack/webpack) from 5.99.9 to 5.105.0.
- [Release notes](https://github.com/webpack/webpack/releases)
- [Changelog](https://github.com/webpack/webpack/blob/main/CHANGELOG.md)
- [Commits](https://github.com/webpack/webpack/compare/v5.99.9...v5.105.0)

---
updated-dependencies:
- dependency-name: webpack
  dependency-version: 5.105.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-07 21:43:51 -05:00
RickyRister
a096a0e3bb
[VDE] Reduce spacing in deck view toolbar (#6591) 2026-02-07 21:43:41 -05:00
BruebachL
d410078673
Refresh chat view colors on theme changed. (#6581)
Took 35 minutes

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-01-28 21:54:11 +01:00
BruebachL
bf5891a910
[ThemeManager] Proper Fusion Palettes (#6580) 2026-01-28 15:36:28 -05:00
RickyRister
c7249dfbd9
[PrintingSelector] Don't change font size (#6573)
* [PrintingSelector] Don't change font size

* remove connection to slider

* update comments
2026-01-28 09:39:54 -08:00
RickyRister
1b29e0bfa8
[PrintingSelector] Reduce spacing (#6574)
* [PrintingSelector] Reduce spacing

* align top

* reduce spacing in flowLayout
2026-01-27 20:50:46 -05:00
RickyRister
5cc5767c87
[CardInfoPictureWidget] Refactor constant fields to static const (#6575)
* [CardInfoPictureWidget] Refactor constant fields to static const

* rename constants

* reformat

* comment out unused
2026-01-27 20:50:19 -05:00
RickyRister
165c4ddd2a
[PrintingSelector] Properly clamp text size to picture on load (#6576) 2026-01-27 20:49:54 -05:00
Rob Blanckaert
7b64970e97
[App] Add Fusion Theme (#6577)
Adds a new default theme that causes the QT Fusion theme to be
selected. This theme looks a bit nicer than 'Windows' and supports
both light and dark mode out of the box.
2026-01-27 20:49:40 -05:00
ebbit1q
5309dd17be
fix typo (#6572) 2026-01-25 20:44:03 -05:00
transifex-integration[bot]
364470b3c8
Updates for project Cockatrice and language en_US (#6543)
* Translate oracle/oracle_en@source.ts in en_US

100% translated source file: 'oracle/oracle_en@source.ts'
on 'en_US'.

* Translate cockatrice_en@source.ts in en_US

100% translated source file: 'cockatrice_en@source.ts'
on 'en_US'.

---------

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2026-01-25 16:51:03 -05:00
transifex-integration[bot]
915da79cad
Translate cockatrice/cockatrice_en@source.ts in de (#6541)
100% translated source file: 'cockatrice/cockatrice_en@source.ts'
on 'de'.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2026-01-25 16:50:40 -05:00
BruebachL
630c71f123
[VDE] Insert at correct index onDataChanged() instead of just appending. (#6556)
Took 25 minutes

Took 3 seconds

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-01-25 16:06:06 -05:00
ebbit1q
0b4e7be596
followup to #6563 (#6569)
* mess with the font rendering of the home screen until it works

* add more fonts

* increase font weight

* fix outline on the text
2026-01-25 16:05:53 -05:00
ebbit1q
303bd8b607
detect recursive redirects (#6570)
* detect recursive redirects

* handle failure with normal failure handler
2026-01-25 16:05:19 -05:00
RickyRister
c02cf5e89e
[VDE] Fix crash from alt-click when card has unknown set (#6566) 2026-01-25 01:36:10 -08:00
ebbit1q
92f02fa4ee
mess with the font rendering of the home screen until it works (#6563)
* mess with the font rendering of the home screen until it works

* add more fonts
2026-01-25 01:37:19 -05:00
ebbit1q
49e6cf95c4
fix package description (#6565) 2026-01-24 22:34:53 -05:00
ebbit1q
8a126263a9
do not ignore return value of opening log in servatrice when rotating (#6564) 2026-01-24 15:05:26 -05:00
ebbit1q
afdb385770
move returning cards to server_game (#6561)
lock the game mutex instead of player mutex when returning cards
2026-01-24 12:54:29 -05:00
tooomm
5b8897231d
CI: Include submodules in repo checkout (Docs deployment) (#6557)
* Enable submodule checkout in documentation build

* Change submodule checkout to recursive
2026-01-24 13:24:19 +01:00
BruebachL
ffc55aff10
[VDE] Add shortcut to increment cards [Alt + LMB] (#6555)
Took 13 minutes

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-01-24 11:28:06 +01:00
tooomm
2b372c14e4
Docs: Use doxygen-awesome-css theme (#6512)
* add doxygen-theme-css submodule

* enable theme and disable not needed code references

* hide nav sync button

* css and cleanup

* Move comments to dedicated README to not fail config check
2026-01-24 11:22:43 +01:00
BruebachL
12b5525a2d
[TabArchidekt] Cleaner filters, infinite scrolling, and a "go back button" (#6545)
* [TabArchidekt] Cleaner filters, infinite scrolling, and a "go back button"

Took 46 minutes

Took 5 seconds

* Fix infinite scroll triggering in detail view.

Took 25 minutes

Took 3 seconds

* Use setLabelText() so it's white

Took 2 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-01-24 11:21:12 +01:00
RickyRister
3c48d92663
[DeckEditor] Show info in PrintingSelector dock when override printings enabled (#6554)
* don't hide printing selector dock

* extract warning message to separate file

* create printing disabled info widget
2026-01-24 02:20:16 -08:00
BruebachL
948ec9e042
[VDE] Accurately represent card amounts (#6547) 2026-01-23 08:47:08 -05:00
RickyRister
5a274fdbed
[DeckListModel] Mark all cards in token zone as legal (#6551) 2026-01-23 02:35:36 -08:00
RickyRister
bfeb3a7ca9
[DeckListModel] Refactor to use forEachCard in legality check (#6550) 2026-01-23 01:05:25 -08:00
RickyRister
d363ec5154
[VDS] Fix crash when tab is opened before card database is loaded (#6553) 2026-01-23 09:58:46 +01:00
RickyRister
999733fc0f
[VDE] Fix right click to remove card not working (#6549)
* fix typo

* fix crash
2026-01-23 09:18:05 +01:00
RickyRister
8d274c1924
[DeckListModel] Correctly refresh legality on add card (#6537) 2026-01-22 23:34:24 -05:00
RickyRister
2e1a0bec93
[CardInfo] Refactor: add getLegalityProp method (#6536) 2026-01-22 23:33:21 -05:00
RickyRister
39ddaa0c35
[VDS] Reload deck on hover if file has been modified since last load (#6507)
* add reload to DeckLoader

* [VDS] Reload deck on hover if file has been modified since last load

* fix version incompatibility
2026-01-22 23:31:39 -05:00
RickyRister
d9b9c79112
[VDS] Add option to hide color identity (#6533) 2026-01-19 00:38:48 -08:00
RickyRister
485d5a8b48
[DeckListModel] Fix exception precedence in legality check (#6535) 2026-01-19 00:28:13 -08:00
RickyRister
f7e71a0868
[DeckList] Add optional restrictToZone param to getZoneNodes (#6534) 2026-01-19 00:27:58 -08:00
RickyRister
af2995ba96
[VDS] Ignore tokens when calculating color identity (#6532) 2026-01-18 20:51:57 -05:00
BruebachL
f7ffcc58fe
[Sample hand widget] Create container widget before declaring it as parent (#6530) 2026-01-17 12:18:13 -05:00
RickyRister
792f077071
[VDE] Use splitter in sample hand widget (#6528)
* [VDE] Use splitter in sample hand widget

* remove unused code
2026-01-16 17:23:41 -08:00
RickyRister
9c07c7a963
[TabGame] Automatically sync view menu actions (#6529) 2026-01-16 17:22:48 -08:00
RickyRister
d579c82cb9
[DeckLoader] Make save/load methods static (#6476)
* const

* [DeckLoader] make methods static

* use static methods

* add docs

* add docs
2026-01-16 13:20:36 -05:00
RickyRister
c7c7bf550a
[TabGame] Don't create replay dock if not replay tab (#6527)
* [TabGame] Don't create replay dock if not replay tab

* use replayDock to determine if replay tab

* null check replayManager in dtor
2026-01-16 09:45:10 -08:00
RickyRister
84483c56d7
[TabDeckEditor] Generalize visibility filter and extract it to a separate file (#6526)
* create class

* use new class in old code
2026-01-16 10:12:46 -05:00
RickyRister
1b71519ec6
[VDE] Make sample hand widget look nicer (#6525) 2026-01-16 10:11:39 -05:00
RickyRister
154b9ace92
[TabDeckEditor] Move cardDatabase dock action to top of menu (#6523) 2026-01-16 10:10:36 -05:00
RickyRister
93f0715d02
[TabDeckEditor] Save cardDatabase dock size in settings (#6524) 2026-01-15 21:54:50 -05:00
RickyRister
57e6c91689
[TabDeckEditor] Automatically sync view menu actions (#6522) 2026-01-15 21:05:19 -05:00
github-actions[bot]
6213ccff48
Update translation files (#6521)
Co-authored-by: github-actions <github-actions@github.com>
2026-01-15 06:53:55 +01:00
BruebachL
c075deeb2d
[Placeholder images] Update color. (#6519)
Took 19 minutes

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-01-14 14:47:27 +01:00
BruebachL
29f60c4a67
[VDE] Placeholder image for deck view if deck is empty (#6516)
* [VDE] A stab at things

Took 14 minutes

Took 10 minutes

Took 5 minutes

Took 4 minutes


Took 41 seconds

Took 10 minutes

Took 3 minutes

* [VDE] Use placeholder image for deck view if deck is empty.

Took 15 minutes

Took 9 seconds

Took 5 seconds

* Sort CMakeList correctly.

Took 35 seconds

Took 23 seconds

* Visibility updates got lost in the rebase.

Took 7 minutes

* Same treatment for printing selector.

Took 42 minutes

* Actually add file.

Took 4 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-01-14 14:41:54 +01:00
RickyRister
c553e15036
[TabDeckEditor] Fix bug in #6499 causing view menu actions to sometimes not work (#6518)
* remove a special case

* fix
2026-01-14 14:20:12 +01:00
BruebachL
a4eef648bc
[VDD] Move main type and format filter to quick settings (#6511)
* [VDD] Reorder quick filters

Took 1 hour 10 minutes

Took 5 seconds


Took 49 seconds

* [VDD] Use Font Awesome Icons

Took 49 minutes

Took 5 seconds

* [VDD] Shuffle some widgets around, label things.

Took 31 minutes

Took 5 seconds

* Change buttons to be push rather than toggle.

Took 17 minutes

Took 9 seconds

* Reduce margins, retranslate button texts.

Took 15 minutes

Took 9 seconds

* Actually do it, don't commit the commented out testing version lol

Took 3 minutes

* Start sets in include, correct subtype include/exact match logic.

Took 12 minutes

* Block sync.

Took 16 minutes

Took 8 seconds

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-01-14 11:56:09 +01:00
RickyRister
47720ff286
[ColorIdentityWidget] Refactor (#6506)
* [ColorIdentityWidget] Refactor and add setter

* rename manaCost field

* nvm, just refactor for now

* use QtUtils

* move clearLayout into populate

* add back cardInfo constructor
2026-01-14 02:41:03 -08:00
BruebachL
289b139be9
[DeckAnalytics] Enforce WUBRGC ordering for analytics. (#6509)
* [DeckAnalytics] Enforce WUBRGC ordering for analytics.

Took 6 minutes

Took 7 seconds

* Include QSet

Took 51 seconds

* Move include out of namespace.

Took 6 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-01-14 11:25:45 +01:00
RickyRister
21d60ec3f1
Reduce padding in settings popup (#6504)
* Reduce padding in settings popup

* reduce padding in PrintingSelectors settings

* reduce padding in one of the VDD filter settings
2026-01-14 01:49:07 -08:00
BruebachL
ed1115f4c0
[HomeTab] Add setting to display card info in bottom right for non-theme backgrounds (#6513)
* [HomeTab] Add setting to display card info in bottom right for non-theme backgrounds

Took 43 minutes

Took 9 seconds

* [HomeTab] Also hide shuffle frequency setting on theme background source.

Took 3 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-01-14 10:05:45 +01:00
BruebachL
cc5e2ab10a
[VDE] Change sort quick settings button icon from gear to sort arrow (#6514)
* [VDE] Change sort quick settings button icon from gear to sort arrow

Took 12 minutes

* Actually include the icon.

Took 4 minutes

Took 13 seconds

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-01-14 10:05:38 +01:00
RickyRister
b19312be70
[TabDeckEditor] Consolidate dockWidget management (#6499) 2026-01-14 09:48:26 +01:00
BruebachL
a0d1359860
[VDE] Minor cleanups, possibly fullscreen width-lock fix (#6438)
* Refactor some constructor things to their own methods.

* Saner size policies, no manual resize management.


Took 15 seconds

Took 23 seconds

* VDE doesn't need to manually resize either.

Took 6 minutes

* Add plate comments and re-order .cpp to be more structured.

Took 9 minutes

Took 30 seconds

* Add plate comments and re-order DeckCardZoneDisplay.cpp to be more structured

Took 7 minutes

Took 5 seconds

* Add plate comments and re-order CardGroupDisplayWidget.cpp to be more structured

Took 7 minutes

Took 4 minutes

* Include declaration.

Took 3 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-01-13 09:08:03 +01:00
transifex-integration[bot]
52547bbfe8
Updates for project Cockatrice and language de (#6508)
* Translate oracle/oracle_en@source.ts in de

100% translated source file: 'oracle/oracle_en@source.ts'
on 'de'.

* Translate oracle/oracle_en@source.ts in de

100% translated source file: 'oracle/oracle_en@source.ts'
on 'de'.

---------

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2026-01-11 14:41:16 +01:00
BruebachL
9ab398f08d
[Doxygen] Add documentation for beta channel. (#6510)
* [Doxygen] Add documentation for beta channel.

Took 36 minutes

* [Doxygen] Use default theme.

Took 13 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-01-11 12:48:31 +01:00
RickyRister
0deaa9d9b4
[DeckEditor] Don't change widget focus when adding card (#6503) 2026-01-09 18:27:54 -08:00
RickyRister
7c7755b61d
[VDE] Fix crash vy adding null check for card in PrintingSelector (#6500) 2026-01-06 22:41:40 -08:00
github-actions[bot]
6340c4a6b7
Update translation source strings (#6465) 2026-01-06 19:35:53 +01:00
RickyRister
0a2fdb05ad
[VDS] Try to fix memory leak by properly parenting widgets (#6498)
* [VDS] Try to fix memory leak by properly parenting widgets

* format
2026-01-06 11:42:35 +01:00
dependabot[bot]
b86853b65c
Bump actions/cache from 4 to 5 (#6496) 2026-01-05 21:18:21 +01:00
RickyRister
192dac0396
[DeckListModel] Consolidate methods and signals for card change (#6466) 2026-01-05 18:28:59 +01:00
RickyRister
85c9d8a9ff
[DeckEditor] Fix tokens being added to maindeck (#6495) 2026-01-05 01:18:38 -08:00
RickyRister
ee2699413c
[TabDeckEditor] Make card database a dock widget (#6472)
* [TabDeckEditor] Make card database a dock widget

* delete eventFilter implementation in abstract
2026-01-05 00:06:22 -08:00
RickyRister
d50297bbe6
[AnalyticsPanel] Use cogwheel icon for configure button (#6494) 2026-01-05 00:03:22 -08:00
RickyRister
489ce416c3
[VDS] Add search query option for comments (#6477) 2026-01-05 08:31:10 +01:00
RickyRister
731c487ccb
[ServerGame] null check participant in getPlayer (#6493) 2026-01-05 01:43:40 -05:00
RickyRister
2d5e8deb75
[Server_AbstractParticipant] Rename bool getters (#6492)
* [Server_AbstractParticipant] Rename bool getters

* reformat
2026-01-05 00:34:32 -05:00
RickyRister
746f2af044
[DeckListModel] optimize by iterating over cardNodes instead of ExactCards (#6485)
* [DeckListModel] optimize by iterating over cardNodes instead of ExactCards

* fix build failure

* another optimization

* fix build failure
2026-01-03 19:19:04 -08:00
RickyRister
f16c552d97
[PrintingSelector] Don't refresh display if "bump cards to top" is off (#6486) 2026-01-04 01:08:39 +01:00
Bruno Alexandre Rosa
72a85b58cf
ci: make fat qt libs thin (#6281)
* ci: strip fat qt binaries

* parallelize

* cache thin qt

* print libs

* change qt install dir in the action

* move qt install logic to separate job

* lookup only

* debug: show contents of QTDIR

* enableCrossOsArchive also when saving

* check one dir up

* change install dir

* keep debugging

* try deleting cache

* force delete cache

* pass gh_token

* pass missing params

* use api

* change cache key, disable cross os archive

* move job directly to steps

* add comments

* set cache param directly

* address comments

* fixup

* Update .ci/thin_macos_qtlib.sh

* resolve qt version

* move resolution to separate script

* use single line for run:

* improve error handling in new scripts

---------

Co-authored-by: ebbit1q <ebbit1q@gmail.com>
2026-01-04 01:00:05 +01:00
transifex-integration[bot]
b88a98b09a
Translate cockatrice/cockatrice_en@source.ts in fr (#6488)
100% translated source file: 'cockatrice/cockatrice_en@source.ts'
on 'fr'.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2026-01-03 18:58:59 +01:00
RickyRister
4fbb9d9682
[PrintingSelector] optimize amount calculation (#6478) 2026-01-03 01:04:56 -08:00
RickyRister
84aefda486
[DeckListModel] add getCardNodes method (#6484)
* [DeckListModel] add getCardNodes method

* Update one usage
2026-01-02 18:55:27 -08:00
BruebachL
73cc0541f5
[Game] Add shortcuts for same size and hand size - 1 mulligans (#6483)
Took 21 minutes

Took 3 seconds

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-01-03 00:03:11 +01:00
transifex-integration[bot]
bcf3939fee
Translate cockatrice/cockatrice_en@source.ts in fr (#6480) 2026-01-02 16:26:16 +01:00
tooomm
2e6f1128bb
Docs: Fix search help rendering and open external link in new tab (#6440)
* Use HTML link to open webpage in new tab

* Fix rendering for doxygen
2026-01-02 14:38:25 +01:00
RickyRister
bbd8671e6e
[DeckDockWidget] Fix VDE crash due to not mapping proxy index (#6479) 2026-01-02 14:32:22 +01:00
RickyRister
84e6907fa9
[DeckList] Store sideboardPlans by value to fix crash (#6475) 2026-01-02 09:10:41 +01:00
RickyRister
93a4647b04
[DeckList] move SideboardPlan into separate file (#6474) 2026-01-01 16:24:47 -08:00
BruebachL
c1f93b37ab
[TabRoom] Add a setting to hide the new filter toolbar (#6469)
* [TabRoom] Add a setting to hide the new filter toolbar

Took 56 minutes

Took 4 seconds

* Proper macro.

Took 5 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-01-01 14:49:09 +01:00
BruebachL
e7a1f86cbb
[View Zone Widget] Properly calculate titleBar close rect on Windows. (#6468) 2026-01-01 14:17:34 +01:00
RickyRister
987fe9c9e2
[DeckDockWidget] clean up grouping and format sync (#6467)
* [DeckDockWidget] clean up grouping and format sync

* refresh legalities in rebuildTree

* extract applyActiveGroupCriteria

* Fix build failure
2025-12-31 23:35:43 -08:00
BruebachL
df9a8b2272
[VDE] Deck Analytics Widgets overhaul (#6463)
* [VDE] Deck Analytics Widgets overhaul

Took 2 minutes

Took 3 minutes

Took 3 minutes

* Qt5 version guards.

Took 33 minutes


Took 3 seconds

* Include QtMath

Took 3 minutes

Took 8 seconds

* Use getCards()

Took 4 minutes

* Non pointer stuff

Took 52 seconds

* Add a newline to the tooltip

Took 2 minutes

Took 27 seconds

* Fix build failure on macOS 15

* Rename some things.

Took 17 minutes

Took 11 seconds


Took 18 seconds

* Address overloads, fix default configuration.

Took 1 hour 9 minutes

Took 8 seconds

* Fix mana curve default config.

Took 4 minutes

* Namespace to Qt libs

Took 5 minutes

* Selection overlay is transparent for mouse events.

Took 2 minutes

* Brace initialize.

Took 8 minutes

* Debian 11.

Took 5 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
Co-authored-by: RickyRister <ricky.rister.wang@gmail.com>
2025-12-31 19:45:49 +01:00
tooomm
36d8280765
Readme: Reorder Contribute section (#6435)
* Reorder contribute section

* fix

* Wording updates in related section
2025-12-31 17:57:19 +01:00
tooomm
28c800dd37
Docs: Add & link xsd schema information (#6439)
* Add xsd scheme information

* further references as list
2025-12-31 17:56:24 +01:00
ebbit1q
9f90de2242
change the release channel based on version string (#6447)
* change the release channel based on version string

* Apply suggestions from code review

* format
2025-12-31 17:55:31 +01:00
RickyRister
b2dd8eed3f
[TabDeckEditor] Create class to centralize deck state (#6459)
* create new file

* use QSharedPointer in DeckListModel

* [TabDeckEditor] Create class to centralize deck state

* delete method

* update docs
2025-12-31 17:54:47 +01:00
Alex Okonechnikov
0085015ebe
manual drag override (#6461)
* manual drag override

* fix styles

* pr comments

* better close button rect calc
2025-12-31 17:48:30 +01:00
RickyRister
db3bdb586b
[CardInfo] clean up signatures (#6462) 2025-12-31 04:13:32 -08:00
RickyRister
d722b2569c
[DeckListModel] Refactor: general code cleanup (#6460)
* change one usage

* move method

* move format check code

* make group criteria method static

* move method

* make method private

* more comments
2025-12-31 12:01:49 +01:00
RickyRister
968be8a06f
Fix bug with next/prev buttons in PrintingSelector (#6453)
* Hacky fix and debug messages

* remove debug

* add todo
2025-12-31 12:00:23 +01:00
RickyRister
daa7db7ce3
[DeckDockWidget] Fix tree unexpanding when changing group by (#6458) 2025-12-29 22:43:34 -08:00
RickyRister
cb2cf31cec
[DeckListModel] Clean up recursive updates (#6457) 2025-12-29 22:13:34 -08:00
ebbit1q
ce4a3bf118
compile in debug mode on ubuntu 22.04 (#6418)
* compile in debug mode on ubuntu 22.04

* Update card_info_display_widget.cpp

Use c++ instead of c-style cast

---------

Co-authored-by: BruebachL <44814898+BruebachL@users.noreply.github.com>
2025-12-30 00:16:02 -05:00
Bruno Alexandre Rosa
9d0bb0d51a
fix: manage ccache caches manually on macos (#6449)
* fix: manage ccache caches manually on macos

* install ccache

* fix issues shown by bugbot

* readd cache size limit
2025-12-29 17:21:59 +01:00
RickyRister
296866a675
[DeckListModel] Refactor api for offset count (#6454) 2025-12-29 17:19:03 +01:00
RickyRister
96c82a0377
[Refactor] Clean up some PrintingSelector widgets (#6451)
* remove currentZone from PrintingSelector

* don't store constructor args in fields if they're just passed

* simplify some methods

* refactor

* clean up initializeFormats

* more refactoring in CardAmountWidget
2025-12-29 12:03:44 +01:00
RickyRister
ca3f6bba02
[Refactor] Move prev/next card logic out of PrintingSelector (#6450) 2025-12-26 13:29:35 +01:00
RickyRister
70f9982c29
Bump minimum Qt version from 5.8 to 5.15 (#6442)
* Bump minimum Qt version from 5.8 to 5.15

* remove version check

* remove version checks
2025-12-23 09:58:23 -08:00
ebbit1q
521046fb09
Hashing tests (#5026)
* add deck hashing tests

* format

* fix header

* fix cmakelists

* fix test

* add 5 second timeout to test

let the optimising begin

* expand tests

* remove debug message

* manually format

* I installed cmake format from the aur

* use decklist library

* format
2025-12-23 17:48:10 +01:00
RickyRister
421d6b334a
[DeckDockWidget] Correctly handle auto-expanding tree (#6446)
* move method

* remove expandAll calls

* update recursiveExpand

* Refactor DeckModel access

* [DeckDockWidget] Correctly handle auto-expand
2025-12-23 16:21:47 +01:00
BruebachL
e7af1bbec9
[EDHRec] New layout for commander details (#6405)
* Stuff

Took 22 minutes

* New layout for commander details.

Took 1 hour 18 minutes

* Update plate to encompass everything, update font sizes.

Took 10 minutes

* Include map.

Took 2 minutes

* Include QSet

Took 5 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2025-12-23 16:00:07 +01:00
RickyRister
01e8e4d589
[DeckDockWidget] Fix swap not auto-expanding tree (#6443) 2025-12-23 15:45:27 +01:00
RickyRister
be17ee1902
[DeckListModel] Refactor to use column num constants (#6441) 2025-12-23 06:07:39 -08:00
dependabot[bot]
e557ae0f2a
Bump actions/upload-artifact from 5 to 6 (#6445) 2025-12-22 18:09:50 +01:00
RickyRister
e80f13b78e
[DeckDockWidget] Refactor to move down some methods in AbstractTabDeckEditor (#6444)
* move actSwapCard down

* rename method

* move actAddCard down
2025-12-22 05:48:55 -08:00
RickyRister
c12f4e9d2a
[DeckListModel] remove more access to underlying decklist for iteration (#6436)
* [DeckListModel] remove more access to underlying decklist for iteration

* remove one last direct iteration of decklist
2025-12-21 16:19:57 -08:00
RickyRister
a0f977e80c
[DeckList] refactor: pass DeckList by const ref (#6437)
* [DeckList] refactor: pass DeckList by const ref

* Change getDeckList to return a const ref
2025-12-21 16:19:33 -08:00
tooomm
73a90bdf38
Doxygen: Add bullet points to subpages lists & link webpage on welcome page (#6377)
* add bullet points to subpages

* Link to webpage from welcome page

* Add bullet points to subpages

* grouping

* Add TODO note to card database documentation

Added a TODO note for future updates.

* Fix GH alerts commands to be doxygen compatible
2025-12-20 17:46:13 +01:00
BruebachL
7f1d891e26
[Deprecation] Remove DBConverter from sources. (#6431)
Took 10 minutes

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2025-12-20 15:25:13 +01:00
RickyRister
d6db21419c
[Refactor] Pass around LoadedDeck instead of DeckLoader (#6422) 2025-12-20 13:39:00 +01:00
RickyRister
367507e054
[DeckListModel] Refactor: Don't access underlying decklist for iteration (#6427)
* [DeckListModel] Refactor: Don't access underlying decklist for iteration

* add docs

* extract method
2025-12-20 13:25:30 +01:00
BruebachL
715ee1d6fe
Revert "Enable internal documentation in Doxyfile (#6432)" (#6433)
This reverts commit ad06a81765.
2025-12-19 23:55:19 +01:00
tooomm
ad06a81765
Enable internal documentation in Doxyfile (#6432) 2025-12-19 23:51:56 +01:00
BruebachL
ebb02b27b2
[Card DB] Properly pass along set priority controller to parsers (#6430)
* [Card DB] Properly pass along set priority controller to parsers

Took 16 minutes

Took 35 seconds

* More adjustments.

Took 13 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2025-12-18 09:01:06 +01:00
BruebachL
d47dc35885
[TabArchidekt] Set game format when importing (#6416)
* [TabArchidekt] Set game format when importing

Took 5 minutes

* Move formats to file.

Took 9 minutes

Took 4 seconds

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2025-12-16 13:39:19 +01:00
BruebachL
41aca8467a
[CardInfoPicture] Defer enlargedPixmap creation until needed (#6426)
* [CardInfoPicture] Defer enlargedPixmap creation until needed

Took 4 minutes


Took 1 minute

* Disregard const_cast

Took 2 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2025-12-16 13:12:51 +01:00
BruebachL
cd44392866
Static helpers. (#6425)
Took 2 minutes


Took 29 seconds

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2025-12-16 13:03:14 +01:00
dependabot[bot]
64bb5355ff
Bump peter-evans/create-pull-request from 7 to 8 (#6423) 2025-12-15 23:38:49 +01:00
dependabot[bot]
1198db8891
Bump actions/cache from 4 to 5 (#6424) 2025-12-15 23:35:18 +01:00
tooomm
9471adb4f7
Add repo activity with top contributors acknowledgment (#6420) 2025-12-15 22:55:57 +01:00
RickyRister
b29909bdbe
[DeckList] Refactor: Create class to RAII underlying tree (#6412)
* [DeckList] Create class to RAII underlying tree

* Update usages

* fixes after rebase

* update docs
2025-12-14 15:56:58 -08:00
RickyRister
589e9a15a6
[DeckFilterString] Add search query for format (#6414)
* [DeckFilterString] Rename file search expression

* [DeckFilterString] Add search query for format
2025-12-15 00:14:19 +01:00
RickyRister
c218a66bcd
[DeckFilterString] Rename file search expression (#6413) 2025-12-15 00:14:11 +01:00
tooomm
8485bbe575
Docs: Use Doxygen \todo comments (#6421)
* format todo comments for doxygen

* Mention Todo List & link search
2025-12-15 00:13:16 +01:00
RickyRister
5d9d7d3aa5
[DeckLoader] remove unused private methods (#6417) 2025-12-14 14:26:06 -08:00
BruebachL
ccdda39e78
Deck format legality checker (#6166)
* Deck legality checker.

Took 51 seconds

Took 1 minute

Took 1 minute

Took 5 minutes

Took 3 minutes

* Adjust format parsing.

Took 8 minutes


Took 3 seconds

* toString() the xmlName

Took 4 minutes

* more toStrings()

Took 5 minutes

* Comments

Took 3 minutes

* Layout

Took 2 minutes

* Layout part 2: Electric boogaloo

Took 59 seconds

* Update cockatrice/src/interface/widgets/visual_database_display/visual_database_display_format_legality_filter_widget.cpp

Co-authored-by: RickyRister <42636155+RickyRister@users.noreply.github.com>

* Move layout.

Took 4 minutes


Took 10 seconds

* Emit deckModified

Took 6 minutes

* Fix qOverloads

Took 4 minutes

* Fix qOverloads

Took 12 seconds

* Consider text and name in a special way.

Took 11 minutes

* Adjust "Any number of" oracle text

Took 5 minutes

* Store allowedCounts by format

Took 15 minutes

Took 6 seconds

* Only restrict vintage.

Took 2 minutes

* Adjust for DBConverter.

Took 6 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
Co-authored-by: RickyRister <42636155+RickyRister@users.noreply.github.com>
2025-12-13 15:17:55 +01:00
RickyRister
2e2682aad4
[DeckList] Refactor and cleanup methods that iterate over nodes (#6407)
* remove helpers

* create getZoneNodes method

* replace direct calls to getRoot and forEachCard

* remove more non-const uses of forEachCard

* make node getter return const lists

* one more usage

* address comment

* address comment again

* fix hash

* fix hashes (for real this time)
2025-12-12 12:37:44 -08:00
BruebachL
a390c8ada7
[NetworkManager] Set Version string as user agent (#6411)
* [NetworkManager] Set Version string as user agent

Took 13 minutes

* Update in oracle.

Took 14 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2025-12-12 11:29:18 +01:00
BruebachL
da70344547
[VDD] Implement ExactMatch Name filter (#6409)
* [VDD] Implement ExactMatch Name filter

Took 7 minutes

Took 4 minutes

* Update cockatrice/src/interface/widgets/visual_database_display/visual_database_display_name_filter_widget.cpp

Co-authored-by: RickyRister <42636155+RickyRister@users.noreply.github.com>

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
Co-authored-by: RickyRister <42636155+RickyRister@users.noreply.github.com>
2025-12-12 03:57:18 +01:00
RickyRister
2b690f8c87
[DeckLoader] Extract cardNode functions to own file (#6408)
* [DeckLoader] Extract cardNode functions to own file

* update usages
2025-12-08 00:47:24 -08:00
RickyRister
c8b419888a
[DeckLoader] Extract LoadedDeck struct (#6406)
* [DeckLoader] Extract LoadedDeck struct

* update usages

* Move enum to separate namespace

* format

* format

* format
2025-12-07 15:03:52 +01:00
Lily Huang
d3302d521f
Fix flipped svg for donator/judge/vip (#6400) 2025-12-06 14:09:55 +01:00
tooomm
5c1bb27d5c
README: Add code docs + flathub repo links (#6384)
* Add code docs + flathub repo links

* Update README.md
2025-12-05 23:28:25 +01:00
BruebachL
dde36183ce
[VDE] Proper parent lookup syncs group-by box again (#6396)
* [VDE] Proper parent lookup syncs group-by box again

* [VDE] Proper lib inclusion.

* [VDE] Lint.
2025-12-05 23:27:27 +01:00
BruebachL
7c7f2dd8d5
[Doxygen] Logging (#6399)
* [Doxygen] Logging

Took 50 minutes

Took 36 seconds

* [Doxygen] Newline.

Took 2 minutes

* [Doxygen] Add another example.

Took 7 minutes

* [Doxygen] \note and \warning

Took 4 minutes

Took 32 seconds

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2025-12-05 18:42:45 +01:00
BruebachL
edb0a954e2
[GameInformation] Check for existence of room for create as judge checkbox (#6398) 2025-12-05 17:26:35 +01:00
BruebachL
0a239712dd
[VDD] Add search bar for filters. (#6389)
* [VDD] Add search bar for filters.

* Update cockatrice/src/interface/widgets/visual_database_display/visual_database_display_filter_save_load_widget.cpp

Co-authored-by: RickyRister <42636155+RickyRister@users.noreply.github.com>

---------

Co-authored-by: RickyRister <42636155+RickyRister@users.noreply.github.com>
2025-12-04 23:14:19 +01:00
RickyRister
95c3434205
[TagDisplayWidget] Refactor to just store tags and use signals (#6395) 2025-12-04 10:26:39 -08:00
RickyRister
f0be6972cc
[TagsDisplayWidget] cleanup refactor (#6394)
* Make fields private

* Move method to static

* clean up code

* move code
2025-12-04 09:40:24 -08:00
1933 changed files with 216718 additions and 139488 deletions

View file

@ -8,10 +8,13 @@ RUN pacman --sync --refresh --sysupgrade --needed --noconfirm \
gtest \
mariadb-libs \
ninja \
openssl \
protobuf \
qt6-base \
qt6-declarative \
qt6-imageformats \
qt6-multimedia \
qt6-shadertools \
qt6-svg \
qt6-tools \
qt6-translations \

View file

@ -1,26 +0,0 @@
FROM debian:11
RUN apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
build-essential \
ccache \
clang-format \
cmake \
file \
g++ \
git \
liblzma-dev \
libmariadb-dev-compat \
libprotobuf-dev \
libqt5multimedia5-plugins \
libqt5sql5-mysql \
libqt5svg5-dev \
libqt5websockets5-dev \
ninja-build \
protobuf-compiler \
qt5-image-formats-plugins \
qtmultimedia5-dev \
qttools5-dev \
qttools5-dev-tools \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*

View file

@ -15,11 +15,14 @@ RUN apt-get update && \
libprotobuf-dev \
libqt6multimedia6 \
libqt6sql6-mysql \
libssl-dev \
ninja-build \
protobuf-compiler \
qt6-declarative-dev \
qt6-image-formats-plugins \
qt6-l10n-tools \
qt6-multimedia-dev \
qt6-shadertools-dev \
qt6-svg-dev \
qt6-tools-dev \
qt6-tools-dev-tools \

View file

@ -16,11 +16,14 @@ RUN apt-get update && \
libprotobuf-dev \
libqt6multimedia6 \
libqt6sql6-mysql \
libssl-dev \
ninja-build \
protobuf-compiler \
qt6-declarative-dev \
qt6-image-formats-plugins \
qt6-l10n-tools \
qt6-multimedia-dev \
qt6-shadertools-dev \
qt6-svg-dev \
qt6-tools-dev \
qt6-tools-dev-tools \

View file

@ -7,8 +7,9 @@ RUN dnf install -y \
git \
mariadb-devel \
ninja-build \
openssl-devel \
protobuf-devel \
qt6-{qttools,qtsvg,qtmultimedia,qtwebsockets}-devel \
qt6-{qtdeclarative,qtmultimedia,qtshadertools,qtsvg,qttools,qtwebsockets}-devel \
qt6-qtimageformats \
rpm-build \
xz-devel \

View file

@ -1,4 +1,4 @@
FROM fedora:42
FROM fedora:44
RUN dnf install -y \
ccache \
@ -7,8 +7,9 @@ RUN dnf install -y \
git \
mariadb-devel \
ninja-build \
openssl-devel \
protobuf-devel \
qt6-{qttools,qtsvg,qtmultimedia,qtwebsockets}-devel \
qt6-{qtdeclarative,qtmultimedia,qtshadertools,qtsvg,qttools,qtwebsockets}-devel \
qt6-qtimageformats \
rpm-build \
xz-devel \

View file

@ -1,4 +1,4 @@
FROM debian:11
FROM debian:12
RUN apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
@ -11,11 +11,12 @@ RUN apt-get update && \
git \
libmariadb-dev-compat \
libprotobuf-dev \
libqt5sql5-mysql \
libqt5websockets5-dev \
libqt6sql6-mysql \
libssl-dev \
ninja-build \
protobuf-compiler \
qttools5-dev \
qttools5-dev-tools \
qt6-tools-dev \
qt6-tools-dev-tools \
qt6-websockets-dev \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*

View file

@ -15,11 +15,14 @@ RUN apt-get update && \
libprotobuf-dev \
libqt6multimedia6 \
libqt6sql6-mysql \
libssl-dev \
ninja-build \
protobuf-compiler \
qt6-declarative-dev \
qt6-image-formats-plugins \
qt6-l10n-tools \
qt6-multimedia-dev \
qt6-shadertools-dev \
qt6-svg-dev \
qt6-tools-dev \
qt6-tools-dev-tools \

View file

@ -1,13 +1,15 @@
FROM ubuntu:22.04
FROM ubuntu:26.04
RUN apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
build-essential \
ca-certificates \
ccache \
clang-format \
cmake \
file \
g++ \
gcovr \
git \
libgl-dev \
liblzma-dev \
@ -15,14 +17,17 @@ RUN apt-get update && \
libprotobuf-dev \
libqt6multimedia6 \
libqt6sql6-mysql \
libqt6svg6-dev \
libqt6websockets6-dev \
libssl-dev \
ninja-build \
protobuf-compiler \
qt6-declarative-dev \
qt6-image-formats-plugins \
qt6-l10n-tools \
qt6-multimedia-dev \
qt6-shadertools-dev \
qt6-svg-dev \
qt6-tools-dev \
qt6-tools-dev-tools \
qt6-websockets-dev \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*

View file

@ -8,12 +8,17 @@
# --suffix <suffix> renames package with this suffix, requires arg
# --server compiles servatrice
# --test runs tests
# --sanitize builds with AddressSanitizer and UndefinedBehaviorSanitizer, gcc/clang only
# --coverage instruments the test run for line coverage and prints a gcovr summary, gcc only
# --debug or --release sets the build type ie CMAKE_BUILD_TYPE
# --ccache [<size>] uses ccache and shows stats, optionally provide size
# --evict-ccache <age> runs ccache eviction based on given age after build
# --dir <dir> sets the name of the build dir, default is "build"
# --cmake-generator <generator> sets CMAKE_GENERATOR as used by cmake
# --target-macos-version <version> sets the min os version - only used for macOS builds
# uses env: BUILDTYPE MAKE_INSTALL MAKE_PACKAGE PACKAGE_TYPE PACKAGE_SUFFIX MAKE_SERVER MAKE_NO_CLIENT MAKE_TEST USE_CCACHE CCACHE_SIZE BUILD_DIR CMAKE_GENERATOR TARGET_MACOS_VERSION
# (correspond to args: --debug/--release --install --package <package type> --suffix <suffix> --server --test --ccache <ccache_size> --dir <dir>)
# uses env: BUILDTYPE MAKE_INSTALL MAKE_PACKAGE PACKAGE_TYPE PACKAGE_SUFFIX MAKE_SERVER MAKE_NO_CLIENT MAKE_TEST USE_CCACHE CCACHE_SIZE CCACHE_EVICTION_AGE BUILD_DIR CMAKE_GENERATOR TARGET_MACOS_VERSION MAKE_SANITIZE MAKE_COVERAGE
# (correspond to args: --debug/--release --install --package <package type> --suffix <suffix> --server --test --sanitize --coverage --ccache <ccache_size> --dir <dir>)
# exitcode: 1 for failure, 3 for invalid arguments
# Read arguments
@ -55,6 +60,14 @@ while [[ $# != 0 ]]; do
MAKE_TEST=1
shift
;;
'--sanitize')
MAKE_SANITIZE=1
shift
;;
'--coverage')
MAKE_COVERAGE=1
shift
;;
'--debug')
BUILDTYPE="Debug"
shift
@ -71,6 +84,15 @@ while [[ $# != 0 ]]; do
shift
fi
;;
'--evict-ccache')
shift
if [[ $# == 0 ]]; then
echo "::error file=$0::--evict-ccache expects an argument"
exit 3
fi
CCACHE_EVICTION_AGE=$1
shift
;;
'--vcpkg')
USE_VCPKG=1
shift
@ -84,6 +106,15 @@ while [[ $# != 0 ]]; do
BUILD_DIR="$1"
shift
;;
'--cmake-generator')
shift
if [[ $# == 0 ]]; then
echo "::error file=$0::--cmake-generator expects an argument"
exit 3
fi
export CMAKE_GENERATOR=$1
shift
;;
'--target-macos-version')
shift
if [[ $# == 0 ]]; then
@ -111,6 +142,15 @@ if [[ ! $BUILD_DIR ]]; then
BUILD_DIR="build"
fi
mkdir -p "$BUILD_DIR"
if [[ $MAKE_COVERAGE ]]; then
# Resolved while the working directory is still the source root. $0 is relative to the
# directory the script was invoked from, so after the cd below it no longer resolves, and
# under `set -e` the failed subshell would abort the build rather than just yielding an empty
# variable. Only the coverage report needs this, so only coverage pays for it.
SRC_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
fi
cd "$BUILD_DIR"
# Set minimum CMake Version
@ -122,13 +162,27 @@ if [[ $MAKE_SERVER ]]; then
flags+=("-DWITH_SERVER=1")
fi
if [[ $MAKE_NO_CLIENT ]]; then
flags+=("-DWITH_CLIENT=0" "-DWITH_ORACLE=0" "-DWITH_DBCONVERTER=0")
flags+=("-DWITH_CLIENT=0" "-DWITH_ORACLE=0")
fi
if [[ $MAKE_TEST ]]; then
flags+=("-DTEST=1")
fi
if [[ $MAKE_SANITIZE ]]; then
flags+=("-DENABLE_SANITIZERS=ON")
else
# The release-package step re-runs cmake in the same build dir, so an
# ENABLE_SANITIZERS=ON left over from the debug step would otherwise leak
# into the shipped binaries (libasan/libubsan Depends in the DEB).
flags+=("-DENABLE_SANITIZERS=OFF")
fi
if [[ $MAKE_COVERAGE ]]; then
flags+=("-DENABLE_COVERAGE=ON")
fi
if [[ $USE_CCACHE ]]; then
flags+=("-DUSE_CCACHE=1")
# PCH-aware caching is required or ccache refuses to cache any TU that
# consumes a precompiled header, silently recompiling everything on every run.
ccache --set-config sloppiness=pch_defines,time_macros
if [[ $CCACHE_SIZE ]]; then
# note, this setting persists after running the script
ccache --max-size "$CCACHE_SIZE"
@ -139,6 +193,7 @@ if [[ $PACKAGE_TYPE ]]; then
fi
if [[ $USE_VCPKG ]]; then
flags+=("-DUSE_VCPKG=1")
flags+=("-DVCPKG_INSTALL_OPTIONS=--x-abi-tools-use-exact-versions")
fi
# Add cmake --build flags
@ -156,6 +211,18 @@ function ccachestatsverbose() {
# Compile
if [[ $RUNNER_OS == macOS ]]; then
# QTDIR is needed for macOS since we actually only use the cached thin Qt binaries instead of the install-qt-action,
# which sets a few environment variables
if QTDIR=$(find "$GITHUB_WORKSPACE/Qt" -depth -maxdepth 2 -name macos -type d -print -quit); then
echo "found QTDIR at $QTDIR"
else
echo "could not find QTDIR!"
exit 2
fi
# the qtdir is located at Qt/[qtversion]/macos
# we use find to get the first subfolder with the name "macos"
# this works independent of the qt version as there should be only one version installed on the runner at a time
export QTDIR
if [[ $TARGET_MACOS_VERSION ]]; then
# CMAKE_OSX_DEPLOYMENT_TARGET is a vanilla cmake flag needed to compile to target macOS version
@ -171,7 +238,11 @@ if [[ $RUNNER_OS == macOS ]]; then
arch="x64"
fi
mkdir -p "$triplets_dir"
cp "../vcpkg/triplets/$arch-osx.cmake" "$triplet_file"
triplet_source="../vcpkg/triplets/$arch-osx.cmake"
if [[ ! -f "$triplet_source" ]]; then
triplet_source="../vcpkg/triplets/community/$arch-osx.cmake"
fi
cp "$triplet_source" "$triplet_file"
echo "set(VCPKG_CMAKE_SYSTEM_VERSION $TARGET_MACOS_VERSION)" >>"$triplet_file"
echo "set(VCPKG_OSX_DEPLOYMENT_TARGET $TARGET_MACOS_VERSION)" >>"$triplet_file"
flags+=("-DVCPKG_OVERLAY_TRIPLETS=$triplets_dir")
@ -239,14 +310,21 @@ cmake --build . "${buildflags[@]}"
echo "::endgroup::"
if [[ $USE_CCACHE ]]; then
if [[ $CCACHE_EVICTION_AGE ]]; then
echo "::group::evict ccache files older than $CCACHE_EVICTION_AGE"
ccache --evict-older-than "$CCACHE_EVICTION_AGE"
echo "::endgroup::"
fi
echo "::group::Show ccache stats again"
ccachestatsverbose
echo "::endgroup::"
elif [[ $CCACHE_EVICTION_AGE ]]; then
echo "::error file=$0::ccache eviction is enabled while ccache is disabled!"
fi
if [[ $RUNNER_OS == macOS ]]; then
echo "::group::Inspect Mach-O binaries"
for app in cockatrice oracle servatrice dbconverter; do
for app in cockatrice oracle servatrice; do
binary="$GITHUB_WORKSPACE/build/$app/$app.app/Contents/MacOS/$app"
echo "Inspecting $app..."
vtool -show-build "$binary"
@ -259,7 +337,50 @@ fi
if [[ $MAKE_TEST ]]; then
echo "::group::Run tests"
ctest -C "$BUILDTYPE" --output-on-failure
if [[ $MAKE_SANITIZE ]]; then
# detect_leaks=0: Qt and the sanitizers' own runtimes both leak at exit by design, and
# LeakSanitizer's default is aggressive enough to bury our real findings under hundreds
# of process-teardown reports. Malloc/free misuse and use-after-free are what ASan is for
# and they stay fully on.
# abort_on_error=1: a bad access leaves the process in an unusable state, so continuing
# only produces a second, more confusing report from the wreckage.
# UBSAN halt_on_error=1: without it UBSan prints "runtime error: ..." but the
# process exits 0 (recover mode), so gtest/ctest report a pass and the job
# stays green. Combined with -fno-sanitize-recover=undefined at compile time,
# the first finding aborts the test process with a non-zero exit.
export ASAN_OPTIONS="detect_leaks=0:abort_on_error=1:print_stacktrace=1:symbolize=1"
export UBSAN_OPTIONS="print_stacktrace=1:halt_on_error=1:symbolize=1"
fi
# Every TEST() is its own CTest entry, so the suite is process-spawn bound rather than CPU
# bound and -j recovers nearly all of the per-process overhead. The test binaries are safe to
# run concurrently: the only one that claims a fixed resource is single_instance_manager_test,
# which already scopes its local socket name to its own pid so parallel copies cannot collide.
ctest -C "$BUILDTYPE" --output-on-failure -j "${CTEST_JOBS:-4}"
echo "::endgroup::"
fi
if [[ $MAKE_COVERAGE ]]; then
# gcovr is not a build dependency and is not available everywhere, so a missing one is reported
# and skipped rather than failing an otherwise green build.
echo "::group::Coverage report"
GCOVR="$(command -v gcovr || true)"
if [[ -z $GCOVR ]]; then
echo "gcovr not found; install it to produce a coverage report"
else
"$GCOVR" --version
# This script configures with `cmake ..`, so the build dir it was started from is the object
# directory and the source root is one level up. Both are passed explicitly: gcovr otherwise
# walks the current directory and trips over in-tree build directories left by other tooling,
# whose stale .gcno files cannot be resolved. --config is named too, so the filters apply no
# matter what directory gcovr happens to start from.
GCOVR_ARGS=("." --root "$SRC_ROOT" --object-directory "." --config "$SRC_ROOT/gcovr.cfg")
"$GCOVR" "${GCOVR_ARGS[@]}" --txt --decisions
"$GCOVR" "${GCOVR_ARGS[@]}" --html-details coverage-details.html --html-title "Cockatrice line coverage"
"$GCOVR" "${GCOVR_ARGS[@]}" --lcov -o coverage.lcov
"$GCOVR" "${GCOVR_ARGS[@]}" --cobertura-pretty -o coverage.xml
"$GCOVR" "${GCOVR_ARGS[@]}" --json-summary-pretty -o coverage-summary.json
echo "Coverage artifacts written to coverage-details.html, coverage.lcov, coverage.xml and coverage-summary.json"
fi
echo "::endgroup::"
fi
@ -280,4 +401,153 @@ if [[ $MAKE_PACKAGE ]]; then
BUILD_DIR="$BUILD_DIR" .ci/name_build.sh "$PACKAGE_SUFFIX"
echo "::endgroup::"
fi
if [[ $RUNNER_OS == Windows ]]; then
echo "::group::Check installer for build-tree artifacts"
cd "$BUILD_DIR"
package="$(find . -maxdepth 1 -type f -name 'Cockatrice-*.exe' -print -quit)"
if [[ ! $package ]]; then
echo "::error file=$0::Could not find installer to inspect"
exit 1
fi
seven_zip="$(command -v 7z || true)"
if [[ ! $seven_zip ]]; then
seven_zip="/c/Program Files/7-Zip/7z.exe"
fi
if [[ ! -f $seven_zip ]]; then
echo "::warning file=$0::7-Zip not found, skipping installer content check"
else
echo "Inspecting $package"
# Fail the build if the installer contains any path left behind by the MSBuild or
# Qt AUTOMOC tooling (build-tree artifacts must live in the build dir, not the install)
if "$seven_zip" l "$package" |
grep -E "_autogen|\.dir[\\/]|\.tlog|(^|[\\/])x64[\\/]|(^|[\\/])\.qt[\\/]|(^|[\\/])\.qsb[\\/]|(^|[\\/])\.lupdate[\\/]|CMakeFiles"; then
echo "::error file=$0::Installer contains build-tree artifacts"
exit 1
fi
echo "Installer content is clean"
# Fail the build if the installer does not carry the runtime the
# applications link against - a file that must be there, or a DLL that must
# not be. The runtime DLLs reach the package by globbing every *.dll out of
# the build output directories, so anything that build produced can ride
# along and nothing else in the build objects to it: a Qt module the client
# links statically (Qt6Multimedia.dll, Qt6QuickWidgets.dll) can be left out,
# and the test framework's gtest.dll can be shipped. Either only surfaces on
# a user's machine after an update has already replaced their install, as
# "The code execution cannot proceed because Qt6Multimedia.dll was not
# found" or "abseil_dll.dll is either not designed to run on Windows or it
# contains an error". required-runtime.txt is written by the top level
# CMakeLists.txt from the same module lists the targets are built against
# plus the vcpkg bin directory the app-local copies come from.
if [[ ! -f required-runtime.txt ]]; then
echo "::error file=$0::required-runtime.txt not found in the build dir, cannot verify the runtime"
exit 1
fi
# 7-Zip writes CRLF on Windows, so the \r has to come off before any
# whole-line comparison below. Normalize separators to /, and strip the
# install-root prefixes an NSIS listing may carry, so the entries can be
# compared as exact relative paths. -xF keeps a '.' in a DLL name from
# being a wildcard; -i because that is how the installed tree behaves -
# the uninstaller clears both $INSTDIR\plugins and $INSTDIR\Plugins.
# The archive side and the manifest side have to agree on line endings.
# CMake writes required-runtime.txt with the platform's line ending and
# 7-Zip writes CRLF on Windows, so a trailing \r survives on $entry and
# defeats the whole-line match - every entry then reads as missing even
# though the listing holds it verbatim.
# [$] rather than \$ so the literal dollar is a bracket expression and the
# linter does not read these as unexpanded shell variables (SC2016).
installer_paths="$("$seven_zip" l -slt "$package" | tr -d '\r' |
sed -n 's/^Path = //p' |
sed -e 's|\\|/|g' -e 's|^[$]INSTDIR/||' -e 's|^[$]OUTDIR/||' -e 's|^[$]PLUGINSDIR/||' -e 's|^\./||' -e 's|^/||')"
manifest="$(tr -d '\r' <required-runtime.txt)"
missing=""
checked=0
while IFS= read -r entry; do
if [[ -z $entry || $entry == \#* ]]; then
continue
fi
checked=$((checked + 1))
if ! grep -qxiF "$entry" <<<"$installer_paths"; then
missing+=" $entry"
fi
done <<<"$manifest"
if [[ -n $missing ]]; then
echo "::error file=$0::Installer is missing required runtime files:$missing"
# Print both sides of the comparison. A mismatch here has twice been
# caused by the shape of 7-Zip's output rather than by a missing file,
# and a bare "missing" line cannot tell those apart.
echo "required-runtime.txt asked for $checked path(s); 7-Zip listed $(grep -c '' <<<"$installer_paths") path(s)"
# Indent with parameter expansion rather than sed 's/^/ /', which the
# linter reports as SC2001.
echo "--- required ---"
echo "${manifest//$'\n'/$'\n '}"
echo "--- first 40 paths 7-Zip lists in the installer ---"
listed_head="$(head -40 <<<"$installer_paths")"
echo "${listed_head//$'\n'/$'\n '}"
exit 1
fi
echo "Installer carries the Qt runtime of every module the three applications link ($checked files)"
# Now the other direction: a top level DLL in the package that the manifest
# does not name. The missing check above cannot see a runtime that was
# never expected.
#
# Two things are reported here and neither may fail the build.
#
# The test framework is a fact rather than a guess - no shipped executable
# links gtest or gmock, and the build output directories the "*.dll" glob
# reads are shared with the test targets, so gtest.dll and gmock.dll are
# one filter away from shipping in every release. Even so it is reported
# instead of enforced: a check that has never passed on a real package has
# not earned the right to block a release, and the first Windows run of an
# enforcing version is exactly when its own wrong assumptions show up.
#
# Everything else is expected to be there. required-runtime.txt names the
# Qt modules the three applications link, and the protobuf, Abseil,
# OpenSSL, liblzma, ffmpeg and Qt debug DLLs in the package come in on the
# build output glob instead, because nothing in the build declares them by
# name. So this list is a report of what the package carries, not a verdict.
# Read it when a dependency is added; do not fail on it.
forbidden=""
unexpected=""
while IFS= read -r entry; do
[[ -z $entry ]] && continue
# Top level only. A DLL under Plugins/ or a nested image format plugin is
# a plugin the platform loads by name, not a runtime the executable
# imports, and requiring one here would list all of them every run.
[[ $entry == */* ]] && continue
if grep -qxiF "$entry" <<<"$manifest"; then
continue
fi
# NSIS extracts its plugins to $PLUGINSDIR, which the path normalization
# above strips, so they arrive here looking like payload files. They are
# part of the installer, not of the installed tree.
if [[ $entry == [Nn][Ss][Dd][Ii][Aa][Ll][Oo][Gg][Ss].dll ||
$entry == [Nn][Ss][Ee][Xx][Ee][Cc].dll ||
$entry == [Ss][Yy][Ss][Tt][Ee][Mm].dll ||
$entry == [Uu][Ss][Ee][Rr][Ii][Nn][Ff][Oo].dll ||
$entry == [Ii][Nn][Ee][Tt].dll ]]; then
continue
fi
if [[ $entry == [Gg][Tt][Ee][Ss][Tt]* || $entry == [Gg][Mm][Oo][Cc][Kk]* ]]; then
forbidden+=" $entry"
else
unexpected+=" $entry"
fi
done < <(grep -i '\.dll$' <<<"$installer_paths")
if [[ -n $forbidden ]]; then
echo "::warning file=$0::Installer contains test framework DLLs that no shipped executable links:$forbidden"
echo "A build output directory shared with the test targets has leaked into the package."
fi
if [[ -n $unexpected ]]; then
echo "Top level DLLs in the package beyond the linked Qt modules:$unexpected"
echo "These arrive on the build output glob, not from required-runtime.txt, so they are reported and not required."
fi
if [[ -z $forbidden && -z $unexpected ]]; then
echo "Installer carries no runtime files beyond the linked Qt modules"
fi
fi
echo "::endgroup::"
fi
fi

View file

@ -3,17 +3,34 @@
# This script is to be used by the ci environment from the project root directory, do not use it from somewhere else.
# Creates or loads docker images to use in compilation, creates RUN function to start compilation on the docker image.
# <arg> sets the name of the docker image, these correspond to directories in .ci
#
# usage: source <script> <name> [--get] [--build] [--save] [--interactive] [--set-cache <location>]
# <name> sets the name of the docker image, these correspond to directories in .ci
# --get loads the image from a previously saved image cache, will build if no image is found
# --build builds the image from the Dockerfile in .ci/$NAME
# --save stores the image, if an image was loaded it will not be stored
# --interactive immediately starts the image interactively for debugging
# --set-cache <location> sets the location to cache the image or for ccache
#
# requires: docker
# uses env: NAME CACHE BUILD GET SAVE INTERACTIVE
# uses env: NAME IMAGE_SUFFIX CACHE BUILD GET SAVE INTERACTIVE
# (correspond to args: <name> --set-cache <cache> --build --get --save --interactive)
# sets env: RUN CCACHE_DIR IMAGE_NAME RUN_ARGS RUN_OPTS BUILD_SCRIPT
# exitcode: 1 for failure, 2 for missing dockerfile, 3 for invalid arguments
#
# IMAGE_SUFFIX is optional and only appended to the image name, never to NAME, so that a matrix
# leg can have its own image and image cache without needing its own directory under .ci. Two legs
# building and tagging the same image concurrently would race on `docker build --tag`.
# It is deliberately not called SUFFIX: compile.sh already uses SUFFIX for output file names, and
# a job-level SUFFIX would be silently overridden by those per-step env blocks.
#
# exported RUN function will run the BUILD_SCRIPT inside of the docker container.
# note that the docker container will not inherit any environment variables!
#
# usage: RUN [arguments for build script]
# roughly equivalent to `docker run $IMAGE_NAME bash $BUILD_SCRIPT $@`
# uses env: CCACHE_DIR IMAGE_NAME RUN_ARGS RUN_OPTS BUILD_SCRIPT
# exitcode: 3 for invalid arguments, returns the returncode of docker run
export BUILD_SCRIPT=".ci/compile.sh"
project_name="cockatrice"
@ -41,12 +58,17 @@ while [[ $# != 0 ]]; do
shift
;;
'--set-cache')
CACHE=$2
shift
if [[ $# == 0 ]]; then
echo "--set-cache expects an argument" >&2
exit 3
fi
CACHE=$1
shift
if ! [[ -d $CACHE ]]; then
echo "could not find cache path: $CACHE" >&2
return 3
fi
shift 2
;;
*)
if [[ ${1:0:1} == - ]]; then
@ -65,7 +87,7 @@ if ! [[ $NAME ]]; then
return 3
fi
export IMAGE_NAME="${project_name,,}_${NAME,,}" # lower case
export IMAGE_NAME="${project_name,,}_${NAME,,}${IMAGE_SUFFIX:+_${IMAGE_SUFFIX,,}}" # lower case
docker_dir=".ci/$NAME"
if ! [[ -r $docker_dir/Dockerfile ]]; then
@ -149,10 +171,11 @@ function RUN ()
args+=(--mount "type=bind,source=$CCACHE_DIR,target=/.ccache")
args+=(--env "CCACHE_DIR=/.ccache")
fi
if [[ -n "$CMAKE_GENERATOR" ]]; then
args+=(--env "CMAKE_GENERATOR=$CMAKE_GENERATOR")
if [[ $GITHUB_OUTPUT ]]; then
args+=(--mount "type=bind,source=$GITHUB_OUTPUT,target=/gh_output")
args+=(--env "GITHUB_OUTPUT=/gh_output")
fi
# shellcheck disable=2086
# shellcheck disable=2086
docker run "${args[@]}" $RUN_ARGS "$IMAGE_NAME" bash "$BUILD_SCRIPT" $RUN_OPTS "$@"
return $?
else

View file

@ -13,17 +13,9 @@ fi
# Check formatting using format.sh
echo "Checking your code using format.sh..."
diff="$(./format.sh --diff --cmake --shell --print-version --branch origin/master)"
./format.sh --color-diff --cmake --shell --print-version --branch origin/master
err=$?
sep="
----------
"
used_version="${diff%%"$sep"*}"
diff="${diff#*"$sep"}"
changes_to_make="${diff%%"$sep"*}"
files_to_edit="${diff#*"$sep"}"
case $err in
1)
cat <<EOM
@ -36,19 +28,10 @@ case $err in
*** Then commit and push those changes to this branch. ***
*** Check our CONTRIBUTING.md file for more details. ***
*** ***
*** Thank you ❤️ ***
*** Thank you ❤️ ***
*** ***
***********************************************************
Used version:
$used_version
Affected files:
$files_to_edit
The following changes should be made:
$changes_to_make
Exiting...
EOM
exit 2
@ -65,9 +48,6 @@ EOM
*** ***
***********************************************************
Used version:
$used_version
Exiting...
EOM
exit 0

View file

@ -2,7 +2,6 @@
# used by the ci to rename build artifacts
# renames the file to [original name][SUFFIX].[original extension]
# where SUFFIX is either available in the environment or as the first arg
# if MAKE_ZIP is set instead a zip is made
# expected to be run in the build directory unless BUILD_DIR is set
# adds output to GITHUB_OUTPUT
builddir="${BUILD_DIR:=.}"
@ -22,8 +21,8 @@ set -e
# find file
found="$(find "$builddir" -maxdepth 1 -type f -name "$findrx" -print -quit)"
path="${found%/*}" # remove all after last /
file="${found##*/}" # remove all before last /
path="${found%/*}" # remove all including first "/" from right side
file="${found##*/}" # remove all including last "/" from left side
if [[ ! $file ]]; then
echo "::error file=$0::could not find package"
exit 1
@ -35,21 +34,16 @@ if ! cd "$path"; then
fi
# set filename
name="${file%.*}" # remove all after last .
new_name="$name$SUFFIX."
if [[ $MAKE_ZIP ]]; then
filename="${new_name}zip"
echo "creating zip '$filename' from '$file'"
zip "$filename" "$file"
else
extension="${file##*.}" # remove all before last .
filename="$new_name$extension"
echo "renaming '$file' to '$filename'"
mv "$file" "$filename"
fi
name="${file%.*}" # remove all including first "." from right side
new_name="$name$SUFFIX"
extension="${file##*.}" # remove all including last "." from left side
filename="$new_name.$extension"
echo "renaming '$file' to '$filename'"
mv "$file" "$filename"
cd "$oldpwd"
relative_path="$path/$filename"
ls -l "$relative_path"
echo "path=$relative_path" >>"$GITHUB_OUTPUT"
echo "name=$filename" >>"$GITHUB_OUTPUT"
echo "name=$new_name" >>"$GITHUB_OUTPUT"
echo "fullname=$filename" >>"$GITHUB_OUTPUT"

View file

@ -89,6 +89,8 @@ else
echo "'$previous' to '$TAG' ($count commits)"
# --> is the markdown comment escape sequence, emojis are way better
generated_list="${generated_list//-->/→}"
# Escape & to preserve it from commit message into markdown output
generated_list="${generated_list//&/\\&}"
body="${body//--REPLACE-WITH-GENERATED-LIST--/$generated_list}"
body="${body//--REPLACE-WITH-COMMIT-COUNT--/$count}"
body="${body//--REPLACE-WITH-PREVIOUS-RELEASE-TAG--/$previous}"

View file

@ -10,24 +10,24 @@ Available pre-compiled binaries for installation:
<b>Windows</b>
• <kbd>Windows 10+</kbd>
• <kbd>Windows 7+</kbd>
<b>macOS</b>
• <kbd>macOS 15+</kbd> <sub><i>Sequoia</i></sub> <sub>Apple M</sub>
• <kbd>macOS 14+</kbd> <sub><i>Sonoma</i></sub> <sub>Apple M</sub>
• <kbd>macOS 13+</kbd> <sub><i>Ventura</i></sub> <sub>Intel</sub>
• <kbd>macOS 15+</kbd> <sub><i>Sequoia</i></sub>
• <kbd>macOS 14+</kbd> <sub><i>Sonoma</i></sub>
• <kbd>macOS 13+</kbd> <sub><i>Ventura (x86)</i></sub>
<b>Linux</b>
• <kbd>Ubuntu 26.04 LTS</kbd> <sub><i>Resolute Racoon</i></sub>
• <kbd>Ubuntu 24.04 LTS</kbd> <sub><i>Noble Numbat</i></sub>
• <kbd>Ubuntu 22.04 LTS</kbd> <sub><i>Jammy Jellyfish</i></sub>
• <kbd>Debian 13</kbd> <sub><i>Trixie</i></sub>
• <kbd>Debian 12</kbd> <sub><i>Bookworm</i></sub>
• <kbd>Debian 11</kbd> <sub><i>Bullseye</i></sub>
• <kbd>Fedora 44</kbd>
• <kbd>Fedora 43</kbd>
• <kbd>Fedora 42</kbd>
<sub>We are also packaged in <kbd>Arch Linux</kbd>'s <a href="https://archlinux.org/packages/extra/x86_64/cockatrice">official extra repository</a>, courtesy of @FFY00.</sub>
<sub>General Linux support is available via a <kbd>flatpak</kbd> package at <a href="https://flathub.org/apps/io.github.Cockatrice.cockatrice">Flathub</a>!</sub>
<sub>General Linux support is available via a <kbd>flatpak</kbd> package hosted at <a href="https://flathub.org/apps/io.github.Cockatrice.cockatrice">Flathub</a> (x86 & ARM)!</sub>
<sub>Thanks to courtesy of @FFY00, the app is also available in <kbd>Arch Linux</kbd>'s <a href="https://archlinux.org/packages/extra/x86_64/cockatrice">official extra repository</a>.</sub>
<sub>We maintain a <kbd>Docker</kbd> image for "Servatrice" in <a href="https://github.com/Cockatrice/Cockatrice/pkgs/container/servatrice">GHCR</a> (x86 & ARM). You can docker pull it or use our Docker Compose files!</sub>
</pre>
@ -83,7 +83,6 @@ Remove empty headers when done.
### Under the Hood
### Oracle
### Servatrice
### Webatrice
</details>

View file

@ -0,0 +1,49 @@
#!/bin/bash
# This script is used to resolve the latest patch version of Qt using aqtinstall.
# It interprets wildcards to get the latest patch version. E.g. "6.6.*" -> "6.6.3".
# This script is meant to be used by the ci enironment.
# It uses the runner's GITHUB_OUTPUT env variable.
# Usage example: .ci/resolve_latest_aqt_qt_version.sh "6.6.*"
qt_spec=$1
if [[ ! $qt_spec ]]; then
echo "usage: $0 [version]"
exit 2
fi
# If version is already specific (no wildcard), use it as-is
if [[ $qt_spec != *"*" ]]; then
echo "version $qt_spec is already resolved"
echo "version=$qt_spec" >> "$GITHUB_OUTPUT"
exit 0
fi
if ! hash aqt; then
echo "aqt could not be found, has aqtinstall been installed?"
exit 2
fi
# Resolve latest patch
if [[ $RUNNER_OS == macOS ]]; then
if ! qt_resolved=$(aqt list-qt mac desktop --spec "$qt_spec" --latest-version); then
exit 1
fi
elif [[ $RUNNER_OS == Windows ]]; then
if ! qt_resolved=$(aqt list-qt windows desktop --spec "$qt_spec" --latest-version); then
exit 1
fi
else
echo "aqt command for $RUNNER_OS not defined."
exit 1
fi
echo "resolved $qt_spec to $qt_resolved"
if [[ ! $qt_resolved ]]; then
echo "Error: Could not resolve Qt version for $qt_spec"
exit 1
fi
echo "version=$qt_resolved" >> "$GITHUB_OUTPUT"

25
.ci/thin_macos_qtlib.sh Executable file
View file

@ -0,0 +1,25 @@
#!/bin/bash
# The macos binaries from aqt are fat (universal), so we thin them to the target architecture to reduce the size of
# the packages and caches using lipo.
# This script is meant to be used by the ci enironment on macos runners only.
# It uses the runner's GITHUB_WORKSPACE env variable.
arch=$(uname -m)
nproc=$(sysctl -n hw.ncpu)
function thin() {
local libfile=$1
if [[ $(file -b --mime-type "$libfile") == application/x-mach-binary* ]]; then
echo "Processing $libfile"
lipo "$libfile" -thin "$arch" -output "$libfile"
fi
return 0
}
export -f thin # export to allow use in xargs
export arch
set -eo pipefail
echo "::group::Thinning Qt libraries to $arch using $nproc cores"
find "$GITHUB_WORKSPACE/Qt" -type f -print0 | xargs -0 -n1 -P"$nproc" -I{} bash -c "thin '{}'"
echo "::endgroup::"

View file

@ -3,7 +3,9 @@ AccessModifierOffset: -4
ColumnLimit: 120
---
Language: Cpp
BreakBeforeBraces: Custom
AllowAllParametersOfDeclarationOnNextLine: false
AllowShortFunctionsOnASingleLine: None
BinPackParameters: false
BraceWrapping:
AfterClass: true
AfterControlStatement: false
@ -18,16 +20,14 @@ BraceWrapping:
SplitEmptyFunction: true
SplitEmptyRecord: true
SplitEmptyNamespace: true
AllowShortFunctionsOnASingleLine: None
BinPackParameters: false
AllowAllParametersOfDeclarationOnNextLine: false
IndentCaseLabels: true
PointerAlignment: Right
SortIncludes: true
BreakBeforeBraces: Custom
IncludeBlocks: Regroup
IndentCaseLabels: true
InsertBraces: true
PointerAlignment: Right
RemoveSemicolon: true
SortIncludes: true
StatementAttributeLikeMacros: [emit]
# requires clang-format 16
# RemoveSemicolon: true
---
Language: Proto
AllowShortFunctionsOnASingleLine: None

View file

@ -1,5 +0,0 @@
---
exclude_paths:
- '**/translations/*.ts'
# codacy config documentation: https://support.codacy.com/hc/en-us/articles/115002130625-Codacy-Configuration-File

View file

@ -1,4 +1,3 @@
.git/
build/
.github/
.tx/

View file

@ -209,6 +209,16 @@ nowadays and clean it up for you.
Lines should be 120 characters or less. Please break up lines that are too long
into smaller parts, for example at spaces or after opening a brace.
### Documentation Comments ###
Use [Doxygen](https://www.doxygen.nl/) for code documentation:
- **Doc blocks**: Use `/** @brief Description */` (Javadoc-style), not `///`
- **Member comments**: Use trailing `///<` for inline member documentation
- **TODOs**: Use `//! \todo Description` (Qt-style), Doxygen collects them into a Todo List
(uses [Qt-style comments](https://www.doxygen.nl/manual/docblocks.html) with
Doxygen's [\todo command](https://www.doxygen.nl/manual/commands.html#cmdtodo))
### Memory Management ###
New code should be written using references over pointers and stack allocation
@ -324,7 +334,7 @@ the tr() call, also you can add an extra string as a hint for translators:
QString message = tr("Everyone draws %n cards", "english hint for translators", amount);
```
See [Qt's wiki on translations](
https://doc.qt.io/qt-5/i18n-source-translation.html#handling-plurals)
https://doc.qt.io/qt-6/i18n-source-translation.html#handling-plurals)
If you're about to propose a change that adds or modifies any translatable
string in the code, you don't need to take care of adding the new strings to
@ -461,7 +471,11 @@ revoke the tag by doing the following:
git push --delete upstream $TAG_NAME
git tag -d $TAG_NAME
```
You can also do this on GitHub, you'll also want to delete the false release.
You can also do this on GitHub.
> [!NOTE]
> If you want to push a new release to replace it immediately with the same
> name you have to delete the automatically created release first!
In the first lines of [CMakeLists.txt](
https://github.com/Cockatrice/Cockatrice/blob/master/CMakeLists.txt)

View file

@ -1,9 +1,12 @@
blank_issues_enabled: false
contact_links:
- name: 💬 Discord Community (Get help with server issues, e.g. Login)
url: https://discord.gg/3Z9yzmA
url: https://discord.com/invite/3Z9yzmA
about: Need help with using the client? Want to find some games? Try the Discord server!
- name: 🌐 Translations (Help improve the localization of the app)
url: https://explore.transifex.com/cockatrice/cockatrice/
# it is not possible to add a link to the wiki to this description
about: For more information and guidance check our Translation FAQ on our wiki!
- name: 📖 Code Documentation
url: https://cockatrice.github.io/docs/
about: Helpful source focusing on developers, but there are also references for users!

View file

@ -2,19 +2,21 @@
version: 2
updates:
# # Enable version updates for git submodules
# Not yet possible to bump only on tags or releases, see:
# Enable version updates for git submodules
# If SemVer is used, updates will happen to new releases only (not HEAD)
# https://github.com/dependabot/dependabot-core/issues/1639
# https://github.com/dependabot/dependabot-core/issues/2192
# Alternative: Action that updates submodule and can be manually run on demand (workflow_dispatch)
# - package-ecosystem: "gitsubmodule"
# # Look for `.gitmodules` in the `root` directory
# directory: "/"
# # Check for updates once a month
# schedule:
# interval: "monthly"
# # Limit the amout of open PR's (default = 5, disabled = 0, security updates are not impacted)
# open-pull-requests-limit: 1
- package-ecosystem: "gitsubmodule"
# Look for `.gitmodules` in the `root` directory
directory: "/"
ignore:
# Ignore updates for vcpkg (Bump to latest tag not working (no SemVer used)
- dependency-name: "vcpkg"
# Check for updates once a month
schedule:
interval: "monthly"
# Limit the amout of open PR's (default = 5, disabled = 0, security updates are not impacted)
open-pull-requests-limit: 2
# # Enable version updates for Docker
# Not yet possible to bump from one LTS version to the next and skip others, see:
@ -37,13 +39,3 @@ updates:
interval: "weekly"
# Limit the amout of open PR's (default = 5, disabled = 0, security updates are not impacted)
open-pull-requests-limit: 2
# # Enable version updates for npm
# - package-ecosystem: "npm"
# # Look for `package.json` and `lock` files in the `webclient` subdirectory
# directory: "/webclient"
# # Check the npm registry for updates once a week
# schedule:
# interval: "weekly"
# # Limit the amout of open PR's (default = 5, disabled = 0, security updates are not impacted)
# open-pull-requests-limit: 5

95
.github/workflows/codeql.yml vendored Normal file
View file

@ -0,0 +1,95 @@
# GitHub Docs on Code Scanning:
# https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning
# https://docs.github.com/en/code-security/how-tos/find-and-fix-code-vulnerabilities/manage-your-configuration
# https://docs.github.com/en/code-security/reference/code-scanning/workflow-configuration-options
#
# CodeQL Docs:
# https://codeql.github.com/docs/
name: CodeQL
permissions:
contents: read
security-events: write # needed to post results
on:
push:
branches:
- master
pull_request:
# Cancel earlier, unfinished runs of this workflow on the same branch
concurrency:
group: "${{ github.workflow }} @ ${{ github.ref_name }}"
cancel-in-progress: true
jobs:
analyze:
name: Analyze (${{ matrix.language }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
# https://codeql.github.com/docs/codeql-overview/supported-languages-and-frameworks/
- language: cpp
build-mode: manual
- language: actions
build-mode: none
steps:
- name: "Checkout repository"
uses: actions/checkout@v7
- name: "Initialize CodeQL"
uses: github/codeql-action/init@v4
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
# https://docs.github.com/en/code-security/reference/code-scanning/codeql/codeql-queries/c-cpp-built-in-queries
# https://docs.github.com/en/code-security/reference/code-scanning/codeql/codeql-queries/actions-built-in-queries
queries: security-extended
dependency-caching: true
- name: "[C++] Install dependencies"
if: matrix.language == 'cpp' && matrix.build-mode == 'manual'
shell: bash
env:
DEBIAN_FRONTEND: noninteractive
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
cmake \
g++ \
libgl-dev \
liblzma-dev \
libmariadb-dev-compat \
libprotobuf-dev \
ninja-build \
protobuf-compiler \
qt6-declarative-dev \
qt6-multimedia-dev \
qt6-shadertools-dev \
qt6-svg-dev \
qt6-tools-dev \
qt6-tools-dev-tools \
qt6-websockets-dev
# Minimize dependency install
# Add ccache usage for faster compilation, (install ccache dep, actions/cache step + append DUSE_CCACHE=1 in cmake config, CCACHE env values)
- name: "[C++] Configure CMake"
if: matrix.language == 'cpp' && matrix.build-mode == 'manual'
shell: bash
run: cmake -S . -B build -G Ninja -DWITH_SERVER=1 -DCMAKE_BUILD_TYPE=Release
- name: "[C++] Build application"
if: matrix.language == 'cpp' && matrix.build-mode == 'manual'
shell: bash
run: cmake --build build
- name: "Perform CodeQL Analysis"
uses: github/codeql-action/analyze@v4
with:
category: "/language:${{ matrix.language }}"

View file

@ -1,10 +1,10 @@
name: Build Desktop
permissions:
contents: write
id-token: write
attestations: write
actions: write # needed for ccache action to be able to delete gha caches
actions: write # needed to delete entries in GHA cache (update ccache)
attestations: write # needed to persist the attestation.
contents: write # needed for e.g. vcpkg dependency graph updates
id-token: write # needed for signing certificate in attestation
on:
push:
@ -14,14 +14,12 @@ on:
- '*/**' # matches all files not in root
- '!**.md'
- '!.github/**'
- '!.husky/**'
- '!.tx/**'
- '!doc/**'
- '!webclient/**'
- '.github/workflows/desktop-build.yml'
- 'CMakeLists.txt'
- 'vcpkg.json'
- 'vcpkg'
- 'vcpkg' # needed to match submodule bumps (gitlink)
tags:
- '*'
pull_request:
@ -29,221 +27,283 @@ on:
- '*/**' # matches all files not in root
- '!**.md'
- '!.github/**'
- '!.husky/**'
- '!.tx/**'
- '!doc/**'
- '!webclient/**'
- '.github/workflows/desktop-build.yml'
- 'CMakeLists.txt'
- 'vcpkg.json'
- 'vcpkg'
- 'vcpkg' # needed to match submodule bumps (gitlink)
# Cancel earlier, unfinished runs of this workflow on the same branch (unless on master)
# Cancel earlier, unfinished runs of this workflow on the same branch (unless on tag --> release)
concurrency:
group: "${{ github.workflow }} @ ${{ github.ref_name }}"
cancel-in-progress: ${{ github.ref_name != 'master' }}
cancel-in-progress: ${{ github.ref_type != 'tag' }}
jobs:
configure:
name: Configure
runs-on: ubuntu-latest
runs-on: ubuntu-slim # https://github.com/actions/runner-images/blob/main/images/ubuntu-slim/ubuntu-slim-Readme.md
outputs:
tag: ${{steps.configure.outputs.tag}}
sha: ${{steps.configure.outputs.sha}}
tag: ${{ steps.configure.outputs.tag }}
sha: ${{ steps.configure.outputs.sha }}
steps:
- name: Configure
steps:
- name: "Configure"
env:
RESOLVED_SHA: ${{ case(github.event_name == 'pull_request', github.event.pull_request.head.sha, github.sha) }}
id: configure
shell: bash
run: |
tag_regex='^refs/tags/'
if [[ $GITHUB_EVENT_NAME == pull-request ]]; then # pull request
sha="${{github.event.pull_request.head.sha}}"
elif [[ $GITHUB_REF =~ $tag_regex ]]; then # release
sha="$GITHUB_SHA"
tag="${GITHUB_REF/refs\/tags\//}"
echo "tag=$tag" >>"$GITHUB_OUTPUT"
else # push to branch
sha="$GITHUB_SHA"
if [[ "$GITHUB_REF_TYPE" == 'tag' ]]; then # release
echo "tag=$GITHUB_REF_NAME" >> "$GITHUB_OUTPUT"
fi
echo "sha=$sha" >>"$GITHUB_OUTPUT"
echo "sha=$RESOLVED_SHA" >> "$GITHUB_OUTPUT"
- name: Checkout
- name: "Checkout"
if: steps.configure.outputs.tag != null
uses: actions/checkout@v6
uses: actions/checkout@v7
with:
fetch-depth: 0
fetch-depth: 0 # fetch all history for all branches and tags
- name: Prepare release parameters
- name: "Prepare release parameters"
id: prepare
if: steps.configure.outputs.tag != null
shell: bash
env:
TAG: ${{steps.configure.outputs.tag}}
TAG: ${{ steps.configure.outputs.tag }}
run: .ci/prep_release.sh
- name: Create release
- name: "Create release"
if: steps.configure.outputs.tag != null
id: create_release
shell: bash
env:
GH_TOKEN: ${{github.token}}
tag_name: ${{steps.configure.outputs.tag}}
target: ${{steps.configure.outputs.sha}}
release_name: ${{steps.prepare.outputs.title}}
body_path: ${{steps.prepare.outputs.body_path}}
prerelease: ${{steps.prepare.outputs.is_beta}}
GH_TOKEN: ${{ github.token }}
tag_name: ${{ steps.configure.outputs.tag }}
target: ${{ steps.configure.outputs.sha }}
release_name: ${{ steps.prepare.outputs.title }}
body_path: ${{ steps.prepare.outputs.body_path }}
prerelease: ${{ steps.prepare.outputs.is_beta }}
run: |
if [[ $prerelease == yes ]]; then
args="--prerelease"
fi
gh release create "$tag_name" --draft --verify-tag $args \
--target "$target" --title "$release_name" \
--notes-file "$body_path"
args=()
[[ $prerelease == yes ]] && args+=(--prerelease)
gh release create "$tag_name" --verify-tag --draft "${args[@]}" \
--target "$target" \
--title "$release_name" \
--notes-file "$body_path"
build-linux:
strategy:
fail-fast: false
matrix:
# These names correspond to the files in ".ci/$distro$version"
# The files in ".ci/$distro$version" correspond to the values given here
include:
- distro: Arch
package: skip # We are packaged in Arch already
allow-failure: yes
- distro: Debian
version: 11
package: DEB
allow-failure: yes
package: skip # We are packaged in Arch already
- distro: Servatrice_Debian
version: 11
version: 12
package: DEB
test: skip
server_only: yes
test: skip
- distro: Debian
version: 12
package: DEB
test: skip # Running tests on all distros is superfluous
- distro: Debian
version: 13
package: DEB
- distro: Fedora
version: 42
package: RPM
test: skip # Running tests on all distros is superfluous
package: DEB
- distro: Fedora
version: 43
package: RPM
test: skip # Running tests on all distros is superfluous
- distro: Fedora
version: 44
package: RPM
- distro: Ubuntu
version: 22.04
version: 24.04
package: DEB
test: skip # Running tests on all distros is superfluous
- distro: Ubuntu
version: 24.04
version: 26.04
package: DEB
name: ${{matrix.distro}} ${{matrix.version}}
# The one leg that runs the suite under AddressSanitizer and
# UndefinedBehaviorSanitizer. Debian 13 and Fedora 44 keep covering the
# uninstrumented suite, so nothing is lost by instrumenting this one.
sanitize: yes
- distro: Ubuntu
version: 26.04
variant: cover # separate image and ccache, same distro
# The one leg that runs instrumented for line coverage and publishes the report as
# a workflow artifact. It is a separate leg rather than a second flag on the
# sanitizer leg because the two instruments answer different questions: the
# sanitizer leg reports what the run did wrong, the coverage leg reports what the
# run never touched at all. Combining them would also slow both down. Packaging is
# skipped because the leg above already publishes the DEB for this distro.
coverage: yes
package: skip
name: ${{ matrix.distro }} ${{ matrix.version }}${{ matrix.variant && format(' {0}', matrix.variant) || '' }}
needs: configure
runs-on: ubuntu-latest
continue-on-error: ${{matrix.allow-failure == 'yes'}}
runs-on: ubuntu-latest # https://github.com/actions/runner-images
continue-on-error: ${{ matrix.allow-failure == 'yes' }}
timeout-minutes: 70
env:
NAME: ${{matrix.distro}}${{matrix.version}}
CACHE: ${{github.workspace}}/.cache/${{matrix.distro}}${{matrix.version}} # directory for caching docker image and ccache
# Cache size over the entire repo is 10Gi:
# https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#usage-limits-and-eviction-policy
CCACHE_SIZE: 500M
CACHE: ${{ github.workspace }}/.cache/${{ matrix.distro }}${{ matrix.version }}${{ matrix.variant }} # directory for caching docker image and ccache
CCACHE_EVICTION_AGE: 7d
CCACHE_SIZE: 600M # space of all repo is 10Gi: https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#usage-limits-and-eviction-policy
CMAKE_GENERATOR: 'Ninja'
NAME: ${{ matrix.distro }}${{ matrix.version }}
IMAGE_SUFFIX: ${{ matrix.variant }}
steps:
- name: Checkout
uses: actions/checkout@v6
- name: "Checkout"
uses: actions/checkout@v7
- name: Restore compiler cache (ccache)
id: ccache_restore
uses: actions/cache/restore@v4
env:
BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
- name: "Restore compiler cache (ccache)"
id: restore_ccache
uses: actions/cache/restore@v6
with:
path: ${{env.CACHE}}
key: ccache-${{matrix.distro}}${{matrix.version}}-${{env.BRANCH_NAME}}
restore-keys: ccache-${{matrix.distro}}${{matrix.version}}-
key: ccache-${{ matrix.distro }}${{ matrix.version }}${{ matrix.variant }}
path: ${{ env.CACHE }}
- name: Build ${{matrix.distro}} ${{matrix.version}} Docker image
- name: "Build ${{ matrix.distro }} ${{ matrix.version }} Docker image"
shell: bash
run: source .ci/docker.sh --build
- name: Build debug and test
- name: "Build debug and test"
if: matrix.test != 'skip'
shell: bash
env:
CMAKE_GENERATOR: '${{env.CMAKE_GENERATOR}}'
sanitize: '${{ matrix.sanitize }}'
coverage: '${{ matrix.coverage }}'
run: |
source .ci/docker.sh
RUN --server --debug --test --ccache "$CCACHE_SIZE"
args=()
[[ $GITHUB_REF == "refs/heads/master" ]] && args+=(--evict-ccache "$CCACHE_EVICTION_AGE")
args+=(--ccache "$CCACHE_SIZE")
args+=(--cmake-generator "$CMAKE_GENERATOR")
- name: Build release package
extra=()
[[ $sanitize == yes ]] && extra+=(--sanitize)
[[ $coverage == yes ]] && extra+=(--coverage)
RUN --server --debug --test "${extra[@]}" "${args[@]}"
# RUN bind-mounts the workspace into the container, so the report the build wrote into
# build/ is already on the host and no second container is needed to collect it.
- name: "Collect coverage report"
if: matrix.coverage == 'yes'
shell: bash
run: |
mkdir -p coverage
# --html-details writes one page per source file plus a stylesheet and script, and the
# index links to all of them by relative name. Copying the index alone would publish a
# report where every link 404s, so the whole family has to travel together.
cp build/coverage-details* coverage/
cp build/coverage.lcov build/coverage.xml build/coverage-summary.json coverage/
ls -la coverage/
- name: "Upload coverage artifact"
if: matrix.coverage == 'yes'
uses: actions/upload-artifact@v4
with:
name: coverage-report
path: coverage/
if-no-files-found: warn
- name: "Build release package"
id: build
if: matrix.package != 'skip'
shell: bash
env:
BUILD_DIR: build
SUFFIX: '-${{matrix.distro}}${{matrix.version}}'
package: '${{matrix.package}}'
CMAKE_GENERATOR: '${{env.CMAKE_GENERATOR}}'
NO_CLIENT: ${{matrix.server_only == 'yes' && '--no-client' || '' }}
SUFFIX: '-${{ matrix.distro }}${{ matrix.version }}'
package: '${{ matrix.package }}'
server_only: '${{ matrix.server_only }}'
run: |
source .ci/docker.sh
RUN --server --release --package "$package" --dir "$BUILD_DIR" \
--ccache "$CCACHE_SIZE" $NO_CLIENT
.ci/name_build.sh
args=()
[[ $server_only == yes ]] && args+=(--no-client)
[[ $GITHUB_REF == "refs/heads/master" ]] && args+=(--evict-ccache "$CCACHE_EVICTION_AGE")
args+=(--ccache "$CCACHE_SIZE")
args+=(--cmake-generator "$CMAKE_GENERATOR")
args+=(--suffix "$SUFFIX")
- name: Save compiler cache (ccache)
RUN --server --release --package "$package" "${args[@]}"
# Delete used cache to emulate a ccache update. See https://github.com/actions/cache/issues/342
- name: "Delete remote compiler cache (ccache)"
if: github.ref == 'refs/heads/master' && steps.restore_ccache.outputs.cache-hit
continue-on-error: true
env:
CACHE_PRIMARY_KEY: ${{ steps.restore_ccache.outputs.cache-primary-key }}
GH_TOKEN: ${{ github.token }}
run: |
if gh cache delete --repo "$GITHUB_REPOSITORY" "$CACHE_PRIMARY_KEY"; then
echo "Cache deleted successfully"
fi
- name: "Save updated compiler cache (ccache)"
if: github.ref == 'refs/heads/master'
uses: actions/cache/save@v4
uses: actions/cache/save@v6
with:
path: ${{env.CACHE}}
key: ${{ steps.ccache_restore.outputs.cache-primary-key }}
key: ${{ steps.restore_ccache.outputs.cache-primary-key }}
path: ${{ env.CACHE }}
- name: Upload artifact
- name: "Upload artifact"
id: upload_artifact
if: matrix.package != 'skip'
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v7
with:
name: ${{matrix.distro}}${{matrix.version}}-package
path: ${{steps.build.outputs.path}}
archive: false
if-no-files-found: error
path: ${{ steps.build.outputs.path }}
- name: Upload to release
- name: "Upload to release"
id: upload_release
if: matrix.package != 'skip' && needs.configure.outputs.tag != null
shell: bash
env:
GH_TOKEN: ${{github.token}}
tag_name: ${{needs.configure.outputs.tag}}
asset_path: ${{steps.build.outputs.path}}
asset_name: ${{steps.build.outputs.name}}
asset_name: ${{ steps.build.outputs.fullname }}
asset_path: ${{ steps.build.outputs.path }}
GH_TOKEN: ${{ github.token }}
tag_name: ${{ needs.configure.outputs.tag }}
run: gh release upload "$tag_name" "$asset_path#$asset_name"
- name: Attest binary provenance
- name: "Attest binary provenance"
id: attestation
if: steps.upload_release.outcome == 'success'
uses: actions/attest-build-provenance@v3
uses: actions/attest@v4
with:
subject-name: ${{steps.build.outputs.name}}
subject-digest: sha256:${{ steps.upload_artifact.outputs.artifact-digest }}
show-summary: false
subject-path: ${{ steps.build.outputs.path }}
- name: Verify binary attestation
- name: "Verify binary attestation"
if: steps.attestation.outcome == 'success'
shell: bash
env:
GH_TOKEN: ${{github.token}}
run: gh attestation verify ${{steps.build.outputs.path}} -R Cockatrice/Cockatrice
BUILD_PATH: ${{ steps.build.outputs.path }}
GH_TOKEN: ${{ github.token }}
run: gh attestation verify "$BUILD_PATH" --repo Cockatrice/Cockatrice
build-vcpkg:
strategy:
@ -251,176 +311,252 @@ jobs:
matrix:
include:
- os: macOS
target: 13
runner: macos-15-intel
soc: Intel
xcode: "16.4"
type: Release
target: 13 # EOL 2025-09-15
runner: macos-15-intel # https://github.com/actions/runner-images/blob/main/images/macos/macos-15-Readme.md
# macos-26-intel is very slow and fails in CPack during DMG config if not increasing Finder timeout
ccache_eviction_age: 7d
cmake_generator: Ninja
make_package: 1
override_target: 13
make_package: 1
package_suffix: "-macOS13_Intel"
artifact_name: macOS13_Intel-package
qt_version: 6.6.*
qt_arch: clang_64
qt_modules: qtimageformats qtmultimedia qtwebsockets
cache_qt: false # qt caches take too much space for macOS (1.1Gi)
cmake_generator: Ninja
qt_version: 6.11.*
qt_modules: qtimageformats qtmultimedia qtshadertools qtwebsockets
soc: Intel
type: Release
use_ccache: 1
xcode: "26.3"
- os: macOS
target: 14
runner: macos-14
soc: Apple
xcode: "15.4"
type: Release
target: 14 # EOL 2026-??
runner: macos-26 # https://github.com/actions/runner-images/blob/main/images/macos/macos-26-arm64-Readme.md
ccache_eviction_age: 7d
cmake_generator: Ninja
make_package: 1
override_target: 14
package_suffix: "-macOS14"
artifact_name: macOS14-package
qt_version: 6.6.*
qt_arch: clang_64
qt_modules: qtimageformats qtmultimedia qtwebsockets
cache_qt: false
cmake_generator: Ninja
qt_version: 6.11.*
qt_modules: qtimageformats qtmultimedia qtshadertools qtwebsockets
soc: Apple
type: Release
use_ccache: 1
xcode: "26.6"
- os: macOS
target: 15
runner: macos-15
soc: Apple
xcode: "16.4"
type: Release
runner: macos-26 # https://github.com/actions/runner-images/blob/main/images/macos/macos-26-arm64-Readme.md
ccache_eviction_age: 7d
cmake_generator: Ninja
make_package: 1
override_target: 15
package_suffix: "-macOS15"
artifact_name: macOS15-package
qt_version: 6.6.*
qt_arch: clang_64
qt_modules: qtimageformats qtmultimedia qtwebsockets
cache_qt: false
cmake_generator: Ninja
qt_version: 6.11.*
qt_modules: qtimageformats qtmultimedia qtshadertools qtwebsockets
soc: Apple
type: Release
use_ccache: 1
xcode: "26.6"
- os: macOS
target: 15
runner: macos-15
soc: Apple
xcode: "16.4"
type: Debug
qt_version: 6.6.*
qt_arch: clang_64
qt_modules: qtimageformats qtmultimedia qtwebsockets
cache_qt: false
cmake_generator: Ninja
use_ccache: 1
target: 26
runner: macos-26 # https://github.com/actions/runner-images/blob/main/images/macos/macos-26-arm64-Readme.md
- os: Windows
target: 7
runner: windows-2022
type: Release
make_package: 1
package_suffix: "-Win7"
artifact_name: Windows7-installer
qt_version: 5.15.*
qt_arch: win64_msvc2019_64
cache_qt: true
cmake_generator: "Visual Studio 17 2022"
cmake_generator_platform: x64
ccache_eviction_age: 7d
cmake_generator: Ninja
qt_version: 6.11.*
qt_modules: qtimageformats qtmultimedia qtshadertools qtwebsockets
soc: Apple
type: Debug
use_ccache: 1
xcode: "26.6"
- os: Windows
target: 10
runner: windows-2022
type: Release
runner: windows-2025 # https://github.com/actions/runner-images/blob/main/images/windows/Windows2025-VS2026-Readme.md
cmake_generator: "Visual Studio 18 2026"
cmake_generator_platform: x64
make_package: 1
package_suffix: "-Win10"
artifact_name: Windows10-installer
qt_version: 6.6.*
qt_arch: win64_msvc2019_64
qt_modules: qtimageformats qtmultimedia qtwebsockets
cache_qt: true
cmake_generator: "Visual Studio 17 2022"
cmake_generator_platform: x64
qt_version: 6.11.*
qt_modules: qtimageformats qtmultimedia qtshadertools qtwebsockets
type: Release
name: ${{matrix.os}} ${{matrix.target}}${{ matrix.soc == 'Intel' && ' Intel' || '' }}${{ matrix.type == 'Debug' && ' Debug' || '' }}
name: ${{ matrix.os }} ${{ matrix.target }}${{ matrix.soc == 'Intel' && ' Intel' || '' }}${{ matrix.type == 'Debug' && ' Debug' || '' }}
needs: configure
runs-on: ${{matrix.runner}}
runs-on: ${{ matrix.runner }}
timeout-minutes: 100
env:
CCACHE_DIR: ${{ github.workspace }}/.cache/
CCACHE_SIZE: 600M # space of all repo is 10Gi: https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#usage-limits-and-eviction-policy
steps:
- name: Checkout
uses: actions/checkout@v6
- name: "Checkout"
uses: actions/checkout@v7
with:
submodules: recursive
- name: Add msbuild to PATH
- name: "[Windows] Add msbuild to PATH"
if: matrix.os == 'Windows'
id: add-msbuild
uses: microsoft/setup-msbuild@v2
uses: microsoft/setup-msbuild@v3
with:
msbuild-architecture: x64
# Using jianmingyong/ccache-action to setup ccache without using brew
# It tries to download a binary of ccache from GitHub Release and falls back to building from source if it fails
- name: Setup ccache
if: matrix.use_ccache == 1
uses: jianmingyong/ccache-action@v1
with:
install-type: "binary"
ccache-key-prefix: ccache-${{matrix.runner}}-${{matrix.soc}}-${{matrix.type}}
max-size: 500M
gh-token: ${{ secrets.GITHUB_TOKEN }}
- name: "[macOS] Setup ccache"
if: matrix.os == 'macOS' && matrix.use_ccache == 1
run: brew install ccache
- name: Install Qt ${{matrix.qt_version}}
- name: "[macOS] Restore compiler cache (ccache)"
if: matrix.os == 'macOS' && matrix.use_ccache == 1
id: restore_ccache
uses: actions/cache/restore@v6
with:
key: ccache-${{ matrix.runner }}_${{ matrix.override_target }}-Xcode${{ matrix.xcode }}
path: ${{ env.CCACHE_DIR }}
- name: "[macOS] Install aqtinstall"
if: matrix.os == 'macOS'
run: pipx install aqtinstall
# Resolve given wildcard versions (e.g. Qt 6.6.*) to latest version via aqtinstall to avoid stale caches on new releases
- name: "[macOS] Resolve latest Qt from ${{ matrix.qt_version }} input"
if: matrix.os == 'macOS'
env:
QT_VERSION: ${{ matrix.qt_version }}
id: resolve_qt_version
shell: bash
run: .ci/resolve_latest_aqt_qt_version.sh "$QT_VERSION"
- name: "[macOS] Restore thin Qt ${{ steps.resolve_qt_version.outputs.version }}"
if: matrix.os == 'macOS'
id: restore_qt
uses: actions/cache/restore@v6
with:
key: Qt-${{ steps.resolve_qt_version.outputs.version }}-macOS-${{ matrix.soc }}-${{ matrix.qt_modules }}-thin
path: ${{ github.workspace }}/Qt
# Using jurplel/install-qt-action to install Qt without using brew
# Qt build using vcpkg either just fails or takes too long to build
- name: "[macOS] Install fat Qt ${{ matrix.qt_version }}"
if: matrix.os == 'macOS' && steps.restore_qt.outputs.cache-hit != 'true'
uses: jurplel/install-qt-action@v4
with:
version: ${{matrix.qt_version}}
arch: ${{matrix.qt_arch}}
modules: ${{matrix.qt_modules}}
cache: ${{matrix.cache_qt}}
cache: false
# cache-key-prefix: Qt
dir: ${{ github.workspace }} # thinning script depends on this location
modules: ${{ matrix.qt_modules }}
version: ${{ matrix.qt_version }}
- name: Setup vcpkg cache
- name: "[macOS] Create thin Qt libraries"
if: matrix.os == 'macOS' && steps.restore_qt.outputs.cache-hit != 'true'
run: .ci/thin_macos_qtlib.sh
- name: "[macOS] Cache thin Qt libraries"
if: matrix.os == 'macOS' && steps.restore_qt.outputs.cache-hit != 'true' && github.ref == 'refs/heads/master'
uses: actions/cache/save@v6
with:
key: ${{ steps.restore_qt.outputs.cache-primary-key }}
path: ${{ github.workspace }}/Qt
- name: "[Windows] Install Qt ${{ matrix.qt_version }}"
if: matrix.os == 'Windows'
uses: jurplel/install-qt-action@v4
with:
# Qt 6.11.0 only works with aqtinstall directly from git until aqtinstall 3.4 is released
aqtsource: git+https://github.com/miurahr/aqtinstall.git
cache: ${{ github.ref == 'refs/heads/master' }}
cache-key-prefix: Qt
modules: ${{ matrix.qt_modules }}
version: ${{ matrix.qt_version }}
- name: "[Windows] Install NSIS"
if: matrix.os == 'Windows'
shell: bash
run: choco install nsis
- name: "Setup vcpkg cache"
id: vcpkg-cache
uses: TAServers/vcpkg-cache@v3
with:
token: ${{ secrets.GITHUB_TOKEN }}
# uses environment variables, see compile.sh for more details
- name: Build Cockatrice
# Uses environment variables, see compile.sh for more details
- name: "Build Cockatrice"
id: build
shell: bash
env:
BUILDTYPE: '${{matrix.type}}'
MAKE_PACKAGE: '${{matrix.make_package}}'
PACKAGE_SUFFIX: '${{matrix.package_suffix}}'
CMAKE_GENERATOR: ${{matrix.cmake_generator}}
CMAKE_GENERATOR_PLATFORM: ${{matrix.cmake_generator_platform}}
USE_CCACHE: ${{matrix.use_ccache}}
VCPKG_DISABLE_METRICS: 1
VCPKG_BINARY_SOURCES: 'clear;files,${{ steps.vcpkg-cache.outputs.path }},readwrite'
# macOS-specific environment variables, will be ignored on Windows
BUILDTYPE: '${{ matrix.type }}'
CCACHE_EVICTION_AGE: ${{ matrix.ccache_eviction_age }}
CMAKE_GENERATOR: ${{ matrix.cmake_generator }}
CMAKE_GENERATOR_PLATFORM: ${{ matrix.cmake_generator_platform }}
DEVELOPER_DIR: '/Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer'
GITHUB_TOKEN: ${{ github.token }} # needed for vcpkg dependency graph updates, see VCPKG_FEATURE_FLAGS
MACOS_CERTIFICATE: ${{ secrets.PROD_MACOS_CERTIFICATE }}
MACOS_CERTIFICATE_PWD: ${{ secrets.PROD_MACOS_CERTIFICATE_PWD }}
MACOS_CERTIFICATE_NAME: ${{ secrets.PROD_MACOS_CERTIFICATE_NAME }}
MACOS_CERTIFICATE_PWD: ${{ secrets.PROD_MACOS_CERTIFICATE_PWD }}
MACOS_CI_KEYCHAIN_PWD: ${{ secrets.PROD_MACOS_CI_KEYCHAIN_PWD }}
DEVELOPER_DIR: '/Applications/Xcode_${{matrix.xcode}}.app/Contents/Developer'
MAKE_PACKAGE: '${{ matrix.make_package }}'
PACKAGE_SUFFIX: '${{ matrix.package_suffix }}'
TARGET_MACOS_VERSION: ${{ matrix.override_target }}
USE_CCACHE: ${{ matrix.use_ccache }}
VCPKG_BINARY_SOURCES: "clear;files,${{ steps.vcpkg-cache.outputs.path }},${{ case(github.ref == 'refs/heads/master', 'readwrite', 'read') }}"
VCPKG_DISABLE_METRICS: 1
VCPKG_FEATURE_FLAGS: dependencygraph
run: .ci/compile.sh --server --test --vcpkg
- name: Sign app bundle
if: matrix.os == 'macOS' && matrix.make_package && (github.ref == 'refs/heads/master' || needs.configure.outputs.tag != null)
# CPack reports a failed makensis run as "Problem running NSIS command"
# plus a path to NSISOutput.log, and that log lives only on the runner, so
# the actual compile error is otherwise nowhere in the workflow output.
# Dump it, or a broken installer script is undiagnosable from CI alone.
# shell: bash matters here - the default pwsh resolves `find` to Windows'
# text-search find.exe, which errors instead of searching.
- name: "Dump NSIS compile log"
if: failure() && matrix.os == 'Windows'
shell: bash
run: find . -name NSISOutput.log -exec cat {} +
# Delete used cache to emulate a ccache update. See https://github.com/actions/cache/issues/342
- name: "[macOS] Delete remote compiler cache (ccache)"
if: matrix.os == 'macOS' && matrix.use_ccache == 1 && github.ref == 'refs/heads/master' && steps.restore_ccache.outputs.cache-hit
continue-on-error: true
env:
CACHE_PRIMARY_KEY: ${{ steps.restore_ccache.outputs.cache-primary-key }}
GH_TOKEN: ${{ github.token }}
run: |
if gh cache delete --repo "$GITHUB_REPOSITORY" "$CACHE_PRIMARY_KEY"; then
echo "Cache deleted successfully"
fi
- name: "[macOS] Cache updated compiler cache (ccache)"
if: matrix.os == 'macOS' && matrix.use_ccache == 1 && github.ref == 'refs/heads/master'
uses: actions/cache/save@v6
with:
key: ${{ steps.restore_ccache.outputs.cache-primary-key }}
path: ${{ env.CCACHE_DIR }}
- name: "[macOS] Sign app bundle"
if: matrix.os == 'macOS' && matrix.make_package && needs.configure.outputs.tag != null
id: sign_macos
env:
BUILD_PATH: ${{ steps.build.outputs.path }}
MACOS_CERTIFICATE_NAME: ${{ secrets.PROD_MACOS_CERTIFICATE_NAME }}
MACOS_CI_KEYCHAIN_PWD: ${{ secrets.PROD_MACOS_CI_KEYCHAIN_PWD }}
run: |
if [[ -n "$MACOS_CERTIFICATE_NAME" ]]
then
security unlock-keychain -p "$MACOS_CI_KEYCHAIN_PWD" build.keychain
/usr/bin/codesign --sign="$MACOS_CERTIFICATE_NAME" --entitlements=".ci/macos.entitlements" --options=runtime --force --deep --timestamp --verbose ${{steps.build.outputs.path}}
/usr/bin/codesign --sign="$MACOS_CERTIFICATE_NAME" --entitlements=".ci/macos.entitlements" --options=runtime --force --deep --timestamp --verbose "$BUILD_PATH"
fi
- name: Notarize app bundle
if: matrix.os == 'macOS' && matrix.make_package && (github.ref == 'refs/heads/master' || needs.configure.outputs.tag != null)
- name: "[macOS] Notarize app bundle"
if: matrix.os == 'macOS' && steps.sign_macos.outcome == 'success'
env:
BUILD_PATH: ${{ steps.build.outputs.path }}
MACOS_NOTARIZATION_APPLE_ID: ${{ secrets.PROD_MACOS_NOTARIZATION_APPLE_ID }}
MACOS_NOTARIZATION_TEAM_ID: ${{ secrets.PROD_MACOS_NOTARIZATION_TEAM_ID }}
MACOS_NOTARIZATION_PWD: ${{ secrets.PROD_MACOS_NOTARIZATION_PWD }}
MACOS_NOTARIZATION_TEAM_ID: ${{ secrets.PROD_MACOS_NOTARIZATION_TEAM_ID }}
run: |
if [[ -n "$MACOS_NOTARIZATION_APPLE_ID" ]]
then
@ -432,7 +568,7 @@ jobs:
# Therefore, we create a zip file containing our app bundle, so that we can send it to the
# notarization service
echo "Creating temp notarization archive"
ditto -c -k --keepParent ${{steps.build.outputs.path}} "notarization.zip"
ditto -c -k --keepParent "$BUILD_PATH" "notarization.zip"
# Here we send the notarization request to the Apple's Notarization service, waiting for the result.
# This typically takes a few seconds inside a CI environment, but it might take more depending on the App
@ -444,50 +580,52 @@ jobs:
# Finally, we need to "attach the staple" to our executable, which will allow our app to be
# validated by macOS even when an internet connection is not available.
echo "Attach staple"
xcrun stapler staple ${{steps.build.outputs.path}}
xcrun stapler staple "$BUILD_PATH"
fi
- name: Upload artifact
id: upload_artifact
- name: "Upload artifact"
if: matrix.make_package
uses: actions/upload-artifact@v5
id: upload_artifact
uses: actions/upload-artifact@v7
with:
name: ${{matrix.artifact_name}}
path: ${{steps.build.outputs.path}}
archive: false
if-no-files-found: error
path: ${{ steps.build.outputs.path }}
- name: Upload pdb database
if: matrix.os == 'Windows'
uses: actions/upload-artifact@v5
- name: "[Windows] Upload PDBs (Program Databases)"
if: matrix.os == 'Windows' && github.ref_type != 'tag'
uses: actions/upload-artifact@v7
with:
name: Windows${{matrix.target}}-debug-pdbs
if-no-files-found: error
name: ${{ steps.build.outputs.name }}-PDBs
path: |
build/cockatrice/Release/*.pdb
build/oracle/Release/*.pdb
build/servatrice/Release/*.pdb
if-no-files-found: error
- name: Upload to release
- name: "Upload to release"
if: needs.configure.outputs.tag != null && matrix.make_package == '1'
id: upload_release
if: needs.configure.outputs.tag != null
shell: bash
env:
GH_TOKEN: ${{github.token}}
tag_name: ${{needs.configure.outputs.tag}}
asset_path: ${{steps.build.outputs.path}}
asset_name: ${{steps.build.outputs.name}}
asset_name: ${{ steps.build.outputs.fullname }}
asset_path: ${{ steps.build.outputs.path }}
GH_TOKEN: ${{ github.token }}
tag_name: ${{ needs.configure.outputs.tag }}
run: gh release upload "$tag_name" "$asset_path#$asset_name"
- name: Attest binary provenance
id: attestation
- name: "Attest binary provenance"
if: steps.upload_release.outcome == 'success'
uses: actions/attest-build-provenance@v3
id: attestation
uses: actions/attest@v4
with:
subject-name: ${{steps.build.outputs.name}}
subject-digest: sha256:${{ steps.upload_artifact.outputs.artifact-digest }}
show-summary: false
subject-path: ${{ steps.build.outputs.path }}
- name: Verify binary attestation
- name: "Verify binary attestation"
if: steps.attestation.outcome == 'success'
shell: bash
env:
GH_TOKEN: ${{github.token}}
run: gh attestation verify ${{steps.build.outputs.path}} -R Cockatrice/Cockatrice
BUILD_PATH: ${{ steps.build.outputs.path }}
GH_TOKEN: ${{ github.token }}
run: gh attestation verify "$BUILD_PATH" --repo Cockatrice/Cockatrice

View file

@ -1,17 +1,18 @@
name: Code Style (C++)
permissions:
contents: read
on:
# push trigger not needed for linting, we do not allow direct pushes to master
# Push trigger not needed for linting, we do not allow direct pushes to master
pull_request:
paths:
- '*/**' # matches all files not in root
- '!**.md'
- '!.ci/**'
- '!.github/**'
- '!.husky/**'
- '!.tx/**'
- '!doc/**'
- '!webclient/**'
- '.ci/lint_cpp.sh'
- '.github/workflows/desktop-lint.yml'
- '.clang-format'
@ -20,20 +21,23 @@ on:
jobs:
format:
runs-on: ubuntu-22.04
runs-on: ubuntu-slim
steps:
- name: Checkout
uses: actions/checkout@v6
- name: "Checkout"
uses: actions/checkout@v7
with:
fetch-depth: 20 # should be enough to find merge base
fetch-depth: 20 # should be enough to find merge base
- name: Install dependencies
- name: "Install dependencies"
shell: bash
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends clang-format cmake-format shellcheck
sudo apt-get install -y --no-install-recommends \
clang-format \
cmake-format \
shellcheck
- name: Check code formatting
- name: "Check code formatting"
shell: bash
run: ./.ci/lint_cpp.sh

View file

@ -1,9 +1,11 @@
name: Build Docker Image
name: Build Docker
permissions:
contents: read # needed to checkout repo
packages: write # needed for interacting with GHCR
on:
push:
tags:
- '*Release*'
branches:
- master
pull_request:
@ -11,56 +13,185 @@ on:
- master
paths:
- '.github/workflows/docker-release.yml'
- '.dockerignore'
- 'Dockerfile'
- 'docker-compose.yml'
- 'docker-compose.yml.windows'
release:
types:
- released # publishing of stable releases
workflow_dispatch: # (re)publish the image for an existing stable release
inputs:
tag:
description: 'Existing release tag to build and publish (e.g. 2026-10-03-Release-3.1.0)'
required: true
type: string
latest:
description: 'Also update the "latest" tag'
required: true
type: boolean
default: true
# Cancel earlier, unfinished runs of this workflow on the same branch (unless publishing)
concurrency:
group: "${{ github.workflow }} @ ${{ inputs.tag || github.ref_name }}"
cancel-in-progress: ${{ github.event_name != 'release' && github.event_name != 'workflow_dispatch' }}
env:
GHCR_IMAGE: ghcr.io/cockatrice/servatrice
OCI_DESCRIPTION: Server for Cockatrice, a cross-platform virtual tabletop for multiplayer card games
OCI_TITLE: Servatrice
OCI_URL: https://cockatrice.github.io/
jobs:
docker:
name: amd64 & arm64
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
# Create one platform-specific image and publish its OCI image manifest per matrix job
build:
name: "Servatrice (${{ matrix.label }})"
if: github.repository_owner == 'Cockatrice'
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
matrix:
include:
- label: x86
platform: linux/amd64
runner: ubuntu-latest # https://github.com/actions/runner-images
- label: arm
platform: linux/arm64
runner: ubuntu-24.04-arm # https://github.com/actions/runner-images/blob/main/images/ubuntu/Ubuntu2404-Arm64-Readme.md, replace with "ubuntu-latest-arm" once available
env:
CACHE_SCOPE: servatrice-${{ matrix.label }}
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Docker metadata
id: metadata
uses: docker/metadata-action@v5
- name: "Checkout"
uses: actions/checkout@v7
with:
images: |
ghcr.io/cockatrice/servatrice
labels: |
org.opencontainers.image.title=Servatrice
org.opencontainers.image.url=https://cockatrice.github.io/
org.opencontainers.image.description=Server for Cockatrice, a cross-platform virtual tabletop for multiplayer card games
annotations: |
org.opencontainers.image.title=Servatrice
org.opencontainers.image.url=https://cockatrice.github.io/
org.opencontainers.image.description=Server for Cockatrice, a cross-platform virtual tabletop for multiplayer card games
ref: ${{ case(github.event_name == 'workflow_dispatch', format('refs/tags/{0}', inputs.tag), '') }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: "Set up Docker buildx"
uses: docker/setup-buildx-action@v4
- name: Set up Docker buildx
uses: docker/setup-buildx-action@v3
- name: Login to GitHub Container Registry
if: github.ref_type == 'tag'
uses: docker/login-action@v3
- name: "Login to GitHub Container Registry (GHCR)"
if: (github.event_name == 'release' && github.event.release.prerelease == false) || github.event_name == 'workflow_dispatch'
id: login
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ github.token }}
- name: Build and push Docker image
uses: docker/build-push-action@v6
# Don't push for non-publishing triggers
- name: "Build image"
if: steps.login.outcome != 'success'
uses: docker/build-push-action@v7
with:
cache-from: type=gha,scope=${{ env.CACHE_SCOPE }}
cache-to: ${{ case(github.ref == 'refs/heads/master', format('type=gha,mode=max,scope={0}', env.CACHE_SCOPE), '') }}
context: .
platforms: linux/amd64,linux/arm64
push: ${{ github.ref_type == 'tag' }}
tags: ${{ steps.metadata.outputs.tags }}
labels: ${{ steps.metadata.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
platforms: ${{ matrix.platform }}
push: false
# Add OCI labels and push single-platform image by digest (without tags)
- name: "Build image and push by digest"
if: steps.login.outcome == 'success'
id: build
uses: docker/build-push-action@v7
with:
cache-from: type=gha,scope=${{ env.CACHE_SCOPE }}
cache-to: type=gha,mode=max,scope=${{ env.CACHE_SCOPE }}
context: .
labels: |
org.opencontainers.image.description=${{ env.OCI_DESCRIPTION }}
org.opencontainers.image.title=${{ env.OCI_TITLE }}
org.opencontainers.image.url=${{ env.OCI_URL }}
outputs: type=image,name=${{ env.GHCR_IMAGE }},name-canonical=true,push=true,push-by-digest=true
platforms: ${{ matrix.platform }}
provenance: mode=max # Do not pass secrets as build arguments with this option
sbom: true
- name: "Export digest"
if: steps.login.outcome == 'success'
env:
DIGEST: ${{ steps.build.outputs.digest }}
run: |
mkdir -p "$RUNNER_TEMP/digests"
touch "$RUNNER_TEMP/digests/${DIGEST#sha256:}"
- name: "Upload digest"
if: steps.login.outcome == 'success'
uses: actions/upload-artifact@v7
with:
if-no-files-found: error
name: digest-${{ matrix.label }}
path: ${{ runner.temp }}/digests/*
retention-days: 1
# Create an OCI image index from the platform-specific image manifests
index:
name: "Publish multi-platform Servatrice image"
if: github.repository_owner == 'Cockatrice' && ((github.event_name == 'release' && github.event.release.prerelease == false) || github.event_name == 'workflow_dispatch')
needs: build
runs-on: ubuntu-slim # https://github.com/actions/runner-images/blob/main/images/ubuntu-slim/ubuntu-slim-Readme.md
steps:
- name: "Download digests"
uses: actions/download-artifact@v8
with:
path: ${{ runner.temp }}/digests
pattern: digest-*
merge-multiple: true
- name: "Login to GitHub Container Registry (GHCR)"
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ github.token }}
- name: "Docker metadata"
id: metadata
uses: docker/metadata-action@v6
with:
images: ${{ env.GHCR_IMAGE }}
flavor: |
latest=auto
# if semver, also: type=semver,pattern={{version}} / {{major}}.{{minor}}
tags: |
type=ref,event=tag
type=raw,value=${{ inputs.tag }},enable=${{ github.event_name == 'workflow_dispatch' }}
type=raw,value=latest,enable=${{ github.event_name == 'workflow_dispatch' && inputs.latest }}
# Add OCI annotations to image index and publish tags
- name: "Create image index"
env:
DOCKER_TAGS: ${{ steps.metadata.outputs.tags }}
working-directory: ${{ runner.temp }}/digests
run: |
TAG_ARGS=()
while IFS= read -r tag; do
TAG_ARGS+=(--tag "$tag")
done <<< "$DOCKER_TAGS"
DIGEST_ARGS=()
for digest in *; do
DIGEST_ARGS+=("$GHCR_IMAGE@sha256:$digest")
done
docker buildx imagetools create \
--prefer-index=true \
--annotation "index:org.opencontainers.image.description=$OCI_DESCRIPTION" \
--annotation "index:org.opencontainers.image.title=$OCI_TITLE" \
--annotation "index:org.opencontainers.image.url=$OCI_URL" \
"${TAG_ARGS[@]}" \
"${DIGEST_ARGS[@]}"
- name: "Inspect images"
env:
GITHUB_TAG: ${{ inputs.tag || github.ref_name }}
run: |
docker buildx imagetools inspect "$GHCR_IMAGE:latest"
docker buildx imagetools inspect "$GHCR_IMAGE:$GITHUB_TAG"

View file

@ -1,18 +1,26 @@
name: Generate Docs
permissions:
contents: read # write permission to the destination repo come from 'deploy_key'
on:
push:
tags:
- '*' # Only re-generate docs when a new tagged version is pushed
pull_request:
paths:
- 'doc/doxygen/**'
- '.github/workflows/documentation-build.yml'
- 'Doxyfile'
release:
types:
- published # publishing of stable releases and pre-releases
workflow_dispatch:
# Cancel earlier, unfinished runs of this workflow on the same branch (unless on release)
concurrency:
group: "${{ github.workflow }} @ ${{ github.ref_name }}"
cancel-in-progress: ${{ github.event_name != 'release' }}
env:
COCKATRICE_REF: ${{ github.ref_name }} # Tag name if the commit is tagged, otherwise branch name
COCKATRICE_REF: ${{ github.ref_name }} # tag name if the commit is tagged, otherwise branch name
jobs:
docs:
@ -20,20 +28,24 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: "Checkout"
uses: actions/checkout@v7
with:
submodules: recursive
- name: Install Graphviz
- name: "Install Graphviz"
shell: bash
run: |
sudo apt-get install -y graphviz
dot -V
- name: Install Doxygen
uses: ssciwr/doxygen-install@v1
- name: "Install Doxygen"
uses: ssciwr/doxygen-install@v2
with:
version: "1.14.0"
version: "1.18.0"
- name: Update Doxygen Configuration
- name: "Update Doxygen Configuration"
shell: bash
run: |
git diff Doxyfile
doxygen -u Doxyfile
@ -46,16 +58,22 @@ jobs:
exit 1
fi
- name: Generate Documentation
- name: "Generate Documentation"
if: always()
shell: bash
run: doxygen Doxyfile
- name: Deploy to cockatrice.github.io
if: github.event_name != 'pull_request'
- name: "Deploy to cockatrice.github.io/docs"
if: github.event_name == 'release' || github.event_name == 'workflow_dispatch'
uses: peaceiris/actions-gh-pages@v4
with:
deploy_key: ${{ secrets.DOCS_DEPLOY_KEY }}
destination_dir: docs # docs will be available at https://cockatrice.github.io/docs/
external_repository: Cockatrice/cockatrice.github.io
publish_branch: master
publish_dir: ./docs/html
destination_dir: docs # Docs will live under https://cockatrice.github.io/docs/
- name: Link to Documentation Page
if: github.event_name == 'release' || github.event_name == 'workflow_dispatch'
shell: bash
run: echo "::notice title=New documentation published::Shortly available at https://cockatrice.github.io/docs/"

View file

@ -1,14 +1,18 @@
name: Update Translations
permissions:
contents: write
pull-requests: write
on:
workflow_dispatch:
schedule:
# runs in the middle of each month starting a quarter (UTC) = two weeks after new strings are built
- cron: '0 0 15 1,4,7,10 *'
pull_request:
paths:
- '.tx/**'
- '.github/workflows/translations-pull.yml'
schedule:
# Runs in the middle of each month starting a quarter (UTC) = two weeks after new strings are built
- cron: '0 0 15 1,4,7,10 *'
workflow_dispatch:
jobs:
translations:
@ -16,58 +20,56 @@ jobs:
if: github.event_name != 'schedule' || github.repository_owner == 'Cockatrice'
name: Pull languages
runs-on: ubuntu-latest
runs-on: ubuntu-slim
steps:
- name: Checkout repo
uses: actions/checkout@v6
- name: "Checkout repo"
uses: actions/checkout@v7
- name: Pull translated strings from Transifex
- name: "Pull translated strings from Transifex"
# Do not run this step for PR's from forks, they don't have access to the secret
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false
uses: transifex/cli-action@v2
with:
# used config file: https://github.com/Cockatrice/Cockatrice/blob/master/.tx/config
# https://github.com/transifex/cli#pulling-files-from-transifex
token: ${{ secrets.TX_TOKEN }}
# Used config file: https://github.com/Cockatrice/Cockatrice/blob/master/.tx/config
# Docs: https://github.com/transifex/cli#pulling-files-from-transifex
args: pull --force --all
token: ${{ secrets.TX_TOKEN }}
- name: Create pull request
- name: "Create pull request"
if: github.event_name != 'pull_request'
id: create_pr
uses: peter-evans/create-pull-request@v7
uses: peter-evans/create-pull-request@v8
with:
add-paths: |
cockatrice/translations/*.ts
oracle/translations/*.ts
webclient/public/locales/*/translation.json
commit-message: Update translation files
# author is the owner of the commit
author: github-actions <github-actions@github.com>
branch: ci-update_translations
delete-branch: true
title: 'Update translations'
author: github-actions <github-actions@github.com> # owner of the commit
body: |
Pulled all translated strings from [Transifex][1].
---
*This PR is automatically generated and updated by the workflow at `.github/workflows/translations-pull.yml`. Review [action runs][2].*<br>
*After merging, all new languages and translations are available in the next build.*
[1]: https://explore.transifex.com/cockatrice/cockatrice/
[2]: https://github.com/Cockatrice/Cockatrice/actions/workflows/translations-pull.yml?query=branch%3Amaster
branch: ci-update_translations
commit-message: Update translation files
delete-branch: true
draft: false
labels: |
CI
Translation
draft: false
title: 'Update translations'
- name: PR Status
- name: "PR Status"
if: github.event_name != 'pull_request'
shell: bash
env:
STATUS: ${{ steps.create_pr.outputs.pull-request-operation }}
PR_NUMBER: ${{ steps.create_pr.outputs.pull-request-number }}
PR_URL: ${{ steps.create_pr.outputs.pull-request-url }}
STATUS: ${{ case(steps.create_pr.outputs.pull-request-operation == 'none', 'unchanged', steps.create_pr.outputs.pull-request-operation) }}
run: |
if [[ "$STATUS" == "none" ]]; then
echo "PR #${{ steps.create_pr.outputs.pull-request-number }} unchanged!" >> $GITHUB_STEP_SUMMARY
else
echo "PR #${{ steps.create_pr.outputs.pull-request-number }} $STATUS!" >> $GITHUB_STEP_SUMMARY
fi
echo "URL: ${{ steps.create_pr.outputs.pull-request-url }}" >> $GITHUB_STEP_SUMMARY
echo "PR #$PR_NUMBER $STATUS!" >> "$GITHUB_STEP_SUMMARY"
echo "URL: $PR_URL" >> "$GITHUB_STEP_SUMMARY"

View file

@ -1,14 +1,18 @@
name: Update Translation Source
permissions:
contents: write
pull-requests: write
on:
workflow_dispatch:
schedule:
# runs at the start of each quarter (UTC)
- cron: '0 0 1 1,4,7,10 *'
pull_request:
paths:
- '.ci/update_translation_source_strings.sh'
- '.github/workflows/translations-push.yml'
schedule:
# Runs at the start of each quarter (UTC)
- cron: '0 0 1 1,4,7,10 *'
workflow_dispatch:
jobs:
translations:
@ -16,37 +20,38 @@ jobs:
if: github.event_name != 'schedule' || github.repository_owner == 'Cockatrice'
name: Push strings
runs-on: ubuntu-latest
runs-on: ubuntu-slim
steps:
- name: Checkout repo
uses: actions/checkout@v6
- name: "Checkout repo"
uses: actions/checkout@v7
- name: Install lupdate
- name: "Install lupdate"
shell: bash
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends qttools5-dev-tools
- name: Update Cockatrice translation source
- name: "Update Cockatrice translation source"
env:
FILE: cockatrice/cockatrice_en@source.ts
id: cockatrice
shell: bash
run: |
FILE="cockatrice/cockatrice_en@source.ts"
export DIRS="cockatrice/src $(find . -maxdepth 1 -type d -name 'libcockatrice_*')"
FILE="$FILE" DIRS="$DIRS" .ci/update_translation_source_strings.sh
run: >
DIRS="cockatrice/src $(find . -maxdepth 1 -type d -name 'libcockatrice_*')"
.ci/update_translation_source_strings.sh
- name: Update Oracle translation source
- name: "Update Oracle translation source"
id: oracle
shell: bash
env:
FILE: 'oracle/oracle_en@source.ts'
DIRS: 'oracle/src'
FILE: 'oracle/oracle_en@source.ts'
run: .ci/update_translation_source_strings.sh
- name: Render template
- name: "Render template"
id: template
uses: chuhlomin/render-template@v1
uses: chuhlomin/render-template/binary@v1
with:
template: .ci/update_translation_source_strings_template.md
vars: |
@ -54,35 +59,32 @@ jobs:
oracle_output: ${{ steps.oracle.outputs.output }}
commit: ${{ github.sha }}
- name: Create pull request
- name: "Create pull request"
if: github.event_name != 'pull_request'
id: create_pr
uses: peter-evans/create-pull-request@v7
uses: peter-evans/create-pull-request@v8
with:
add-paths: |
cockatrice/cockatrice_en@source.ts
oracle/oracle_en@source.ts
commit-message: Update translation source strings
# author is the owner of the commit
author: github-actions <github-actions@github.com>
branch: ci-update_translation_source
delete-branch: true
title: 'Update source strings'
author: github-actions <github-actions@github.com> # owner of the commit
body: ${{ steps.template.outputs.result }}
branch: ci-update_translation_source
commit-message: Update translation source strings
delete-branch: true
draft: false
labels: |
CI
Translation
draft: false
title: 'Update source strings'
- name: PR Status
- name: "PR Status"
if: github.event_name != 'pull_request'
shell: bash
env:
STATUS: ${{ steps.create_pr.outputs.pull-request-operation }}
PR_NUMBER: ${{ steps.create_pr.outputs.pull-request-number }}
PR_URL: ${{ steps.create_pr.outputs.pull-request-url }}
STATUS: ${{ case(steps.create_pr.outputs.pull-request-operation == 'none', 'unchanged', steps.create_pr.outputs.pull-request-operation) }}
run: |
if [[ "$STATUS" == "none" ]]; then
echo "PR #${{ steps.create_pr.outputs.pull-request-number }} unchanged!" >> $GITHUB_STEP_SUMMARY
else
echo "PR #${{ steps.create_pr.outputs.pull-request-number }} $STATUS!" >> $GITHUB_STEP_SUMMARY
fi
echo "URL: ${{ steps.create_pr.outputs.pull-request-url }}" >> $GITHUB_STEP_SUMMARY
echo "PR #$PR_NUMBER $STATUS!" >> "$GITHUB_STEP_SUMMARY"
echo "URL: $PR_URL" >> "$GITHUB_STEP_SUMMARY"

View file

@ -1,54 +0,0 @@
name: Build Web
on:
push:
branches:
- master
paths:
- '.husky/**'
- 'webclient/**'
- '!**.md'
- '.github/workflows/web-build.yml'
pull_request:
paths:
- '.husky/**'
- 'webclient/**'
- '!**.md'
- '.github/workflows/web-build.yml'
jobs:
build-web:
name: React (Node ${{matrix.node_version}})
runs-on: ubuntu-latest
defaults:
run:
working-directory: webclient
strategy:
fail-fast: false
matrix:
node_version:
- 16
- lts/*
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: ${{matrix.node_version}}
cache: 'npm'
cache-dependency-path: 'webclient/package-lock.json'
- name: Install dependencies
run: npm clean-install
- name: Build app
run: npm run build
- name: Test app
run: npm run test

View file

@ -1,33 +0,0 @@
name: Code Style (TypeScript)
on:
# push trigger not needed for linting, we do not allow direct pushes to master
pull_request:
paths:
- 'webclient/**'
- '!**.md'
- '.github/workflows/web-lint.yml'
jobs:
ESLint:
runs-on: ubuntu-latest
defaults:
run:
working-directory: webclient
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v6
with:
cache: 'npm'
cache-dependency-path: 'webclient/package-lock.json'
- name: Install ESLint
run: npm clean-install --ignore-scripts
- name: Run ESLint
run: npm run lint

1
.gitignore vendored
View file

@ -6,6 +6,7 @@ mysql.cnf
.DS_Store
.idea/
*.aps
*.cache
cmake-build*
preferences
compile_commands.json

3
.gitmodules vendored
View file

@ -1,3 +1,6 @@
[submodule "vcpkg"]
path = vcpkg
url = https://github.com/microsoft/vcpkg.git
[submodule "doxygen-awesome-css"]
path = doc/doxygen/theme
url = https://github.com/jothepro/doxygen-awesome-css.git

View file

@ -1,7 +0,0 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
cd webclient
npm run translate
git add src/i18n-default.json

View file

@ -16,11 +16,3 @@ source_file = oracle/oracle_en@source.ts
file_filter = oracle/translations/oracle_<lang>.ts
type = QT
minimum_perc = 10
[o:cockatrice:p:cockatrice:r:webclient-src-i18n-default-json--master]
resource_name = Webclient
source_lang = en
source_file = webclient/src/i18n-default.json
file_filter = webclient/public/locales/<lang>/translation.json
type = KEYVALUEJSON
minimum_perc = 10

View file

@ -5,25 +5,27 @@
# This file sets all the variables shared between the projects
# like the installation path, compilation flags etc..
# cmake 3.16 is required if using qt6
cmake_minimum_required(VERSION 3.10)
# 3.16 required for Qt6 and target_precompile_headers()
cmake_minimum_required(VERSION 3.16)
# Early detect ccache
# Use compiler cache (ccache)
option(USE_CCACHE "Cache the build results with ccache" ON)
# Treat warnings as errors (Debug builds only)
option(WARNING_AS_ERROR "Treat warnings as errors in debug builds" ON)
# Check for translation updates
option(UPDATE_TRANSLATIONS "Update translations on compile" OFF)
# Compile servatrice
option(WITH_SERVER "build servatrice" OFF)
# Compile cockatrice
option(WITH_CLIENT "build cockatrice" ON)
# Compile oracle
option(WITH_ORACLE "build oracle" ON)
# Compile dbconverter
option(WITH_DBCONVERTER "build dbconverter" ON)
# Compile Cockatrice
option(WITH_CLIENT "Build Cockatrice client" ON)
# Compile Oracle
option(WITH_ORACLE "Build Cockatrice card database tool (Oracle)" ON)
# Compile Servatrice
option(WITH_SERVER "Build Cockatrice server (Servatrice)" OFF)
# Compile tests
option(TEST "build tests" OFF)
option(TEST "Build tests" OFF)
# Build with AddressSanitizer and UndefinedBehaviorSanitizer
option(ENABLE_SANITIZERS "Build with AddressSanitizer and UndefinedBehaviorSanitizer" OFF)
# Build instrumented for GCC line coverage, reported with gcovr
option(ENABLE_COVERAGE "Build instrumented for GCC line coverage" OFF)
# Use vcpkg regardless of OS
option(USE_VCPKG "Use vcpkg regardless of OS" OFF)
@ -41,13 +43,24 @@ else()
)
endif()
if(USE_CCACHE)
# ccache does not support MSVC and must not auto-engage on Windows
# (it is installed unintentionally on the Windows CI runner).
# NOTE: this keys off the target OS, so a mingw/Ninja configuration on Windows
# also opts out of ccache even though the GNUCXX branch below supports it.
if(USE_CCACHE AND NOT WIN32)
find_program(CCACHE_PROGRAM ccache)
if(CCACHE_PROGRAM)
# Support Unix Makefiles and Ninja
set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${CCACHE_PROGRAM}")
# PCH-aware caching, matching .ci/compile.sh: without this ccache refuses
# to cache any TU that consumes a precompiled header, so every PCH-backed
# target recompiles from scratch on each build.
execute_process(COMMAND ${CCACHE_PROGRAM} --set-config sloppiness=pch_defines,time_macros)
message(STATUS "Found CCache ${CCACHE_PROGRAM}")
endif()
elseif(USE_CCACHE AND WIN32)
# An explicit opt-in must not disappear silently on Windows.
message(STATUS "ccache disabled: not supported for the MSVC toolchain on Windows")
endif()
if(WIN32 OR USE_VCPKG)
@ -66,21 +79,22 @@ if(WIN32 OR USE_VCPKG)
else()
set(QTDIR
""
CACHE PATH "Path to Qt (e.g. C:/Qt/5.7/msvc2015_64)"
CACHE PATH "Path to Qt (e.g. C:/Qt/6.4.2/msvc2019_64)"
)
message(
WARNING "QTDIR variable is missing. Please set this variable to specify path to Qt (e.g. C:/Qt/5.7/msvc2015_64)"
WARNING
"QTDIR variable is missing. Please set this variable to specify path to Qt (e.g. C:/Qt/6.4.2/msvc2019_64)"
)
endif()
endif()
# A project name is needed for CPack
# Version can be overriden by git tags, see cmake/getversion.cmake
project("Cockatrice" VERSION 2.11.0)
project("Cockatrice" VERSION 3.1.2)
# Set release name if not provided via env/cmake var
if(NOT DEFINED GIT_TAG_RELEASENAME)
set(GIT_TAG_RELEASENAME "Omenpath")
set(GIT_TAG_RELEASENAME "Reality Check")
endif()
# Use c++20 for all targets
@ -176,6 +190,7 @@ elseif(CMAKE_COMPILER_IS_GNUCXX)
-Wno-error=delete-non-virtual-dtor
-Wno-error=sign-compare
-Wno-error=missing-declarations
-Wno-error=sfinae-incomplete # GCC 16+: Qt MOC + protobuf forward decls trigger this
)
foreach(FLAG ${ADDITIONAL_DEBUG_FLAGS})
@ -184,6 +199,9 @@ elseif(CMAKE_COMPILER_IS_GNUCXX)
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} ${FLAG}")
endif()
endforeach()
# Reduce compiler I/O by using pipes between stages instead of temp files
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pipe")
else()
# other: osx/llvm, bsd/llvm
set(CMAKE_CXX_FLAGS_RELEASE "-O2")
@ -192,8 +210,17 @@ else()
else()
set(CMAKE_CXX_FLAGS_DEBUG "-g -O0 -Wall -Wextra")
endif()
# Reduce compiler I/O by using pipes between stages instead of temp files
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pipe")
endif()
# Always included, because they define cockatrice_disable_sanitizers_for() and
# cockatrice_disable_coverage_for() for the vendored trees. They only touch the global flags
# when ENABLE_SANITIZERS or ENABLE_COVERAGE is on.
include(Sanitizers)
include(Coverage)
# GNU systems need to define the Mersenne exponent for the RNG to compile w/o warning
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
add_definitions("-DSFMT_MEXP=19937")
@ -239,11 +266,6 @@ if(WIN32)
find_package(OpenSSL REQUIRED)
if(OPENSSL_FOUND)
include_directories(${OPENSSL_INCLUDE_DIRS})
else()
message(
WARNING
"Could not find OpenSSL runtime libraries. They are not required for compiling, but needs to be available at runtime."
)
endif()
endif()
@ -256,7 +278,9 @@ endif()
set(CPACK_PACKAGE_CONTACT "Zach Halpern <zach@cockatrice.us>")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "${PROJECT_NAME}")
set(CPACK_PACKAGE_VENDOR "Cockatrice Development Team")
set(CPACK_PACKAGE_DESCRIPTION_FILE "${PROJECT_SOURCE_DIR}/README.md")
set(CPACK_PACKAGE_DESCRIPTION
"Cockatrice is an open-source, multiplatform application for playing tabletop card games over a network. The program's server design prevents users from manipulating the game for unfair advantage. The client also provides a single-player mode, which allows users to brew while offline."
)
set(CPACK_RESOURCE_FILE_LICENSE "${PROJECT_SOURCE_DIR}/LICENSE")
set(CPACK_PACKAGE_VERSION_MAJOR "${PROJECT_VERSION_MAJOR}")
set(CPACK_PACKAGE_VERSION_MINOR "${PROJECT_VERSION_MINOR}")
@ -279,11 +303,7 @@ if(UNIX)
if(CPACK_GENERATOR STREQUAL "RPM")
set(CPACK_RPM_PACKAGE_LICENSE "GPLv2")
set(CPACK_RPM_MAIN_COMPONENT "cockatrice")
if(Qt6_FOUND)
set(CPACK_RPM_PACKAGE_REQUIRES "protobuf, qt6-qttools, qt6-qtsvg, qt6-qtmultimedia, qt6-qtimageformats")
elseif(Qt5_FOUND)
set(CPACK_RPM_PACKAGE_REQUIRES "protobuf, qt5-qttools, qt5-qtsvg, qt5-qtmultimedia")
endif()
set(CPACK_RPM_PACKAGE_REQUIRES "protobuf, qt6-qtimageformats, qt6-qtmultimedia, qt6-qtsvg, qt6-qttools")
set(CPACK_RPM_PACKAGE_GROUP "Amusements/Games")
set(CPACK_RPM_PACKAGE_URL "http://github.com/Cockatrice/Cockatrice")
# stop directories from making package conflicts
@ -301,12 +321,8 @@ if(UNIX)
set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON)
set(CPACK_DEBIAN_PACKAGE_SECTION "games")
set(CPACK_DEBIAN_PACKAGE_HOMEPAGE "http://github.com/Cockatrice/Cockatrice")
if(Qt6_FOUND)
set(CPACK_DEBIAN_PACKAGE_DEPENDS "libqt6multimedia6, libqt6svg6, qt6-qpa-plugins, qt6-image-formats-plugins")
set(CPACK_DEBIAN_PACKAGE_RECOMMENDS "libqt6sql6-mysql") # for connecting servatrice to a mysql db
elseif(Qt5_FOUND)
set(CPACK_DEBIAN_PACKAGE_DEPENDS "libqt5multimedia5-plugins, libqt5svg5")
endif()
set(CPACK_DEBIAN_PACKAGE_DEPENDS "libqt6multimedia6, libqt6svg6, qt6-image-formats-plugins, qt6-qpa-plugins")
set(CPACK_DEBIAN_PACKAGE_RECOMMENDS "libqt6sql6-mysql") # for connecting servatrice to a mysql db
endif()
endif()
elseif(WIN32)
@ -320,6 +336,55 @@ elseif(WIN32)
# Configure file with custom definitions for NSIS.
configure_file("${COCKATRICE_CMAKE_PATH}/NSIS.definitions.nsh.in" "${PROJECT_BINARY_DIR}/NSIS.definitions.nsh")
# Every runtime DLL a Windows install has to contain, as paths relative to the
# install root. .ci/compile.sh reads this and fails the build when the packaged
# installer is missing one of them, or carries a runtime it does not know about.
#
# All three applications are packaged into the same directory on Windows, so
# this is the union of their Qt module lists, not just the client's: Servatrice
# contributes Qt6Sql.dll and Oracle contributes nothing the client lacks, and a
# client-only manifest leaves Qt6Sql.dll unasserted even though servatrice.exe
# is installed next to cockatrice.exe and cannot start without it.
#
# A statically linked Qt module ships no runtime DLL, so requiring one would
# fail the package check on a static build for no reason. cockatrice/CMakeLists.txt
# installs the runtime of every module listed here, and skips static ones the
# same way, so the two can never disagree.
#
# Only the Qt modules, and that is deliberate. protobuf, Abseil, liblzma, zlib
# and OpenSSL also ride in on the "*.dll" glob, and a client whose runtime is
# absent, stale or of the wrong architecture fails to start with "abseil_dll.dll
# is either not designed to run on Windows or it contains an error", so they
# are exactly the files worth asserting.
#
# Their names cannot be derived from the vcpkg bin directory, which is what a
# first attempt did and what a Windows build immediately refuted: bin holds
# everything vcpkg installed, not everything the applications import. It also
# contains gmock.dll, libprotobuf-lite.dll, libprotoc.dll, legacy.dll and three
# pthreadVC*.dll FindThreads probes, none of which any shipped executable
# links, so requiring them failed a build over a package that was correct.
#
# Deriving them from the executables' own import tables is the real answer,
# and fixup_bundle already walks it to decide what to copy. Until that result
# is available to CMake, requiring the vcpkg half would mean guessing names
# again, which is what produced this bug report in the first place. So assert
# what is derived from the build's own link lists and leave the rest to the
# purge in cmake/NSIS.template.in, which removes every DLL regardless of name.
set(_required_runtime "")
foreach(_qt_module IN LISTS COCKATRICE_QT_MODULES SERVATRICE_QT_MODULES ORACLE_QT_MODULES)
get_target_property(_qt_module_type ${_qt_module} TYPE)
if(_qt_module_type STREQUAL "SHARED_LIBRARY")
string(REGEX REPLACE "^Qt6::" "" _qt_component "${_qt_module}")
list(APPEND _required_runtime "Qt6${_qt_component}.dll")
endif()
endforeach()
list(APPEND _required_runtime "Plugins/platforms/qwindows.dll")
list(REMOVE_DUPLICATES _required_runtime)
list(SORT _required_runtime)
string(REPLACE ";" "\n" _required_runtime "${_required_runtime}")
file(WRITE "${PROJECT_BINARY_DIR}/required-runtime.txt" "${_required_runtime}\n")
# include vcredist into the package; NSIS will take care of running it
if(VCREDISTRUNTIME_FOUND)
install(FILES "${VCREDISTRUNTIME_FILE}" DESTINATION ./)
@ -330,7 +395,12 @@ include(CPack)
add_subdirectory(${CMAKE_SOURCE_DIR}/libcockatrice_interfaces ${CMAKE_BINARY_DIR}/libcockatrice_interfaces)
add_subdirectory(${CMAKE_SOURCE_DIR}/libcockatrice_protocol ${CMAKE_BINARY_DIR}/libcockatrice_protocol)
add_subdirectory(${CMAKE_SOURCE_DIR}/libcockatrice_network ${CMAKE_BINARY_DIR}/libcockatrice_network)
if(WITH_CLIENT
OR WITH_SERVER
OR WITH_ORACLE
)
add_subdirectory(${CMAKE_SOURCE_DIR}/libcockatrice_network ${CMAKE_BINARY_DIR}/libcockatrice_network)
endif()
add_subdirectory(${CMAKE_SOURCE_DIR}/libcockatrice_deck_list ${CMAKE_BINARY_DIR}/libcockatrice_deck_list)
add_subdirectory(${CMAKE_SOURCE_DIR}/libcockatrice_rng ${CMAKE_BINARY_DIR}/libcockatrice_rng)
add_subdirectory(${CMAKE_SOURCE_DIR}/libcockatrice_card ${CMAKE_BINARY_DIR}/libcockatrice_card)
@ -356,11 +426,6 @@ if(WITH_ORACLE)
set(CPACK_INSTALL_CMAKE_PROJECTS "Oracle;Oracle;ALL;/" ${CPACK_INSTALL_CMAKE_PROJECTS})
endif()
if(WITH_DBCONVERTER)
add_subdirectory(dbconverter)
set(CPACK_INSTALL_CMAKE_PROJECTS "Dbconverter;Dbconverter;ALL;/" ${CPACK_INSTALL_CMAKE_PROJECTS})
endif()
if(TEST)
include(CTest)
add_subdirectory(tests)

View file

@ -1,37 +1,49 @@
# -------- Build Stage --------
FROM ubuntu:24.04 AS build
FROM ubuntu:26.04 AS build
ARG DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
cmake \
file \
g++ \
git \
libmariadb-dev-compat \
libprotobuf-dev \
libqt6sql6-mysql \
qt6-websockets-dev \
protobuf-compiler \
qt6-tools-dev \
qt6-tools-dev-tools
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
build-essential \
cmake \
ninja-build \
file \
g++ \
git \
libmariadb-dev-compat \
libprotobuf-dev \
libqt6sql6-mysql \
libssl-dev \
qt6-websockets-dev \
protobuf-compiler \
qt6-tools-dev \
qt6-tools-dev-tools
WORKDIR /src
COPY . .
RUN mkdir build && cd build && \
cmake .. -DWITH_SERVER=1 -DWITH_CLIENT=0 -DWITH_ORACLE=0 -DWITH_DBCONVERTER=0 && \
make -j$(nproc) && \
make install
RUN cmake \
-S . \
-B build \
-G Ninja \
-DWITH_CLIENT=0 \
-DWITH_ORACLE=0 \
-DWITH_SERVER=1 \
&& cmake --build build \
&& cmake --install build
# -------- Runtime Stage (clean) --------
FROM ubuntu:24.04
FROM ubuntu:26.04
RUN apt-get update && apt-get install -y --no-install-recommends \
libprotobuf32t64 \
libqt6sql6-mysql \
libqt6websockets6 \
ARG DEBIAN_FRONTEND=noninteractive
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
libprotobuf32t64 \
libqt6sql6-mysql \
libqt6websockets6 \
libssl3 \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*

227
Doxyfile
View file

@ -1,4 +1,4 @@
# Doxyfile 1.14.0
# Doxyfile 1.18.0
# This file describes the settings to be used by the documentation system
# Doxygen (www.doxygen.org) for a project.
@ -11,7 +11,7 @@
# TAG = value [value, ...]
# For lists, items can also be appended using:
# TAG += value [value, ...]
# Values that contain spaces should be placed between quotes (\" \").
# Values that contain spaces should be placed between quotes (" ").
#
# Note:
#
@ -54,7 +54,7 @@ PROJECT_NUMBER = $(COCKATRICE_REF)
# for a project that appears at the top of each page and should give viewers a
# quick idea about the purpose of the project. Keep the description short.
PROJECT_BRIEF = "A cross-platform virtual tabletop for multiplayer card games"
PROJECT_BRIEF = "A virtual tabletop for multiplayer card games"
# With the PROJECT_LOGO tag one can specify a logo or an icon that is included
# in the documentation. The maximum height of the logo should not exceed 55
@ -81,7 +81,7 @@ OUTPUT_DIRECTORY = docs
# and will distribute the generated files over these directories. Enabling this
# option can be useful when feeding Doxygen a huge amount of source files, where
# putting all generated files in the same directory would otherwise cause
# performance problems for the file system. Adapt CREATE_SUBDIRS_LEVEL to
# performance problems for the file system. Adjust CREATE_SUBDIRS_LEVEL to
# control the number of sub-directories.
# The default value is: NO.
@ -349,7 +349,7 @@ OPTIMIZE_OUTPUT_SLICE = NO
#
# Note see also the list of default file extension mappings.
EXTENSION_MAPPING =
EXTENSION_MAPPING = proto=C++
# If the MARKDOWN_SUPPORT tag is enabled then Doxygen pre-processes all comments
# according to the Markdown format, which allows for more readable
@ -361,6 +361,20 @@ EXTENSION_MAPPING =
MARKDOWN_SUPPORT = YES
# If the MARKDOWN_STRICT tag is enabled then Doxygen treats text in comments as
# Markdown formatted also in cases where Doxygen's native markup format
# conflicts with that of Markdown. This is only relevant in cases where
# backticks are used. Doxygen's native markup style allows a single quote to end
# a text fragment started with a backtick and then treat it as a piece of quoted
# text, whereas in Markdown such text fragment is treated as verbatim and only
# ends when a second matching backtick is found. Also, Doxygen's native markup
# format requires double quotes to be escaped when they appear in a backtick
# section, whereas this is not needed for Markdown.
# The default value is: YES.
# This tag requires that the tag MARKDOWN_SUPPORT is set to YES.
MARKDOWN_STRICT = YES
# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up
# to that level are automatically included in the table of contents, even if
# they do not have an id attribute.
@ -392,8 +406,8 @@ AUTOLINK_SUPPORT = YES
# This tag specifies a list of words that, when matching the start of a word in
# the documentation, will suppress auto links generation, if it is enabled via
# AUTOLINK_SUPPORT. This list does not affect links explicitly created using \#
# or the \link or commands.
# AUTOLINK_SUPPORT. This list does not affect links explicitly created using #
# or the \link or \ref commands.
# This tag requires that the tag AUTOLINK_SUPPORT is set to YES.
AUTOLINK_IGNORE_WORDS =
@ -510,9 +524,9 @@ LOOKUP_CACHE_SIZE = 0
# which effectively disables parallel processing. Please report any issues you
# encounter. Generating dot graphs in parallel is controlled by the
# DOT_NUM_THREADS setting.
# Minimum value: 0, maximum value: 32, default value: 1.
# Minimum value: 0, maximum value: 512, default value: 1.
NUM_PROC_THREADS = 1
NUM_PROC_THREADS = 0
# If the TIMESTAMP tag is set different from NO then each generated page will
# contain the date or date and time when the page was generated. Setting this to
@ -779,6 +793,27 @@ GENERATE_BUGLIST = YES
GENERATE_DEPRECATEDLIST= YES
# The GENERATE_REQUIREMENTS tag can be used to enable (YES) or disable (NO) the
# requirements page. When enabled, this page is automatically created when at
# least one comment block with a \requirement command appears in the input.
# The default value is: YES.
GENERATE_REQUIREMENTS = YES
# The REQ_TRACEABILITY_INFO tag controls if traceability information is shown on
# the requirements page (only relevant when using \requirement comment blocks).
# The setting NO will disable the traceability information altogether. The
# setting UNSATISFIED_ONLY will show a list of requirements that are missing a
# satisfies relation (through the command: \satisfies). Similarly the setting
# UNVERIFIED_ONLY will show a list of requirements that are missing a verifies
# relation (through the command: \verifies). Setting the tag to YES (the
# default) will show both lists if applicable.
# Possible values are: YES, NO, UNSATISFIED_ONLY and UNVERIFIED_ONLY.
# The default value is: YES.
# This tag requires that the tag GENERATE_REQUIREMENTS is set to YES.
REQ_TRACEABILITY_INFO = YES
# The ENABLED_SECTIONS tag can be used to enable conditional documentation
# sections, marked by \if <section_label> ... \endif and \cond <section_label>
# ... \endcond blocks.
@ -820,8 +855,8 @@ SHOW_NAMESPACES = YES
# The FILE_VERSION_FILTER tag can be used to specify a program or script that
# Doxygen should invoke to get the current version for each file (typically from
# the version control system). Doxygen will invoke the program by executing (via
# popen()) the command command input-file, where command is the value of the
# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided
# popen()) the command command input_file, where command is the value of the
# FILE_VERSION_FILTER tag, and input_file is the name of an input file provided
# by Doxygen. Whatever the program writes to standard output is used as the file
# version. For an example see the documentation.
@ -1031,27 +1066,14 @@ INPUT_FILE_ENCODING =
# provided as Doxygen C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08,
# *.f18, *.f, *.for, *.vhd, *.vhdl, *.ucf, *.qsf and *.ice.
FILE_PATTERNS = *.cc \
*.cxx \
*.cxxm \
*.cpp \
FILE_PATTERNS = *.cpp \
*.cppm \
*.ccm \
*.c++ \
*.c++m \
*.ii \
*.ixx \
*.ipp \
*.i++ \
*.inl \
*.h \
*.hh \
*.hxx \
*.hpp \
*.h++ \
*.markdown \
*.md \
*.dox
*.dox \
*.proto
# The RECURSIVE tag can be used to specify whether or not subdirectories should
# be searched for input files as well.
@ -1068,9 +1090,10 @@ RECURSIVE = YES
EXCLUDE = build/ \
cmake/ \
dbconverter/ \
vcpkg/ \
webclient/
cmake-build-debug/ \
doc/doxygen/theme/docs/ \
doc/doxygen/theme/include/ \
vcpkg/
# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
# directories that are symbolic links (a Unix file system feature) are excluded
@ -1160,7 +1183,7 @@ INPUT_FILTER =
# need to set EXTENSION_MAPPING for the extension otherwise the files are not
# properly processed by Doxygen.
FILTER_PATTERNS =
FILTER_PATTERNS = "*.proto=python doc/doxygen/filters/proto2cpp.py"
# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
# INPUT_FILTER) will also be used to filter the input files that are used for
@ -1431,7 +1454,9 @@ HTML_STYLESHEET =
# documentation.
# This tag requires that the tag GENERATE_HTML is set to YES.
HTML_EXTRA_STYLESHEET = doc/doxygen/css/doxygen_style.css
HTML_EXTRA_STYLESHEET = doc/doxygen/theme/doxygen-awesome.css \
doc/doxygen/css/hide_nav_sync.css \
doc/doxygen/css/cockatrice_docs_style.css
# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or
# other source files which should be copied to the HTML output directory. Note
@ -1454,7 +1479,7 @@ HTML_EXTRA_FILES = doc/doxygen/js/graph_toggle.js
# The default value is: AUTO_LIGHT.
# This tag requires that the tag GENERATE_HTML is set to YES.
HTML_COLORSTYLE = AUTO_DARK
HTML_COLORSTYLE = LIGHT
# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen
# will adjust the colors in the style sheet and background images according to
@ -1604,8 +1629,8 @@ DOCSET_PUBLISHER_NAME = Publisher
# a.o. the download links, offline (the HTML help workshop was already many
# years in maintenance mode). You can download the HTML help workshop from the
# web archives at Installation executable (see:
# http://web.archive.org/web/20160201063255/http://download.microsoft.com/downlo
# ad/0/A/9/0A939EF6-E31C-430F-A3DF-DFAE7960D564/htmlhelp.exe).
# http://web.archive.org/web/20160201063255/https://download.microsoft.com/downl
# oad/0/A/9/0A939EF6-E31C-430F-A3DF-DFAE7960D564/htmlhelp.exe).
#
# The HTML Help Workshop contains a compiler that can convert all HTML output
# generated by Doxygen into a single compiled HTML file (.chm). Compiled HTML
@ -1765,7 +1790,7 @@ ECLIPSE_DOC_ID = org.doxygen.Project
# The default value is: NO.
# This tag requires that the tag GENERATE_HTML is set to YES.
DISABLE_INDEX = YES
DISABLE_INDEX = NO
# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
# structure should be generated to display hierarchical information. If the tag
@ -1884,7 +1909,7 @@ USE_MATHJAX = NO
# regards to the different settings, so it is possible that also other MathJax
# settings have to be changed when switching between the different MathJax
# versions.
# Possible values are: MathJax_2 and MathJax_3.
# Possible values are: MathJax_2, MathJax_3 and MathJax_4.
# The default value is: MathJax_2.
# This tag requires that the tag USE_MATHJAX is set to YES.
@ -1893,9 +1918,10 @@ MATHJAX_VERSION = MathJax_2
# When MathJax is enabled you can set the default output format to be used for
# the MathJax output. For more details about the output format see MathJax
# version 2 (see:
# http://docs.mathjax.org/en/v2.7-latest/output.html) and MathJax version 3
# https://docs.mathjax.org/en/v2.7/output.html), MathJax version 3 (see:
# https://docs.mathjax.org/en/v3.2/output/index.html) and MathJax version 4
# (see:
# http://docs.mathjax.org/en/latest/web/components/output.html).
# https://docs.mathjax.org/en/v4.0/output/index.htm).
# Possible values are: HTML-CSS (which is slower, but has the best
# compatibility. This is the name for Mathjax version 2, for MathJax version 3
# this will be translated into chtml), NativeMML (i.e. MathML. Only supported
@ -1908,36 +1934,50 @@ MATHJAX_VERSION = MathJax_2
MATHJAX_FORMAT = HTML-CSS
# When MathJax is enabled you need to specify the location relative to the HTML
# output directory using the MATHJAX_RELPATH option. The destination directory
# should contain the MathJax.js script. For instance, if the mathjax directory
# is located at the same level as the HTML output directory, then
# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax
# Content Delivery Network so you can quickly see the result without installing
# MathJax. However, it is strongly recommended to install a local copy of
# MathJax from https://www.mathjax.org before deployment. The default value is:
# output directory using the MATHJAX_RELPATH option. For Mathjax version 2 the
# destination directory should contain the MathJax.js script. For instance, if
# the mathjax directory is located at the same level as the HTML output
# directory, then MATHJAX_RELPATH should be ../mathjax. For Mathjax versions 3
# and 4 the destination directory should contain the tex-<format>.js script
# (where <format> is either chtml or svg). The default value points to the
# MathJax Content Delivery Network so you can quickly see the result without
# installing MathJax. However, it is strongly recommended to install a local
# copy of MathJax from https://www.mathjax.org before deployment. The default
# value is:
# - in case of MathJax version 2: https://cdn.jsdelivr.net/npm/mathjax@2
# - in case of MathJax version 3: https://cdn.jsdelivr.net/npm/mathjax@3
# - in case of MathJax version 4: https://cdn.jsdelivr.net/npm/mathjax@4
# This tag requires that the tag USE_MATHJAX is set to YES.
MATHJAX_RELPATH =
# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax
# extension names that should be enabled during MathJax rendering. For example
# for MathJax version 2 (see
# https://docs.mathjax.org/en/v2.7-latest/tex.html#tex-and-latex-extensions):
# for MathJax version 2 (see https://docs.mathjax.org/en/v2.7/tex.html):
# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols
# For example for MathJax version 3 (see
# http://docs.mathjax.org/en/latest/input/tex/extensions/index.html):
# https://docs.mathjax.org/en/v3.2/input/tex/extensions/):
# MATHJAX_EXTENSIONS = ams
# For example for MathJax version 4 (see
# https://docs.mathjax.org/en/v4.0/input/tex/extensions/):
# MATHJAX_EXTENSIONS = units
# Note that for Mathjax version 4 quite a few extensions are already
# automatically loaded. To disable a package in Mathjax version 4 one can use
# the package name prepended with a minus sign (- like MATHJAX_EXTENSIONS +=
# -textmacros)
# This tag requires that the tag USE_MATHJAX is set to YES.
MATHJAX_EXTENSIONS =
# The MATHJAX_CODEFILE tag can be used to specify a file with JavaScript pieces
# of code that will be used on startup of the MathJax code. See the MathJax site
# (see:
# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an
# example see the documentation.
# of code that will be used on startup of the MathJax code. See the Mathjax site
# for more details:
# - MathJax version 2 (see:
# https://docs.mathjax.org/en/v2.7/)
# - MathJax version 3 (see:
# https://docs.mathjax.org/en/v3.2/)
# - MathJax version 4 (see:
# https://docs.mathjax.org/en/v4.0/) For an example see the documentation.
# This tag requires that the tag USE_MATHJAX is set to YES.
MATHJAX_CODEFILE =
@ -2118,9 +2158,8 @@ EXTRA_PACKAGES =
#
# Note: Only use a user-defined header if you know what you are doing!
# Note: The header is subject to change so you typically have to regenerate the
# default header when upgrading to a newer version of Doxygen. The following
# commands have a special meaning inside the header (and footer): For a
# description of the possible markers and block names see the documentation.
# default header when upgrading to a newer version of Doxygen. For a description
# of the possible markers and block names see the documentation.
# This tag requires that the tag GENERATE_LATEX is set to YES.
LATEX_HEADER =
@ -2598,11 +2637,24 @@ HAVE_DOT = YES
# processors available in the system. You can set it explicitly to a value
# larger than 0 to get control over the balance between CPU load and processing
# speed.
# Minimum value: 0, maximum value: 32, default value: 0.
# Minimum value: 0, maximum value: 512, default value: 0.
# This tag requires that the tag HAVE_DOT is set to YES.
DOT_NUM_THREADS = 0
# The DOT_BATCH_SIZE specifies the number of dot graphs Doxygen is allowed to
# compile in a single invocation of dot. When set to 1 Doxygen will invoke dot
# for each graph separately, which can cause significant process creation
# overhead especially on systems with many CPU cores. Together with
# DOT_NUM_THREADS this setting can be used to optimise the dot processing speed
# for a particular system. Doxygen will try to give each thread a balanced batch
# of work. If the total number of graphs to process exceeds DOT_NUM_THREADS *
# DOT_BATCH_SIZE then additional batches will be created for dot to process.
# Minimum value: 1, maximum value: 1000, default value: 50.
# This tag requires that the tag HAVE_DOT is set to YES.
DOT_BATCH_SIZE = 50
# DOT_COMMON_ATTR is common attributes for nodes, edges and labels of
# subgraphs. When you want a differently looking font in the dot files that
# Doxygen generates you can specify fontname, fontcolor and fontsize attributes.
@ -2894,6 +2946,58 @@ PLANTUML_INCLUDE_PATH =
PLANTUMLFILE_DIRS =
# When using Mermaid diagrams with CLI rendering, the MERMAID_PATH tag should be
# used to specify the directory where the mmdc (Mermaid CLI) executable can be
# found. If left blank, CLI-based rendering is disabled. For HTML output,
# client-side rendering via JavaScript is used by default and does not require
# mmdc. For LaTeX/PDF output, mmdc is required to pre-generate images. Doxygen
# will generate a warning when CLI rendering is needed but mmdc is not
# available.
MERMAID_PATH =
# When using Mermaid diagrams, the MERMAID_CONFIG_FILE tag can be used to
# specify a JSON configuration file for the Mermaid CLI tool (mmdc). This file
# can contain theme settings and other Mermaid configuration options.
MERMAID_CONFIG_FILE =
# The MERMAID_RENDER_MODE tag selects how Mermaid diagrams are rendered.
# Possible values are: AUTO (use client-side rendering for HTML and mmdc for
# LaTeX/PDF and other formats. If MERMAID_PATH is not set, non-HTML diagrams
# will produce a warning), CLI (use the mmdc tool to pre-generate images
# (requires Node.js and mermaid-js/mermaid-cli). Works for all output formats)
# and CLIENT_SIDE (embed mermaid.js in HTML output for client-side rendering.
# Does not require mmdc but only works for HTML output).
# The default value is: AUTO.
MERMAID_RENDER_MODE = AUTO
# The MERMAID_JS_URL tag specifies the URL to load mermaid.js from when using
# client-side rendering (MERMAID_RENDER_MODE is CLIENT_SIDE or AUTO). The
# default points to the latest Mermaid v11 release on the jsDelivr CDN.
#
# The default CDN URL requires internet access when viewing the generated
# documentation. For offline use, download mermaid.esm.min.mjs and set this to a
# relative path, or use MERMAID_RENDER_MODE=CLI to pre-generate images instead.
# Examples:
# - Latest v11 (default):
# https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs
# - Pinned version:
# https://cdn.jsdelivr.net/npm/mermaid@11.3.0/dist/mermaid.esm.min.mjs
# - Local copy: ./mermaid.esm.min.mjs (user must place file in HTML output
# directory)
# The default value is:
# https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs.
MERMAID_JS_URL = https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs
# The MERMAIDFILE_DIRS tag can be used to specify one or more directories that
# contain Mermaid files that are included in the documentation (see the
# \mermaidfile command).
MERMAIDFILE_DIRS =
# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes
# that will be shown in the graph. If the number of nodes in a graph becomes
# larger than this value, Doxygen will truncate the graph, which is visualized
@ -2918,15 +3022,6 @@ DOT_GRAPH_MAX_NODES = 50
MAX_DOT_GRAPH_DEPTH = 0
# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output
# files in one run (i.e. multiple -o and -T options on the command line). This
# makes dot run faster, but since only newer versions of dot (>1.8.10) support
# this, this feature is disabled by default.
# The default value is: NO.
# This tag requires that the tag HAVE_DOT is set to YES.
DOT_MULTI_TARGETS = NO
# If the GENERATE_LEGEND tag is set to YES Doxygen will generate a legend page
# explaining the meaning of the various boxes and arrows in the dot generated
# graphs.

View file

@ -8,7 +8,7 @@
<a href="#related-repositories">Related</a> <b>|</b>
<a href="#community-resources-">Community</a> <b>|</b>
<a href="#contribute">Contribute</a> <b>|</b>
<a href="#build---">Build</a> <b>|</b>
<a href="#build--">Build</a> <b>|</b>
<a href="#run">Run</a>
</p>
@ -25,7 +25,6 @@
Cockatrice is an open-source, multiplatform application for playing tabletop card games over a network. The program's server design prevents users from manipulating the game for unfair advantage. The client also provides a single-player mode, which allows users to brew while offline.<br><br>
This project uses <kbd>C++</kbd> and the <kbd>Qt</kbd> libraries.<br>
First work on a webclient with <kbd>Typescript</kbd> was started as well.<br>
# Download [![Cockatrice Eternal Download Count](https://img.shields.io/github/downloads/cockatrice/cockatrice/total.svg)](https://tooomm.github.io/github-release-stats/?username=Cockatrice&repository=Cockatrice&search=0)
@ -44,24 +43,43 @@ Latest <kbd>beta</kbd> version:
# Related Repositories
- [Magic-Token](https://github.com/Cockatrice/Magic-Token): MtG token data to use in Cockatrice
- [Magic-Spoiler](https://github.com/Cockatrice/Magic-Spoiler): Script to generate MtG spoiler data from [MTGJSON](https://github.com/mtgjson/mtgjson) to use in Cockatrice
- [cockatrice.github.io](https://github.com/Cockatrice/cockatrice.github.io): Code of the official webpage of the Cockatrice project
- [Magic-Token](https://github.com/Cockatrice/Magic-Token): File with MtG token data for use in Cockatrice
- [Magic-Spoiler](https://github.com/Cockatrice/Magic-Spoiler): Code to generate MtG spoiler data from [MTGJSON](https://github.com/mtgjson/mtgjson) for use in Cockatrice
- [cockatrice.github.io](https://github.com/Cockatrice/cockatrice.github.io): Code of the official Cockatrice webpage
- [io.github.Cockatrice.cockatrice](https://github.com/flathub/io.github.Cockatrice.cockatrice): Configuration of our Linux `flatpak` package hosted at [Flathub](https://flathub.org/en/apps/io.github.Cockatrice.cockatrice)
- [Webatrice](https://github.com/Cockatrice/Webatrice): Web client for Cockatrice servers (TypeScript / React)
# Community Resources [![Discord](https://img.shields.io/discord/314987288398659595?label=Discord&logo=discord&logoColor=white)](https://discord.gg/3Z9yzmA)
Join our [Discord community](https://discord.gg/3Z9yzmA) to connect with other projet contributors (`#dev` channel) or fellow users of the app. Come here to talk about the application, features, or just to hang out.
Join our [Discord community](https://discord.gg/3Z9yzmA) to connect with other project contributors (`#dev` channel) or fellow users of the app. Come here to talk about the application, features, or just to hang out.
- [Official Website](https://cockatrice.github.io)
- [Official Wiki](https://github.com/Cockatrice/Cockatrice/wiki)
- [Official Discord](https://discord.gg/3Z9yzmA)
- [reddit r/Cockatrice](https://reddit.com/r/cockatrice)
>[!IMPORTANT]
>For support regarding specific servers, please contact that server's admin/mods and use their dedicated communication channels rather than contacting the team building the software.
> [!IMPORTANT]
> For support regarding specific servers, please contact that server's admin/mods and use their dedicated communication channels rather than contacting the team building the software.
# Contribute
<p>
<a href="#code">Code</a> <b>|</b>
<a href="#documentation-">Documentation</a> <b>|</b>
<a href="#translation-">Translation</a>
</p>
#### Repository Activity
![Cockatrice Repo Analytics](https://repobeats.axiom.co/api/embed/c7cec938789a5bbaeb4182a028b4dbb96db8f181.svg "Cockatrice Repo Analytics by Repobeats")
<details>
<summary><b>Kudos to all our amazing contributors ❤️</b></summary>
<br>
<a href="https://github.com/Cockatrice/Cockatrice/graphs/contributors">
<img src="https://contrib.rocks/image?repo=Cockatrice/Cockatrice" />
</a><br>
<sub><i>Made with <a href="https://contrib.rocks">contrib.rocks</a></i></sub>
</details>
### Code
@ -75,25 +93,27 @@ This tag is used for issues that we are looking for somebody to pick up. Often t
For both tags, we're willing to provide help to contributors in showing them where and how they can make changes, as well as code reviews for submitted changes.<br>
We'll happily advice on how best to implement a feature, or we can show you where the codebase is doing something similar before you get too far along - put a note on an issue you want to discuss more on!
You can also have a look at our `Todo List` in our [Code Documentation](https://cockatrice.github.io/docs) or search the repo for [`\todo` comments](https://github.com/search?q=repo%3ACockatrice%2FCockatrice%20%5Ctodo&type=code).
### Documentation [![CI Docs](https://github.com/Cockatrice/Cockatrice/actions/workflows/documentation-build.yml/badge.svg?event=push)](https://github.com/Cockatrice/Cockatrice/actions/workflows/documentation-build.yml?query=event%3Apush)
There are various places where useful information for different needs are maintained:
- [Official Code Documentation](https://cockatrice.github.io/docs/)
- [Official Wiki](https://github.com/Cockatrice/Cockatrice/wiki) `Community supported`
- [Official Webpage](https://cockatrice.github.io/)
- [Official README](https://github.com/Cockatrice/Cockatrice/blob/master/README.md) `This file`
- [Official CONTRIBUTING](https://github.com/Cockatrice/Cockatrice/blob/master/.github/CONTRIBUTING.md)
Cockatrice tries to use the [Google Developer Documentation Style Guide](https://developers.google.com/style/) to ensure consistent documentation. We encourage you to improve the documentation by suggesting edits based on this guide.
<details>
<summary><b>Kudos to our amazing contributors ❤️</b></summary>
<br>
<a href="https://github.com/Cockatrice/Cockatrice/graphs/contributors">
<img src="https://contrib.rocks/image?repo=Cockatrice/Cockatrice" />
</a><br>
<sub><i>Made with <a href="https://contrib.rocks">contrib.rocks</a></i></sub>
</details>
### Translation [![Transifex Project](https://img.shields.io/badge/translate-on%20transifex-brightgreen)](https://explore.transifex.com/cockatrice/cockatrice/)
### Translations [![Transifex Project](https://img.shields.io/badge/translate-on%20transifex-brightgreen)](https://explore.transifex.com/cockatrice/cockatrice/)
Cockatrice uses Transifex to manage translations. You can help us bring <kbd>Cockatrice</kbd> and <kbd>Oracle</kbd> to your language and just adjust single wordings right from within your browser by visiting our [Transifex project page](https://explore.transifex.com/cockatrice/cockatrice/). The [Webatrice](https://github.com/seavor/Webatrice) web client manages its own translations in its repo.<br>
Cockatrice uses Transifex to manage translations. You can help us bring <kbd>Cockatrice</kbd>, <kbd>Oracle</kbd> and <kbd>Webatrice</kbd> to your language and just adjust single wordings right from within your browser by visiting our [Transifex project page](https://explore.transifex.com/cockatrice/cockatrice/).<br>
Check out our [Translator FAQ](https://github.com/Cockatrice/Cockatrice/wiki/Translation-FAQ) for more information about getting invovled, and join a group of hundreds of others!<br>
Check out our [Translator FAQ](https://github.com/Cockatrice/Cockatrice/wiki/Translation-FAQ) for more information about getting involved, and join a group of hundreds of others!<br>
# Build [![CI Desktop](https://github.com/Cockatrice/Cockatrice/actions/workflows/desktop-build.yml/badge.svg?branch=master&event=push)](https://github.com/Cockatrice/Cockatrice/actions/workflows/desktop-build.yml?query=branch%3Amaster+event%3Apush) [![CI Docker](https://github.com/Cockatrice/Cockatrice/actions/workflows/docker-release.yml/badge.svg?branch=master&event=push)](https://github.com/Cockatrice/Cockatrice/actions/workflows/docker-release.yml?query=branch%3Amaster+event%3Apush) [![CI Web](https://github.com/Cockatrice/Cockatrice/actions/workflows/web-build.yml/badge.svg?branch=master&event=push)](https://github.com/Cockatrice/Cockatrice/actions/workflows/web-build.yml?query=branch%3Amaster+event%3Apush)
# Build [![CI Desktop](https://github.com/Cockatrice/Cockatrice/actions/workflows/desktop-build.yml/badge.svg?branch=master&event=push)](https://github.com/Cockatrice/Cockatrice/actions/workflows/desktop-build.yml?query=branch%3Amaster+event%3Apush) [![CI Docker](https://github.com/Cockatrice/Cockatrice/actions/workflows/docker-release.yml/badge.svg?branch=master&event=push)](https://github.com/Cockatrice/Cockatrice/actions/workflows/docker-release.yml?query=branch%3Amaster+event%3Apush)
Dependencies: *(for minimum versions search our [CMake file](https://github.com/Cockatrice/Cockatrice/blob/master/CMakeLists.txt))*
- [Qt](https://www.qt.io/developers/)
@ -124,22 +144,27 @@ You can then
make package
```
>[!NOTE]
>Detailed compiling instructions can be found in the Cockatrice wiki at [Compiling Cockatrice](https://github.com/Cockatrice/Cockatrice/wiki/Compiling-Cockatrice)
> [!NOTE]
> Detailed compiling instructions can be found in the Cockatrice wiki at [Compiling Cockatrice](https://github.com/Cockatrice/Cockatrice/wiki/Compiling-Cockatrice)
<br>
The following flags (with their non-default values) can be passed to `cmake`:
| Flag | Description |
| --- | --- |
| `-DWITH_SERVER=1` | Build <kbd>Servatrice</kbd> server |
| `-DWITH_CLIENT=0` | Don't build <kbd>Cockatrice</kbd> client |
| `-DWITH_ORACLE=0` | Don't build <kbd>Oracle</kbd> card database tool |
| `-DCMAKE_BUILD_TYPE=Debug` | Compile in debug mode<br> Enables extra logging output, debug symbols, and much more verbose compiler warnings |
| `-DWARNING_AS_ERROR=0` | Don't treat compilation warnings as errors in debug mode |
| `-DUPDATE_TRANSLATIONS=1` | Configure `make` to update the translation .ts files for new strings in the source code<br> **Note:** `make clean` will remove the .ts files |
| `-DTEST=1` | Enable regression tests<br> **Note:** `make test` to run tests, *googletest* will be downloaded if not available |
| `-DFORCE_USE_QT5=1` | Skip looking for Qt6 before trying to find Qt5 |
| Flag | Description |
| -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| `-DWITH_SERVER=1` | Build <kbd>Servatrice</kbd> server |
| `-DWITH_CLIENT=0` | Don't build <kbd>Cockatrice</kbd> client |
| `-DWITH_ORACLE=0` | Don't build <kbd>Oracle</kbd> card database tool |
| `-DCMAKE_BUILD_TYPE=Debug` | Compile in debug mode<br> Enables extra logging output, debug symbols, and much more verbose compiler warnings |
| `-DWARNING_AS_ERROR=0` | Don't treat compilation warnings as errors in debug mode |
| `-DUPDATE_TRANSLATIONS=1` | Configure `make` to update the translation .ts files for new strings in the source code<br> **Note:** `make clean` will remove the .ts files |
| `-DTEST=1` | Enable regression tests<br> **Note:** `make test` to run tests, *googletest* will be downloaded if not available |
> [!TIP]
> Build code documentation ([Doxygen](https://www.doxygen.nl)) according to our [configuration](https://github.com/Cockatrice/Cockatrice/blob/master/Doxyfile) into `/docs`:
> ```bash
> doxygen Doxyfile
> ```
# Run

View file

@ -42,7 +42,6 @@ tell disk image_name
set position of item "Cockatrice.app" to { 139, 214 }
set position of item "Oracle.app" to { 139, 414 }
set position of item "Servatrice.app" to { 139, 614 }
set position of item "dbconverter.app" to { 1400, 1400 }
set position of item "Applications" to { 861, 414 }
end tell
update without registering applications

75
cmake/Coverage.cmake Normal file
View file

@ -0,0 +1,75 @@
# Line-coverage instrumentation opt-in, plus per-source opt-out
# Input: ENABLE_COVERAGE
# Defines: cockatrice_disable_coverage_for
#
# Included once from the root CMakeLists.txt, immediately after the per-compiler flag
# block, so add_compile_options() reaches every library and executable defined below.
#
# Note the name deliberately shadows CMake's own built-in Modules/Coverage.cmake. It resolves
# to this file only because the root CMakeLists.txt puts cmake/ at the front of
# CMAKE_MODULE_PATH; if that ever moves, include(Coverage) would silently pick up the built-in
# module, which only defines a `COVERAGE` custom target and touches no global flags, so
# instrumentation would quietly stop happening rather than erroring.
include(CheckCXXSourceCompiles)
# Opt a set of vendored sources out of instrumentation.
#
# cockatrice_disable_coverage_for(<source> ...)
#
# Third-party code drags the total down with lines no test can ever reach, and a total that
# cannot move is not a useful signal. This is the same carve-out the sanitizer build uses.
#
# The sources must be named relative to the directory that compiles them. CMake resolves a
# source-file property against the directory scope the target was declared in, so calling
# this from the root with absolute paths silently does nothing. That is why the call sites
# live next to the add_library/add_executable that pull the vendored code in.
#
# SKIP_PRECOMPILE_HEADERS mirrors the sanitizer helper. The precompiled header carries the
# instrumentation flags, and mixing an instrumented PCH with an uninstrumented translation
# unit makes GCC reject it.
function(cockatrice_disable_coverage_for)
if(NOT ENABLE_COVERAGE)
return()
endif()
# There is no -fno-coverage: --coverage is shorthand for -fprofile-arcs -ftest-coverage, so both
# halves have to be negated. CC rejects "-fno-coverage" outright, which is how the vendored C
# files below hit it first.
set_source_files_properties(
${ARGN} PROPERTIES COMPILE_OPTIONS "-fno-profile-arcs;-fno-test-coverage" SKIP_PRECOMPILE_HEADERS ON
)
endfunction()
if(ENABLE_COVERAGE)
# Line coverage needs the compiler's own toolchain, so only GCC is supported. Clang's
# equivalent is llvm-cov, which reads a different intermediate format and would need a
# separate report path; rejecting it is better than emitting .gcno files that gcovr cannot
# read.
if(NOT CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
message(FATAL_ERROR "ENABLE_COVERAGE requires GCC, but the compiler is ${CMAKE_CXX_COMPILER_ID}. "
"Clang coverage needs llvm-cov and is not wired up."
)
endif()
# Probe the way the flag will actually be used: compile AND link. A .gcno file comes from the
# compile step, but the runtime that writes .gcda files comes from the link step, so probing
# only the compile step would pass on a toolchain that cannot actually produce coverage.
set(_cockatrice_saved_required_flags "${CMAKE_REQUIRED_FLAGS}")
set(_cockatrice_saved_required_link_options "${CMAKE_REQUIRED_LINK_OPTIONS}")
set(CMAKE_REQUIRED_FLAGS "-O0 -g")
set(CMAKE_REQUIRED_LINK_OPTIONS -fprofile-arcs -ftest-coverage)
check_cxx_source_compiles("int main() { return 0; }" COCKATRICE_HAVE_COVERAGE_FLAGS)
set(CMAKE_REQUIRED_FLAGS "${_cockatrice_saved_required_flags}")
set(CMAKE_REQUIRED_LINK_OPTIONS "${_cockatrice_saved_required_link_options}")
if(NOT COCKATRICE_HAVE_COVERAGE_FLAGS)
message(FATAL_ERROR "ENABLE_COVERAGE is on, but this toolchain rejected the coverage flags.")
endif()
# -O0 keeps the line mapping honest: at higher optimisation levels the compiler merges and
# relocates lines, and the report describes code that no longer exists in that form.
add_compile_options(--coverage -O0 -g)
add_link_options(--coverage)
message(STATUS "Coverage instrumentation enabled (--coverage). Report with gcovr over the build dir.")
endif()

View file

@ -1,14 +1,18 @@
# Find a compatible Qt version
# Inputs: WITH_SERVER, WITH_CLIENT, WITH_ORACLE, WITH_DBCONVERTER, FORCE_USE_QT5
# Inputs: WITH_SERVER, WITH_CLIENT, WITH_ORACLE
# Optional Input: QT6_DIR -- Hint as to where Qt6 lives on the system
# Optional Input: QT5_DIR -- Hint as to where Qt5 lives on the system
# Output: COCKATRICE_QT_VERSION_NAME -- Example values: Qt5, Qt6
# Output: COCKATRICE_QT_VERSION_NAME -- Example values: Qt6
# Output: SERVATRICE_QT_MODULES
# Output: COCKATRICE_QT_MODULES
# Output: ORACLE_QT_MODULES
# Output: DBCONVERTER_QT_MODULES
# Output: TEST_QT_MODULES
# Every Qt module an application links against, as the DLL names a Windows
# install has to contain. Core is listed explicitly rather than left to arrive
# transitively: it is the one module nothing can do without, and required-runtime.txt
# is built from these lists, so a module missing here is a runtime the package
# check never asserts. That is how Qt6Core.dll went unasserted while every other
# Qt module was checked.
set(REQUIRED_QT_COMPONENTS Core)
if(WITH_SERVER)
set(_SERVATRICE_NEEDED Network Sql WebSockets)
@ -16,94 +20,67 @@ endif()
if(WITH_CLIENT)
set(_COCKATRICE_NEEDED
Concurrent
Core
Gui
Multimedia
Network
PrintSupport
ShaderTools
Svg
WebSockets
Widgets
Xml
Quick
QuickWidgets
)
endif()
if(WITH_ORACLE)
set(_ORACLE_NEEDED Concurrent Network Svg Widgets)
endif()
if(WITH_DBCONVERTER)
set(_DBCONVERTER_NEEDED Network Widgets)
set(_ORACLE_NEEDED Concurrent Network Svg Widgets Xml)
endif()
if(TEST)
set(_TEST_NEEDED Widgets)
# Union of Qt modules required across all test targets (independent of application targets).
# When adding a new test that needs additional Qt modules, add them here rather than in the test's CMakeLists.txt.
# Test is QtTest: it brings QAbstractItemModelTester and QSignalSpy, which are how the Qt model
# and signal contracts get verified without standing up a GUI.
set(_TEST_NEEDED Concurrent Network Svg Widgets Test)
endif()
set(REQUIRED_QT_COMPONENTS ${REQUIRED_QT_COMPONENTS} ${_SERVATRICE_NEEDED} ${_COCKATRICE_NEEDED} ${_ORACLE_NEEDED}
${_DBCONVERTER_NEEDED} ${_TEST_NEEDED}
${_TEST_NEEDED}
)
list(REMOVE_DUPLICATES REQUIRED_QT_COMPONENTS)
if(NOT FORCE_USE_QT5)
# Linguist is now a component in Qt6 instead of an external package
find_package(
Qt6 6.2.3
COMPONENTS ${REQUIRED_QT_COMPONENTS} Linguist
QUIET HINTS ${Qt6_DIR}
)
# Linguist is now a component in Qt6 instead of an external package
find_package(
Qt6 6.4.2
COMPONENTS ${REQUIRED_QT_COMPONENTS} Linguist
QUIET HINTS ${Qt6_DIR}
)
if(NOT Qt6_FOUND)
message(FATAL_ERROR "No suitable version of Qt was found")
endif()
if(Qt6_FOUND)
set(COCKATRICE_QT_VERSION_NAME Qt6)
set(COCKATRICE_QT_VERSION_NAME Qt6)
list(FIND Qt6LinguistTools_TARGETS Qt6::lrelease QT6_LRELEASE_INDEX)
if(QT6_LRELEASE_INDEX EQUAL -1)
message(WARNING "Qt6 lrelease not found.")
endif()
list(FIND Qt6LinguistTools_TARGETS Qt6::lupdate QT6_LUPDATE_INDEX)
if(QT6_LUPDATE_INDEX EQUAL -1)
message(WARNING "Qt6 lupdate not found.")
endif()
else()
find_package(
Qt5 5.8.0
COMPONENTS ${REQUIRED_QT_COMPONENTS}
QUIET HINTS ${Qt5_DIR}
)
if(Qt5_FOUND)
set(COCKATRICE_QT_VERSION_NAME Qt5)
else()
message(FATAL_ERROR "No suitable version of Qt was found")
endif()
# Qt5 Linguist is in a separate package
find_package(Qt5LinguistTools QUIET)
if(Qt5LinguistTools_FOUND)
if(NOT Qt5_LRELEASE_EXECUTABLE)
message(WARNING "Qt5 lrelease not found.")
endif()
if(NOT Qt5_LUPDATE_EXECUTABLE)
message(WARNING "Qt5 lupdate not found.")
endif()
else()
message(WARNING "Linguist Tools not found, cannot handle translations")
endif()
list(FIND Qt6LinguistTools_TARGETS Qt6::lrelease QT6_LRELEASE_INDEX)
if(QT6_LRELEASE_INDEX EQUAL -1)
message(WARNING "Qt6 lrelease not found.")
endif()
if(Qt5_POSITION_INDEPENDENT_CODE OR Qt6_FOUND)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
list(FIND Qt6LinguistTools_TARGETS Qt6::lupdate QT6_LUPDATE_INDEX)
if(QT6_LUPDATE_INDEX EQUAL -1)
message(WARNING "Qt6 lupdate not found.")
endif()
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
# Establish Qt Plugins directory & Library directories
get_target_property(QT_LIBRARY_DIR ${COCKATRICE_QT_VERSION_NAME}::Core LOCATION)
get_filename_component(QT_LIBRARY_DIR ${QT_LIBRARY_DIR} DIRECTORY)
if(Qt6_FOUND)
get_filename_component(QT_PLUGINS_DIR "${Qt6Core_DIR}/../../../${QT6_INSTALL_PLUGINS}" ABSOLUTE)
get_filename_component(QT_LIBRARY_DIR "${QT_LIBRARY_DIR}/../../.." ABSOLUTE)
if(UNIX AND APPLE)
# Mac needs a bit more help finding all necessary components
list(APPEND QT_LIBRARY_DIR "/usr/local/lib")
endif()
elseif(Qt5_FOUND)
get_filename_component(QT_PLUGINS_DIR "${Qt5Core_DIR}/../../../plugins" ABSOLUTE)
get_filename_component(QT_LIBRARY_DIR "${QT_LIBRARY_DIR}/.." ABSOLUTE)
get_filename_component(QT_PLUGINS_DIR "${Qt6Core_DIR}/../../../${QT6_INSTALL_PLUGINS}" ABSOLUTE)
get_filename_component(QT_LIBRARY_DIR "${QT_LIBRARY_DIR}/../../.." ABSOLUTE)
if(UNIX AND APPLE)
# Mac needs a bit more help finding all necessary components
list(APPEND QT_LIBRARY_DIR "/usr/local/lib")
endif()
message(DEBUG "QT_PLUGINS_DIR = ${QT_PLUGINS_DIR}")
message(DEBUG "QT_LIBRARY_DIR = ${QT_LIBRARY_DIR}")
@ -112,7 +89,6 @@ message(DEBUG "QT_LIBRARY_DIR = ${QT_LIBRARY_DIR}")
string(REGEX REPLACE "([^;]+)" "${COCKATRICE_QT_VERSION_NAME}::\\1" SERVATRICE_QT_MODULES "${_SERVATRICE_NEEDED}")
string(REGEX REPLACE "([^;]+)" "${COCKATRICE_QT_VERSION_NAME}::\\1" COCKATRICE_QT_MODULES "${_COCKATRICE_NEEDED}")
string(REGEX REPLACE "([^;]+)" "${COCKATRICE_QT_VERSION_NAME}::\\1" ORACLE_QT_MODULES "${_ORACLE_NEEDED}")
string(REGEX REPLACE "([^;]+)" "${COCKATRICE_QT_VERSION_NAME}::\\1" DB_CONVERTER_QT_MODULES "${_DBCONVERTER_NEEDED}")
string(REGEX REPLACE "([^;]+)" "${COCKATRICE_QT_VERSION_NAME}::\\1" TEST_QT_MODULES "${_TEST_NEEDED}")
# Core-only export (useful for headless libs)

View file

@ -1,38 +1,118 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<!-- ========================= -->
<!-- CORE BUNDLE METADATA -->
<!-- ========================= -->
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleExecutable</key>
<string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string>
<key>CFBundleGetInfoString</key>
<string>${MACOSX_BUNDLE_INFO_STRING}</string>
<key>CFBundleIconFile</key>
<string>${MACOSX_BUNDLE_ICON_FILE}</string>
<key>CFBundleIdentifier</key>
<string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleLongVersionString</key>
<string>${MACOSX_BUNDLE_LONG_VERSION_STRING}</string>
<key>CFBundleName</key>
<string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string>
<key>CSResourcesFileMapped</key>
<true/>
<key>LSRequiresCarbon</key>
<true/>
<key>NSHumanReadableCopyright</key>
<string>${MACOSX_BUNDLE_COPYRIGHT}</string>
<key>NSHighResolutionCapable</key>
<true/>
<!-- ========================= -->
<!-- FILE TYPE (.cod) SUPPORT -->
<!-- ========================= -->
<key>UTExportedTypeDeclarations</key>
<array>
<dict>
<key>UTTypeIdentifier</key>
<string>org.cockatrice.deck</string>
<key>UTTypeDescription</key>
<string>Cockatrice Deck</string>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
</array>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>cod</string>
</array>
</dict>
</dict>
</array>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeName</key>
<string>Cockatrice Deck</string>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>LSHandlerRank</key>
<string>Default</string>
<key>LSItemContentTypes</key>
<array>
<string>org.cockatrice.deck</string>
</array>
</dict>
</array>
<!-- ========================= -->
<!-- URL SCHEME (cockatrice://) -->
<!-- ========================= -->
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>Cockatrice URL Scheme</string>
<key>CFBundleURLSchemes</key>
<array>
<string>cockatrice</string>
</array>
</dict>
</array>
</dict>
</plist>

File diff suppressed because it is too large Load diff

109
cmake/Sanitizers.cmake Normal file
View file

@ -0,0 +1,109 @@
# AddressSanitizer + UndefinedBehaviorSanitizer opt-in, plus per-source opt-out
# Input: ENABLE_SANITIZERS
# Defines: cockatrice_disable_sanitizers_for
#
# Included once from the root CMakeLists.txt, immediately after the per-compiler flag
# block, so add_compile_options() reaches every library and executable defined below.
include(CheckCXXSourceCompiles)
# Opt a set of vendored sources out of instrumentation.
#
# cockatrice_disable_sanitizers_for(<source> ...)
#
# Sanitizer findings inside vendored code are not actionable for us, and letting them
# through buries our own bugs in noise until everyone learns to skip the job.
#
# The sources must be named relative to the directory that compiles them. CMake resolves a
# source-file property against the directory scope the target was declared in, so calling
# this from the root with absolute paths silently does nothing. That is why the call sites
# live next to the add_library/add_executable that pull the vendored code in.
#
# SKIP_PRECOMPILE_HEADERS is not optional. The precompiled header is built with the
# sanitizer flags, so it defines __SANITIZE_ADDRESS__; a translation unit compiled with
# -fno-sanitize=all does not, and GCC then refuses the PCH with
# "not used because __SANITIZE_ADDRESS__ not defined", which is a fatal -Werror=invalid-pch
# in the servatrice and oracle builds.
function(cockatrice_disable_sanitizers_for)
if(NOT ENABLE_SANITIZERS)
return()
endif()
set_source_files_properties(${ARGN} PROPERTIES COMPILE_OPTIONS "-fno-sanitize=all" SKIP_PRECOMPILE_HEADERS ON)
endfunction()
if(ENABLE_SANITIZERS)
# Probe a flag the way it will actually be used: on the compile step AND the link step.
# check_cxx_compiler_flag() only probes the compile step, which makes every sanitizer
# flag look unsupported because linking an ASan object without -fsanitize=address leaves
# __asan_init undefined. That is a silent misconfiguration rather than an error, so the
# probe has to link.
function(cockatrice_probe_flag FLAG VARNAME)
set(CMAKE_REQUIRED_FLAGS "${FLAG}")
set(CMAKE_REQUIRED_LINK_OPTIONS "${FLAG}")
check_cxx_source_compiles("int main() { return 0; }" ${VARNAME})
unset(CMAKE_REQUIRED_FLAGS)
unset(CMAKE_REQUIRED_LINK_OPTIONS)
endfunction()
set(SANITIZER_FLAGS
-fsanitize=address,undefined
-fno-omit-frame-pointer
# UBSan defaults to recover mode: it prints "runtime error: ..." but the
# process exits 0, so gtest/ctest report a pass and CI stays green.
# -fno-sanitize-recover makes the first finding abort instead. This is
# probed like the other cosmetic flags, but CI also sets
# UBSAN_OPTIONS=halt_on_error=1 as the runtime-side guarantee.
-fno-sanitize-recover=undefined
# Qt builds QObject subclasses into raw storage and writes the vptr afterwards, which
# the vptr check reports on essentially every QObject instantiation. The check has no
# value for us because nothing outside moc-generated code inherits QObject, so it is
# off outright rather than suppressed piecemeal.
-fno-sanitize=vptr
)
# MSVC has no ASan/UBSan equivalent, and clang-cl would need /fsanitize=address plus a
# matching runtime link, which is a separate piece of work.
if(MSVC)
message(FATAL_ERROR "ENABLE_SANITIZERS is not supported with MSVC")
endif()
# Probe rather than assume, so an older toolchain gets the sanitizers it can support
# instead of failing the build on a flag it does not recognize.
set(SANITIZER_SUPPORTED_FLAGS)
foreach(FLAG ${SANITIZER_FLAGS})
string(MAKE_C_IDENTIFIER "COCKATRICE_HAS${FLAG}" _FLAG_VAR)
cockatrice_probe_flag("${FLAG}" ${_FLAG_VAR})
if(${_FLAG_VAR})
list(APPEND SANITIZER_SUPPORTED_FLAGS ${FLAG})
else()
message(STATUS "Sanitizers: ${FLAG} unsupported by ${CMAKE_CXX_COMPILER_ID}, skipping")
endif()
endforeach()
# The whole point of the option is the sanitizers, so an unsupported pair is fatal even
# though the cosmetic flags above are allowed to degrade.
if(NOT "-fsanitize=address,undefined" IN_LIST SANITIZER_SUPPORTED_FLAGS)
message(FATAL_ERROR "ENABLE_SANITIZERS requires -fsanitize=address,undefined, which "
"${CMAKE_CXX_COMPILER_ID} does not accept"
)
endif()
add_compile_options(${SANITIZER_SUPPORTED_FLAGS})
add_link_options(-fsanitize=address,undefined)
# Recorded so the test tree can label its entries and a plain `make` at the same time
# cannot quietly disagree with what CI ran.
set(COCKATRICE_SANITIZERS
ON
CACHE INTERNAL "AddressSanitizer and UBSan are enabled"
)
else()
# ENABLE_SANITIZERS is OFF, but COCKATRICE_SANITIZERS is CACHE INTERNAL, so a
# reused build dir (e.g. CI's release-package step reconfiguring the same dir
# after the sanitized debug step) would otherwise keep skipping NO_SANITIZE
# tests and keep the 300s discovery timeout. Reset it explicitly.
set(COCKATRICE_SANITIZERS
OFF
CACHE INTERNAL "AddressSanitizer and UBSan are enabled" FORCE
)
endif()

View file

@ -3,7 +3,7 @@
string(LENGTH "$ENV{MACOS_CERTIFICATE_NAME}" MACOS_CERTIFICATE_NAME_LEN)
if(APPLE AND MACOS_CERTIFICATE_NAME_LEN GREATER 0)
set(APPLICATIONS "cockatrice" "servatrice" "oracle" "dbconverter")
set(APPLICATIONS "cockatrice" "servatrice" "oracle")
foreach(app_name IN LISTS APPLICATIONS)
set(FULL_APP_PATH "${CPACK_TEMPORARY_INSTALL_DIRECTORY}/${app_name}.app")

View file

@ -213,7 +213,8 @@ set(PROJECT_VERSION_FRIENDLY "${PROJECT_VERSION} (${GIT_COMMIT_DATE_FRIENDLY})")
# Format: <program name>[-ReleaseName]-MAJ.MIN.PATCH[-prerelease_label]
set(PROJECT_VERSION_FILENAME "${PROJECT_NAME}")
if(PROJECT_VERSION_RELEASENAME)
set(PROJECT_VERSION_FILENAME "${PROJECT_VERSION_FILENAME}-${PROJECT_VERSION_RELEASENAME}")
string(REPLACE " " "-" PROJECT_VERSION_RELEASENAME_SAFE "${PROJECT_VERSION_RELEASENAME}")
set(PROJECT_VERSION_FILENAME "${PROJECT_VERSION_FILENAME}-${PROJECT_VERSION_RELEASENAME_SAFE}")
endif()
set(PROJECT_VERSION_FILENAME "${PROJECT_VERSION_FILENAME}-${PROJECT_VERSION}")

View file

@ -1,15 +1,16 @@
cmake_minimum_required(VERSION 3.2)
cmake_minimum_required(VERSION 3.10)
project(gtest-download LANGUAGES NONE)
include(ExternalProject)
ExternalProject_Add(googletest
URL https://github.com/google/googletest/archive/release-1.11.0.zip
URL_HASH SHA1=9ffb7b5923f4a8fcdabf2f42c6540cce299f44c0
externalproject_add(
googletest
URL https://github.com/google/googletest/archive/refs/tags/v1.17.0.zip
URL_HASH SHA1=f638fa0e724760e2ba07ff8cfba32cd644e1ce28
SOURCE_DIR "${CMAKE_BINARY_DIR}/gtest-src"
BINARY_DIR "${CMAKE_BINARY_DIR}/gtest-build"
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND ""
TEST_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND ""
TEST_COMMAND ""
)

55
cmake/pch/qtcore_pch.h Normal file
View file

@ -0,0 +1,55 @@
/** @file qtcore_pch.h
* @brief Precompiled header for all Qt targets (Qt Core only).
*
* Safe for every target that links Qt Core, including the headless
* Servatrice binary. Keep this header free of any widget/gui types.
*
* Candidates are chosen from IWYU include statistics: a header earns a
* spot when it is not already reachable from the rest of this list and
* enough translation units re-parse it. Qt headers are stable across
* builds, so the precompiled header rarely invalidates.
*/
#include <QBasicTimer>
#include <QByteArray>
#include <QCommandLineParser>
#include <QCoreApplication>
#include <QCryptographicHash>
#include <QDateTime>
#include <QDebug>
#include <QDir>
#include <QElapsedTimer>
#include <QFile>
#include <QFileInfo>
#include <QHash>
#include <QItemSelectionModel>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonValue>
#include <QList>
#include <QLoggingCategory>
#include <QMap>
#include <QMetaEnum>
#include <QMetaObject>
#include <QMutex>
#include <QMutexLocker>
#include <QObject>
#include <QPointer>
#include <QPropertyAnimation>
#include <QRandomGenerator>
#include <QReadWriteLock>
#include <QRegularExpression>
#include <QSet>
#include <QSettings>
#include <QSharedPointer>
#include <QSortFilterProxyModel>
#include <QString>
#include <QStringList>
#include <QTemporaryDir>
#include <QThread>
#include <QTimer>
#include <QUuid>
#include <QVariant>
#include <QXmlStreamReader>
#include <QXmlStreamWriter>

59
cmake/pch/qtwidgets_pch.h Normal file
View file

@ -0,0 +1,59 @@
/** @file qtwidgets_pch.h
* @brief Precompiled header for GUI targets (Cockatrice client, Oracle).
*
* Includes the Qt Core precompiled header plus the heavy Gui, Widgets and
* Network layers that virtually every client translation unit re-parses.
* Do not use on Servatrice (headless, QT_DONT_USE_QTGUI).
*
* Candidates are chosen from IWYU include statistics: a header earns a
* spot when it is not already reachable from the rest of this list and
* enough translation units re-parse it. Qt headers are stable across
* builds, so the precompiled header rarely invalidates.
*/
#include "qtcore_pch.h"
#include <QAction>
#include <QApplication>
#include <QCheckBox>
#include <QComboBox>
#include <QCompleter>
#include <QDialogButtonBox>
#include <QDockWidget>
#include <QFileDialog>
#include <QFormLayout>
#include <QFrame>
#include <QGraphicsItem>
#include <QGraphicsScene>
#include <QGraphicsSceneMouseEvent>
#include <QGraphicsView>
#include <QGridLayout>
#include <QGroupBox>
#include <QHBoxLayout>
#include <QHeaderView>
#include <QImage>
#include <QImageReader>
#include <QInputDialog>
#include <QLabel>
#include <QLayout>
#include <QListWidget>
#include <QMainWindow>
#include <QMenu>
#include <QMessageBox>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QPainter>
#include <QPlainTextEdit>
#include <QPushButton>
#include <QScrollArea>
#include <QSpinBox>
#include <QSplitter>
#include <QStandardItemModel>
#include <QTabWidget>
#include <QTableWidget>
#include <QTableWidgetItem>
#include <QToolBar>
#include <QToolButton>
#include <QTreeWidget>
#include <QVBoxLayout>
#include <QWidget>

View file

@ -7,6 +7,7 @@ project(Cockatrice VERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${
set(cockatrice_SOURCES
${VERSION_STRING_CPP}
# sort by alphabetical order, so that there is no debate about where to add new sources to the list
src/client/network/connection_controller/remote_connection_controller.cpp
src/client/network/update/client/update_downloader.cpp
src/client/network/interfaces/deck_stats_interface.cpp
src/client/network/interfaces/tapped_out_interface.cpp
@ -14,12 +15,18 @@ set(cockatrice_SOURCES
src/client/network/update/client/client_update_checker.cpp
src/client/network/update/client/release_channel.cpp
src/client/network/update/card_spoiler/spoiler_background_updater.cpp
src/client/latency_graph_widget.cpp
src/client/latency_status_widget.cpp
src/client/lag_monitor.cpp
src/client/sound_engine.cpp
src/client/settings/cache_settings.cpp
src/client/settings/card_counter_settings.cpp
src/client/settings/shortcut_treeview.cpp
src/client/settings/shortcuts_settings.cpp
src/interface/deck_loader/card_node_function.cpp
src/interface/deck_loader/deck_file_format.cpp
src/interface/deck_loader/deck_loader.cpp
src/interface/deck_loader/loaded_deck.cpp
src/interface/widgets/dialogs/dlg_connect.cpp
src/interface/widgets/dialogs/dlg_convert_deck_to_cod_format.cpp
src/interface/widgets/dialogs/dlg_create_game.cpp
@ -32,87 +39,112 @@ set(cockatrice_SOURCES
src/interface/widgets/dialogs/dlg_forgot_password_challenge.cpp
src/interface/widgets/dialogs/dlg_forgot_password_request.cpp
src/interface/widgets/dialogs/dlg_forgot_password_reset.cpp
src/interface/widgets/dialogs/dlg_invite_to_game.cpp
src/interface/widgets/dialogs/dlg_load_deck.cpp
src/interface/widgets/dialogs/dlg_load_deck_from_clipboard.cpp
src/interface/widgets/dialogs/dlg_load_deck_from_website.cpp
src/interface/widgets/dialogs/dlg_load_remote_deck.cpp
src/interface/widgets/dialogs/dlg_local_game_options.cpp
src/interface/widgets/dialogs/dlg_login_prompt.cpp
src/interface/widgets/dialogs/dlg_manage_sets.cpp
src/interface/widgets/dialogs/dlg_my_reports.cpp
src/interface/widgets/dialogs/dlg_register.cpp
src/interface/widgets/dialogs/dlg_report_user.cpp
src/interface/widgets/dialogs/dlg_select_set_for_cards.cpp
src/interface/widgets/dialogs/dlg_share_deck.cpp
src/interface/widgets/dialogs/dlg_shared_decks_preview.cpp
src/interface/widgets/dialogs/dlg_settings.cpp
src/interface/widgets/dialogs/dlg_startup_card_check.cpp
src/interface/widgets/dialogs/dlg_tip_of_the_day.cpp
src/interface/widgets/dialogs/dlg_update.cpp
src/interface/widgets/dialogs/dlg_view_log.cpp
src/interface/widgets/dialogs/override_printing_warning.cpp
src/interface/widgets/dialogs/path_safety_warning.cpp
src/interface/widgets/dialogs/tip_of_the_day.cpp
src/interface/widgets/deck_share/deck_share_utils.cpp
src/interface/widgets/deck_share/shared_deck_preview_widget.cpp
src/interface/widgets/deck_share/share_bar_widget.cpp
src/filters/deck_filter_string.cpp
src/filters/filter_builder.cpp
src/filters/filter_tree_model.cpp
src/filters/syntax_help.cpp
src/game/abstract_game.cpp
src/game/board/abstract_card_drag_item.cpp
src/game/board/abstract_card_item.cpp
src/game/board/abstract_counter.cpp
src/game/board/arrow_item.cpp
src/game/board/arrow_target.cpp
src/game/board/card_drag_item.cpp
src/game/board/card_item.cpp
src/game/arrow_registry.cpp
src/game_graphics/board/abstract_card_drag_item.cpp
src/game_graphics/board/abstract_card_item.cpp
src/game_graphics/board/abstract_counter.cpp
src/game/board/arrow_data.cpp
src/game_graphics/board/arrow_item.cpp
src/game_graphics/board/arrow_target.cpp
src/game_graphics/board/card_drag_item.cpp
src/game_graphics/board/card_item.cpp
src/game/board/card_list.cpp
src/game/board/counter_general.cpp
src/game/board/translate_counter_name.cpp
src/game/deckview/deck_view.cpp
src/game/deckview/deck_view_container.cpp
src/game/deckview/tabbed_deck_view_container.cpp
src/game/dialogs/dlg_create_token.cpp
src/game/dialogs/dlg_move_top_cards_until.cpp
src/game/dialogs/dlg_roll_dice.cpp
src/game/board/card_state.cpp
src/game_graphics/board/counter_general.cpp
src/game/board/counter_state.cpp
src/game_graphics/board/translate_counter_name.cpp
src/game_graphics/deckview/deck_view.cpp
src/game_graphics/deckview/deck_view_container.cpp
src/game_graphics/deckview/tabbed_deck_view_container.cpp
src/game_graphics/dialogs/dlg_create_token.cpp
src/game_graphics/dialogs/dlg_move_top_cards_until.cpp
src/game_graphics/dialogs/dlg_roll_dice.cpp
src/game/game.cpp
src/game/game_event_handler.cpp
src/game/game_meta_info.cpp
src/game/game_scene.cpp
src/game_graphics/game_scene.cpp
src/game/game_state.cpp
src/game/game_view.cpp
src/game/hand_counter.cpp
src/game/log/message_log_widget.cpp
src/game_graphics/game_view.cpp
src/game_graphics/hand_counter.cpp
src/game_graphics/log/message_log_widget.cpp
src/game/phase.cpp
src/game/phases_toolbar.cpp
src/game/player/menu/card_menu.cpp
src/game/player/menu/custom_zone_menu.cpp
src/game/player/menu/grave_menu.cpp
src/game/player/menu/hand_menu.cpp
src/game/player/menu/library_menu.cpp
src/game/player/menu/move_menu.cpp
src/game/player/menu/player_menu.cpp
src/game/player/menu/pt_menu.cpp
src/game/player/menu/rfg_menu.cpp
src/game/player/menu/say_menu.cpp
src/game/player/menu/sideboard_menu.cpp
src/game/player/menu/utility_menu.cpp
src/game/player/player.cpp
src/game_graphics/phases_toolbar.cpp
src/game_graphics/player/menu/card_menu.cpp
src/game_graphics/player/menu/custom_zone_menu.cpp
src/game_graphics/player/menu/grave_menu.cpp
src/game_graphics/player/menu/hand_menu.cpp
src/game_graphics/player/menu/library_menu.cpp
src/game_graphics/player/menu/move_menu.cpp
src/game_graphics/player/menu/player_menu.cpp
src/game_graphics/player/menu/pt_menu.cpp
src/game_graphics/player/menu/rfg_menu.cpp
src/game_graphics/player/menu/say_menu.cpp
src/game_graphics/player/menu/sideboard_menu.cpp
src/game_graphics/player/menu/tally_menu.cpp
src/game_graphics/player/menu/utility_menu.cpp
src/game_graphics/tally/stats_tally.cpp
src/game_graphics/tally/subtype_tally.cpp
src/game_graphics/tally/tally.cpp
src/game/player/player_actions.cpp
src/game/player/player_area.cpp
src/game_graphics/player/player_area.cpp
src/game_graphics/player/player_dialogs.cpp
src/game/player/player_event_handler.cpp
src/game/player/player_graphics_item.cpp
src/game_graphics/player/player_graphics_item.cpp
src/game/player/player_info.cpp
src/game/player/player_list_widget.cpp
src/game_graphics/player/player_list_widget.cpp
src/game/player/player_logic.cpp
src/game/player/player_manager.cpp
src/game/player/player_target.cpp
src/game_graphics/player/player_target.cpp
src/game/replay.cpp
src/game/zones/card_zone.cpp
src/game/zones/hand_zone.cpp
src/game/zones/logic/card_zone_logic.cpp
src/game/zones/logic/hand_zone_logic.cpp
src/game/zones/logic/pile_zone_logic.cpp
src/game/zones/logic/stack_zone_logic.cpp
src/game/zones/logic/table_zone_logic.cpp
src/game/zones/logic/view_zone_logic.cpp
src/game/zones/pile_zone.cpp
src/game/zones/select_zone.cpp
src/game/zones/stack_zone.cpp
src/game/zones/table_zone.cpp
src/game/zones/view_zone.cpp
src/game/zones/view_zone_widget.cpp
src/game/zones/card_zone_logic.cpp
src/game/zones/hand_zone_logic.cpp
src/game/zones/pile_zone_logic.cpp
src/game/zones/stack_zone_logic.cpp
src/game/zones/table_zone_logic.cpp
src/game/zones/view_zone_logic.cpp
src/game_graphics/zones/card_zone.cpp
src/game_graphics/zones/hand_zone.cpp
src/game_graphics/zones/pile_zone.cpp
src/game_graphics/zones/select_zone.cpp
src/game_graphics/zones/stack_zone.cpp
src/game_graphics/zones/table_zone.cpp
src/game_graphics/zones/view_zone.cpp
src/game_graphics/zones/view_zone_widget.cpp
src/game_graphics/board/abstract_graphics_item.cpp
src/interface/card_database_update/card_database_update_status_bar.cpp
src/interface/card_database_update/card_database_update_status_bar.h
src/interface/card_database_update/card_update_progress.cpp
src/interface/card_database_update/card_update_progress.h
src/interface/card_picture_loader/card_picture_loader.cpp
src/interface/card_picture_loader/card_picture_loader_local.cpp
src/interface/card_picture_loader/card_picture_loader_request_status_display_widget.cpp
@ -120,14 +152,33 @@ set(cockatrice_SOURCES
src/interface/card_picture_loader/card_picture_loader_worker.cpp
src/interface/card_picture_loader/card_picture_loader_worker_work.cpp
src/interface/card_picture_loader/card_picture_to_load.cpp
src/interface/intents/intent.cpp
src/interface/intents/intent.h
src/interface/intents/intent_open_local_deck.cpp
src/interface/intents/intent_open_local_deck.h
src/interface/intents/intent_wait_for_database_load.cpp
src/interface/intents/intent_wait_for_database_load.h
src/interface/layouts/flow_layout.cpp
src/interface/layouts/overlap_layout.cpp
src/interface/widgets/utility/card_completer_delegate.cpp
src/interface/widgets/utility/card_completer_styler.cpp
src/interface/widgets/utility/completer_utils.cpp
src/interface/widgets/utility/line_edit_completer.cpp
src/interface/widgets/utility/reversed_completer_model.cpp
src/interface/pixel_map_generator.cpp
src/interface/theme_config.cpp
src/interface/theme_manager.cpp
src/interface/palette_editor/color_button.cpp
src/interface/palette_editor/palette_generator.cpp
src/interface/palette_editor/quick_setup_panel.cpp
src/interface/palette_editor/palette_grid_widget.cpp
src/interface/palette_editor/palette_editor_dialog.cpp
src/interface/widgets/cards/additional_info/color_identity_widget.cpp
src/interface/widgets/cards/additional_info/deck_color_identity.cpp
src/interface/widgets/cards/additional_info/mana_cost_widget.cpp
src/interface/widgets/cards/additional_info/mana_symbol_widget.cpp
src/interface/widgets/cards/art_crop_attribution.cpp
src/interface/widgets/cards/card_art_utils.cpp
src/interface/widgets/cards/card_group_display_widgets/card_group_display_widget.cpp
src/interface/widgets/cards/card_group_display_widgets/flat_card_group_display_widget.cpp
src/interface/widgets/cards/card_group_display_widgets/overlapped_card_group_display_widget.cpp
@ -141,29 +192,59 @@ set(cockatrice_SOURCES
src/interface/widgets/cards/card_size_widget.cpp
src/interface/widgets/cards/deck_card_zone_display_widget.cpp
src/interface/widgets/cards/deck_preview_card_picture_widget.cpp
src/interface/widgets/deck_analytics/abstract_analytics_panel_widget.cpp
src/interface/widgets/deck_analytics/add_analytics_panel_dialog.cpp
src/interface/widgets/deck_analytics/analytics_panel_widget_factory.cpp
src/interface/widgets/deck_analytics/analytics_panel_widget_registrar.cpp
src/interface/widgets/deck_analytics/deck_analytics_widget.cpp
src/interface/widgets/deck_analytics/deck_list_statistics_analyzer.cpp
src/interface/widgets/deck_analytics/mana_base_widget.cpp
src/interface/widgets/deck_analytics/mana_curve_widget.cpp
src/interface/widgets/deck_analytics/mana_devotion_widget.cpp
src/interface/widgets/deck_analytics/resizable_panel.cpp
src/interface/widgets/deck_analytics/analyzer_modules/draw_probability/draw_probability_config.cpp
src/interface/widgets/deck_analytics/analyzer_modules/draw_probability/draw_probability_config_dialog.cpp
src/interface/widgets/deck_analytics/analyzer_modules/draw_probability/draw_probability_widget.cpp
src/interface/widgets/deck_analytics/analyzer_modules/mana_base/mana_base_config.cpp
src/interface/widgets/deck_analytics/analyzer_modules/mana_base/mana_base_config_dialog.cpp
src/interface/widgets/deck_analytics/analyzer_modules/mana_base/mana_base_widget.cpp
src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_config.cpp
src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_config_dialog.cpp
src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_widget.cpp
src/interface/widgets/deck_analytics/analyzer_modules/mana_devotion/mana_devotion_config.cpp
src/interface/widgets/deck_analytics/analyzer_modules/mana_devotion/mana_devotion_config_dialog.cpp
src/interface/widgets/deck_analytics/analyzer_modules/mana_devotion/mana_devotion_widget.cpp
src/interface/widgets/deck_analytics/analyzer_modules/mana_distribution/mana_distribution_config.cpp
src/interface/widgets/deck_analytics/analyzer_modules/mana_distribution/mana_distribution_config_dialog.cpp
src/interface/widgets/deck_analytics/analyzer_modules/mana_distribution/mana_distribution_widget.cpp
src/interface/widgets/deck_analytics/analyzer_modules/mana_distribution/mana_distribution_single_display_widget.cpp
src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_total_widget.cpp
src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_category_widget.cpp
src/interface/widgets/deck_editor/card_database_view.cpp
src/interface/widgets/deck_editor/deck_list_history_manager_widget.cpp
src/interface/widgets/deck_editor/deck_editor_card_database_dock_widget.cpp
src/interface/widgets/deck_editor/deck_editor_card_info_dock_widget.cpp
src/interface/widgets/deck_editor/deck_editor_database_display_widget.cpp
src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.cpp
src/interface/widgets/deck_editor/deck_editor_filter_dock_widget.cpp
src/interface/widgets/deck_editor/deck_editor_printing_selector_dock_widget.cpp
src/interface/widgets/deck_editor/deck_list_style_proxy.cpp
src/interface/widgets/deck_editor/deck_state_manager.cpp
src/interface/widgets/deck_editor/deck_zone_dialog.cpp
src/interface/widgets/deck_editor/printing_disabled_info_widget.cpp
src/interface/widgets/general/background_sources.cpp
src/interface/widgets/general/display/background_plate_widget.cpp
src/interface/widgets/general/display/banner_widget.cpp
src/interface/widgets/general/display/bar_widget.cpp
src/interface/widgets/general/display/color_bar.cpp
src/interface/widgets/general/display/dynamic_font_size_label.cpp
src/interface/widgets/general/display/dynamic_font_size_push_button.cpp
src/interface/widgets/general/display/labeled_input.cpp
src/interface/widgets/general/display/percent_bar_widget.cpp
src/interface/widgets/general/display/shadow_background_label.cpp
src/interface/widgets/general/display/charts/bars/bar_widget.cpp
src/interface/widgets/general/display/charts/bars/color_bar.cpp
src/interface/widgets/general/display/charts/bars/percent_bar_widget.cpp
src/interface/widgets/general/display/charts/bars/bar_chart_widget.cpp
src/interface/widgets/general/display/charts/bars/bar_chart_background_widget.cpp
src/interface/widgets/general/display/charts/bars/segmented_bar_widget.cpp
src/interface/widgets/general/display/charts/pies/color_pie.cpp
src/interface/widgets/general/home_styled_button.cpp
src/interface/widgets/general/home_tab_button_color.h
src/interface/widgets/general/home_widget.cpp
src/interface/widgets/general/layout_containers/flow_widget.cpp
src/interface/widgets/general/layout_containers/overlap_control_widget.cpp
@ -174,31 +255,63 @@ set(cockatrice_SOURCES
src/interface/widgets/printing_selector/printing_selector.cpp
src/interface/widgets/printing_selector/printing_selector_card_display_widget.cpp
src/interface/widgets/printing_selector/printing_selector_card_overlay_widget.cpp
src/interface/widgets/printing_selector/printing_selector_placeholder_widget.cpp
src/interface/widgets/printing_selector/printing_selector_card_search_widget.cpp
src/interface/widgets/printing_selector/printing_selector_card_selection_widget.cpp
src/interface/widgets/printing_selector/printing_selector_card_sorting_widget.cpp
src/interface/widgets/printing_selector/set_name_and_collectors_number_display_widget.cpp
src/interface/widgets/playmat/playmat_collection_dialog.cpp
src/interface/widgets/playmat/playmat_collection_dialog.h
src/interface/widgets/playmat/playmat_preview_widget.cpp
src/interface/widgets/playmat/playmat_settings_dialog.cpp
src/interface/widgets/quick_settings/settings_button_widget.cpp
src/interface/widgets/quick_settings/settings_popup_widget.cpp
src/interface/widgets/replay/replay_manager.cpp
src/interface/widgets/replay/replay_quick_settings_widget.cpp
src/interface/widgets/replay/replay_timeline_widget.cpp
src/interface/widgets/replay/replay_widget.cpp
src/interface/widgets/server/chat_view/chat_view.cpp
src/interface/widgets/server/game_filter_configs.cpp
src/interface/widgets/server/game_link.cpp
src/interface/widgets/server/game_selector.cpp
src/interface/widgets/server/game_selector_quick_filter_toolbar.cpp
src/interface/widgets/server/games_model.cpp
src/interface/widgets/server/handle_public_servers.cpp
src/interface/widgets/server/remote/remote_decklist_tree_widget.cpp
src/interface/widgets/server/remote/remote_replay_list_tree_widget.cpp
src/interface/widgets/server/user/user_avatar_provider.cpp
src/interface/widgets/server/user/user_card_art_provider.cpp
src/interface/widgets/server/user/user_card_settings_dialog.cpp
src/interface/widgets/server/user/user_context_menu.cpp
src/interface/widgets/server/user/user_info_box.cpp
src/interface/widgets/server/user/user_info_connection.cpp
src/interface/widgets/server/user/user_list_dialog.cpp
src/interface/widgets/server/user/user_list_manager.cpp
src/interface/widgets/server/user/user_list_painter.cpp
src/interface/widgets/server/user/user_list_panel_widget.cpp
src/interface/widgets/server/user/user_list_widget.cpp
src/interface/widgets/settings_page/abstract_settings_page.cpp
src/interface/widgets/settings_page/appearance_settings_page.cpp
src/interface/widgets/settings_page/deck_editor_settings_page.cpp
src/interface/widgets/settings_page/general_settings_page.cpp
src/interface/widgets/settings_page/messages_settings_page.cpp
src/interface/widgets/settings_page/settings_search_delegate.cpp
src/interface/widgets/settings_page/settings_search_model.cpp
src/interface/widgets/settings_page/shortcut_settings_page.cpp
src/interface/widgets/settings_page/sound_settings_page.cpp
src/interface/widgets/settings_page/storage_settings_page.cpp
src/interface/widgets/settings_page/user_interface_settings_page.cpp
src/interface/widgets/utility/custom_line_edit.cpp
src/interface/widgets/utility/get_text_with_max.cpp
src/interface/widgets/utility/report_utils.cpp
src/interface/widgets/utility/report_utils.h
src/interface/widgets/utility/sequence_edit.cpp
src/interface/widgets/utility/visibility_change_listener.cpp
src/interface/widgets/utility/visibility_change_listener.h
src/interface/widgets/visual_database_display/visual_database_display_color_filter_widget.cpp
src/interface/widgets/visual_database_display/visual_database_display_filter_save_load_widget.cpp
src/interface/widgets/visual_database_display/visual_database_display_filter_toolbar_widget.cpp
src/interface/widgets/visual_database_display/visual_database_display_format_legality_filter_widget.cpp
src/interface/widgets/visual_database_display/visual_database_display_main_type_filter_widget.cpp
src/interface/widgets/visual_database_display/visual_database_display_name_filter_widget.cpp
src/interface/widgets/visual_database_display/visual_database_display_set_filter_widget.cpp
@ -206,6 +319,7 @@ set(cockatrice_SOURCES
src/interface/widgets/visual_database_display/visual_database_display_widget.cpp
src/interface/widgets/visual_database_display/visual_database_filter_display_widget.cpp
src/interface/widgets/visual_deck_editor/visual_deck_display_options_widget.cpp
src/interface/widgets/visual_deck_editor/visual_deck_editor_placeholder_widget.cpp
src/interface/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.cpp
src/interface/widgets/visual_deck_editor/visual_deck_editor_widget.cpp
src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_color_identity_filter_widget.cpp
@ -215,17 +329,23 @@ set(cockatrice_SOURCES
src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_display_widget.cpp
src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_item_widget.cpp
src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_widget.cpp
src/interface/widgets/visual_deck_storage/deck_preview/public_deck_preview_widget.cpp
src/interface/widgets/visual_deck_storage/remote_public_decks_model.cpp
src/interface/widgets/visual_deck_storage/visual_deck_storage_folder_display_widget.cpp
src/interface/widgets/visual_deck_storage/visual_deck_storage_model.cpp
src/interface/widgets/visual_deck_storage/visual_deck_storage_quick_settings_widget.cpp
src/interface/widgets/visual_deck_storage/visual_deck_storage_search_widget.cpp
src/interface/widgets/visual_deck_storage/visual_deck_storage_sort_filter_proxy_model.cpp
src/interface/widgets/visual_deck_storage/visual_deck_storage_sort_widget.cpp
src/interface/widgets/visual_deck_storage/visual_deck_storage_tag_filter_widget.cpp
src/interface/widgets/visual_deck_storage/visual_deck_storage_widget.cpp
src/interface/window_main.cpp
src/main.cpp
src/single_instance_manager.cpp
src/interface/widgets/tabs/abstract_tab_deck_editor.cpp
src/interface/widgets/tabs/api/archidekt/tab_archidekt.cpp
src/interface/widgets/tabs/api/archidekt/api_response/archidekt_deck_listing_api_response.cpp
src/interface/widgets/tabs/api/archidekt/api_response/archidekt_formats.h
src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card.cpp
src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card_entry.cpp
src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_edition.cpp
@ -237,6 +357,13 @@ set(cockatrice_SOURCES
src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_entry_display_widget.cpp
src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_listings_display_widget.cpp
src/interface/widgets/tabs/api/archidekt/display/archidekt_deck_preview_image_display_widget.cpp
src/interface/widgets/tabs/api/commander_spellbook/api_response/card_in_deck_request.cpp
src/interface/widgets/tabs/api/commander_spellbook/api_response/commander_spellbook_deck_request.cpp
src/interface/widgets/tabs/api/commander_spellbook/api_response/commander_spellbook_card_result.cpp
src/interface/widgets/tabs/api/commander_spellbook/api_response/commander_spellbook_variant_result.cpp
src/interface/widgets/tabs/api/commander_spellbook/api_response/commander_spellbook_estimate_bracket_result.cpp
src/interface/widgets/tabs/api/commander_spellbook/commander_spellbook_bracket_explainer.cpp
src/interface/widgets/tabs/api/commander_spellbook/commander_spellbook_api_accessor.cpp
src/interface/widgets/tabs/api/edhrec/api_response/archidekt_links/edhrec_api_response_archidekt_links.cpp
src/interface/widgets/tabs/api/edhrec/api_response/average_deck/edhrec_average_deck_api_response.cpp
src/interface/widgets/tabs/api/edhrec/api_response/average_deck/edhrec_deck_api_response.cpp
@ -266,12 +393,17 @@ set(cockatrice_SOURCES
src/interface/widgets/tabs/tab.cpp
src/interface/widgets/tabs/tab_account.cpp
src/interface/widgets/tabs/tab_admin.cpp
src/interface/widgets/tabs/tab_card_art_rules.cpp
src/interface/widgets/tabs/tab_deck_editor.cpp
src/interface/widgets/tabs/tab_deck_storage.cpp
src/interface/widgets/tabs/tab_developer.cpp
src/interface/widgets/tabs/tab_game.cpp
src/interface/widgets/tabs/tab_home.cpp
src/interface/widgets/tabs/tab_logs.cpp
src/interface/widgets/tabs/tab_message.cpp
src/interface/widgets/tabs/tab_moderation.cpp
src/interface/widgets/tabs/tab_public_decks.cpp
src/interface/widgets/tabs/tab_report.cpp
src/interface/widgets/tabs/tab_replays.cpp
src/interface/widgets/tabs/tab_room.cpp
src/interface/widgets/tabs/tab_server.cpp
@ -282,6 +414,57 @@ set(cockatrice_SOURCES
src/interface/widgets/tabs/visual_deck_storage/tab_deck_storage_visual.cpp
src/interface/key_signals.cpp
src/interface/logger.cpp
src/interface/widgets/tabs/api/commander_spellbook/commander_bracket_service.cpp
src/interface/widgets/tabs/api/commander_spellbook/commander_bracket_widget.cpp
src/interface/widgets/tabs/api/commander_spellbook/handle_commander_brackets.cpp
src/interface/widgets/onboarding/banner_shader_config.h
src/interface/widgets/onboarding/brand_colors.h
src/interface/widgets/onboarding/first_run_wizard.cpp
src/interface/widgets/onboarding/first_run_wizard.h
src/interface/widgets/onboarding/first_run_wizard_page.cpp
src/interface/widgets/onboarding/first_run_wizard_page.h
src/interface/widgets/onboarding/pages/account_setup_page.cpp
src/interface/widgets/onboarding/pages/account_setup_page.h
src/interface/widgets/onboarding/pages/card_database_setup_page.cpp
src/interface/widgets/onboarding/pages/card_database_setup_page.h
src/interface/widgets/onboarding/pages/finish_page.cpp
src/interface/widgets/onboarding/pages/finish_page.h
src/interface/widgets/onboarding/pages/preferences_setup_page.cpp
src/interface/widgets/onboarding/pages/preferences_setup_page.h
src/interface/widgets/onboarding/pages/theme_setup_page.cpp
src/interface/widgets/onboarding/pages/theme_setup_page.h
src/interface/widgets/onboarding/pages/welcome_page.cpp
src/interface/widgets/onboarding/pages/welcome_page.h
src/interface/widgets/onboarding/shader_banner_widget.cpp
src/interface/widgets/onboarding/shader_banner_widget.h
src/interface/widgets/onboarding/step_indicator_widget.cpp
src/interface/widgets/onboarding/step_indicator_widget.h
src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_bracket_navigation_widget.cpp
src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_bracket_navigation_widget.h
src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_budget_navigation_widget.cpp
src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_budget_navigation_widget.h
src/interface/widgets/utility/compact_push_button.cpp
src/interface/widgets/utility/compact_push_button.h
src/single_instance_manager.h
src/client/url_scheme_event_filter.h
src/interface/intents/intent_connect_to_server.cpp
src/interface/intents/intent_connect_to_server.h
src/interface/intents/intent_disconnect_from_server.cpp
src/interface/intents/intent_disconnect_from_server.h
src/interface/intents/intent_join_server_game.cpp
src/interface/intents/intent_join_server_game.h
src/interface/intents/intent_join_server_room.cpp
src/interface/intents/intent_join_server_room.h
src/interface/intents/intent_login.cpp
src/interface/intents/intent_login.h
src/interface/intents/intent_open_server_room_by_name.cpp
src/interface/intents/intent_open_server_room_by_name.h
src/interface/intents/intent_open_shared_deck.cpp
src/interface/intents/intent_open_shared_deck.h
src/interface/intents/url_parser.cpp
src/interface/intents/url_parser.h
src/interface/widgets/server/user/user_info_popup.cpp
src/interface/widgets/server/user/user_info_popup.h
)
add_subdirectory(sounds)
@ -323,11 +506,7 @@ if(APPLE)
set(cockatrice_SOURCES ${cockatrice_SOURCES} ${CMAKE_CURRENT_SOURCE_DIR}/resources/appicon.icns)
endif(APPLE)
if(Qt6_FOUND)
qt6_add_resources(cockatrice_RESOURCES_RCC ${cockatrice_RESOURCES})
elseif(Qt5_FOUND)
qt5_add_resources(cockatrice_RESOURCES_RCC ${cockatrice_RESOURCES})
endif()
qt6_add_resources(cockatrice_RESOURCES_RCC ${cockatrice_RESOURCES})
# Declare path variables
set(ICONDIR
@ -339,71 +518,62 @@ set(DESKTOPDIR
CACHE STRING "desktop file destination"
)
set(MIMEDIR
share/mime/packages
CACHE STRING "mime file destination"
)
set(COCKATRICE_MAC_QM_INSTALL_DIR "cockatrice.app/Contents/Resources/translations")
set(COCKATRICE_UNIX_QM_INSTALL_DIR "share/cockatrice/translations")
set(COCKATRICE_WIN32_QM_INSTALL_DIR "translations")
if(Qt6_FOUND)
qt6_add_executable(
cockatrice
WIN32
MACOSX_BUNDLE
${cockatrice_SOURCES}
${cockatrice_RESOURCES_RCC}
${cockatrice_MOC_SRCS}
MANUAL_FINALIZATION
)
elseif(Qt5_FOUND)
# Qt5 Translations need to be linked at executable creation time
if(Qt5LinguistTools_FOUND)
if(UPDATE_TRANSLATIONS)
qt5_create_translation(cockatrice_QM ${translate_SRCS} ${cockatrice_TS})
else()
qt5_add_translation(cockatrice_QM ${cockatrice_TS})
endif()
endif()
add_executable(
cockatrice WIN32 MACOSX_BUNDLE ${cockatrice_MOC_SRCS} ${cockatrice_QM} ${cockatrice_RESOURCES_RCC}
${cockatrice_SOURCES}
)
if(UNIX)
if(APPLE)
install(FILES ${cockatrice_QM} DESTINATION ${COCKATRICE_MAC_QM_INSTALL_DIR})
else()
install(FILES ${cockatrice_QM} DESTINATION ${COCKATRICE_UNIX_QM_INSTALL_DIR})
endif()
elseif(WIN32)
install(FILES ${cockatrice_QM} DESTINATION ${COCKATRICE_WIN32_QM_INSTALL_DIR})
endif()
endif()
qt6_add_executable(
cockatrice
WIN32
MACOSX_BUNDLE
${cockatrice_SOURCES}
${cockatrice_RESOURCES_RCC}
${cockatrice_MOC_SRCS}
MANUAL_FINALIZATION
)
if(Qt5_FOUND)
target_link_libraries(
cockatrice
libcockatrice_card
libcockatrice_deck_list
libcockatrice_filters
libcockatrice_utility
libcockatrice_network
libcockatrice_models
libcockatrice_rng
libcockatrice_settings
${COCKATRICE_QT_MODULES}
)
else()
target_link_libraries(
cockatrice
PUBLIC libcockatrice_card
libcockatrice_deck_list
libcockatrice_filters
libcockatrice_utility
libcockatrice_network
libcockatrice_models
libcockatrice_rng
libcockatrice_settings
${COCKATRICE_QT_MODULES}
)
endif()
target_precompile_headers(cockatrice PRIVATE "${CMAKE_SOURCE_DIR}/cmake/pch/qtwidgets_pch.h")
qt6_add_shaders(
cockatrice
"onboarding_shaders"
PREFIX
"/onboarding/shaders"
BASE
"src/interface/widgets/onboarding/shaders"
FILES
src/interface/widgets/onboarding/shaders/brand_banner.frag
src/interface/widgets/onboarding/shaders/brand_plate.frag
)
qt6_add_resources(
cockatrice
"onboarding_qml"
PREFIX
"/onboarding/qml"
BASE
"src/interface/widgets/onboarding/qml"
FILES
src/interface/widgets/onboarding/qml/BrandBanner.qml
)
target_link_libraries(
cockatrice
PUBLIC libcockatrice_card
libcockatrice_deck_list
libcockatrice_filters
libcockatrice_utility
libcockatrice_network
libcockatrice_models
libcockatrice_rng
libcockatrice_settings
${COCKATRICE_QT_MODULES}
)
if(UNIX)
if(APPLE)
@ -423,6 +593,23 @@ if(UNIX)
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/resources/cockatrice.png DESTINATION ${ICONDIR}/hicolor/48x48/apps)
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/resources/cockatrice.svg DESTINATION ${ICONDIR}/hicolor/scalable/apps)
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/cockatrice.desktop DESTINATION ${DESKTOPDIR})
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/cockatrice-cod.xml DESTINATION ${MIMEDIR})
# Refresh the freedesktop databases so the file associations and scheme
# handler register without requiring the user to run them manually. The
# tools may be missing on minimal systems; that is fine, packaging systems
# usually refresh these databases through their own triggers.
find_program(UPDATE_MIME_DATABASE update-mime-database)
if(UPDATE_MIME_DATABASE)
install(CODE "execute_process(COMMAND \"${UPDATE_MIME_DATABASE}\" \"${CMAKE_INSTALL_PREFIX}/share/mime\")")
endif()
find_program(UPDATE_DESKTOP_DATABASE update-desktop-database)
if(UPDATE_DESKTOP_DATABASE)
install(
CODE "execute_process(COMMAND \"${UPDATE_DESKTOP_DATABASE}\" \"${CMAKE_INSTALL_PREFIX}/share/applications\")"
)
endif()
endif()
elseif(WIN32)
install(TARGETS cockatrice RUNTIME DESTINATION ./)
@ -433,7 +620,7 @@ if(APPLE)
set(plugin_dest_dir cockatrice.app/Contents/Plugins)
set(qtconf_dest_dir cockatrice.app/Contents/Resources)
# Qt plugins: audio (Qt5), iconengines, imageformats, multimedia (Qt6), platforms, printsupport (Qt5), styles, tls (Qt6)
# Qt plugins: audio, iconengines, imageformats, multimedia, platforms, printsupport, styles, tls
install(
DIRECTORY "${QT_PLUGINS_DIR}/"
DESTINATION ${plugin_dest_dir}
@ -486,20 +673,56 @@ if(WIN32)
set(qtconf_dest_dir .)
install(
DIRECTORY "${CMAKE_BINARY_DIR}/${PROJECT_NAME}/${CMAKE_BUILD_TYPE}/"
DIRECTORY "$<TARGET_FILE_DIR:cockatrice>/"
DESTINATION ./
FILES_MATCHING
PATTERN "*.dll"
PATTERN "*.pdb" EXCLUDE
PATTERN "*.dir*" EXCLUDE
PATTERN "*_autogen*" EXCLUDE
PATTERN "*.tlog*" EXCLUDE
PATTERN "CMakeFiles*" EXCLUDE
PATTERN "x64*" EXCLUDE
PATTERN ".qt*" EXCLUDE
PATTERN ".qsb*" EXCLUDE
PATTERN ".lupdate*" EXCLUDE
)
# fixup_bundle further down copies only the DLLs it can find in Cockatrice.exe's
# import table, and the glob above only sees DLLs that were already copied next
# to the executable. Qt6::Concurrent is linked but never imported - QtConcurrent::run
# and QFutureWatcher are header-only in Qt 6 - so neither step ships
# Qt6Concurrent.dll, even though required-runtime.txt lists it and the
# Windows package check then fails on a package that is fine to run.
# Qt6::ShaderTools is skipped for the same reason.
#
# Install the runtime of every linked module explicitly. An unused DLL costs a
# few hundred kilobytes; a missing one is what stops the client starting after
# an update, and it stays invisible until then.
foreach(_qt_module IN LISTS COCKATRICE_QT_MODULES)
get_target_property(_qt_module_type ${_qt_module} TYPE)
if(_qt_module_type STREQUAL "SHARED_LIBRARY")
install(FILES "$<TARGET_FILE:${_qt_module}>" DESTINATION ./)
endif()
endforeach()
install(
DIRECTORY "${CMAKE_BINARY_DIR}/cockatrice/"
DESTINATION ./
FILES_MATCHING
PATTERN "*.ini"
PATTERN "CMakeFiles*" EXCLUDE
PATTERN "*.dir*" EXCLUDE
PATTERN "*_autogen*" EXCLUDE
PATTERN "*.tlog*" EXCLUDE
PATTERN "*.pdb" EXCLUDE
PATTERN "x64*" EXCLUDE
PATTERN ".qt*" EXCLUDE
PATTERN ".qsb*" EXCLUDE
PATTERN ".lupdate*" EXCLUDE
)
# Qt plugins: audio (Qt5), iconengines, imageformats, multimedia (Qt6) platforms, printsupport (Qt5), styles, tls (Qt6)
# Qt plugins: audio, iconengines, imageformats, multimedia, platforms, printsupport, styles, tls
install(
DIRECTORY "${QT_PLUGINS_DIR}/"
DESTINATION ${plugin_dest_dir}
@ -521,16 +744,23 @@ if(WIN32)
PATTERN "styles/qopensslbackend.dll"
PATTERN "styles/qschannelbackend.dll"
PATTERN "styles/qwindowsvistastyle.dll"
PATTERN "styles/qwindows11style.dll"
PATTERN "styles/qmodernwindowsstyle.dll"
PATTERN "styles/qmodernwindowsstyled.dll"
PATTERN "tls/qcertonlybackend.dll"
PATTERN "tls/qopensslbackend.dll"
PATTERN "tls/qschannelbackend.dll"
)
# The Windows install has no Resources directory: the .qm files go to
# ${COCKATRICE_WIN32_QM_INSTALL_DIR} and main.cpp looks for them at
# applicationDirPath() + "/translations". The macOS bundle above keeps
# Resources/translations, which is correct for it.
install(
CODE "
file(WRITE \"\${CMAKE_INSTALL_PREFIX}/${qtconf_dest_dir}/qt.conf\" \"[Paths]
Plugins = Plugins
Translations = Resources/translations
Translations = translations
Data = Resources\")
"
COMPONENT Runtime
@ -546,13 +776,9 @@ Data = Resources\")
"
COMPONENT Runtime
)
if(OPENSSL_FOUND)
install(FILES ${OPENSSL_INCLUDE_DIRS} DESTINATION ./)
endif()
endif()
if(Qt6_FOUND AND Qt6LinguistTools_FOUND)
if(Qt6LinguistTools_FOUND)
#Qt6 Translations happen after the executable is built up
if(UPDATE_TRANSLATIONS)
qt6_add_translations(
@ -577,8 +803,18 @@ if(Qt6_FOUND AND Qt6LinguistTools_FOUND)
elseif(WIN32)
install(FILES ${cockatrice_QM} DESTINATION ${COCKATRICE_WIN32_QM_INSTALL_DIR})
endif()
# Qt attaches the .qm files to the default build target only, so building the executable on its own
# (CLion, --target cockatrice) leaves it without any translations: nothing is translated and the
# language selector stays empty.
# Depend on Qt's own lrelease target instead of attaching the .qm custom commands to a second
# target: two independent targets owning the same outputs race on MSBuild/Makefiles and are an
# error on Xcode. Older Qt versions already add this dependency themselves.
foreach(_lrelease_target Cockatrice_lrelease cockatrice_lrelease)
if(TARGET ${_lrelease_target})
add_dependencies(cockatrice ${_lrelease_target})
endif()
endforeach()
endif()
if(Qt6_FOUND)
qt6_finalize_target(cockatrice)
endif()
qt6_finalize_target(cockatrice)

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info">
<mime-type type="application/x-cockatrice">
<comment>Cockatrice Deck File</comment>
<glob pattern="*.cod"/>
</mime-type>
</mime-info>

View file

@ -3,6 +3,7 @@
Version=1.0
Type=Application
Name=Cockatrice
Exec=cockatrice
Exec=cockatrice %U
Icon=cockatrice
Categories=Game;CardGame;
MimeType=application/x-cockatrice;x-scheme-handler/cockatrice;

View file

@ -2,6 +2,7 @@
<qresource prefix="/">
<file>resources/cardback.svg</file>
<file>resources/cockatrice.svg</file>
<file>resources/cockatrice-logo-white.svg</file>
<file>resources/hand.svg</file>
<file>resources/hr.jpg</file>
@ -15,18 +16,24 @@
<file>resources/icons/arrow_top_green.svg</file>
<file>resources/icons/arrow_up_green.svg</file>
<file>resources/icons/arrow_undo.svg</file>
<file>resources/icons/circle_half_stroke.svg</file>
<file>resources/icons/clearsearch.svg</file>
<file>resources/icons/cogwheel.svg</file>
<file>resources/icons/conceded.svg</file>
<file>resources/icons/decrement.svg</file>
<file>resources/icons/delete.svg</file>
<file>resources/icons/dragon.svg</file>
<file>resources/icons/dropdown_collapsed.svg</file>
<file>resources/icons/dropdown_expanded.svg</file>
<file>resources/icons/filter.svg</file>
<file>resources/icons/floppy_disk.svg</file>
<file>resources/icons/forgot_password.svg</file>
<file>resources/icons/gear.svg</file>
<file>resources/icons/increment.svg</file>
<file>resources/icons/info.svg</file>
<file>resources/icons/lock.svg</file>
<file>resources/icons/not_ready_start.svg</file>
<file>resources/icons/pen_to_square.svg</file>
<file>resources/icons/pencil.svg</file>
<file>resources/icons/pin.svg</file>
<file>resources/icons/player.svg</file>
@ -34,10 +41,13 @@
<file>resources/icons/reload.svg</file>
<file>resources/icons/remove_row.svg</file>
<file>resources/icons/rename.svg</file>
<file>resources/icons/scale_balanced.svg</file>
<file>resources/icons/scales.svg</file>
<file>resources/icons/scroll.svg</file>
<file>resources/icons/search.svg</file>
<file>resources/icons/settings.svg</file>
<file>resources/icons/share.svg</file>
<file>resources/icons/sort_arrow_down.svg</file>
<file>resources/icons/spectator.svg</file>
<file>resources/icons/swap.svg</file>
<file>resources/icons/sync.svg</file>
@ -46,18 +56,24 @@
<file>resources/icons/view.svg</file>
<file>resources/icons/mana/B.svg</file>
<file>resources/icons/mana/C.svg</file>
<file>resources/icons/mana/G.svg</file>
<file>resources/icons/mana/R.svg</file>
<file>resources/icons/mana/U.svg</file>
<file>resources/icons/mana/W.svg</file>
<file>resources/backgrounds/home.png</file>
<file>resources/backgrounds/home.jpg</file>
<file>resources/backgrounds/home-dark.jpg</file>
<file>resources/backgrounds/home-light.jpg</file>
<file>resources/backgrounds/card_triplet.svg</file>
<file>resources/backgrounds/placeholder_printing_selector.svg</file>
<file>resources/config/general.svg</file>
<file>resources/config/appearance.svg</file>
<file>resources/config/interface.svg</file>
<file>resources/config/messages.svg</file>
<file>resources/config/deckeditor.svg</file>
<file>resources/config/storage.svg</file>
<file>resources/config/shorcuts.svg</file>
<file>resources/config/sound.svg</file>
<file>resources/config/debug.ini</file>
@ -349,6 +365,8 @@
<file>resources/usericons/pawn_single.svg</file>
<file>resources/usericons/pawn_double.svg</file>
<file>resources/usericons/pawn_dev_single.svg</file>
<file>resources/usericons/pawn_dev_double.svg</file>
<file>resources/usericons/pawn_donator_single.svg</file>
<file>resources/usericons/pawn_donator_double.svg</file>
<file>resources/usericons/pawn_judge_single.svg</file>

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
version="1.1"
width="250"
id="svg13"
height="231.66667"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg">
<defs
id="defs13" />
<g
transform="matrix(1.705559,0,0,1.705559,-18.310328,-4.2419088)"
id="g13">
<path
d="M 90.069854,3.479957 C 89.356513,1.2235709 86.980392,-0.01102897 84.723451,0.70218215 L 3.4767601,26.377781 C 1.2199188,27.090982 -0.01486587,29.46663 0.69839437,31.723116 L 33.512365,135.52112 c 0.713341,2.25639 3.089462,3.49099 5.346403,2.77777 l 81.246672,-25.6756 c 2.25684,-0.71319 3.49163,-3.08884 2.77837,-5.34533 L 90.074852,3.479957 Z"
style="display:none;fill:#c0c0c0;fill-opacity:1;stroke:#989898;stroke-width:1;stroke-opacity:1"
id="path1" />
<path
d="m 110.61293,7.4983294 c -0.36657,-2.337853 -2.53055,-3.9150142 -4.86886,-3.5484627 L 21.563382,17.14452 c -2.338314,0.366502 -3.915784,2.529976 -3.549207,4.867929 L 34.876507,129.55893 c 0.366577,2.33786 2.530549,3.91502 4.868863,3.54847 l 84.18069,-13.19466 c 2.33831,-0.3665 3.91578,-2.52997 3.5492,-4.86793 L 110.61093,7.4983294 Z"
style="fill:#c0c0c0;fill-opacity:1;stroke:#989898;stroke-width:1;stroke-opacity:1"
id="path4" />
<path
d="m 130.53623,15.555064 c 0,-2.366441 -1.89356,-4.259575 -4.26046,-4.259575 H 41.067426 c -2.366905,0 -4.260468,1.893134 -4.260468,4.259575 V 124.41102 c 0,2.36644 1.893563,4.25957 4.260468,4.25957 h 85.208344 c 2.3669,0 4.26046,-1.89313 4.26046,-4.25957 z"
style="fill:#c0c0c0;fill-opacity:1;stroke:#989898;stroke-width:1;stroke-opacity:1"
id="path7" />
<path
d="m 149.43988,26.480639 c 0.38018,-2.335754 -1.1846,-4.508374 -3.52082,-4.88852 L 61.817351,7.9076636 C 59.481136,7.5275576 57.308066,9.0920839 56.927894,11.427736 L 39.439773,118.87426 c -0.380182,2.33576 1.184602,4.50838 3.520816,4.88852 l 84.102711,13.68346 c 2.33622,0.38011 4.50929,-1.18442 4.88946,-3.52007 L 149.43688,26.479639 Z"
style="display:inline;fill:#c0c0c0;fill-opacity:1;stroke:#989898;stroke-width:1;stroke-opacity:1"
id="path10" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 MiB

View file

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!--!Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.-->
<svg
version="1.1"
width="172.65051"
id="svg13"
height="213.30714"
xml:space="preserve"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"><defs
id="defs13" /><g
transform="matrix(1.705559,0,0,1.705559,-97.653345,-68.741256)"
id="g13"><path
d="m 151.48519,45.063813 c 0,-2.366441 -1.89356,-4.259575 -4.26046,-4.259575 H 62.016385 c -2.366905,0 -4.260468,1.893134 -4.260468,4.259575 V 153.91977 c 0,2.36644 1.893563,4.25957 4.260468,4.25957 h 85.208345 c 2.3669,0 4.26046,-1.89313 4.26046,-4.25957 z"
style="fill:#c0c0c0;fill-opacity:1;stroke:#989898;stroke-width:1;stroke-opacity:1"
id="path7" /><path
d="m 154.70135,48.441704 c 0,-2.366441 -1.89356,-4.259575 -4.26046,-4.259575 H 65.232545 c -2.366905,0 -4.260468,1.893134 -4.260468,4.259575 V 157.29767 c 0,2.36644 1.893563,4.25957 4.260468,4.25957 h 85.208345 c 2.3669,0 4.26046,-1.89313 4.26046,-4.25957 z"
style="fill:#c0c0c0;fill-opacity:1;stroke:#989898;stroke-width:1;stroke-opacity:1"
id="path7-5" /><path
d="m 157.98403,51.75453 c 0,-2.366441 -1.89356,-4.259575 -4.26046,-4.259575 H 68.515228 c -2.366905,0 -4.260468,1.893134 -4.260468,4.259575 v 108.85596 c 0,2.36644 1.893563,4.25957 4.260468,4.25957 h 85.208342 c 2.3669,0 4.26046,-1.89313 4.26046,-4.25957 z"
style="fill:#c0c0c0;fill-opacity:1;stroke:#989898;stroke-width:1;stroke-opacity:1"
id="path7-6" /></g><path
d="m 196.24576,207.42361 c 0,0.11213 0,0.22413 0,0.33621 -0.0498,4.54511 -4.18399,7.63329 -8.72909,7.63329 h -12.19086 c -3.29988,0 -5.97713,2.67727 -5.97713,5.97712 0,0.4234 0.0498,0.83433 0.12449,1.23279 0.26149,1.27014 0.80939,2.49046 1.34485,3.72325 0.75959,1.71843 1.50674,3.4244 1.50674,5.22998 0,3.95986 -2.68971,7.55859 -6.64956,7.72046 -0.43583,0.0128 -0.87166,0.025 -1.31995,0.025 -17.60761,0 -31.878,-14.27038 -31.878,-31.878 0,-17.60761 14.28284,-31.878 31.89046,-31.878 17.60762,0 31.87801,14.27039 31.87801,31.878 z m -47.81703,3.98475 c 0,-2.20407 -1.78067,-3.98475 -3.98473,-3.98475 -2.20407,0 -3.98477,1.78068 -3.98477,3.98475 0,2.20406 1.7807,3.98475 3.98477,3.98475 2.20406,0 3.98473,-1.78069 3.98473,-3.98475 z m 0,-11.95426 c 2.20407,0 3.98477,-1.78068 3.98477,-3.98475 0,-2.20407 -1.7807,-3.98475 -3.98477,-3.98475 -2.20405,0 -3.98473,1.78068 -3.98473,3.98475 0,2.20407 1.78068,3.98475 3.98473,3.98475 z m 19.92376,-11.95424 c 0,-2.20408 -1.78068,-3.98477 -3.98473,-3.98477 -2.20407,0 -3.98477,1.78069 -3.98477,3.98477 0,2.20405 1.7807,3.98474 3.98477,3.98474 2.20405,0 3.98473,-1.78069 3.98473,-3.98474 z m 11.95426,11.95424 c 2.20407,0 3.98475,-1.78068 3.98475,-3.98475 0,-2.20407 -1.78068,-3.98475 -3.98475,-3.98475 -2.20406,0 -3.98475,1.78068 -3.98475,3.98475 0,2.20407 1.78069,3.98475 3.98475,3.98475 z"
id="path1"
style="display:none;fill:#3b3b3b;fill-opacity:1;stroke:#000000;stroke-width:2.53798;stroke-dasharray:none;stroke-opacity:1" /><path
d="M 126.20915,54.574783 82.324247,83.8512 c -5.76807,3.845383 -9.435059,10.089163 -10.029703,16.90777 12.348823,2.53716 22.081191,12.26955 24.638191,24.63819 6.838435,-0.59465 13.062395,-4.26163 16.907775,-10.0297 l 29.2566,-43.904722 c 1.32804,-2.001984 2.04162,-4.340923 2.04162,-6.75915 0,-6.719506 -5.45092,-12.170428 -12.17043,-12.170428 -2.3984,0 -4.75718,0.713573 -6.75915,2.041623 z M 88.052677,131.81933 c 0,-12.26953 -9.930593,-22.20012 -22.200138,-22.20012 -12.269532,0 -22.200126,9.93059 -22.200126,22.20012 0,0.77305 0.03966,1.54609 0.118929,2.2993 0.356787,3.46877 -2.021792,7.21505 -5.510393,7.21505 h -0.951431 c -3.508409,0 -6.342895,2.83447 -6.342895,6.3429 0,3.5084 2.834486,6.34289 6.342895,6.34289 h 28.543021 c 12.269545,0 22.200138,-9.93059 22.200138,-22.20014 z"
id="path1-2"
style="fill:#989898;fill-opacity:1;stroke-width:0.198215" /></svg>

After

Width:  |  Height:  |  Size: 3.9 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.8 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Before After
Before After

View file

@ -1,6 +1,10 @@
[Rules]
# The default log level is info
*.debug = false
#*.info = true
#*.warning = true
#*.critical = true
#*.fatal = true
# Uncomment a rule to see debug level logs for that category,
# or set <category> = false to disable logging
@ -24,6 +28,8 @@
#dlg_tip_of_the_day = true
#dlg_update = true
#general_settings_page = true
#settings_cache = true
#servers_settings = true
#shortcuts_settings = true

View file

@ -0,0 +1,799 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/"
width="62.636364"
height="62.090908"
id="svg2"
sodipodi:version="0.32"
inkscape:version="1.4.3 (0d15f75042, 2025-12-25)"
sodipodi:docname="storage.svg"
inkscape:output_extension="org.inkscape.output.svg.inkscape"
version="1.0"
xml:space="preserve"
xmlns="http://www.w3.org/2000/svg"><defs
id="defs4"><linearGradient
id="linearGradient3169"><stop
style="stop-color:#0000ff;stop-opacity:1;"
offset="0"
id="stop3171" /><stop
style="stop-color:#000067;stop-opacity:1;"
offset="1"
id="stop3173" /></linearGradient><linearGradient
id="linearGradient4766"><stop
style="stop-color:#784421;stop-opacity:1;"
offset="0"
id="stop4768" /><stop
style="stop-color:#3d2210;stop-opacity:0;"
offset="1"
id="stop4770" /></linearGradient><linearGradient
id="linearGradient4758"><stop
style="stop-color:#a05a2c;stop-opacity:1;"
offset="0"
id="stop4760" /><stop
style="stop-color:#3d2210;stop-opacity:1;"
offset="1"
id="stop4762" /></linearGradient><inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 526.18109 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="744.09448 : 526.18109 : 1"
inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
id="perspective10" /><inkscape:perspective
id="perspective2484"
inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
inkscape:vp_z="744.09448 : 526.18109 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 526.18109 : 1"
sodipodi:type="inkscape:persp3d" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient4758"
id="linearGradient4764"
x1="466.09601"
y1="485.96021"
x2="715.14801"
y2="485.96021"
gradientUnits="userSpaceOnUse" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient4766"
id="linearGradient4772"
x1="496.548"
y1="485.26816"
x2="683.31201"
y2="485.26816"
gradientUnits="userSpaceOnUse" /><radialGradient
inkscape:collect="always"
xlink:href="#linearGradient3169"
id="radialGradient3175"
cx="120.07376"
cy="56.138123"
fx="120.07376"
fy="56.138123"
r="82.790039"
gradientTransform="matrix(1,0,0,0.2116376,0,44.257186)"
gradientUnits="userSpaceOnUse" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient6482"
id="linearGradient6488"
x1="32.18182"
y1="3.2835093"
x2="32.18182"
y2="13.02554"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.0281354,0,0,1.0429299,85.21874,131.0326)" /><linearGradient
id="linearGradient6482"><stop
style="stop-color:#ffffff;stop-opacity:1;"
offset="0"
id="stop6484" /><stop
style="stop-color:#00ff00;stop-opacity:0;"
offset="1"
id="stop6486" /></linearGradient><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient6464"
id="linearGradient6470"
x1="32.090908"
y1="1.8181819"
x2="31.09091"
y2="62.909088"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(0,-0.1818182)" /><linearGradient
id="linearGradient6464"><stop
style="stop-color:#0061ff;stop-opacity:1;"
offset="0"
id="stop6466" /><stop
style="stop-color:#001c4c;stop-opacity:1;"
offset="1"
id="stop6468" /></linearGradient><linearGradient
y2="62.909088"
x2="31.09091"
y1="1.8181819"
x1="32.090908"
gradientTransform="translate(86.2151,131.5372)"
gradientUnits="userSpaceOnUse"
id="linearGradient4477"
xlink:href="#linearGradient6464"
inkscape:collect="always" /><linearGradient
inkscape:collect="always"
id="linearGradient2916"><stop
style="stop-color:white;stop-opacity:1;"
offset="0"
id="stop2918" /><stop
style="stop-color:white;stop-opacity:0;"
offset="1"
id="stop2920" /></linearGradient><linearGradient
inkscape:collect="always"
id="linearGradient2902"><stop
style="stop-color:black;stop-opacity:1;"
offset="0"
id="stop2905" /><stop
style="stop-color:black;stop-opacity:0;"
offset="1"
id="stop2907" /></linearGradient><linearGradient
id="linearGradient2064"><stop
id="stop2066"
offset="0"
style="stop-color:white;stop-opacity:1;" /><stop
style="stop-color:#555753;stop-opacity:0.60000002;"
offset="0.5"
id="stop2070" /><stop
id="stop2068"
offset="1"
style="stop-color:#555753;stop-opacity:0;" /></linearGradient><linearGradient
id="linearGradient9641"><stop
style="stop-color:white;stop-opacity:1"
offset="0"
id="stop9643" /><stop
style="stop-color:#888a85;stop-opacity:1"
offset="1"
id="stop9645" /></linearGradient><linearGradient
id="linearGradient9633"><stop
style="stop-color:#eeeeec;stop-opacity:1"
offset="0"
id="stop9635" /><stop
style="stop-color:#888a85;stop-opacity:1"
offset="1"
id="stop9639" /></linearGradient><linearGradient
id="linearGradient9613"><stop
style="stop-color:white;stop-opacity:1"
offset="0"
id="stop9615" /><stop
id="stop9619"
offset="0.5"
style="stop-color:white;stop-opacity:1;" /><stop
style="stop-color:#cccfca;stop-opacity:1"
offset="1"
id="stop9617" /></linearGradient><linearGradient
id="linearGradient8710"><stop
style="stop-color:black;stop-opacity:1;"
offset="0"
id="stop8712" /><stop
style="stop-color:white;stop-opacity:1;"
offset="1"
id="stop8714" /></linearGradient><linearGradient
id="linearGradient8631"><stop
id="stop8633"
offset="0"
style="stop-color:#eeeeec;stop-opacity:1" /><stop
style="stop-color:#eeeeec;stop-opacity:1;"
offset="0.2"
id="stop8637" /><stop
id="stop8635"
offset="1"
style="stop-color:#babdb6;stop-opacity:1" /></linearGradient><linearGradient
id="linearGradient8625"><stop
id="stop8627"
offset="0"
style="stop-color:white;stop-opacity:1" /><stop
id="stop8629"
offset="1"
style="stop-color:#babdb6;stop-opacity:1" /></linearGradient><linearGradient
id="linearGradient8613"><stop
style="stop-color:#babdb6;stop-opacity:1"
offset="0"
id="stop8615" /><stop
style="stop-color:#2e3436;stop-opacity:1"
offset="1"
id="stop8617" /></linearGradient><linearGradient
id="linearGradient5740"><stop
style="stop-color:#d0d0cb;stop-opacity:1;"
offset="0"
id="stop5742" /><stop
style="stop-color:#babdb6;stop-opacity:1"
offset="1"
id="stop5744" /></linearGradient><linearGradient
id="linearGradient5690"><stop
style="stop-color:white;stop-opacity:1;"
offset="0"
id="stop5692" /><stop
style="stop-color:#888a85;stop-opacity:0.59848487"
offset="1"
id="stop5694" /></linearGradient><linearGradient
id="linearGradient2899"><stop
id="stop2901"
offset="0"
style="stop-color:#555753;stop-opacity:1" /><stop
id="stop2903"
offset="1"
style="stop-color:#2e3436;stop-opacity:1" /></linearGradient><linearGradient
id="linearGradient3468"><stop
style="stop-color:#fdfdfc;stop-opacity:1"
offset="0"
id="stop3470" /><stop
style="stop-color:white;stop-opacity:0.37121212"
offset="1"
id="stop3472" /></linearGradient><linearGradient
id="linearGradient2909"><stop
style="stop-color:white;stop-opacity:0;"
offset="0"
id="stop2911" /><stop
id="stop2917"
offset="0.5"
style="stop-color:white;stop-opacity:1;" /><stop
style="stop-color:white;stop-opacity:0;"
offset="1"
id="stop2913" /></linearGradient><linearGradient
id="linearGradient2839"><stop
style="stop-color:white;stop-opacity:0.25773194;"
offset="0"
id="stop2841" /><stop
id="stop2847"
offset="0.5472973"
style="stop-color:white;stop-opacity:1;" /><stop
style="stop-color:white;stop-opacity:0.24705882;"
offset="0.66243607"
id="stop2849" /><stop
id="stop2851"
offset="0.875"
style="stop-color:white;stop-opacity:0.83505154;" /><stop
style="stop-color:white;stop-opacity:0;"
offset="1"
id="stop2843" /></linearGradient><linearGradient
id="linearGradient2900"><stop
style="stop-color:black;stop-opacity:0;"
offset="0"
id="stop2902" /><stop
id="stop2908"
offset="0.5"
style="stop-color:black;stop-opacity:1;" /><stop
style="stop-color:black;stop-opacity:0;"
offset="1"
id="stop2904" /></linearGradient><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3468"
id="linearGradient3474"
x1="24.748737"
y1="35.354588"
x2="24.998737"
y2="14.997767"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1,0,0,0.995556,0,-3.931113)" /><radialGradient
inkscape:collect="always"
xlink:href="#linearGradient2902"
id="radialGradient4700"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(4.095822,0,0,3.101282,-9.53921,-94.5433)"
cx="0"
cy="17"
fx="0"
fy="17"
r="2" /><radialGradient
inkscape:collect="always"
xlink:href="#linearGradient2902"
id="radialGradient4702"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(4.095822,0,0,3.101282,38.20996,-10.90025)"
cx="0"
cy="17"
fx="0"
fy="17"
r="2" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2900"
id="linearGradient4704"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(2.047911,0,0,2.067521,1.347566,6.673675)"
x1="9.8994951"
y1="20"
x2="9.8994951"
y2="13.979153" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2909"
id="linearGradient4711"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1,0,0,1.42294,10.5,-14.95703)"
x1="15.335379"
y1="33.06237"
x2="20.329321"
y2="36.37693" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2909"
id="linearGradient4713"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1,0,0,1.42294,-0.875,-15.04578)"
x1="15.335379"
y1="33.06237"
x2="20.329321"
y2="36.37693" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2909"
id="linearGradient4715"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.459833,0,-0.391165,1.370105,40.62503,-13.29892)"
x1="15.335379"
y1="33.06237"
x2="20.329321"
y2="36.37693" /><radialGradient
inkscape:collect="always"
xlink:href="#linearGradient5740"
id="radialGradient5748"
cx="25.251999"
cy="16.47991"
fx="25.251999"
fy="16.47991"
r="21.980215"
gradientTransform="matrix(1.032991,-0.596398,0.575121,0.99614,-12.23456,11.55448)"
gradientUnits="userSpaceOnUse" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2064"
id="linearGradient5790"
gradientUnits="userSpaceOnUse"
x1="18.048874"
y1="25.461344"
x2="22.211937"
y2="12.143078"
gradientTransform="matrix(0.940224,0,0,0.931632,1.331811,1.401537)" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient8631"
id="linearGradient5865"
x1="24"
y1="36.638382"
x2="25.818018"
y2="6.8314762"
gradientUnits="userSpaceOnUse" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2839"
id="linearGradient7658"
gradientUnits="userSpaceOnUse"
x1="27.057796"
y1="12.669416"
x2="32.042896"
y2="31.219666" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient5690"
id="linearGradient8603"
x1="20.304037"
y1="24.035707"
x2="18.498415"
y2="40.647167"
gradientUnits="userSpaceOnUse" /><radialGradient
inkscape:collect="always"
xlink:href="#linearGradient8613"
id="radialGradient8619"
cx="7.5177727"
cy="30.573555"
fx="7.5177727"
fy="30.573555"
r="0.53125"
gradientTransform="matrix(1.662477,0,0,1.61358,-4.989175,-18.65647)"
gradientUnits="userSpaceOnUse" /><radialGradient
inkscape:collect="always"
xlink:href="#linearGradient9613"
id="radialGradient8623"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.389748,0,0,1.348872,-2.91982,-10.63815)"
cx="7.5191436"
cy="30.304251"
fx="7.5191436"
fy="30.304251"
r="0.53125" /><radialGradient
inkscape:collect="always"
xlink:href="#linearGradient9633"
id="radialGradient8664"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.569487,0,0,1.523325,-4.288627,-15.92107)"
cx="7.5336008"
cy="30.307562"
fx="7.5336008"
fy="30.307562"
r="0.53125" /><radialGradient
inkscape:collect="always"
xlink:href="#linearGradient8613"
id="radialGradient8666"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.662477,0,0,1.61358,-4.989175,-18.65647)"
cx="7.5177727"
cy="30.573555"
fx="7.5177727"
fy="30.573555"
r="0.53125" /><radialGradient
inkscape:collect="always"
xlink:href="#linearGradient8625"
id="radialGradient8676"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.662477,0,0,1.61358,-4.989175,-18.65647)"
cx="7.4792061"
cy="30.36071"
fx="7.4792061"
fy="30.36071"
r="0.53125" /><radialGradient
inkscape:collect="always"
xlink:href="#linearGradient8613"
id="radialGradient8678"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.662477,0,0,1.61358,-4.989175,-18.65647)"
cx="7.5177727"
cy="30.573555"
fx="7.5177727"
fy="30.573555"
r="0.53125" /><radialGradient
inkscape:collect="always"
xlink:href="#linearGradient9641"
id="radialGradient8680"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.662477,0,0,1.61358,-4.989175,-18.65647)"
cx="7.4893188"
cy="30.337601"
fx="7.4893188"
fy="30.337601"
r="0.53125" /><radialGradient
inkscape:collect="always"
xlink:href="#linearGradient8613"
id="radialGradient8682"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.662477,0,0,1.61358,-4.989175,-18.65647)"
cx="7.5177727"
cy="30.573555"
fx="7.5177727"
fy="30.573555"
r="0.53125" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient8710"
id="linearGradient8716"
x1="40.617188"
y1="30.554688"
x2="40.710938"
y2="30.359375"
gradientUnits="userSpaceOnUse" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient8710"
id="linearGradient9605"
gradientUnits="userSpaceOnUse"
x1="40.617188"
y1="30.554688"
x2="40.710938"
y2="30.359375"
gradientTransform="matrix(0.602867,-0.797841,0.797841,0.602867,-41.12611,44.62773)" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient8710"
id="linearGradient9649"
gradientUnits="userSpaceOnUse"
x1="40.617188"
y1="30.554688"
x2="40.710938"
y2="30.359375"
gradientTransform="rotate(-30.000012,-5.5813167,76.089146)" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient8710"
id="linearGradient9654"
gradientUnits="userSpaceOnUse"
x1="40.617188"
y1="30.554688"
x2="40.710938"
y2="30.359375"
gradientTransform="matrix(0.707107,0.527555,-0.707107,0.527555,29.0058,-24.09196)" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2916"
id="linearGradient2973"
x1="12.5"
y1="43.1875"
x2="12.5"
y2="34.045513"
gradientUnits="userSpaceOnUse" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2899"
id="linearGradient5655"
gradientUnits="userSpaceOnUse"
x1="53.812813"
y1="43.573235"
x2="-2.8138931"
y2="35.500015"
gradientTransform="translate(0,50)" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2902"
id="linearGradient2992"
x1="21.9375"
y1="39"
x2="21.9375"
y2="37.995617"
gradientUnits="userSpaceOnUse" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2902"
id="linearGradient2910"
x1="22.101398"
y1="27.658131"
x2="22.971142"
y2="20.903238"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(0,2)" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2916"
id="linearGradient2922"
x1="24.847851"
y1="28.908398"
x2="24.847851"
y2="25.757175"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(0,2)" /></defs><sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="5.8830488"
inkscape:cx="-3.9945275"
inkscape:cy="-14.363301"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="2560"
inkscape:window-height="1408"
inkscape:window-x="0"
inkscape:window-y="32"
inkscape:window-maximized="1"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:showpageshadow="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#505050" /><metadata
id="metadata7"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><g
inkscape:label="Ebene 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-86.987816,-132.85536)"><rect
style="fill:url(#linearGradient4477);fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-opacity:1"
id="rect6462"
width="61.636364"
height="61.090908"
x="87.487816"
y="133.35536"
ry="5.6363635" /><rect
style="fill:url(#linearGradient6488);fill-opacity:1;fill-rule:nonzero;stroke:none"
id="rect6472"
width="59.796619"
height="13.251164"
x="88.407707"
y="134.45705"
ry="4.7325583" /><g
inkscape:label="Livello 1"
id="layer1-3"
style="display:inline"
transform="matrix(1.1537183,0,0,1.1537183,91.003924,136.40297)"><g
id="g3519"
style="opacity:0.7"
transform="matrix(1.030831,0,0,1.151147,-0.73609,-12.57431)"
inkscape:export-filename="/home/lapo/Desktop/uhm.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90"><rect
transform="scale(-1)"
y="-48.024086"
x="-9.5392103"
height="12.405126"
width="8.1916437"
id="rect2884"
style="opacity:1;fill:url(#radialGradient4700);fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /><rect
y="35.618961"
x="38.209965"
height="12.405126"
width="8.1916437"
id="rect2894"
style="opacity:1;fill:url(#radialGradient4702);fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /><rect
y="35.618961"
x="9.5392103"
height="12.405126"
width="28.670753"
id="rect2898"
style="opacity:1;fill:url(#linearGradient4704);fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /></g><g
id="g5672"
transform="translate(0,-48.99747)"><path
sodipodi:nodetypes="ccccccccccccc"
id="rect2010"
d="M 4.5182287,80.500013 H 43.481768 c 0.564099,0 1.018229,0.45413 1.018229,1.018229 v 2.963543 c 0,1.315584 -0.450231,3.018228 -2.455729,3.018228 L 40.5,87.5 v 1 h -33 v -1 l -1.8567713,1.3e-5 c -1.2712053,0 -2.1432282,-0.884627 -2.1432282,-2.255665 v -3.726106 c 0,-0.564099 0.4541297,-1.018229 1.0182282,-1.018229 z"
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:url(#linearGradient5655);fill-opacity:1;fill-rule:nonzero;stroke:#2e3436;stroke-width:1;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.4;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none" /><path
transform="translate(0,50)"
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.1;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:url(#linearGradient2973);stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dashoffset:1.4;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none"
d="m 4.59375,31.59375 v 3.729743 c 0,0.599619 0.3756505,1.104854 0.8863276,1.104854 H 42.426407 c 0.512469,0 0.979843,-0.507235 0.979843,-1.016466 V 31.59375 Z"
id="path2076"
sodipodi:nodetypes="ccccccc" /><g
transform="translate(0,50)"
style="opacity:0.5"
id="g4706"><path
style="opacity:0.109524;fill:url(#linearGradient4711);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 26.144738,32.088747 c 0,0 -1.502602,5.533939 -3.226175,5.911253 0,0 6.231378,-0.125771 6.231378,-0.125771 1.387072,-0.317461 3.358758,-5.785482 3.358758,-5.785482 z"
id="path2907"
sodipodi:nodetypes="ccccc"
inkscape:export-filename="/home/lapo/Desktop/uhm.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90" /><path
inkscape:export-ydpi="90"
inkscape:export-xdpi="90"
inkscape:export-filename="/home/lapo/Desktop/uhm.png"
sodipodi:nodetypes="ccccc"
id="path2892"
d="m 14.769738,32 c 0,0 -1.502602,5.533939 -3.226175,5.911253 0,0 6.231378,-0.125771 6.231378,-0.125771 C 19.162013,37.468021 21.133699,32 21.133699,32 Z"
style="opacity:0.109524;fill:url(#linearGradient4713);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /><path
inkscape:export-ydpi="90"
inkscape:export-xdpi="90"
inkscape:export-filename="/home/lapo/Desktop/uhm.png"
sodipodi:nodetypes="ccccc"
id="path2896"
d="m 34.886139,32 c 0,0 -2.212224,5.328458 -3.108503,5.691761 0,0 2.899969,-0.121101 2.899969,-0.121101 C 35.402697,37.264987 37.8125,32 37.8125,32 Z"
style="opacity:0.109524;fill:url(#linearGradient4715);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /></g></g><path
style="fill:url(#radialGradient5748);fill-opacity:1;stroke:#888a85;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1"
d="m 11.693127,10.498788 h 24.572566 c 1.68417,0 2.396517,0.117479 3.040019,2.385005 l 5.074491,17.881119 c 0.501024,1.765471 -1.355848,2.735101 -3.040018,2.735101 H 6.6186312 c -1.868408,0 -3.4893833,-1.181417 -3.0400182,-2.735101 L 8.8290448,12.611497 c 0.5683008,-1.964905 1.1799122,-2.112709 2.8640822,-2.112709 z"
id="rect1879"
sodipodi:nodetypes="cczzcczzc"
inkscape:export-filename="/home/lapo/Desktop/uhm.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90" /><path
sodipodi:type="inkscape:offset"
inkscape:radius="-0.5"
inkscape:original="M 11.6875 10.5 C 10.00333 10.5 9.4120513 10.660095 8.84375 12.625 L 3.59375 30.75 C 3.1443849 32.303684 4.7565918 33.500002 6.625 33.5 L 41.34375 33.5 C 43.02792 33.5 44.876024 32.515471 44.375 30.75 L 39.3125 12.875 C 38.668998 10.607474 37.965419 10.5 36.28125 10.5 L 11.6875 10.5 z "
style="display:inline;opacity:0.462406;fill:url(#linearGradient7658);fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1"
id="path5806"
d="m 11.6875,11 c -0.826242,0 -1.28475,0.05742 -1.5625,0.242188 -0.2777497,0.184768 -0.5284825,0.580009 -0.8007812,1.521484 l -5.2500001,18.125 c -0.1708248,0.590628 0.021709,1.039316 0.4902344,1.4375 C 5.0329784,32.724356 5.7975106,33.000001 6.625,33 h 34.71875 c 0.744655,0 1.538941,-0.232575 2.03125,-0.609375 0.492309,-0.3768 0.719298,-0.799984 0.519531,-1.503906 l -5.0625,-17.875 C 38.52278,11.922001 38.224454,11.462814 37.910156,11.253906 37.595859,11.044998 37.112699,11 36.28125,11 Z" /><ellipse
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:url(#radialGradient8623);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.4;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none"
id="path8621"
transform="matrix(-2.628602,0,0,1.777765,27.79309,-23.77739)"
cx="7.625"
cy="30.578125"
rx="0.53125"
ry="0.515625" /><path
style="fill:none;fill-opacity:1;stroke:url(#linearGradient5790);stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
d="m 16.110953,12.552805 c -0.573581,0 -1.02837,0.431821 -1.02837,0.989859 l -0.940223,3.230801 c -2.859962,1.276514 -4.6423552,3.099073 -4.6423552,5.123976 0,3.856957 6.4790242,6.987239 14.4853222,6.98724 8.006296,0 14.514705,-3.130284 14.514704,-6.98724 0,-2.039034 -1.835591,-3.875388 -4.730501,-5.153089 l -0.940224,-3.201688 c 0,-0.558038 -0.454788,-0.989859 -1.02837,-0.989859 z"
id="path2784"
sodipodi:nodetypes="cccssscccc" /><path
style="display:inline;fill:none;fill-opacity:1;stroke:url(#linearGradient3474);stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1"
d="m 11.6875,11.500005 c -0.803124,0 -1.097168,0.07051 -1.21875,0.155556 -0.121582,0.08504 -0.357707,0.40212 -0.6875,1.306667 l -5.25,18.137786 c -0.1337204,0.366765 -0.054827,0.533865 0.3125,0.84 0.3673267,0.306136 1.066693,0.56 1.78125,0.560001 h 34.71875 c 0.639793,0 1.393345,-0.237954 1.78125,-0.52889 0.387905,-0.290935 0.488311,-0.382809 0.3125,-0.871111 L 38.375,13.242228 c -0.377206,-1.04766 -0.68208,-1.439297 -0.84375,-1.555556 -0.16167,-0.116259 -0.443711,-0.186667 -1.25,-0.186667 z"
id="path3394"
sodipodi:nodetypes="csccsccsccscc" /><g
id="g5657"
transform="translate(7,-1)"
style="opacity:0.302857"><rect
ry="0.74712253"
rx="0.75130093"
y="35.500008"
x="18.499996"
height="1.9999924"
width="14.000004"
id="rect5641"
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#eeeeec;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.4;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none" /><rect
y="36"
x="19"
height="1"
width="1"
id="rect5645"
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.4;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none" /><rect
y="36"
x="22"
height="1"
width="1"
id="rect5647"
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.4;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none" /><rect
y="36"
x="24"
height="1"
width="1"
id="rect5649"
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.4;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none" /><rect
y="36"
x="26"
height="1"
width="1"
id="rect5651"
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.4;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none" /><rect
y="36"
x="29"
height="1"
width="2"
id="rect5653"
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.4;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none" /></g><path
sodipodi:type="inkscape:offset"
inkscape:radius="-0.44194174"
inkscape:original="M 16.125 12.5625 C 15.55142 12.5625 15.09375 12.973212 15.09375 13.53125 L 14.15625 16.78125 C 11.296288 18.057765 9.5 19.881347 9.5 21.90625 C 9.5 25.763206 15.993702 28.874999 24 28.875 C 32.006296 28.874999 38.500001 25.763206 38.5 21.90625 C 38.5 19.867215 36.67616 18.027701 33.78125 16.75 L 32.84375 13.53125 C 32.843748 12.973212 32.386082 12.5625 31.8125 12.5625 L 16.125 12.5625 z "
style="display:inline;fill:url(#linearGradient5865);fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="path5857"
d="m 16.125,13.003906 c -0.362612,0 -0.589844,0.210942 -0.589844,0.527344 a 0.44198593,0.44198593 0 0 1 -0.01758,0.123047 l -0.9375,3.25 a 0.44198593,0.44198593 0 0 1 -0.24414,0.28125 c -1.389903,0.620369 -2.504368,1.368471 -3.25586,2.177734 -0.751491,0.809263 -1.1386718,1.661199 -1.1386717,2.542969 0,1.680455 1.4530107,3.311153 3.9980467,4.533203 2.545037,1.22205 6.114654,1.99414 10.060547,1.994141 3.945892,-1e-6 7.51551,-0.772091 10.060547,-1.994141 2.545037,-1.22205 3.998047,-2.852748 3.998047,-4.533203 0,-0.887751 -0.391823,-1.747213 -1.154297,-2.5625 -0.762474,-0.815287 -1.893636,-1.568394 -3.300781,-2.189453 a 0.44198593,0.44198593 0 0 1 -0.246094,-0.28125 l -0.9375,-3.21875 a 0.44198593,0.44198593 0 0 1 -0.01758,-0.123047 c -10e-7,-0.316404 -0.22723,-0.527344 -0.589844,-0.527344 z" /><ellipse
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.9;fill:#000000;fill-opacity:0.0530303;fill-rule:nonzero;stroke:url(#linearGradient8603);stroke-width:2.52015;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.4;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none"
id="path8595"
transform="matrix(0.449978,0,0,0.349909,16.36363,12.21469)"
cx="16.970562"
cy="25.107418"
rx="7.7781744"
ry="4.2868347" /><ellipse
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:url(#radialGradient8619);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.4;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none"
id="path8611"
transform="matrix(1.411772,0,0,0.969697,-3.014767,0.848485)"
cx="7.625"
cy="30.578125"
rx="0.53125"
ry="0.515625" /><ellipse
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:url(#radialGradient8664);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.462594;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.4;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none"
id="path8660"
transform="matrix(-2.628602,0,0,1.777765,60.79309,-23.77739)"
cx="7.625"
cy="30.578125"
rx="0.53125"
ry="0.515625" /><ellipse
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:url(#radialGradient8666);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.4;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none"
id="path8662"
transform="matrix(1.411772,0,0,0.969697,29.98523,0.848485)"
cx="7.625"
cy="30.578125"
rx="0.53125"
ry="0.515625" /><ellipse
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:url(#radialGradient8676);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.4;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none"
id="path8668"
transform="matrix(-2.628602,0,0,1.777765,31.79309,-40.77739)"
cx="7.625"
cy="30.578125"
rx="0.53125"
ry="0.515625" /><ellipse
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:url(#radialGradient8678);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.4;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none"
id="path8670"
transform="matrix(1.411772,0,0,0.969697,0.985233,-16.15152)"
cx="7.625"
cy="30.578125"
rx="0.53125"
ry="0.515625" /><ellipse
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:url(#radialGradient8680);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.4;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none"
id="path8672"
transform="matrix(-2.628602,0,0,1.777765,56.3029,-40.77739)"
cx="7.625"
cy="30.578125"
rx="0.53125"
ry="0.515625" /><ellipse
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:url(#radialGradient8682);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.4;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none"
id="path8674"
transform="matrix(1.411772,0,0,0.969697,25.49504,-16.15152)"
cx="7.625"
cy="30.578125"
rx="0.53125"
ry="0.515625" /><path
style="opacity:0.4;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient8716);stroke-width:0.3;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 40.328109,30.261401 0.874999,0.430332"
id="path8700" /><path
style="display:inline;opacity:0.4;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient9605);stroke-width:0.3;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 7.330186,30.695906 8.201031,30.257228"
id="path9603" /><path
style="display:inline;opacity:0.4;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient9649);stroke-width:0.3;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 11.263531,13.446473 0.972937,-0.06482"
id="path9647" /><path
style="display:inline;opacity:0.4;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient9654);stroke-width:0.3;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 36.124038,13.147874 0.314427,0.688634"
id="path9652" /><rect
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.12;fill:url(#linearGradient2992);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.681836;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none"
id="rect2984"
width="32.03125"
height="1"
x="8"
y="38" /><path
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.12;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:url(#linearGradient2910);stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none"
d="M 10.460155,15.082355 6.8513979,27.675762 C 8.2982685,28.375511 10.625,29.167061 10.429825,31.533131 H 37.299883 C 37.869398,29.640915 39.875,28.375 41.34614,28.25 L 37.498106,15.082355 32.350135,12.523347 H 14.318912 Z"
id="path1997"
sodipodi:nodetypes="ccccccccc" /><path
sodipodi:nodetypes="ccccc"
id="path2912"
d="m 7.9763979,27.050762 c 1.4468706,0.699749 3.1789321,1.433241 3.4256991,3.357369 H 36.857941 C 37.427456,28.515915 38.875,27.5 40.34614,27.375 Z"
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.834286;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:url(#linearGradient2922);stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none" /></g></g></svg>

After

Width:  |  Height:  |  Size: 40 KiB

View file

@ -52,8 +52,7 @@
id="defs8">
<polygon
id="s"
points="-301930,415571 0,-513674 301930,415571 -488533,-158734 488533,-158734 "
transform="scale(1.94676e-6,1.94676e-6)"
points="-0.587785,0.809017 0,-1 0.587785,0.809017 -0.951057,-0.309017 0.951057,-0.309017 "
style="fill:#ffde00" />
<clipPath
clipPathUnits="userSpaceOnUse"

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Before After
Before After

View file

@ -16,6 +16,6 @@
<rect id="rect10" height="2.88" width="12.8" y="4.278e-9" x="0"/>
<rect id="rect12" height="2.88" width="12.8" y="3.36" x="0" fill="#da121a"/>
<polygon id="polygon14" points="0 0 8.6603 5 0 10" fill="#0f47af" transform="scale(.96)"/>
<polygon id="polygon16" points="4.1557e5 -3.0193e5 -5.1367e5 0 4.1557e5 3.0193e5 -1.5873e5 -4.8853e5 -1.5873e5 4.8853e5" fill="#fcdd09" transform="matrix(.0000029902 0 0 .0000029902 2.7713 4.8)"/>
<polygon id="polygon16" points="4.01394 3.897169 1.23532 4.8 4.01394 5.702831 2.296666 3.3392 2.296666 6.2608" fill="#fcdd09"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Before After
Before After

View file

@ -1,11 +1,13 @@
@page deck_search_syntax_help Deck Search Syntax Help
## Deck Search Syntax Help
-----
The search bar recognizes a set of special commands.<br>
In this list of examples below, each entry has an explanation and can be clicked to test the query. Note that all
searches are case insensitive.
<dl>
<dt>Display Name (The deck name, or the filename if the deck name isn't set):</dt>
<dd>[red deck wins](#red deck wins) <small>(Any deck with a display name containing the words red, deck, and wins)</small></dd>
<dd>["red deck wins"](#%22red deck wins%22) <small>(Any deck with a display name containing the exact phrase "red deck wins")</small></dd>
@ -15,15 +17,23 @@ searches are case insensitive.
<dd>[n:red n:deck n:wins](#n:red n:deck n:wins) <small>(Any deck with a name containing the words red, deck, and wins)</small></dd>
<dd>[n:"red deck wins"](#n:%22red deck wins%22) <small>(Any deck with a name containing the exact phrase "red deck wins")</small></dd>
<dt><u>F</u>ile Name:</dt>
<dd>[f:aggro](#f:aggro) <small>(Any deck with a filename containing the word aggro)</small></dd>
<dd>[f:red f:deck f:wins](#f:red f:deck f:wins) <small>(Any deck with a filename containing the words red, deck, and wins)</small></dd>
<dd>[f:"red deck wins"](#f:%22red deck wins%22) <small>(Any deck with a filename containing the exact phrase "red deck wins")</small></dd>
<dt><u>F</u>ile <u>N</u>ame:</dt>
<dd>[fn:aggro](#fn:aggro) <small>(Any deck with a filename containing the word aggro)</small></dd>
<dd>[fn:red fn:deck fn:wins](#fn:red fn:deck fn:wins) <small>(Any deck with a filename containing the words red, deck, and wins)</small></dd>
<dd>[fn:"red deck wins"](#fn:%22red deck wins%22) <small>(Any deck with a filename containing the exact phrase "red deck wins")</small></dd>
<dt>Relative <u>P</u>ath (starting from the deck folder):</dt>
<dd>[p:aggro](#p:aggro) <small>(Any deck that has "aggro" somewhere in its relative path)</small></dd>
<dd>[p:edh/](#p:edh/) <small>(Any deck with "edh/" in its relative path, A.K.A. decks in the "edh" folder)</small></dd>
<dt><u>F</u>ormat:</dt>
<dd>[f:standard](#f:standard) <small>(Any deck with format set to standard)</small></dd>
<dt><u>C</u>omments:</dt>
<dd>[c:good](#c:good) <small>(Any deck with comments containing the word good)</small></dd>
<dd>[c:good c:deck](#c:good c:deck) <small>(Any deck with comments containing the words good and deck)</small></dd>
<dd>[c:"good deck"](#c:%22good deck%22) <small>(Any deck with comments containing the exact phrase "good deck")</small></dd>
<dt>Deck Contents (Uses [card search expressions](#cardSearchSyntaxHelp)):</dt>
<dd><a href="#[[plains]]">[[plains]]</a> <small>(Any deck that contains at least one card with "plains" in its name)</small></dd>
<dd><a href="#[[t:legendary]]">[[t:legendary]]</a> <small>(Any deck that contains at least one legendary)</small></dd>
@ -37,6 +47,6 @@ searches are case insensitive.
<dd>[t:aggro OR o:control](#t:aggro OR o:control) <small>(Any deck filename that contains either aggro or control)</small></dd>
<dt>Grouping:</dt>
<dd><a href="#red -([[]]:100 or aggro)">red -([[]]:100 or aggro)</a> <small>(Any deck that has red in its filename but is not 100 cards or has aggro in its filename)</small></dd>
<dd><a href="#red -([[]]:100 OR aggro)">red -([[]]:100 OR aggro)</a> <small>(Any deck that has red in its filename but is not 100 cards or has aggro in its filename)</small></dd>
</dl>
</dl>

View file

@ -1,10 +1,12 @@
@page search_syntax_help Search Syntax Help
## Search Syntax Help
-----
The search bar recognizes a set of special commands similar to some other card databases.<br>
In this list of examples below, each entry has an explanation and can be clicked to test the query. Note that all searches are case insensitive.
<dl>
<dt>Name:</dt>
<dd>[birds of paradise](#birds of paradise) <small>(Any card name containing the words birds, of, and paradise)</small></dd>
<dd>["birds of paradise"](#%22birds of paradise%22) <small>(Any card name containing the exact phrase "birds of paradise")</small></dd>
@ -49,20 +51,21 @@ In this list of examples below, each entry has an explanation and can be clicked
<dt><u>E</u>dition:</dt>
<dd>[set:lea](#set:lea) <small>(Cards that appear in Alpha, which has the set code LEA)</small></dd>
<dd>[e:lea or e:leb](#e:lea or e:leb) <small>(Cards that appear in Alpha or Beta)</small></dd>
<dd>[e:lea OR e:leb](#e:lea OR e:leb) <small>(Cards that appear in Alpha or Beta)</small></dd>
<dd>[e&lt;8ED](#e<8ED) <small>(Cards that appear before 8th edition)</small></dd>
<dt>Negate:</dt>
<dd>[c:wu -c:m](#c:wu -c:m) <small>(Any card that is white or blue, but not multicolored)</small></dd>
<dt>Branching:</dt>
<dd>[t:sliver or o:changeling](#t:sliver or o:changeling) <small>(Any card that is either a sliver or has changeling)</small></dd>
<dd>[t:sliver OR o:changeling](#t:sliver OR o:changeling) <small>(Any card that is either a sliver or has changeling)</small></dd>
<dt>Grouping:</dt>
<dd><a href="#t:angel -(angel or c:w)">t:angel -(angel or c:w)</a> <small>(Any angel that doesn't have angel in its name and isn't white)</small></dd>
<dd><a href="#t:angel -(angel OR c:w)">t:angel -(angel OR c:w)</a> <small>(Any angel that doesn't have angel in its name and isn't white)</small></dd>
<dt>Regular Expression:</dt>
<dd>[/^fell/](#/^fell/) <small>(Any card name that begins with "fell")</small></dd>
<dd>[o:/counter target .* spell/](#o:/counter target .* spell/) <small>(Any card text with "counter target *something* spell")</small></dd>
<dd>[o:/for each .* and\/or .*/](#o:/for each .* and\/or .*/) <small>(/'s can be escaped with a \)</small></dd>
</dl>
</dl>

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><!--!Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path d="M512 320C512 214 426 128 320 128L320 512C426 512 512 426 512 320zM64 320C64 178.6 178.6 64 320 64C461.4 64 576 178.6 576 320C576 461.4 461.4 576 320 576C178.6 576 64 461.4 64 320z"/></svg>

After

Width:  |  Height:  |  Size: 410 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><!--!Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path d="M352 188.5L300.1 175.5C293.6 173.9 288.8 168.4 288.1 161.7C287.4 155 290.9 148.6 296.8 145.6L337.6 125.2L294.3 92.7C288.8 88.6 286.5 81.4 288.7 74.8C290.9 68.2 297.1 64 304 64L464 64C494.2 64 522.7 78.2 540.8 102.4L598.4 179.2C604.6 187.5 608 197.6 608 208C608 234.5 586.5 256 560 256L538.5 256C521.5 256 505.2 249.3 493.2 237.3L479.9 224L447.9 224L447.9 245.5C447.9 270.3 460.7 293.4 481.7 306.6L588.3 373.2C620.4 393.3 639.9 428.4 639.9 466.3C639.9 526.9 590.8 576.1 530.1 576.1L32.3 576C29 576 25.7 575.6 22.7 574.6C13.5 571.8 6 565 2.3 556C1 552.7 .1 549.1 0 545.3C-.2 541.6 .3 538 1.3 534.6C4.1 525.4 10.9 517.9 19.9 514.2C22.9 513 26.1 512.2 29.4 512L433.3 476C441.6 475.3 448 468.3 448 459.9C448 455.6 446.3 451.5 443.3 448.5L398.9 404.1C368.9 374.1 352 333.4 352 291L352 188.5zM512 136.3C512 136.2 512 136.1 512 136C512 135.9 512 135.8 512 135.7L512 136.3zM510.7 143.7L464.3 132.1C464.1 133.4 464 134.7 464 136C464 149.3 474.7 160 488 160C498.6 160 507.5 153.2 510.7 143.7zM130.9 180.5C147.2 166 171.3 164.3 189.4 176.4L320 263.4L320 290.9C320 323.7 328.4 355.7 344 383.9L112 383.9C105.3 383.9 99.3 379.7 97 373.5C94.7 367.3 96.5 360.2 101.6 355.8L171 296.3L18.4 319.8C11.4 320.9 4.5 317.2 1.5 310.8C-1.5 304.4 .1 296.8 5.4 292L130.9 180.5z"/></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg fill="#000000" version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg"
width="800px" height="800px" viewBox="0 0 971.986 971.986"
xml:space="preserve">
<g>
<path d="M370.216,459.3c10.2,11.1,15.8,25.6,15.8,40.6v442c0,26.601,32.1,40.101,51.1,21.4l123.3-141.3
c16.5-19.8,25.6-29.601,25.6-49.2V500c0-15,5.7-29.5,15.8-40.601L955.615,75.5c26.5-28.8,6.101-75.5-33.1-75.5h-873
c-39.2,0-59.7,46.6-33.1,75.5L370.216,459.3z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 676 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><!--!Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path d="M160 96C124.7 96 96 124.7 96 160L96 480C96 515.3 124.7 544 160 544L480 544C515.3 544 544 515.3 544 480L544 237.3C544 220.3 537.3 204 525.3 192L448 114.7C436 102.7 419.7 96 402.7 96L160 96zM192 192C192 174.3 206.3 160 224 160L384 160C401.7 160 416 174.3 416 192L416 256C416 273.7 401.7 288 384 288L224 288C206.3 288 192 273.7 192 256L192 192zM320 352C355.3 352 384 380.7 384 416C384 451.3 355.3 480 320 480C284.7 480 256 451.3 256 416C256 380.7 284.7 352 320 352z"/></svg>

After

Width:  |  Height:  |  Size: 693 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><!--!Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path d="M259.1 73.5C262.1 58.7 275.2 48 290.4 48L350.2 48C365.4 48 378.5 58.7 381.5 73.5L396 143.5C410.1 149.5 423.3 157.2 435.3 166.3L503.1 143.8C517.5 139 533.3 145 540.9 158.2L570.8 210C578.4 223.2 575.7 239.8 564.3 249.9L511 297.3C511.9 304.7 512.3 312.3 512.3 320C512.3 327.7 511.8 335.3 511 342.7L564.4 390.2C575.8 400.3 578.4 417 570.9 430.1L541 481.9C533.4 495 517.6 501.1 503.2 496.3L435.4 473.8C423.3 482.9 410.1 490.5 396.1 496.6L381.7 566.5C378.6 581.4 365.5 592 350.4 592L290.6 592C275.4 592 262.3 581.3 259.3 566.5L244.9 496.6C230.8 490.6 217.7 482.9 205.6 473.8L137.5 496.3C123.1 501.1 107.3 495.1 99.7 481.9L69.8 430.1C62.2 416.9 64.9 400.3 76.3 390.2L129.7 342.7C128.8 335.3 128.4 327.7 128.4 320C128.4 312.3 128.9 304.7 129.7 297.3L76.3 249.8C64.9 239.7 62.3 223 69.8 209.9L99.7 158.1C107.3 144.9 123.1 138.9 137.5 143.7L205.3 166.2C217.4 157.1 230.6 149.5 244.6 143.4L259.1 73.5zM320.3 400C364.5 399.8 400.2 363.9 400 319.7C399.8 275.5 363.9 239.8 319.7 240C275.5 240.2 239.8 276.1 240 320.3C240.2 364.5 276.1 400.2 320.3 400z"/></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="169.33115mm"
height="169.59981mm"
viewBox="0 0 169.33115 169.59981"
version="1.1"
id="svg1"
inkscape:export-filename="C.svg"
inkscape:export-xdpi="96.000015"
inkscape:export-ydpi="96.000015"
inkscape:version="1.4.3 (0d15f75042, 2025-12-25)"
sodipodi:docname="colorless.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
inkscape:zoom="0.71535494"
inkscape:cx="397.00572"
inkscape:cy="536.09751"
inkscape:window-width="1853"
inkscape:window-height="1011"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer1">
<inkscape:page
x="0"
y="0"
width="169.33115"
height="169.59981"
id="page2"
margin="0"
bleed="0" />
</sodipodi:namedview>
<defs
id="defs1" />
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-20.334425,-63.700102)">
<ellipse
style="fill:#cccccc;stroke:#000000;stroke-width:0.165333;stroke-linecap:round"
id="path1"
cx="-30.617094"
cy="179.22736"
transform="matrix(0.70654605,-0.70766707,0.70654608,0.70766704,0,0)"
rx="84.650612"
ry="84.65062" />
<rect
style="fill:none;stroke:#000000;stroke-width:14.5003;stroke-linecap:round;stroke-linejoin:bevel;stroke-dasharray:none"
id="rect1"
width="84.683701"
height="84.683769"
x="-221.60611"
y="-73.174698"
ry="0.084683768"
transform="matrix(-0.7073973,-0.70681615,0.7073973,-0.70681615,0,0)" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><!--!Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path d="M535.6 85.7C513.7 63.8 478.3 63.8 456.4 85.7L432 110.1L529.9 208L554.3 183.6C576.2 161.7 576.2 126.3 554.3 104.4L535.6 85.7zM236.4 305.7C230.3 311.8 225.6 319.3 222.9 327.6L193.3 416.4C190.4 425 192.7 434.5 199.1 441C205.5 447.5 215 449.7 223.7 446.8L312.5 417.2C320.7 414.5 328.2 409.8 334.4 403.7L496 241.9L398.1 144L236.4 305.7zM160 128C107 128 64 171 64 224L64 480C64 533 107 576 160 576L416 576C469 576 512 533 512 480L512 384C512 366.3 497.7 352 480 352C462.3 352 448 366.3 448 384L448 480C448 497.7 433.7 512 416 512L160 512C142.3 512 128 497.7 128 480L128 224C128 206.3 142.3 192 160 192L256 192C273.7 192 288 177.7 288 160C288 142.3 273.7 128 256 128L160 128z"/></svg>

After

Width:  |  Height:  |  Size: 899 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><!--!Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path d="M384 96L512 96C529.7 96 544 110.3 544 128C544 145.7 529.7 160 512 160L398.4 160C393.2 185.8 375.5 207.1 352 217.3L352 512L512 512C529.7 512 544 526.3 544 544C544 561.7 529.7 576 512 576L128 576C110.3 576 96 561.7 96 544C96 526.3 110.3 512 128 512L288 512L288 217.3C264.5 207 246.8 185.7 241.6 160L128 160C110.3 160 96 145.7 96 128C96 110.3 110.3 96 128 96L256 96C270.6 76.6 293.8 64 320 64C346.2 64 369.4 76.6 384 96zM439.6 384L584.4 384L512 259.8L439.6 384zM512 480C449.1 480 396.8 446 386 401.1C383.4 390.1 387 378.8 392.7 369L487.9 205.8C492.9 197.2 502.1 192 512 192C521.9 192 531.1 197.3 536.1 205.8L631.3 369C637 378.8 640.6 390.1 638 401.1C627.2 445.9 574.9 480 512 480zM126.8 259.8L54.4 384L199.3 384L126.8 259.8zM.9 401.1C-1.7 390.1 1.9 378.8 7.6 369L102.8 205.8C107.8 197.2 117 192 126.9 192C136.8 192 146 197.3 151 205.8L246.2 369C251.9 378.8 255.5 390.1 252.9 401.1C242.1 445.9 189.8 480 126.9 480C64 480 11.7 446 .9 401.1z"/></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><!--!Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path d="M32 176C32 134.5 63.6 100.4 104 96.4L104 96L384 96C437 96 480 139 480 192L480 368L304 368C264.2 368 232 400.2 232 440L232 500C232 524.3 212.3 544 188 544C163.7 544 144 524.3 144 500L144 272L80 272C53.5 272 32 250.5 32 224L32 176zM268.8 544C275.9 530.9 280 515.9 280 500L280 440C280 426.7 290.7 416 304 416L552 416C565.3 416 576 426.7 576 440L576 464C576 508.2 540.2 544 496 544L268.8 544zM112 144C94.3 144 80 158.3 80 176L80 224L144 224L144 176C144 158.3 129.7 144 112 144z"/></svg>

After

Width:  |  Height:  |  Size: 704 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><!--!Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path d="M278.6 438.6L182.6 534.6C170.1 547.1 149.8 547.1 137.3 534.6L41.3 438.6C28.8 426.1 28.8 405.8 41.3 393.3C53.8 380.8 74.1 380.8 86.6 393.3L128 434.7L128 128C128 110.3 142.3 96 160 96C177.7 96 192 110.3 192 128L192 434.7L233.4 393.3C245.9 380.8 266.2 380.8 278.7 393.3C291.2 405.8 291.2 426.1 278.7 438.6zM352 544C334.3 544 320 529.7 320 512C320 494.3 334.3 480 352 480L384 480C401.7 480 416 494.3 416 512C416 529.7 401.7 544 384 544L352 544zM352 416C334.3 416 320 401.7 320 384C320 366.3 334.3 352 352 352L448 352C465.7 352 480 366.3 480 384C480 401.7 465.7 416 448 416L352 416zM352 288C334.3 288 320 273.7 320 256C320 238.3 334.3 224 352 224L512 224C529.7 224 544 238.3 544 256C544 273.7 529.7 288 512 288L352 288zM352 160C334.3 160 320 145.7 320 128C320 110.3 334.3 96 352 96L576 96C593.7 96 608 110.3 608 128C608 145.7 593.7 160 576 160L352 160z"/></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -0,0 +1,343 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="100"
height="100"
id="svg5322"
version="1.1"
inkscape:version="1.4.2 (ebf0e940, 2025-05-08)"
sodipodi:docname="pawn_dev_double.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<defs
id="defs3">
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 526.18109 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="744.09448 : 526.18109 : 1"
inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
id="perspective5328"/>
<inkscape:perspective
id="perspective5305"
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
inkscape:vp_z="1 : 0.5 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 0.5 : 1"
sodipodi:type="inkscape:persp3d"/>
<linearGradient
id="linearGradient5181">
<stop
style="stop-color:#0fbb00;stop-opacity:1;"
offset="0"
id="stop5183"/>
<stop
style="stop-color:#064400;stop-opacity:1;"
offset="1"
id="stop5185"/>
</linearGradient>
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient3600-2"
id="radialGradient3606-7"
cx="324.32715"
cy="131.40274"
fx="324.32715"
fy="131.40274"
r="25.501276"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.92332021,0.38403097,-0.41592401,1.0000002,78.192026,-120.05314)"/>
<linearGradient
id="linearGradient3600-2">
<stop
style="stop-color:#ffc33d;stop-opacity:1;"
offset="0"
id="stop3602-4"/>
<stop
style="stop-color:#e09900;stop-opacity:1;"
offset="1"
id="stop3604-9"/>
</linearGradient>
<inkscape:perspective
id="perspective5478"
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
inkscape:vp_z="1 : 0.5 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 0.5 : 1"
sodipodi:type="inkscape:persp3d"/>
<linearGradient
id="linearGradient5189">
<stop
style="stop-color:#000ec9;stop-opacity:1;"
offset="0"
id="stop5191"/>
<stop
style="stop-color:#000657;stop-opacity:1;"
offset="1"
id="stop5193"/>
</linearGradient>
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient3600-4"
id="radialGradient3606-1"
cx="324.32715"
cy="131.40274"
fx="324.32715"
fy="131.40274"
r="25.501276"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.92332021,0.38403097,-0.41592401,1.0000002,78.192026,-120.05314)"/>
<linearGradient
id="linearGradient3600-4">
<stop
style="stop-color:#ffc33d;stop-opacity:1;"
offset="0"
id="stop3602-3"/>
<stop
style="stop-color:#e09900;stop-opacity:1;"
offset="1"
id="stop3604-5"/>
</linearGradient>
<inkscape:perspective
id="perspective5559"
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
inkscape:vp_z="1 : 0.5 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 0.5 : 1"
sodipodi:type="inkscape:persp3d"/>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient5173"
id="linearGradient5179"
x1="167.33386"
y1="178.83276"
x2="244.78181"
y2="178.83276"
gradientUnits="userSpaceOnUse"/>
<linearGradient
id="linearGradient5173">
<stop
style="stop-color:#f50000;stop-opacity:1;"
offset="0"
id="stop5175"/>
<stop
style="stop-color:#950000;stop-opacity:1;"
offset="1"
id="stop5177"/>
</linearGradient>
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient3600"
id="radialGradient5169"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.92332021,0.38403097,-0.41592401,1.0000002,78.192026,-120.05314)"
cx="324.32715"
cy="131.40274"
fx="324.32715"
fy="131.40274"
r="25.501276"/>
<linearGradient
id="linearGradient3600">
<stop
style="stop-color:#ffc13d;stop-opacity:1;"
offset="0"
id="stop3602"/>
<stop
style="stop-color:#e09900;stop-opacity:1;"
offset="1"
id="stop3604"/>
</linearGradient>
<radialGradient
r="25.501276"
fy="131.40274"
fx="324.32715"
cy="131.40274"
cx="324.32715"
gradientTransform="matrix(0.92332021,0.38403097,-0.41592401,1.0000002,78.192026,-120.05314)"
gradientUnits="userSpaceOnUse"
id="radialGradient5574"
xlink:href="#linearGradient3600"
inkscape:collect="always"/>
<inkscape:perspective
id="perspective5663"
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
inkscape:vp_z="1 : 0.5 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 0.5 : 1"
sodipodi:type="inkscape:persp3d"/>
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient3600-7"
id="radialGradient3606-8"
cx="324.32715"
cy="131.40274"
fx="324.32715"
fy="131.40274"
r="25.501276"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.92332021,0.38403097,-0.41592401,1.0000002,78.192026,-120.05314)"/>
<linearGradient
id="linearGradient3600-7">
<stop
style="stop-color:#ffc13d;stop-opacity:1;"
offset="0"
id="stop3602-7"/>
<stop
style="stop-color:#e09900;stop-opacity:1;"
offset="1"
id="stop3604-6"/>
</linearGradient>
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient3600-7"
id="radialGradient5254"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.92332021,0.38403097,-0.41592401,1.0000002,78.192026,-120.05314)"
cx="324.32715"
cy="131.40274"
fx="324.32715"
fy="131.40274"
r="25.501276"/>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient5189-1"
id="linearGradient5394"
gradientUnits="userSpaceOnUse"
x1="385.03503"
y1="180.09546"
x2="462.48297"
y2="180.09546"
gradientTransform="matrix(0.96839241,0,0,0.96839241,-360.365,847.52359)"/>
<linearGradient
id="linearGradient5189-1">
<stop
style="stop-color:#000ec9;stop-opacity:1;"
offset="0"
id="stop5191-0"/>
<stop
style="stop-color:#000657;stop-opacity:1;"
offset="1"
id="stop5193-4"/>
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient5173-1"
id="linearGradient5581"
gradientUnits="userSpaceOnUse"
x1="167.33386"
y1="178.83276"
x2="244.78181"
y2="178.83276"
gradientTransform="matrix(0.96839241,0,0,0.96839241,-149.54484,848.74636)"/>
<linearGradient
id="linearGradient5173-1">
<stop
style="stop-color:#f50000;stop-opacity:1;"
offset="0"
id="stop5175-5"/>
<stop
style="stop-color:#950000;stop-opacity:1;"
offset="1"
id="stop5177-3"/>
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient5181-9"
id="linearGradient5782"
gradientUnits="userSpaceOnUse"
x1="282.50455"
y1="181.61069"
x2="359.95248"
y2="181.61069"
gradientTransform="matrix(0.96839241,0,0,0.96839241,-261.07526,846.05625)"/>
<linearGradient
id="linearGradient5181-9">
<stop
style="stop-color:#80d600;stop-opacity:1;"
offset="0"
id="stop5183-3"/>
<stop
style="stop-color:#80d600;stop-opacity:1;"
offset="1"
id="stop5185-0"/>
</linearGradient>
<linearGradient
y2="181.61069"
x2="359.95248"
y1="181.61069"
x1="282.50455"
gradientTransform="matrix(0.96839241,0,0,0.96839241,-175.71812,893.2775)"
gradientUnits="userSpaceOnUse"
id="linearGradient5799"
xlink:href="#linearGradient5181-9"
inkscape:collect="always"/>
</defs>
<sodipodi:namedview
inkscape:document-units="mm"
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="6.0735294"
inkscape:cx="53.757869"
inkscape:cy="53.840194"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="1696"
inkscape:window-height="1051"
inkscape:window-x="98"
inkscape:window-y="1118"
inkscape:window-maximized="1"
inkscape:showpageshadow="2"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
showguides="true">
<sodipodi:guide
position="49.829627,61.114263"
orientation="1,0"
id="guide1"
inkscape:locked="false"/>
</sodipodi:namedview>
<metadata
id="metadata4">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
<dc:title/>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-952.36218)">
<path
style="fill-opacity:1;stroke:black;stroke-width:2.78220296;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
d="M 49.84375 1.71875 C 36.719738 1.71875 26.0625 12.375988 26.0625 25.5 C 26.0625 32.977454 29.538325 39.612734 34.9375 43.96875 C 24.439951 49.943698 17.919149 62.196126 14.3125 75.65625 C 9.0380874 95.34065 30.224013 98.21875 49.84375 98.21875 C 69.463486 98.21875 90.549327 94.96715 85.375 75.65625 C 81.693381 61.916246 75.224585 49.827177 64.8125 43.9375 C 70.181573 39.580662 73.59375 32.953205 73.59375 25.5 C 73.59375 12.375988 62.967762 1.71875 49.84375 1.71875 z "
transform="translate(0,952.36218)"
id="left"/>
<path
style="opacity:1;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.73577702;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 51.28696,1001.834 0,-46.98372 1.434151,0.16768 c 5.155008,0.60274 9.462857,2.72154 12.938257,6.36366 4.74393,4.9715 6.87913,11.35611 6.16464,18.43328 -0.53702,5.31935 -3.09008,10.59498 -6.83833,14.13074 l -1.94072,1.83069 3.04083,2.20427 c 3.58084,2.5957 7.18975,6.4912 9.55296,10.3116 4.89572,7.9144 9.23593,21.4918 8.50487,26.6055 -0.81312,5.6877 -5.43872,9.6977 -13.62216,11.8093 -3.80822,0.9826 -7.68056,1.4713 -14.763321,1.8633 l -4.471177,0.2474 0,-46.9837 z"
id="right"
inkscape:connector-curvature="0"/>
<path
d="m 478.409,116.617 c -0.368,-4.271 -3.181,-7.94 -7.2,-9.403 -4.029,-1.472 -8.539,-0.47 -11.57,2.556 l -62.015,62.011 -68.749,-21.768 -21.768,-68.748 62.016,-62.016 c 3.035,-3.032 4.025,-7.543 2.563,-11.565 -1.477,-4.03 -5.137,-6.837 -9.417,-7.207 -37.663,-3.245 -74.566,10.202 -101.247,36.887 -36.542,36.545 -46.219,89.911 -29.083,135.399 -1.873,1.578 -3.721,3.25 -5.544,5.053 L 19.386,373.152 c -0.073,0.071 -0.145,0.149 -0.224,0.219 -24.345,24.346 -24.345,63.959 0,88.309 24.349,24.344 63.672,24.048 88.013,-0.298 0.105,-0.098 0.201,-0.196 0.297,-0.305 L 301.104,252.456 c 1.765,-1.773 3.404,-3.628 4.949,-5.532 45.5,17.167 98.9,7.513 135.474,-29.056 26.675,-26.687 40.131,-63.593 36.882,-101.251 z M 75.98,435.38 c -8.971,8.969 -23.5,8.963 -32.47,0 -8.967,-8.961 -8.967,-23.502 0,-32.466 8.97,-8.963 23.499,-8.963 32.47,0 8.967,8.964 8.967,23.505 0,32.466 z"
id="path1"
style="display:inline;fill:#A1A1A1;stroke-width:9.87059588;stroke-dasharray:none;fill-opacity:1;stroke:#000000;stroke-opacity:1"
transform="matrix(0.19145387,0,0,0.19145387,4.0816072,956.99677)"
inkscape:label="wrench" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 12 KiB

View file

@ -0,0 +1,211 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="100"
height="100"
id="svg5322"
version="1.1"
inkscape:version="1.4.2 (ebf0e940, 2025-05-08)"
sodipodi:docname="pawn_dev_single.svg"
xml:space="preserve"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/"><defs
id="defs3"><inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 526.18109 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="744.09448 : 526.18109 : 1"
inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
id="perspective5328" /><inkscape:perspective
id="perspective5305"
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
inkscape:vp_z="1 : 0.5 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 0.5 : 1"
sodipodi:type="inkscape:persp3d" /><linearGradient
id="linearGradient5181"><stop
style="stop-color:#0fbb00;stop-opacity:1;"
offset="0"
id="stop5183" /><stop
style="stop-color:#064400;stop-opacity:1;"
offset="1"
id="stop5185" /></linearGradient><radialGradient
inkscape:collect="always"
xlink:href="#linearGradient3600-2"
id="radialGradient3606-7"
cx="324.32715"
cy="131.40274"
fx="324.32715"
fy="131.40274"
r="25.501276"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.92332021,0.38403097,-0.41592401,1.0000002,78.192026,-120.05314)" /><linearGradient
id="linearGradient3600-2"><stop
style="stop-color:#ffc33d;stop-opacity:1;"
offset="0"
id="stop3602-4" /><stop
style="stop-color:#e09900;stop-opacity:1;"
offset="1"
id="stop3604-9" /></linearGradient><inkscape:perspective
id="perspective5478"
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
inkscape:vp_z="1 : 0.5 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 0.5 : 1"
sodipodi:type="inkscape:persp3d" /><linearGradient
id="linearGradient5189"><stop
style="stop-color:#000ec9;stop-opacity:1;"
offset="0"
id="stop5191" /><stop
style="stop-color:#000657;stop-opacity:1;"
offset="1"
id="stop5193" /></linearGradient><radialGradient
inkscape:collect="always"
xlink:href="#linearGradient3600-4"
id="radialGradient3606-1"
cx="324.32715"
cy="131.40274"
fx="324.32715"
fy="131.40274"
r="25.501276"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.92332021,0.38403097,-0.41592401,1.0000002,78.192026,-120.05314)" /><linearGradient
id="linearGradient3600-4"><stop
style="stop-color:#ffc33d;stop-opacity:1;"
offset="0"
id="stop3602-3" /><stop
style="stop-color:#e09900;stop-opacity:1;"
offset="1"
id="stop3604-5" /></linearGradient><inkscape:perspective
id="perspective5559"
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
inkscape:vp_z="1 : 0.5 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 0.5 : 1"
sodipodi:type="inkscape:persp3d" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient5173"
id="linearGradient5179"
x1="167.33386"
y1="178.83276"
x2="244.78181"
y2="178.83276"
gradientUnits="userSpaceOnUse" /><linearGradient
id="linearGradient5173"><stop
style="stop-color:#f50000;stop-opacity:1;"
offset="0"
id="stop5175" /><stop
style="stop-color:#950000;stop-opacity:1;"
offset="1"
id="stop5177" /></linearGradient><radialGradient
inkscape:collect="always"
xlink:href="#linearGradient3600"
id="radialGradient5169"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.92332021,0.38403097,-0.41592401,1.0000002,78.192026,-120.05314)"
cx="324.32715"
cy="131.40274"
fx="324.32715"
fy="131.40274"
r="25.501276" /><linearGradient
id="linearGradient3600"><stop
style="stop-color:#ffc13d;stop-opacity:1;"
offset="0"
id="stop3602" /><stop
style="stop-color:#e09900;stop-opacity:1;"
offset="1"
id="stop3604" /></linearGradient><radialGradient
r="25.501276"
fy="131.40274"
fx="324.32715"
cy="131.40274"
cx="324.32715"
gradientTransform="matrix(0.92332021,0.38403097,-0.41592401,1.0000002,78.192026,-120.05314)"
gradientUnits="userSpaceOnUse"
id="radialGradient5574"
xlink:href="#linearGradient3600"
inkscape:collect="always" /><inkscape:perspective
id="perspective5663"
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
inkscape:vp_z="1 : 0.5 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 0.5 : 1"
sodipodi:type="inkscape:persp3d" /><radialGradient
inkscape:collect="always"
xlink:href="#linearGradient3600-7"
id="radialGradient3606-8"
cx="324.32715"
cy="131.40274"
fx="324.32715"
fy="131.40274"
r="25.501276"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.92332021,0.38403097,-0.41592401,1.0000002,78.192026,-120.05314)" /><linearGradient
id="linearGradient3600-7"><stop
style="stop-color:#ffc13d;stop-opacity:1;"
offset="0"
id="stop3602-7" /><stop
style="stop-color:#e09900;stop-opacity:1;"
offset="1"
id="stop3604-6" /></linearGradient><radialGradient
r="25.501276"
fy="131.40274"
fx="324.32715"
cy="131.40274"
cx="324.32715"
gradientTransform="matrix(0.92332021,0.38403097,-0.41592401,1.0000002,78.192026,-120.05314)"
gradientUnits="userSpaceOnUse"
id="radialGradient5676"
xlink:href="#linearGradient3600-7"
inkscape:collect="always" /></defs><sodipodi:namedview
inkscape:document-units="mm"
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="3.1582091"
inkscape:cx="104.64791"
inkscape:cy="56.04442"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="1920"
inkscape:window-height="1027"
inkscape:window-x="0"
inkscape:window-y="25"
inkscape:window-maximized="1"
inkscape:showpageshadow="2"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
showguides="true"><sodipodi:guide
position="50.002551,111.99556"
orientation="1,0"
id="guide3"
inkscape:locked="false" /></sodipodi:namedview><metadata
id="metadata4"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-952.36218)"
style="display:inline">
<path
style="display:inline;opacity:1;fill-opacity:1;stroke:#000000;stroke-width:2.7822;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 49.84375,1.71875 C 36.719738,1.71875 26.0625,12.375988 26.0625,25.5 c 0,7.477454 3.475825,14.112734 8.875,18.46875 -10.497549,5.974948 -17.018351,18.227376 -20.625,31.6875 -5.2744126,19.6844 15.911513,22.5625 35.53125,22.5625 19.619736,0 40.705577,-3.2516 35.53125,-22.5625 C 81.693381,61.916246 75.224585,49.827177 64.8125,43.9375 70.181573,39.580662 73.59375,32.953205 73.59375,25.5 c 0,-13.124012 -10.625988,-23.78125 -23.75,-23.78125 z"
id="left"
sodipodi:insensitive="true"
transform="translate(0,952.36218)" />
<path
d="m 478.409,116.617 c -0.368,-4.271 -3.181,-7.94 -7.2,-9.403 -4.029,-1.472 -8.539,-0.47 -11.57,2.556 l -62.015,62.011 -68.749,-21.768 -21.768,-68.748 62.016,-62.016 c 3.035,-3.032 4.025,-7.543 2.563,-11.565 -1.477,-4.03 -5.137,-6.837 -9.417,-7.207 -37.663,-3.245 -74.566,10.202 -101.247,36.887 -36.542,36.545 -46.219,89.911 -29.083,135.399 -1.873,1.578 -3.721,3.25 -5.544,5.053 L 19.386,373.152 c -0.073,0.071 -0.145,0.149 -0.224,0.219 -24.345,24.346 -24.345,63.959 0,88.309 24.349,24.344 63.672,24.048 88.013,-0.298 0.105,-0.098 0.201,-0.196 0.297,-0.305 L 301.104,252.456 c 1.765,-1.773 3.404,-3.628 4.949,-5.532 45.5,17.167 98.9,7.513 135.474,-29.056 26.675,-26.687 40.131,-63.593 36.882,-101.251 z M 75.98,435.38 c -8.971,8.969 -23.5,8.963 -32.47,0 -8.967,-8.961 -8.967,-23.502 0,-32.466 8.97,-8.963 23.499,-8.963 32.47,0 8.967,8.964 8.967,23.505 0,32.466 z"
id="path1"
style="display:inline;fill:#A1A1A1;stroke-width:9.87059588;stroke-dasharray:none;fill-opacity:1;stroke:#000000;stroke-opacity:1"
transform="matrix(0.19145387,0,0,0.19145387,4.0816072,956.99677)"
inkscape:label="wrench" /></g></svg>

After

Width:  |  Height:  |  Size: 9.1 KiB

View file

@ -350,11 +350,11 @@
style="fill-opacity:1;stroke:black;stroke-width:2.78220296;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
d="M 49.84375 1.71875 C 36.719738 1.71875 26.0625 12.375988 26.0625 25.5 C 26.0625 32.977454 29.538325 39.612734 34.9375 43.96875 C 24.439951 49.943698 17.919149 62.196126 14.3125 75.65625 C 9.0380874 95.34065 30.224013 98.21875 49.84375 98.21875 C 69.463486 98.21875 90.549327 94.96715 85.375 75.65625 C 81.693381 61.916246 75.224585 49.827177 64.8125 43.9375 C 70.181573 39.580662 73.59375 32.953205 73.59375 25.5 C 73.59375 12.375988 62.967762 1.71875 49.84375 1.71875 z "
transform="translate(0,952.36218)"
id="right" />
id="left" />
<path
style="opacity:1;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.73577702;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 51.28696,1001.834 0,-46.98372 1.434151,0.16768 c 5.155008,0.60274 9.462857,2.72154 12.938257,6.36366 4.74393,4.9715 6.87913,11.35611 6.16464,18.43328 -0.53702,5.31935 -3.09008,10.59498 -6.83833,14.13074 l -1.94072,1.83069 3.04083,2.20427 c 3.58084,2.5957 7.18975,6.4912 9.55296,10.3116 4.89572,7.9144 9.23593,21.4918 8.50487,26.6055 -0.81312,5.6877 -5.43872,9.6977 -13.62216,11.8093 -3.80822,0.9826 -7.68056,1.4713 -14.763321,1.8633 l -4.471177,0.2474 0,-46.9837 z"
id="left"
id="right"
inkscape:connector-curvature="0" />
<path
style="display:inline;fill:url(#linearGradient3);fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:3.77952756;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Before After
Before After

View file

@ -321,11 +321,11 @@
style="fill-opacity:1;stroke:black;stroke-width:2.78220296;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
d="M 49.84375 1.71875 C 36.719738 1.71875 26.0625 12.375988 26.0625 25.5 C 26.0625 32.977454 29.538325 39.612734 34.9375 43.96875 C 24.439951 49.943698 17.919149 62.196126 14.3125 75.65625 C 9.0380874 95.34065 30.224013 98.21875 49.84375 98.21875 C 69.463486 98.21875 90.549327 94.96715 85.375 75.65625 C 81.693381 61.916246 75.224585 49.827177 64.8125 43.9375 C 70.181573 39.580662 73.59375 32.953205 73.59375 25.5 C 73.59375 12.375988 62.967762 1.71875 49.84375 1.71875 z "
transform="translate(0,952.36218)"
id="right" />
id="left" />
<path
style="opacity:1;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.73577702;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 51.28696,1001.834 0,-46.98372 1.434151,0.16768 c 5.155008,0.60274 9.462857,2.72154 12.938257,6.36366 4.74393,4.9715 6.87913,11.35611 6.16464,18.43328 -0.53702,5.31935 -3.09008,10.59498 -6.83833,14.13074 l -1.94072,1.83069 3.04083,2.20427 c 3.58084,2.5957 7.18975,6.4912 9.55296,10.3116 4.89572,7.9144 9.23593,21.4918 8.50487,26.6055 -0.81312,5.6877 -5.43872,9.6977 -13.62216,11.8093 -3.80822,0.9826 -7.68056,1.4713 -14.763321,1.8633 l -4.471177,0.2474 0,-46.9837 z"
id="left"
id="right"
inkscape:connector-curvature="0" />
<path
d="m 46.656521,12.167234 18.055171,18.054184 a 6.6081919,6.6078288 0 0 1 -0.126303,9.352065 6.6804126,6.6800456 0 0 1 -8.233169,1.011048 l -7.944268,7.943843 6.463762,6.445343 a 6.9331851,6.9328042 0 0 1 5.741536,2.022073 l 28.057729,28.092294 a 6.9962797,6.9958953 0 0 1 -9.894222,9.893685 L 50.719018,66.907526 A 7.0595711,7.0591833 0 0 1 49.18433,59.270613 l -5.741527,-5.741238 -7.944298,7.943843 A 6.716523,6.7161541 0 0 1 25.134866,69.832263 L 7.079684,51.778091 a 6.716523,6.7161541 0 0 1 8.39566,-10.345064 L 36.31101,20.59853 a 6.716523,6.7161541 0 0 1 10.345612,-8.431329 z"

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Before After
Before After

View file

@ -340,11 +340,11 @@
style="fill-opacity:1;stroke:black;stroke-width:2.78220296;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
d="M 49.84375 1.71875 C 36.719738 1.71875 26.0625 12.375988 26.0625 25.5 C 26.0625 32.977454 29.538325 39.612734 34.9375 43.96875 C 24.439951 49.943698 17.919149 62.196126 14.3125 75.65625 C 9.0380874 95.34065 30.224013 98.21875 49.84375 98.21875 C 69.463486 98.21875 90.549327 94.96715 85.375 75.65625 C 81.693381 61.916246 75.224585 49.827177 64.8125 43.9375 C 70.181573 39.580662 73.59375 32.953205 73.59375 25.5 C 73.59375 12.375988 62.967762 1.71875 49.84375 1.71875 z "
transform="translate(0,952.36218)"
id="right" />
id="left" />
<path
style="opacity:1;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.73577702;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 51.28696,1001.834 0,-46.98372 1.434151,0.16768 c 5.155008,0.60274 9.462857,2.72154 12.938257,6.36366 4.74393,4.9715 6.87913,11.35611 6.16464,18.43328 -0.53702,5.31935 -3.09008,10.59498 -6.83833,14.13074 l -1.94072,1.83069 3.04083,2.20427 c 3.58084,2.5957 7.18975,6.4912 9.55296,10.3116 4.89572,7.9144 9.23593,21.4918 8.50487,26.6055 -0.81312,5.6877 -5.43872,9.6977 -13.62216,11.8093 -3.80822,0.9826 -7.68056,1.4713 -14.763321,1.8633 l -4.471177,0.2474 0,-46.9837 z"
id="left"
id="right"
inkscape:connector-curvature="0" />
<path
sodipodi:type="star"

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Before After
Before After

View file

@ -0,0 +1,66 @@
#include "lag_monitor.h"
#include <QApplication>
#include <QCoreApplication>
#include <QDateTime>
#include <QEvent>
#include <QMessageLogger>
#include <QTimer>
LagMonitor::LagMonitor(QObject *parent) : QObject(parent)
{
qApp->installEventFilter(this);
timer = new QTimer(this);
timer->setInterval(TICK_INTERVAL_MS);
connect(timer, &QTimer::timeout, this, &LagMonitor::checkTick);
tickClock.start();
timer->start();
}
QList<LagMonitor::StallRecord> LagMonitor::recentStalls() const
{
return stalls;
}
void LagMonitor::clearStalls()
{
stalls.clear();
}
bool LagMonitor::eventFilter(QObject *obj, QEvent *event)
{
if (event->type() == QEvent::ApplicationStateChange) {
// The transition may span a suspend or an arbitrary unfocused period;
// discard the gap so it cannot be mistaken for a stall.
tickClock.restart();
}
return QObject::eventFilter(obj, event);
}
void LagMonitor::checkTick()
{
recordGap(tickClock.restart());
}
void LagMonitor::recordGap(qint64 gapMs)
{
if (gapMs <= STALL_THRESHOLD_MS) {
return;
}
if (gapMs > MAX_PLAUSIBLE_STALL_MS) {
qCDebug(LagMonitorLog, "Ignoring implausible %lld ms gap (likely suspend)", static_cast<long long>(gapMs));
return;
}
const StallRecord record{.timestampMsSinceEpoch = QDateTime::currentMSecsSinceEpoch(), .durationMs = gapMs};
stalls.append(record);
while (stalls.size() > MAX_RECORDED_STALLS) {
stalls.removeFirst();
}
qCWarning(LagMonitorLog, "Event loop stalled for %lld ms (threshold: %d ms)", static_cast<long long>(gapMs),
STALL_THRESHOLD_MS);
}

View file

@ -0,0 +1,87 @@
/**
* @file lag_monitor.h
* @ingroup Client
*/
#ifndef LAG_MONITOR_H
#define LAG_MONITOR_H
#include <QElapsedTimer>
#include <QList>
#include <QLoggingCategory>
#include <QObject>
#include <QtGlobal>
#include <qtmetamacros.h>
inline Q_LOGGING_CATEGORY(LagMonitorLog, "lag_monitor");
class QTimer;
/**
* @brief Detects main-thread event loop stalls ("UI freezes") from the inside.
*
* A timer is expected to fire every TICK_INTERVAL_MS of wall time. When the
* observed gap greatly exceeds that interval, some other task blocked the
* event loop for roughly the overshooting duration. This is what separates
* "my client froze" from "the network is lagging" in user reports.
*
* Gaps that span an application state change (suspend, minimize, focus
* loss) are discarded, and implausibly huge gaps are dropped, so operating
* system power events do not fabricate stalls. This handling is load-bearing
* on Windows, where the monotonic clock used by Qt counts sleep time.
*
* Healthy operation costs one timer wakeup per tick and two integer
* comparisons. Allocations happen only when a stall is actually recorded.
*/
class LagMonitor : public QObject
{
Q_OBJECT
public:
struct StallRecord
{
qint64 timestampMsSinceEpoch = 0; ///< when the stalled period ended
qint64 durationMs = 0; ///< approximate length of the freeze; measured tick to tick, so it can exceed the true
///< stall by up to TICK_INTERVAL_MS
};
static constexpr int TICK_INTERVAL_MS = 500;
static constexpr int STALL_THRESHOLD_MS = 2000;
static constexpr int MAX_RECORDED_STALLS = 32;
/// Gaps beyond this are treated as suspend artifacts rather than stalls.
static constexpr qint64 MAX_PLAUSIBLE_STALL_MS = 600000;
explicit LagMonitor(QObject *parent = nullptr);
/**
* @brief Stalls recorded during this session, oldest first.
*
* Intended consumers are log output and the diagnostics export. The list
* holds at most MAX_RECORDED_STALLS entries.
*/
QList<StallRecord> recentStalls() const;
void clearStalls();
/**
* @brief Feeds a measured tick-to-tick gap through the detection logic.
*
* Split out of checkTick so threshold, plausibility, and trim behavior
* stay unit-testable without real timing.
*/
void recordGap(qint64 gapMs);
protected:
bool eventFilter(QObject *obj, QEvent *event) override;
private slots:
void checkTick();
private:
QTimer *timer;
QElapsedTimer tickClock; ///< monotonic clock, so wall clock steps do not fabricate stalls
QList<StallRecord> stalls;
};
#endif

View file

@ -0,0 +1,55 @@
/**
* @file latency_graph_widget.cpp
* @ingroup Client
*/
#include "latency_graph_widget.h"
#include <QColor>
#include <QPainter>
#include <QRectF>
#include <QtGlobal>
#include <qnumeric.h>
LatencyGraphWidget::LatencyGraphWidget(QWidget *parent) : QWidget(parent)
{
}
void LatencyGraphWidget::setSamples(const QList<int> &samplesMs)
{
samples = samplesMs;
update();
}
void LatencyGraphWidget::paintEvent(QPaintEvent * /* event */)
{
if (samples.isEmpty()) {
return;
}
QPainter painter(this);
// Heights are relative to the window's own worst sample (floored at
// MinScaleMs) so the shape of the variance stays readable even when every
// value is small.
qint64 heightScaleMs = MinScaleMs;
for (int sample : samples) {
heightScaleMs = qMax(heightScaleMs, static_cast<qint64>(sample));
}
const qreal widthPerBar = static_cast<qreal>(width()) / samples.size();
for (int i = 0; i < samples.size(); ++i) {
const qreal heightRatio = qBound(0.0, static_cast<double>(samples.at(i)) / heightScaleMs, 1.0);
const qreal barHeight = heightRatio * height();
// Colors follow an absolute quality ramp: a steady good ping stays
// green no matter how uniform the window is.
const qreal colorRatio = qBound(0.0, static_cast<double>(samples.at(i)) / ColorScaleMs, 1.0);
QColor color;
color.setHsv(qRound(120.0 * (1.0 - colorRatio)), 255, 255);
const QRectF bar(static_cast<qreal>(i) * widthPerBar + 1.0, static_cast<qreal>(height()) - barHeight,
qMax(1.0, widthPerBar - 2.0), barHeight);
painter.fillRect(bar, color);
}
}

View file

@ -0,0 +1,45 @@
/**
* @file latency_graph_widget.h
* @ingroup Client
*/
#ifndef LATENCY_GRAPH_WIDGET_H
#define LATENCY_GRAPH_WIDGET_H
#include <QList>
#include <QWidget>
#include <QtGlobal>
#include <qtmetamacros.h>
/**
* @brief Bar graph of recent network round-trip samples.
*
* Draws one bar per sample, oldest on the left. Bar height is relative to the
* window's own scale so the shape of the variance stays readable, while bar
* color maps each sample onto an absolute quality ramp (green at rest through
* red at ColorScaleMs) so a steady good ping never looks alarming. Size
* agnostic: the status bar embeds a small instance while the latency detail
* popup shows a large one.
*/
class LatencyGraphWidget : public QWidget
{
Q_OBJECT
public:
explicit LatencyGraphWidget(QWidget *parent = nullptr);
/// Sample in milliseconds that maps to a fully red bar.
static constexpr qint64 ColorScaleMs = 500;
void setSamples(const QList<int> &samplesMs);
protected:
void paintEvent(QPaintEvent *event) override;
private:
/// Floor of the vertical scale in milliseconds. Keeps small windows readable.
static constexpr qint64 MinScaleMs = 100;
QList<int> samples;
};
#endif

View file

@ -0,0 +1,115 @@
/**
* @file latency_status_widget.cpp
* @ingroup Client
*/
#include "latency_status_widget.h"
#include "latency_graph_widget.h"
#include "latency_tracker.h"
#include <QEvent>
#include <QHBoxLayout>
#include <QLabel>
#include <QObject>
#include <QPoint>
#include <QVBoxLayout>
#include <qnamespace.h>
LatencyStatusWidget::LatencyStatusWidget(QWidget *parent) : QWidget(parent)
{
pingLabel = new QLabel(this);
pingLabel->setAccessibleName(tr("Ping"));
latencyGraph = new LatencyGraphWidget(this);
latencyGraph->setFixedSize(90, 14);
auto *layout = new QHBoxLayout(this);
layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(4);
layout->addWidget(latencyGraph);
layout->addWidget(pingLabel);
// Clicking anywhere in the area opens the detail view.
for (QObject *child : QList<QObject *>{pingLabel, latencyGraph}) {
child->installEventFilter(this);
}
setCursor(Qt::PointingHandCursor);
hide();
}
void LatencyStatusWidget::updateData(const LatencyTracker::Stats &stats, const QList<int> &samplesMs)
{
latestSamples = samplesMs;
latencyGraph->setSamples(samplesMs);
if (popup && popup->isVisible() && detailGraph) {
detailGraph->setSamples(samplesMs);
}
if (stats.sampleCount == 0) {
hide();
return;
}
const QString statsStr = statsText(stats);
pingLabel->setText(tr("Ping: %1 ms").arg(stats.lastMs));
pingLabel->setToolTip(statsStr);
pingLabel->setAccessibleDescription(statsStr);
if (popup && popup->isVisible() && detailLabel) {
detailLabel->setText(statsStr);
}
show();
}
bool LatencyStatusWidget::eventFilter(QObject *watched, QEvent *event)
{
if ((watched == pingLabel || watched == latencyGraph) && event->type() == QEvent::MouseButtonPress) {
togglePopup();
return true;
}
return QWidget::eventFilter(watched, event);
}
void LatencyStatusWidget::togglePopup()
{
if (!popup) {
popup = new QWidget(this, Qt::Popup | Qt::FramelessWindowHint);
auto *layout = new QVBoxLayout(popup);
layout->setContentsMargins(8, 8, 8, 8);
detailLabel = new QLabel(popup);
detailLabel->setAccessibleName(tr("Connection latency details"));
detailLabel->setTextInteractionFlags(Qt::TextSelectableByMouse);
detailGraph = new LatencyGraphWidget(popup);
detailGraph->setFixedSize(280, 80);
layout->addWidget(detailLabel, 0, Qt::AlignLeft);
layout->addWidget(detailGraph, 0, Qt::AlignHCenter);
}
if (popup->isVisible()) {
popup->hide();
return;
}
// Qt::Popup closes itself on any outside click, so just position and show.
if (latestSamples.isEmpty()) {
return;
}
detailGraph->setSamples(latestSamples);
detailLabel->setText(pingLabel->toolTip());
popup->adjustSize();
const QPoint anchor = mapToGlobal(QPoint(width() / 2, 0));
popup->move(anchor.x() - popup->width() / 2, anchor.y() - popup->height() - 6);
popup->show();
}
QString LatencyStatusWidget::statsText(const LatencyTracker::Stats &stats) const
{
return tr("Connection quality over the last %n sample(s):", "", stats.sampleCount) + "\n" +
tr("Last: %1 ms").arg(stats.lastMs) + "\n" + tr("Median: %1 ms").arg(stats.medianMs) + "\n" +
tr("95th percentile: %1 ms").arg(stats.p95Ms) + "\n" + tr("Maximum: %1 ms").arg(stats.maxMs);
}

View file

@ -0,0 +1,52 @@
/**
* @file latency_status_widget.h
* @ingroup Client
*/
#ifndef LATENCY_STATUS_WIDGET_H
#define LATENCY_STATUS_WIDGET_H
#include <QList>
#include <QString>
#include <QWidget>
#include <libcockatrice/network/client/abstract/latency_tracker.h>
#include <qtmetamacros.h>
class QLabel;
class LatencyGraphWidget;
/**
* @brief Status bar presentation of server round-trip health.
*
* Combines the textual "Ping" readout with a small LatencyGraphWidget
* sparkline of the rolling sample window. Clicking anywhere in the area opens
* a popup with a larger graph and the numeric statistics. It closes on any
* outside click. Stays hidden while disconnected or before any samples exist.
* Owns all latency display state so MainWindow only needs to forward one
* signal here.
*/
class LatencyStatusWidget : public QWidget
{
Q_OBJECT
public:
explicit LatencyStatusWidget(QWidget *parent = nullptr);
public slots:
void updateData(const LatencyTracker::Stats &stats, const QList<int> &samplesMs);
protected:
bool eventFilter(QObject *watched, QEvent *event) override;
private:
void togglePopup();
QString statsText(const LatencyTracker::Stats &stats) const;
QLabel *pingLabel = nullptr;
LatencyGraphWidget *latencyGraph = nullptr;
QWidget *popup = nullptr;
LatencyGraphWidget *detailGraph = nullptr;
QLabel *detailLabel = nullptr;
QList<int> latestSamples;
};
#endif

View file

@ -0,0 +1,611 @@
#include "remote_connection_controller.h"
#include "../../../interface/pixel_map_generator.h"
#include "../../settings/cache_settings.h"
#include "../interface/widgets/dialogs/dlg_connect.h"
#include "../interface/widgets/dialogs/dlg_forgot_password_challenge.h"
#include "../interface/widgets/dialogs/dlg_forgot_password_request.h"
#include "../interface/widgets/dialogs/dlg_forgot_password_reset.h"
#include "../interface/widgets/dialogs/dlg_register.h"
#include "../interface/widgets/utility/get_text_with_max.h"
#include "libcockatrice/protocol/pb/event_connection_closed.pb.h"
#include "libcockatrice/protocol/pb/event_server_shutdown.pb.h"
#include <QChar>
#include <QDateTime>
#include <QDebug>
#include <QLineEdit>
#include <QList>
#include <QMessageBox>
#include <QPixmap>
#include <QStringList>
#include <QStringLiteral>
#include <QThread>
#include <libcockatrice/network/client/remote/remote_client.h>
#include <libcockatrice/protocol/pb/response.pb.h>
#include <libcockatrice/settings/servers_settings.h>
#include <qnamespace.h>
class QWidget;
ConnectionController::ConnectionController(QWidget *dialogParent, QObject *parent)
: QObject(parent), dialogParent(dialogParent)
{
remoteClient = new RemoteClient(nullptr, &SettingsCache::instance());
clientThread = new QThread(this);
remoteClient->moveToThread(clientThread);
clientThread->start();
wireClientSignals();
}
ConnectionController::~ConnectionController()
{
remoteClient->deleteLater();
clientThread->wait();
}
void ConnectionController::wireClientSignals()
{
connect(remoteClient, &RemoteClient::connectionClosedEventReceived, this,
&ConnectionController::onConnectionClosedEvent);
connect(remoteClient, &RemoteClient::serverShutdownEventReceived, this,
&ConnectionController::onServerShutdownEvent);
connect(remoteClient, &RemoteClient::statusChanged, this, &ConnectionController::onStatusChanged);
connect(remoteClient, &AbstractClient::pingStatsUpdated, this, &ConnectionController::pingStatsUpdated);
connect(remoteClient, &RemoteClient::userInfoChanged, this, &ConnectionController::onUserInfoReceived,
Qt::BlockingQueuedConnection);
connect(remoteClient, &RemoteClient::loginError, this,
[this](Response::ResponseCode r, QString rs, quint32 et, QList<QString> mf) {
onLoginError(static_cast<int>(r), rs, et, mf);
});
connect(remoteClient, &RemoteClient::registerError, this,
[this](Response::ResponseCode r, QString rs, quint32 et) { onRegisterError(static_cast<int>(r), rs, et); });
connect(remoteClient, &RemoteClient::activateError, this, &ConnectionController::onActivateError);
connect(remoteClient, &RemoteClient::socketError, this, &ConnectionController::onSocketError);
connect(remoteClient, &RemoteClient::serverTimeout, this, &ConnectionController::onServerTimeout);
connect(remoteClient, &RemoteClient::protocolVersionMismatch, this,
&ConnectionController::onProtocolVersionMismatch);
connect(remoteClient, &RemoteClient::registerAccepted, this, &ConnectionController::onRegisterAccepted);
connect(remoteClient, &RemoteClient::registerAcceptedNeedsActivate, this,
&ConnectionController::onRegisterAcceptedNeedsActivate);
connect(remoteClient, &RemoteClient::activateAccepted, this, &ConnectionController::onActivateAccepted);
connect(remoteClient, &RemoteClient::notifyUserAboutUpdate, this, &ConnectionController::onNotifyUserAboutUpdate);
connect(remoteClient, &RemoteClient::sigForgotPasswordSuccess, this,
&ConnectionController::onForgotPasswordSuccess);
connect(remoteClient, &RemoteClient::sigForgotPasswordError, this, &ConnectionController::onForgotPasswordError);
connect(remoteClient, &RemoteClient::sigPromptForForgotPasswordReset, this,
&ConnectionController::onPromptForgotPasswordReset);
connect(remoteClient, &RemoteClient::sigPromptForForgotPasswordChallenge, this,
&ConnectionController::onPromptForgotPasswordChallenge);
}
void ConnectionController::connectToServer()
{
dlgConnect = new DlgConnect(dialogParent);
connect(dlgConnect, &DlgConnect::sigStartForgotPasswordRequest, this, &ConnectionController::forgotPasswordRequest);
if (dlgConnect->exec()) {
remoteClient->connectToServer(dlgConnect->getHost(), static_cast<unsigned int>(dlgConnect->getPort()),
dlgConnect->getPlayerName(), dlgConnect->getPassword());
}
}
void ConnectionController::connectToServerDirect(const QString &host,
unsigned int port,
const QString &playerName,
const QString &password)
{
remoteClient->connectToServer(host, port, playerName, password);
}
void ConnectionController::disconnectFromServer()
{
remoteClient->disconnectFromServer();
}
void ConnectionController::registerToServer()
{
DlgRegister dlg(dialogParent);
if (dlg.exec()) {
remoteClient->registerToServer(dlg.getHost(), static_cast<unsigned int>(dlg.getPort()), dlg.getPlayerName(),
dlg.getPassword(), dlg.getEmail(), dlg.getCountry(), dlg.getRealName());
}
}
void ConnectionController::forgotPasswordRequest()
{
DlgForgotPasswordRequest dlg(dialogParent);
if (dlg.exec()) {
remoteClient->requestForgotPasswordToServer(dlg.getHost(), static_cast<unsigned int>(dlg.getPort()),
dlg.getPlayerName());
}
}
void ConnectionController::onConnectionClosedEvent(const Event_ConnectionClosed &event)
{
remoteClient->disconnectFromServer();
QString reasonStr;
switch (event.reason()) {
case Event_ConnectionClosed::USER_LIMIT_REACHED: {
reasonStr = tr("The server has reached its maximum user capacity, please check back later.");
break;
}
case Event_ConnectionClosed::TOO_MANY_CONNECTIONS: {
reasonStr = tr("There are too many concurrent connections from your address.");
break;
}
case Event_ConnectionClosed::BANNED: {
reasonStr = tr("Banned by moderator");
if (event.has_end_time()) {
reasonStr.append(
"\n" + tr("Expected end time: %1").arg(QDateTime::fromSecsSinceEpoch(event.end_time()).toString()));
} else {
reasonStr.append("\n" + tr("This ban lasts indefinitely."));
}
if (event.has_reason_str()) {
reasonStr.append("\n\n" + QString::fromStdString(event.reason_str()));
}
break;
}
case Event_ConnectionClosed::SERVER_SHUTDOWN: {
reasonStr = tr("Scheduled server shutdown.");
break;
}
case Event_ConnectionClosed::USERNAMEINVALID: {
reasonStr = tr("Invalid username.");
break;
}
case Event_ConnectionClosed::LOGGEDINELSEWERE: {
reasonStr = tr("You have been logged out due to logging in at another location.");
break;
}
default:
reasonStr = QString::fromStdString(event.reason_str());
}
QMessageBox::critical(dialogParent, tr("Connection closed"),
tr("The server has terminated your connection.\nReason: %1").arg(reasonStr));
}
void ConnectionController::onServerShutdownEvent(const Event_ServerShutdown &event)
{
serverShutdownMessageBox.setInformativeText(tr("The server is going to be restarted in %n minute(s).\nAll running "
"games will be lost.\nReason for shutdown: %1",
"", event.minutes())
.arg(QString::fromStdString(event.reason())));
serverShutdownMessageBox.setIconPixmap(themePixmap(QStringLiteral("cockatrice")).scaled(64, 64));
serverShutdownMessageBox.setText(tr("Scheduled server shutdown"));
serverShutdownMessageBox.setWindowModality(Qt::ApplicationModal);
serverShutdownMessageBox.setVisible(true);
}
void ConnectionController::onStatusChanged(ClientStatus status)
{
// Update the window title first, then let MainWindow handle its own UI
// state via the forwarded signal
updateWindowTitle();
emit statusChanged(status);
// TabSupervisor::stop() needs calling on disconnect; start() is driven by
// onUserInfoReceived → tabSupervisorStartRequested.
if (status == StatusDisconnected) {
emit tabSupervisorStopRequested();
}
}
void ConnectionController::onUserInfoReceived(const ServerInfo_User &info)
{
emit tabSupervisorStartRequested(info);
}
void ConnectionController::onLoginError(int r,
QString reasonStr,
quint32 endTime,
const QList<QString> &missingFeatures)
{
switch (static_cast<Response::ResponseCode>(r)) {
case Response::RespClientUpdateRequired: {
QString formatted = "Missing Features: ";
for (int i = 0; i < missingFeatures.size(); ++i) {
formatted.append(QString("\n %1").arg(QChar(0x2022)) + " " + missingFeatures.value(i));
}
QMessageBox msgBox(dialogParent);
msgBox.setIcon(QMessageBox::Critical);
msgBox.setWindowTitle(tr("Failed Login"));
msgBox.setText(tr("Your client seems to be missing features this server requires for connection.") +
"\n\n" + tr("To update your client, go to 'Help -> Check for Client Updates'."));
msgBox.setDetailedText(formatted);
msgBox.exec();
break;
}
case Response::RespWrongPassword: {
QMessageBox::critical(dialogParent, tr("Error"),
tr("Incorrect username or password. "
"Please check your authentication information and try again."));
break;
}
case Response::RespWouldOverwriteOldSession: {
QMessageBox::critical(dialogParent, tr("Error"),
tr("There is already an active session using this user name.\n"
"Please close that session first and re-login."));
break;
}
case Response::RespUserIsBanned: {
QString bannedStr =
endTime ? tr("You are banned until %1.").arg(QDateTime::fromSecsSinceEpoch(endTime).toString())
: tr("You are banned indefinitely.");
if (!reasonStr.isEmpty()) {
bannedStr.append("\n\n" + reasonStr);
}
QMessageBox::critical(dialogParent, tr("Error"), bannedStr);
break;
}
case Response::RespUsernameInvalid: {
QMessageBox::critical(dialogParent, tr("Error"), extractInvalidUsernameMessage(reasonStr));
break;
}
case Response::RespRegistrationRequired: {
if (QMessageBox::question(dialogParent, tr("Error"),
tr("This server requires user registration. Do you want to register now?"),
QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
registerToServer();
}
return; // don't re-prompt connect
}
case Response::RespClientIdRequired: {
QMessageBox::critical(dialogParent, tr("Error"),
tr("This server requires client IDs. Your client is either failing to generate an "
"ID or you are running a modified client.\n"
"Please close and reopen your client to try again."));
break;
}
case Response::RespContextError: {
QMessageBox::critical(dialogParent, tr("Error"),
tr("An internal error has occurred, please close and reopen Cockatrice before "
"trying again.\nIf the error persists, ensure you are running the latest "
"version of the software and if needed contact the software developers."));
break;
}
case Response::RespAccountNotActivated: {
bool ok = false;
QString token =
getTextWithMax(dialogParent, tr("Account activation"),
tr("Your account has not been activated yet.\n"
"You need to provide the activation token received in the activation email."),
QLineEdit::Normal, QString(), &ok);
if (ok && !token.isEmpty()) {
remoteClient->activateToServer(token);
return;
}
remoteClient->disconnectFromServer();
return;
}
case Response::RespPasswordChangeRequired: {
QMessageBox::information(
dialogParent, tr("Password Change Required"),
tr("An administrator has reset your password. Please contact your server administrator to obtain "
"your temporary password, then log in and change it via Account -> Change Password."));
remoteClient->disconnectFromServer();
return;
}
case Response::RespServerFull: {
QMessageBox::critical(dialogParent, tr("Server Full"),
tr("The server has reached its maximum user capacity, please check back later."));
break;
}
default: {
QMessageBox::critical(dialogParent, tr("Error"),
tr("Unknown login error: %1").arg(r) +
tr("\nThis usually means that your client version is out of date, and the server "
"sent a reply your client doesn't understand."));
break;
}
}
// Re-open the connect dialog after any handled error
connectToServer();
}
void ConnectionController::onRegisterError(int r, QString reasonStr, quint32 endTime)
{
switch (static_cast<Response::ResponseCode>(r)) {
case Response::RespRegistrationDisabled: {
QMessageBox::critical(dialogParent, tr("Registration denied"),
tr("Registration is currently disabled on this server"));
break;
}
case Response::RespUserAlreadyExists: {
QMessageBox::critical(dialogParent, tr("Registration denied"),
tr("There is already an existing account with the same user name."));
break;
}
case Response::RespEmailRequiredToRegister: {
QMessageBox::critical(dialogParent, tr("Registration denied"),
tr("It's mandatory to specify a valid email address when registering."));
break;
}
case Response::RespEmailBlackListed: {
if (reasonStr.isEmpty()) {
reasonStr =
"The email address provider used during registration has been blocked from use on this server.";
}
QMessageBox::critical(dialogParent, tr("Registration denied"), reasonStr);
break;
}
case Response::RespTooManyRequests: {
QMessageBox::critical(dialogParent, tr("Registration denied"),
tr("It appears you are attempting to register a new account on this server yet you "
"already have an account registered with the email provided. This server "
"restricts the number of accounts a user can register per address. Please "
"contact the server operator for further assistance or to obtain your "
"credential information."));
break;
}
case Response::RespPasswordTooShort: {
QMessageBox::critical(dialogParent, tr("Registration denied"), tr("Password too short."));
break;
}
case Response::RespUserIsBanned: {
QString bannedStr =
endTime ? tr("You are banned until %1.").arg(QDateTime::fromSecsSinceEpoch(endTime).toString())
: tr("You are banned indefinitely.");
if (!reasonStr.isEmpty()) {
bannedStr.append("\n\n" + reasonStr);
}
QMessageBox::critical(dialogParent, tr("Error"), bannedStr);
break;
}
case Response::RespUsernameInvalid: {
QMessageBox::critical(dialogParent, tr("Error"), extractInvalidUsernameMessage(reasonStr));
break;
}
case Response::RespRegistrationFailed: {
QMessageBox::critical(dialogParent, tr("Error"),
tr("Registration failed for a technical problem on the server."));
break;
}
case Response::RespNotConnected: {
QMessageBox::critical(dialogParent, tr("Error"), tr("The connection to the server has been lost."));
break;
}
default: {
QMessageBox::critical(dialogParent, tr("Error"),
tr("Unknown registration error: %1").arg(r) +
tr("\nThis usually means that your client version is out of date, and the server "
"sent a reply your client doesn't understand."));
break;
}
}
registerToServer();
}
void ConnectionController::onActivateError()
{
QMessageBox::critical(dialogParent, tr("Error"), tr("Account activation failed"));
remoteClient->disconnectFromServer();
connectToServer();
}
void ConnectionController::onSocketError(const QString &errorStr)
{
QMessageBox::critical(dialogParent, tr("Error"), tr("Socket error: %1").arg(errorStr));
connectToServer();
}
void ConnectionController::onServerTimeout()
{
QMessageBox::critical(dialogParent, tr("Error"), tr("Server timeout"));
connectToServer();
}
void ConnectionController::onProtocolVersionMismatch(int localVersion, int remoteVersion)
{
if (localVersion > remoteVersion) {
QMessageBox::critical(dialogParent, tr("Error"),
tr("You are trying to connect to an obsolete server. Please downgrade your Cockatrice "
"version or connect to a suitable server.\n"
"Local version is %1, remote version is %2.")
.arg(localVersion)
.arg(remoteVersion));
} else {
QMessageBox::critical(dialogParent, tr("Error"),
tr("Your Cockatrice client is obsolete. Please update your Cockatrice version.\n"
"Local version is %1, remote version is %2.")
.arg(localVersion)
.arg(remoteVersion));
}
}
void ConnectionController::onRegisterAccepted()
{
QMessageBox::information(dialogParent, tr("Success"), tr("Registration accepted.\nWill now login."));
}
void ConnectionController::onRegisterAcceptedNeedsActivate()
{
// Server will send activation email; nothing to display here.
}
void ConnectionController::onActivateAccepted()
{
QMessageBox::information(dialogParent, tr("Success"), tr("Account activation accepted.\nWill now login."));
}
void ConnectionController::onNotifyUserAboutUpdate()
{
QMessageBox::information(
dialogParent, tr("Information"),
tr("This server supports additional features that your client doesn't have.\n"
"This is most likely not a problem, but this message might mean there is a new version of "
"Cockatrice available or this server is running a custom or pre-release version.\n\n"
"To update your client, go to Help -> Check for Updates."));
}
void ConnectionController::onForgotPasswordSuccess()
{
QMessageBox::information(
dialogParent, tr("Reset Password"),
tr("Your password has been reset successfully, you can now log in using the new credentials."));
SettingsCache::instance().servers().setFPHostName("");
SettingsCache::instance().servers().setFPPort("");
SettingsCache::instance().servers().setFPPlayerName("");
}
void ConnectionController::onForgotPasswordError()
{
QMessageBox::warning(
dialogParent, tr("Reset Password"),
tr("Failed to reset user account password, please contact the server operator to reset your password."));
SettingsCache::instance().servers().setFPHostName("");
SettingsCache::instance().servers().setFPPort("");
SettingsCache::instance().servers().setFPPlayerName("");
}
void ConnectionController::onPromptForgotPasswordReset()
{
QMessageBox::information(dialogParent, tr("Reset Password"),
tr("Activation request received, please check your email for an activation token."));
DlgForgotPasswordReset dlg(dialogParent);
if (dlg.exec()) {
remoteClient->submitForgotPasswordResetToServer(dlg.getHost(), static_cast<unsigned int>(dlg.getPort()),
dlg.getPlayerName(), dlg.getToken(), dlg.getPassword());
}
}
void ConnectionController::onPromptForgotPasswordChallenge()
{
DlgForgotPasswordChallenge dlg(dialogParent);
if (dlg.exec()) {
remoteClient->submitForgotPasswordChallengeToServer(dlg.getHost(), static_cast<unsigned int>(dlg.getPort()),
dlg.getPlayerName(), dlg.getEmail());
}
}
void ConnectionController::updateWindowTitle()
{
const QString appName = QStringLiteral("Cockatrice");
QString title;
switch (remoteClient->getStatus()) {
case StatusConnecting: {
title = appName + " - " + tr("Connecting to %1...").arg(remoteClient->peerName());
break;
}
case StatusRegistering: {
title = appName + " - " +
tr("Registering to %1 as %2...").arg(remoteClient->peerName()).arg(remoteClient->getUserName());
break;
}
case StatusDisconnected: {
title = appName + " - " + tr("Disconnected");
break;
}
case StatusLoggingIn: {
title = appName + " - " + tr("Connected, logging in at %1").arg(remoteClient->peerName());
break;
}
case StatusLoggedIn: {
title = remoteClient->getUserName() + "@" + remoteClient->peerName();
break;
}
case StatusRequestingForgotPassword:
case StatusSubmitForgotPasswordChallenge:
case StatusSubmitForgotPasswordReset:
title = appName + " - " +
tr("Requesting forgotten password to %1 as %2...")
.arg(remoteClient->peerName())
.arg(remoteClient->getUserName());
break;
default:
title = appName;
}
emit windowTitleChanged(title);
}
// static
QString ConnectionController::extractInvalidUsernameMessage(QString &in)
{
QString out = tr("Invalid username.") + "<br/>";
QStringList rules = in.split(QChar('|'));
if (rules.size() == 7 || rules.size() == 9) {
out += tr("Your username must respect these rules:") + "<ul>";
out += "<li>" + tr("is %1 - %2 characters long").arg(rules.at(0)).arg(rules.at(1)) + "</li>";
out += "<li>" + tr("can %1 contain lowercase characters").arg((rules.at(2).toInt() > 0) ? "" : tr("NOT")) +
"</li>";
out += "<li>" + tr("can %1 contain uppercase characters").arg((rules.at(3).toInt() > 0) ? "" : tr("NOT")) +
"</li>";
out +=
"<li>" + tr("can %1 contain numeric characters").arg((rules.at(4).toInt() > 0) ? "" : tr("NOT")) + "</li>";
if (rules.at(6).size() > 0) {
out += "<li>" + tr("can contain the following punctuation: %1").arg(rules.at(6).toHtmlEscaped()) + "</li>";
}
out += "<li>" +
tr("first character can %1 be a punctuation mark").arg((rules.at(5).toInt() > 0) ? "" : tr("NOT")) +
"</li>";
if (rules.size() == 9) {
if (rules.at(7).size() > 0) {
QString words = rules.at(7).toHtmlEscaped();
if (words.startsWith("\n")) {
out += tr("no unacceptable language as specified by these server rules:",
"note that the following lines will not be translated");
for (QString &line : words.split("\n", Qt::SkipEmptyParts)) {
out += "<li>" + line + "</li>";
}
} else {
out += "<li>" + tr("can not contain any of the following words: %1").arg(words) + "</li>";
}
}
if (rules.at(8).size() > 0) {
out += "<li>" +
tr("can not match any of the following expressions: %1").arg(rules.at(8).toHtmlEscaped()) +
"</li>";
}
}
out += "</ul>";
} else {
out += tr("You may only use A-Z, a-z, 0-9, _, ., and - in your username.");
}
return out;
}

View file

@ -0,0 +1,107 @@
#ifndef COCKATRICE_REMOTE_CONNECTION_CONTROLLER_H
#define COCKATRICE_REMOTE_CONNECTION_CONTROLLER_H
#include "abstract_client.h"
#include "latency_tracker.h"
#include <QMessageBox>
#include <QObject>
#include <QString>
#include <QtGlobal>
#include <qtmetamacros.h>
class RemoteClient;
class ServerInfo_User;
class DlgConnect;
class Event_ConnectionClosed;
class Event_ServerShutdown;
class QThread;
class QWidget;
template <typename T> class QList;
/**
* Owns the RemoteClient and its worker thread.
* Encapsulates all connection, authentication, and registration logic so that
* MainWindow only needs to react to high-level signals.
*/
class ConnectionController : public QObject
{
Q_OBJECT
public:
explicit ConnectionController(QWidget *dialogParent, QObject *parent = nullptr);
~ConnectionController() override;
RemoteClient *client() const
{
return remoteClient;
}
void registerToServer();
void forgotPasswordRequest();
void connectToServer();
void
connectToServerDirect(const QString &host, unsigned int port, const QString &playerName, const QString &password);
void disconnectFromServer();
void refreshWindowTitle()
{
updateWindowTitle();
}
signals:
void windowTitleChanged(const QString &title);
void tabSupervisorStartRequested(const ServerInfo_User &info);
void tabSupervisorStopRequested();
// Passes the raw ClientStatus through so MainWindow can drive its own
// action enable/disable logic
void statusChanged(ClientStatus status);
// Forwarded from AbstractClient::pingStatsUpdated. See that signal for the
// meaning of the parameters.
void pingStatsUpdated(const LatencyTracker::Stats &stats, const QList<int> &samplesMs);
private slots:
// Slots wired directly to RemoteClient signals
void onStatusChanged(ClientStatus status);
void onUserInfoReceived(const ServerInfo_User &info);
void onLoginError(int r, QString reasonStr, quint32 endTime, const QList<QString> &missingFeatures);
void onRegisterAccepted();
void onRegisterAcceptedNeedsActivate();
void onRegisterError(int r, QString reasonStr, quint32 endTime);
void onActivateAccepted();
void onActivateError();
void onProtocolVersionMismatch(int localVersion, int remoteVersion);
void onNotifyUserAboutUpdate();
void onConnectionClosedEvent(const Event_ConnectionClosed &event);
void onServerShutdownEvent(const Event_ServerShutdown &event);
void onSocketError(const QString &errorStr);
void onServerTimeout();
// Forgot-password flow
void onForgotPasswordSuccess();
void onForgotPasswordError();
void onPromptForgotPasswordReset();
void onPromptForgotPasswordChallenge();
private:
void wireClientSignals();
void updateWindowTitle();
/** Parse the server's pipe-delimited username-rule string into HTML. */
static QString extractInvalidUsernameMessage(QString &in);
RemoteClient *remoteClient{nullptr};
QThread *clientThread{nullptr};
QWidget *dialogParent{nullptr}; // used as parent for QMessageBox / dialog calls
// Persistent so it can be updated in-place by onServerShutdownEvent
QMessageBox serverShutdownMessageBox;
// Kept as a member so the forgot-password signal can be wired to it
DlgConnect *dlgConnect{nullptr};
};
#endif // COCKATRICE_REMOTE_CONNECTION_CONTROLLER_H

View file

@ -1,16 +1,28 @@
#include "deck_stats_interface.h"
#include "libcockatrice/card/card_info.h"
#include "libcockatrice/card/database/card_database_querier.h"
#include "libcockatrice/deck_list/tree/inner_deck_list_node.h"
#include <QByteArray>
#include <QDesktopServices>
#include <QMessageBox>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QRegularExpression>
#include <QRegularExpressionMatch>
#include <QSharedPointer>
#include <QString>
#include <QUrl>
#include <QUrlQuery>
#include <functional>
#include <libcockatrice/card/database/card_database_manager.h>
#include <libcockatrice/deck_list/deck_list.h>
#include <libcockatrice/deck_list/tree/deck_list_card_node.h>
#include <version_string.h>
DeckStatsInterface::DeckStatsInterface(CardDatabase &_cardDatabase, QObject *parent)
: QObject(parent), cardDatabase(_cardDatabase)
DeckStatsInterface::DeckStatsInterface(QObject *parent) : QObject(parent)
{
manager = new QNetworkAccessManager(this);
connect(manager, &QNetworkAccessManager::finished, this, &DeckStatsInterface::queryFinished);
@ -42,35 +54,36 @@ void DeckStatsInterface::queryFinished(QNetworkReply *reply)
deleteLater();
}
void DeckStatsInterface::getAnalyzeRequestData(DeckList *deck, QByteArray *data)
void DeckStatsInterface::getAnalyzeRequestData(const DeckList &deck, QByteArray &data)
{
DeckList deckWithoutTokens;
copyDeckWithoutTokens(*deck, deckWithoutTokens);
copyDeckWithoutTokens(deck, deckWithoutTokens);
QUrl params;
QUrlQuery urlQuery;
urlQuery.addQueryItem("deck", deckWithoutTokens.writeToString_Plain());
urlQuery.addQueryItem("decktitle", deck->getName());
urlQuery.addQueryItem("decktitle", deck.getName());
params.setQuery(urlQuery);
data->append(params.query(QUrl::EncodeReserved).toUtf8());
data.append(params.query(QUrl::EncodeReserved).toUtf8());
}
void DeckStatsInterface::analyzeDeck(DeckList *deck)
void DeckStatsInterface::analyzeDeck(const DeckList &deck)
{
QByteArray data;
getAnalyzeRequestData(deck, &data);
getAnalyzeRequestData(deck, data);
QNetworkRequest request(QUrl("https://deckstats.net/index.php"));
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
request.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING));
manager->post(request, data);
}
void DeckStatsInterface::copyDeckWithoutTokens(DeckList &source, DeckList &destination)
void DeckStatsInterface::copyDeckWithoutTokens(const DeckList &source, DeckList &destination)
{
auto copyIfNotAToken = [this, &destination](const auto node, const auto card) {
CardInfoPtr dbCard = cardDatabase.query()->getCardInfo(card->getName());
if (dbCard && !dbCard->getIsToken()) {
auto copyIfNotAToken = [&destination](const auto node, const auto card) {
CardInfoPtr dbCard = CardDatabaseManager::query()->getCardInfo(card->getName());
if (dbCard && !dbCard->getIsToken() && node->getName() != DECK_ZONE_MAYBEBOARD) {
DecklistCardNode *addedCard = destination.addCard(card->getName(), node->getName(), -1);
addedCard->setNumber(card->getNumber());
}

View file

@ -1,14 +1,14 @@
/**
* @file deck_stats_interface.h
* @ingroup ApiInterfaces
* @brief TODO: Document this.
*/
//! \todo Document this file.
#ifndef DECKSTATS_INTERFACE_H
#define DECKSTATS_INTERFACE_H
#include <libcockatrice/card/database/card_database.h>
#include <libcockatrice/deck_list/deck_list.h>
#include <QObject>
#include <qtmetamacros.h>
class QByteArray;
class QNetworkAccessManager;
@ -21,22 +21,20 @@ class DeckStatsInterface : public QObject
private:
QNetworkAccessManager *manager;
CardDatabase &cardDatabase;
/**
* Deckstats doesn't recognize token cards, and instead tries to find the
* closest non-token card instead. So we construct a new deck which has no
* tokens.
*/
void copyDeckWithoutTokens(DeckList &source, DeckList &destination);
void copyDeckWithoutTokens(const DeckList &source, DeckList &destination);
private slots:
void queryFinished(QNetworkReply *reply);
void getAnalyzeRequestData(DeckList *deck, QByteArray *data);
void getAnalyzeRequestData(const DeckList &deck, QByteArray &data);
public:
explicit DeckStatsInterface(CardDatabase &_cardDatabase, QObject *parent = nullptr);
void analyzeDeck(DeckList *deck);
explicit DeckStatsInterface(QObject *parent = nullptr);
void analyzeDeck(const DeckList &deck);
};
#endif

View file

@ -1,16 +1,34 @@
#include "tapped_out_interface.h"
#include "libcockatrice/card/card_info.h"
#include "libcockatrice/card/database/card_database_querier.h"
#include "libcockatrice/deck_list/tree/inner_deck_list_node.h"
#include <QByteArray>
#include <QDebug>
#include <QDesktopServices>
#include <QList>
#include <QMessageBox>
#include <QMessageLogger>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QRegularExpression>
#include <QRegularExpressionMatch>
#include <QRegularExpressionMatchIterator>
#include <QSharedPointer>
#include <QString>
#include <QStringList>
#include <QUrl>
#include <QUrlQuery>
#include <QVariant>
#include <functional>
#include <libcockatrice/card/database/card_database_manager.h>
#include <libcockatrice/deck_list/deck_list.h>
#include <libcockatrice/deck_list/tree/deck_list_card_node.h>
#include <version_string.h>
TappedOutInterface::TappedOutInterface(CardDatabase &_cardDatabase, QObject *parent)
: QObject(parent), cardDatabase(_cardDatabase)
TappedOutInterface::TappedOutInterface(QObject *parent) : QObject(parent)
{
manager = new QNetworkAccessManager(this);
connect(manager, &QNetworkAccessManager::finished, this, &TappedOutInterface::queryFinished);
@ -66,43 +84,48 @@ void TappedOutInterface::queryFinished(QNetworkReply *reply)
deleteLater();
}
void TappedOutInterface::getAnalyzeRequestData(DeckList *deck, QByteArray *data)
void TappedOutInterface::getAnalyzeRequestData(const DeckList &deck, QByteArray &data)
{
DeckList mainboard, sideboard;
copyDeckSplitMainAndSide(*deck, mainboard, sideboard);
copyDeckSplitMainAndSide(deck, mainboard, sideboard);
QUrl params;
QUrlQuery urlQuery;
urlQuery.addQueryItem("name", deck->getName());
urlQuery.addQueryItem("name", deck.getName());
urlQuery.addQueryItem("mainboard", mainboard.writeToString_Plain(false, true));
urlQuery.addQueryItem("sideboard", sideboard.writeToString_Plain(false, true));
params.setQuery(urlQuery);
data->append(params.query(QUrl::EncodeReserved).toUtf8());
data.append(params.query(QUrl::EncodeReserved).toUtf8());
}
void TappedOutInterface::analyzeDeck(DeckList *deck)
void TappedOutInterface::analyzeDeck(const DeckList &deck)
{
QByteArray data;
getAnalyzeRequestData(deck, &data);
getAnalyzeRequestData(deck, data);
QNetworkRequest request(QUrl("https://tappedout.net/mtg-decks/paste/"));
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
request.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING));
// we interpret the redirect and open it in the browser instead, do not follow redirects
request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::ManualRedirectPolicy);
manager->post(request, data);
}
void TappedOutInterface::copyDeckSplitMainAndSide(DeckList &source, DeckList &mainboard, DeckList &sideboard)
void TappedOutInterface::copyDeckSplitMainAndSide(const DeckList &source, DeckList &mainboard, DeckList &sideboard)
{
auto copyMainOrSide = [this, &mainboard, &sideboard](const auto node, const auto card) {
CardInfoPtr dbCard = cardDatabase.query()->getCardInfo(card->getName());
if (!dbCard || dbCard->getIsToken())
auto copyMainOrSide = [&mainboard, &sideboard](const auto node, const auto card) {
CardInfoPtr dbCard = CardDatabaseManager::query()->getCardInfo(card->getName());
if (!dbCard || dbCard->getIsToken() || node->getName() == DECK_ZONE_MAYBEBOARD) {
return;
}
DecklistCardNode *addedCard;
if (node->getName() == DECK_ZONE_SIDE)
if (node->getName() == DECK_ZONE_SIDE) {
addedCard = sideboard.addCard(card->getName(), node->getName(), -1);
else
} else {
addedCard = mainboard.addCard(card->getName(), node->getName(), -1);
}
addedCard->setNumber(card->getNumber());
};

View file

@ -1,14 +1,15 @@
/**
* @file tapped_out_interface.h
* @ingroup ApiInterfaces
* @brief TODO: Document this.
*/
//! \todo Document this file.
#ifndef TAPPEDOUT_INTERFACE_H
#define TAPPEDOUT_INTERFACE_H
#include <libcockatrice/card/database/card_database.h>
#include <libcockatrice/deck_list/deck_list.h>
#include <QLoggingCategory>
#include <QObject>
#include <qtmetamacros.h>
inline Q_LOGGING_CATEGORY(TappedOutInterfaceLog, "tapped_out_interface");
@ -29,15 +30,14 @@ class TappedOutInterface : public QObject
private:
QNetworkAccessManager *manager;
CardDatabase &cardDatabase;
void copyDeckSplitMainAndSide(DeckList &source, DeckList &mainboard, DeckList &sideboard);
void copyDeckSplitMainAndSide(const DeckList &source, DeckList &mainboard, DeckList &sideboard);
private slots:
void queryFinished(QNetworkReply *reply);
void getAnalyzeRequestData(DeckList *deck, QByteArray *data);
void getAnalyzeRequestData(const DeckList &deck, QByteArray &data);
public:
explicit TappedOutInterface(CardDatabase &_cardDatabase, QObject *parent = nullptr);
void analyzeDeck(DeckList *deck);
explicit TappedOutInterface(QObject *parent = nullptr);
void analyzeDeck(const DeckList &deck);
};
#endif

View file

@ -1,6 +1,7 @@
#include "deck_link_to_api_transformer.h"
#include <QRegularExpression>
#include <QRegularExpressionMatch>
namespace DeckLinkToApiTransformer
{

View file

@ -1,8 +1,8 @@
/**
* @file deck_link_to_api_transformer.h
* @ingroup ApiInterfaces
* @brief TODO: Document this.
*/
//! \todo Document this file.
#ifndef DECK_LINK_TO_API_TRANSFORMER_H
#define DECK_LINK_TO_API_TRANSFORMER_H

View file

@ -1,36 +1,39 @@
/**
* @file interface_json_deck_parser.h
* @ingroup ApiInterfaces
* @brief TODO: Document this.
*/
//! \todo Document this file.
#ifndef INTERFACE_JSON_DECK_PARSER_H
#define INTERFACE_JSON_DECK_PARSER_H
#include "../../../interface/deck_loader/deck_loader.h"
#include "../../../interface/deck_loader/card_node_function.h"
#include <QJsonArray>
#include <QJsonObject>
#include <libcockatrice/card/import/card_name_normalizer.h>
#include <libcockatrice/deck_list/deck_list.h>
class IJsonDeckParser
{
public:
virtual ~IJsonDeckParser() = default;
virtual DeckLoader *parse(const QJsonObject &obj) = 0;
virtual DeckList parse(const QJsonObject &obj) = 0;
};
class ArchidektJsonParser : public IJsonDeckParser
{
public:
DeckLoader *parse(const QJsonObject &obj) override
DeckList parse(const QJsonObject &obj) override
{
DeckLoader *loader = new DeckLoader(nullptr);
DeckList deckList;
QString deckName = obj.value("name").toString();
QString deckDescription = obj.value("description").toString();
loader->getDeckList()->setName(deckName);
loader->getDeckList()->setComments(deckDescription);
deckList.setName(deckName);
deckList.setComments(deckDescription);
QString outputText;
QTextStream outStream(&outputText);
@ -47,25 +50,25 @@ public:
outStream << quantity << ' ' << cardName << " (" << setName << ") " << collectorNumber << '\n';
}
loader->getDeckList()->loadFromStream_Plain(outStream, false);
DeckLoader::resolveSetNameAndNumberToProviderID(loader->getDeckList());
deckList.loadFromStream_Plain(outStream, false, CardNameNormalizer());
deckList.forEachCard(CardNodeFunction::ResolveProviderId());
return loader;
return deckList;
}
};
class MoxfieldJsonParser : public IJsonDeckParser
{
public:
DeckLoader *parse(const QJsonObject &obj) override
DeckList parse(const QJsonObject &obj) override
{
DeckLoader *loader = new DeckLoader(nullptr);
DeckList deckList;
QString deckName = obj.value("name").toString();
QString deckDescription = obj.value("description").toString();
loader->getDeckList()->setName(deckName);
loader->getDeckList()->setComments(deckDescription);
deckList.setName(deckName);
deckList.setComments(deckDescription);
QString outputText;
QTextStream outStream(&outputText);
@ -94,8 +97,8 @@ public:
outStream << quantity << ' ' << cardName << " (" << setName << ") " << collectorNumber << '\n';
}
loader->getDeckList()->loadFromStream_Plain(outStream, false);
DeckLoader::resolveSetNameAndNumberToProviderID(loader->getDeckList());
deckList.loadFromStream_Plain(outStream, false, CardNameNormalizer());
deckList.forEachCard(CardNodeFunction::ResolveProviderId());
QJsonObject commandersObj = obj.value("commanders").toObject();
if (!commandersObj.isEmpty()) {
@ -106,12 +109,12 @@ public:
QString collectorNumber = cardData.value("cn").toString();
QString providerId = cardData.value("scryfall_id").toString();
loader->getDeckList()->setBannerCard({commanderName, providerId});
loader->getDeckList()->addCard(commanderName, DECK_ZONE_MAIN, -1, setName, collectorNumber, providerId);
deckList.setBannerCard({commanderName, providerId});
deckList.addCard(commanderName, DECK_ZONE_MAIN, -1, setName, collectorNumber, providerId);
}
}
return loader;
return deckList;
}
};

Some files were not shown because too many files have changed in this diff Show more