Compare commits

...

111 commits

Author SHA1 Message Date
Lukas Brübach
89ef10c862 Make HomeWidget buttons private 2026-08-09 03:43:35 +02:00
Lukas Brübach
6929757095 Extract tutorial sequence setup from TabDeckEditorVisual constructor 2026-08-09 03:43:34 +02:00
Lukas Brübach
f70eb2226a Append display options tutorial steps via reference 2026-08-09 03:43:34 +02:00
Lukas Brübach
217e16630a Append VDS tutorial steps via reference instead of copy 2026-08-09 03:43:34 +02:00
Lukas Brübach
90409e3b65 Add TutorialSequence::addSteps for composability 2026-08-09 03:43:34 +02:00
Lukas Brübach
b6b862cad7 Guard tutorial step target widgets with QPointer 2026-08-09 03:43:34 +02:00
Lukas Brübach
9c0c480b2e Use pointer-to-member signal connections for tutorial validation
Took 10 minutes
2026-08-09 03:43:34 +02:00
Lukas Brübach
569b531113 Various fixes
Took 14 minutes
2026-08-09 03:43:34 +02:00
Lukas Brübach
4779cc9742 braced again
Took 3 minutes
2026-08-09 03:43:34 +02:00
Lukas Brübach
bf562a695d Use aggregate and QList
Took 8 minutes

Took 6 seconds


Took 1 second
2026-08-09 03:43:34 +02:00
Lukas Brübach
3e257f8f13 Fix compile errors.
Took 54 minutes

Took 14 seconds
2026-08-09 03:43:34 +02:00
Lukas Brübach
77b48cd181 Delete on finish
Took 16 minutes

Took 21 seconds
2026-08-09 03:43:34 +02:00
Lukas Brübach
7ca0b92d6c Follow parent window properly.
Took 19 seconds


Took 15 seconds
2026-08-09 03:43:33 +02:00
Brübach, Lukas
f4a1e9ff53 [App] First-run tutorial
Took 5 minutes

Took 3 minutes

Took 2 minutes
2026-08-09 03:43:31 +02: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
647 changed files with 85522 additions and 49855 deletions

View file

@ -159,6 +159,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

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

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

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

View file

@ -1,10 +1,10 @@
name: Build Desktop
permissions:
actions: write # needed to delete entries in GHA cache (update ccache)
attestations: write # needed to persist the attestation.
contents: write
id-token: write
attestations: write
actions: write # needed for ccache action to be able to delete gha caches
id-token: write # needed for signing certificate in attestation
on:
push:
@ -19,7 +19,7 @@ on:
- '.github/workflows/desktop-build.yml'
- 'CMakeLists.txt'
- 'vcpkg.json'
- 'vcpkg'
- 'vcpkg' # needed to match submodule bumps (gitlink)
tags:
- '*'
pull_request:
@ -32,7 +32,7 @@ on:
- '.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 release)
concurrency:
@ -44,216 +44,217 @@ jobs:
name: Configure
runs-on: ubuntu-slim
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
package: skip # We are packaged in Arch already
- distro: Servatrice_Debian
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: 43
package: RPM
test: skip # Running tests on all distros is superfluous
- distro: Fedora
version: 44
package: RPM
- distro: Ubuntu
version: 24.04
package: DEB
test: skip # Running tests on all distros is superfluous
- distro: Ubuntu
version: 26.04
package: DEB
name: ${{matrix.distro}} ${{matrix.version}}
name: ${{ matrix.distro }} ${{ matrix.version }}
needs: configure
runs-on: ubuntu-latest
continue-on-error: ${{matrix.allow-failure == 'yes'}}
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: 550M
CACHE: ${{ github.workspace }}/.cache/${{ matrix.distro }}${{ matrix.version }} # directory for caching docker image and ccache
CCACHE_EVICTION_AGE: 7d
CCACHE_SIZE: 550M # 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 }}
steps:
- name: Checkout
uses: actions/checkout@v6
- name: "Checkout"
uses: actions/checkout@v7
- name: Restore compiler cache (ccache)
- name: "Restore compiler cache (ccache)"
id: ccache_restore
uses: actions/cache/restore@v5
uses: actions/cache/restore@v6
env:
BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
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 }}-${{ env.BRANCH_NAME }}
path: ${{ env.CACHE }}
restore-keys: ccache-${{ matrix.distro }}${{ matrix.version }}-
- 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
run: |
source .ci/docker.sh
RUN --server --debug --test --ccache "$CCACHE_SIZE" \
--cmake-generator "$CMAKE_GENERATOR"
--cmake-generator "$CMAKE_GENERATOR"
- name: Build release package
- name: "Build release package"
id: build
if: matrix.package != 'skip'
shell: bash
env:
SUFFIX: '-${{matrix.distro}}${{matrix.version}}'
package: '${{matrix.package}}'
server_only: '${{matrix.server_only}}'
SUFFIX: '-${{ matrix.distro }}${{ matrix.version }}'
package: '${{ matrix.package }}'
server_only: '${{ matrix.server_only }}'
run: |
source .ci/docker.sh
args=()
if [[ $server_only == yes ]]; then
args+=(--no-client)
fi
if [[ $GITHUB_REF == "refs/heads/master" ]]; then
args+=(--evict-ccache "$CCACHE_EVICTION_AGE")
fi
[[ $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")
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)
- name: "Delete remote compiler cache (ccache)"
if: github.ref == 'refs/heads/master' && steps.ccache_restore.outputs.cache-hit
continue-on-error: true
env:
CACHE_PRIMARY_KEY: ${{ steps.ccache_restore.outputs.cache-primary-key }}
GH_TOKEN: ${{ github.token }}
run: |
if gh cache delete --repo ${{ github.repository }} ${{ steps.ccache_restore.outputs.cache-primary-key }}; then
if gh cache delete --repo "$GITHUB_REPOSITORY" "$CACHE_PRIMARY_KEY"; then
echo "Cache deleted successfully"
fi
- name: Save updated compiler cache (ccache)
- name: "Save updated compiler cache (ccache)"
if: github.ref == 'refs/heads/master'
uses: actions/cache/save@v5
uses: actions/cache/save@v6
with:
path: ${{env.CACHE}}
key: ${{ steps.ccache_restore.outputs.cache-primary-key }}
path: ${{ env.CACHE }}
- name: Upload artifact
- name: "Upload artifact"
id: upload_artifact
if: matrix.package != 'skip'
uses: actions/upload-artifact@v7
with:
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_name: ${{steps.build.outputs.fullname}}
asset_path: ${{steps.build.outputs.path}}
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@v4
with:
subject-path: ${{steps.build.outputs.path}}
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}}" --repo Cockatrice/Cockatrice
BUILD_PATH: ${{ steps.build.outputs.path }}
GH_TOKEN: ${{ github.token }}
run: gh attestation verify "$BUILD_PATH" --repo Cockatrice/Cockatrice
build-vcpkg:
strategy:
@ -263,234 +264,234 @@ jobs:
- os: macOS
target: 13
runner: macos-15-intel
soc: Intel
xcode: "16.4"
type: Release
override_target: 13
ccache_eviction_age: 7d
cmake_generator: Ninja
make_package: 1
override_target: 13
package_suffix: "-macOS13_Intel"
qt_version: 6.11.0
qt_arch: clang_64
qt_modules: qtimageformats qtmultimedia qtwebsockets
cmake_generator: Ninja
soc: Intel
type: Release
use_ccache: 1
ccache_eviction_age: 7d
xcode: "16.4"
- os: macOS
target: 14
runner: macos-14
soc: Apple
xcode: "15.4"
type: Release
ccache_eviction_age: 7d
cmake_generator: Ninja
make_package: 1
package_suffix: "-macOS14"
qt_version: 6.11.0
qt_arch: clang_64
qt_modules: qtimageformats qtmultimedia qtwebsockets
cmake_generator: Ninja
soc: Apple
type: Release
use_ccache: 1
ccache_eviction_age: 7d
xcode: "15.4"
- os: macOS
target: 15
runner: macos-15
soc: Apple
xcode: "16.4"
type: Release
ccache_eviction_age: 7d
cmake_generator: Ninja
make_package: 1
package_suffix: "-macOS15"
qt_version: 6.11.0
qt_arch: clang_64
qt_modules: qtimageformats qtmultimedia qtwebsockets
cmake_generator: Ninja
soc: Apple
type: Release
use_ccache: 1
ccache_eviction_age: 7d
xcode: "16.4"
- os: macOS
target: 15
runner: macos-15
soc: Apple
xcode: "16.4"
type: Debug
qt_version: 6.11.0
qt_arch: clang_64
qt_modules: qtimageformats qtmultimedia qtwebsockets
cmake_generator: Ninja
use_ccache: 1
ccache_eviction_age: 7d
cmake_generator: Ninja
qt_version: 6.11.0
qt_modules: qtimageformats qtmultimedia qtwebsockets
soc: Apple
type: Debug
use_ccache: 1
xcode: "16.4"
- os: Windows
target: 10
runner: windows-2025
type: Release
cmake_generator: "Visual Studio 18 2026"
cmake_generator_platform: x64
make_package: 1
package_suffix: "-Win10"
qt_version: 6.11.0
qt_arch: win64_msvc2022_64
qt_modules: qtimageformats qtmultimedia qtwebsockets
cmake_generator: "Visual Studio 17 2022"
cmake_generator_platform: x64
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/
# 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: 550M
CCACHE_DIR: ${{ github.workspace }}/.cache/
CCACHE_SIZE: 550M # 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@v3
with:
msbuild-architecture: x64
- name: Setup ccache
if: matrix.use_ccache == 1 && matrix.os == 'macOS'
- name: "[macOS] Setup ccache"
if: matrix.os == 'macOS' && matrix.use_ccache == 1
run: brew install ccache
- name: Restore compiler cache (ccache)
if: matrix.use_ccache == 1
- name: "[macOS] Restore compiler cache (ccache)"
if: matrix.os == 'macOS' && matrix.use_ccache == 1
id: ccache_restore
uses: actions/cache/restore@v5
uses: actions/cache/restore@v6
env:
BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
with:
path: ${{env.CCACHE_DIR}}
key: ccache-${{matrix.runner}}-${{matrix.soc}}-${{matrix.type}}-${{env.BRANCH_NAME}}
restore-keys: ccache-${{matrix.runner}}-${{matrix.soc}}-${{matrix.type}}-
key: ccache-${{ matrix.runner }}-${{ matrix.soc }}-${{ matrix.type }}-${{ env.BRANCH_NAME }}
path: ${{ env.CCACHE_DIR }}
restore-keys: ccache-${{ matrix.runner }}-${{ matrix.soc }}-${{ matrix.type }}-
- name: Install aqtinstall
- name: "Install aqtinstall"
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: Resolve latest Qt patch version
- name: "Resolve latest Qt patch version"
env:
QT_VERSION: ${{ matrix.qt_version }}
id: resolve_qt_version
shell: bash
run: .ci/resolve_latest_aqt_qt_version.sh "${{matrix.qt_version}}"
run: .ci/resolve_latest_aqt_qt_version.sh "$QT_VERSION"
- name: Restore thin Qt ${{ steps.resolve_qt_version.outputs.version }} libraries (${{ matrix.soc }} macOS)
- name: "[macOS] Restore thin Qt ${{ steps.resolve_qt_version.outputs.version }} libraries"
if: matrix.os == 'macOS'
id: restore_qt
uses: actions/cache/restore@v5
uses: actions/cache/restore@v6
with:
path: ${{ github.workspace }}/Qt
key: thin-qt-macos-${{ matrix.soc }}-${{ steps.resolve_qt_version.outputs.version }}
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: Install fat Qt ${{ steps.resolve_qt_version.outputs.version }} (${{ matrix.soc }} macOS)
# Qt build using vcpkg either just fails or takes too long to build
- name: "[macOS] Install fat Qt ${{ steps.resolve_qt_version.outputs.version }}"
if: matrix.os == 'macOS' && steps.restore_qt.outputs.cache-hit != 'true'
uses: jurplel/install-qt-action@v4
with:
version: ${{ steps.resolve_qt_version.outputs.version }}
arch: ${{matrix.qt_arch}}
modules: ${{matrix.qt_modules}}
cache: false
dir: ${{github.workspace}}
dir: ${{ github.workspace }}
modules: ${{ matrix.qt_modules }}
version: ${{ steps.resolve_qt_version.outputs.version }}
- name: Thin Qt libraries (${{ matrix.soc }} macOS)
- name: "[macOS] Create thin Qt libraries"
if: matrix.os == 'macOS' && steps.restore_qt.outputs.cache-hit != 'true'
run: .ci/thin_macos_qtlib.sh
- name: Cache thin Qt libraries (${{ matrix.soc }} macOS)
- name: "[macOS] Cache thin Qt libraries"
if: matrix.os == 'macOS' && steps.restore_qt.outputs.cache-hit != 'true'
uses: actions/cache/save@v5
uses: actions/cache/save@v6
with:
path: ${{ github.workspace }}/Qt
key: thin-qt-macos-${{ matrix.soc }}-${{ steps.resolve_qt_version.outputs.version }}
path: ${{ github.workspace }}/Qt
- name: Install Qt ${{matrix.qt_version}} (Windows)
- 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
# 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
version: ${{ steps.resolve_qt_version.outputs.version }}
arch: ${{matrix.qt_arch}}
modules: ${{matrix.qt_modules}}
cache: true
modules: ${{ matrix.qt_modules }}
version: ${{ steps.resolve_qt_version.outputs.version }}
- name: Install NSIS
- name: "[Windows] Install NSIS"
if: matrix.os == 'Windows'
shell: bash
run: choco install nsis
- name: Setup vcpkg cache
- 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
MACOS_CERTIFICATE: ${{ secrets.PROD_MACOS_CERTIFICATE }}
MACOS_CERTIFICATE_PWD: ${{ secrets.PROD_MACOS_CERTIFICATE_PWD }}
MACOS_CERTIFICATE_NAME: ${{ secrets.PROD_MACOS_CERTIFICATE_NAME }}
MACOS_CI_KEYCHAIN_PWD: ${{ secrets.PROD_MACOS_CI_KEYCHAIN_PWD }}
DEVELOPER_DIR: '/Applications/Xcode_${{matrix.xcode}}.app/Contents/Developer'
TARGET_MACOS_VERSION: ${{ matrix.override_target }}
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'
MACOS_CERTIFICATE: ${{ secrets.PROD_MACOS_CERTIFICATE }}
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 }}
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 }},readwrite'
VCPKG_DISABLE_METRICS: 1
run: .ci/compile.sh --server --test --vcpkg
# 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' && matrix.use_ccache == 1 && steps.ccache_restore.outputs.cache-hit
- name: "[macOS] Delete remote compiler cache (ccache)"
if: matrix.os == 'macOS' && matrix.use_ccache == 1 && github.ref == 'refs/heads/master' && steps.ccache_restore.outputs.cache-hit
continue-on-error: true
env:
CACHE_PRIMARY_KEY: ${{ steps.ccache_restore.outputs.cache-primary-key }}
GH_TOKEN: ${{ github.token }}
run: |
if gh cache delete --repo ${{ github.repository }} ${{ steps.ccache_restore.outputs.cache-primary-key }}; then
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' && matrix.use_ccache == 1
uses: actions/cache/save@v5
- name: "[macOS] Save updated compiler cache (ccache)"
if: matrix.os == 'macOS' && matrix.use_ccache == 1 && github.ref == 'refs/heads/master'
uses: actions/cache/save@v6
with:
path: ${{env.CCACHE_DIR}}
key: ${{ steps.ccache_restore.outputs.cache-primary-key }}
path: ${{ env.CCACHE_DIR }}
- name: Sign app bundle
- 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: steps.sign_macos.outcome == 'success'
- 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
@ -502,7 +503,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
@ -514,51 +515,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
- name: "Upload artifact"
if: matrix.make_package
id: upload_artifact
uses: actions/upload-artifact@v7
with:
path: ${{steps.build.outputs.path}}
archive: false
if-no-files-found: error
path: ${{ steps.build.outputs.path }}
- name: Upload PDBs (Program Databases)
- name: "[Windows] Upload PDBs (Program Databases)"
if: matrix.os == 'Windows' && github.ref_type != 'tag'
uses: actions/upload-artifact@v7
with:
name: ${{steps.build.outputs.name}}-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
shell: bash
env:
GH_TOKEN: ${{github.token}}
tag_name: ${{needs.configure.outputs.tag}}
asset_name: ${{steps.build.outputs.fullname}}
asset_path: ${{steps.build.outputs.path}}
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"
if: steps.upload_release.outcome == 'success'
id: attestation
uses: actions/attest@v4
with:
subject-path: ${{steps.build.outputs.path}}
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}}" --repo Cockatrice/Cockatrice
BUILD_PATH: ${{ steps.build.outputs.path }}
GH_TOKEN: ${{ github.token }}
run: gh attestation verify "$BUILD_PATH" --repo Cockatrice/Cockatrice

View file

@ -1,7 +1,7 @@
name: Code Style (C++)
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
@ -21,17 +21,20 @@ jobs:
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
- 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,10 @@
name: Build Docker Image
permissions:
contents: read
packages: write
on:
release:
types:
- released # publishing of stable releases
push:
branches:
- master
@ -13,6 +14,9 @@ on:
paths:
- '.github/workflows/docker-release.yml'
- 'Dockerfile'
release:
types:
- released # publishing of stable releases
# Cancel earlier, unfinished runs of this workflow on the same branch (unless on release)
concurrency:
@ -24,54 +28,51 @@ jobs:
name: amd64 & arm64
if: ${{ github.repository_owner == 'Cockatrice' }}
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout
uses: actions/checkout@v6
- name: "Checkout"
uses: actions/checkout@v7
- name: Docker metadata
- name: "Docker metadata"
id: metadata
uses: docker/metadata-action@v6
env:
DOCKER_METADATA_ANNOTATIONS_LEVELS: index # needed for GHCR
with:
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
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
- name: Set up QEMU
- name: "Set up QEMU"
uses: docker/setup-qemu-action@v4
- name: Set up Docker buildx
- name: "Set up Docker buildx"
uses: docker/setup-buildx-action@v4
- name: Login to GitHub Container Registry
if: contains(github.event.release.tag_name, 'Release') && github.event.release.target_commitish == 'master'
- name: "Login to GitHub Container Registry (GHCR)"
if: github.event_name == 'release' && github.event.release.prerelease == false
id: login
uses: docker/login-action@v4
with:
password: ${{ github.token }}
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ github.token }}
- name: Build and push Docker image
- name: "Build and push Docker image"
uses: docker/build-push-action@v7
with:
context: .
platforms: linux/amd64,linux/arm64
push: ${{ github.ref_type == 'tag' }}
tags: ${{ steps.metadata.outputs.tags }}
labels: ${{ steps.metadata.outputs.labels }}
annotations: ${{ steps.metadata.outputs.annotations }}
cache-from: type=gha,scope=servatrice
cache-to: type=gha,mode=max,scope=servatrice
context: .
labels: ${{ steps.metadata.outputs.labels }}
platforms: linux/amd64,linux/arm64
push: ${{ steps.login.outcome == 'success' }}
tags: ${{ steps.metadata.outputs.tags }}

View file

@ -1,18 +1,18 @@
name: Generate Docs
on:
release:
types:
- published # publishing of stable releases and pre-releases
pull_request:
paths:
- 'doc/doxygen/**'
- '.github/workflows/documentation-build.yml'
- 'Doxyfile'
release:
types:
- published # publishing of stable releases and pre-releases
workflow_dispatch:
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,22 +20,22 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: "Checkout code"
uses: actions/checkout@v7
with:
submodules: recursive
- name: Install Graphviz
- name: "Install Graphviz"
run: |
sudo apt-get install -y graphviz
dot -V
- name: Install Doxygen
- name: "Install Doxygen"
uses: ssciwr/doxygen-install@v2
with:
version: "1.16.1"
- name: Update Doxygen Configuration
- name: "Update Doxygen Configuration"
run: |
git diff Doxyfile
doxygen -u Doxyfile
@ -48,16 +48,16 @@ jobs:
exit 1
fi
- name: Generate Documentation
- name: "Generate Documentation"
if: always()
run: doxygen Doxyfile
- name: Deploy to cockatrice.github.io
- name: "Deploy to cockatrice.github.io"
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/

View file

@ -1,14 +1,14 @@
name: Update Translations
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:
@ -19,18 +19,20 @@ jobs:
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@v8
@ -38,35 +40,32 @@ jobs:
add-paths: |
cockatrice/translations/*.ts
oracle/translations/*.ts
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,14 @@
name: Update Translation Source
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:
@ -19,32 +19,33 @@ jobs:
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/binary@v1
with:
@ -54,7 +55,7 @@ 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@v8
@ -62,27 +63,24 @@ jobs:
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"

1
.gitignore vendored
View file

@ -6,6 +6,7 @@ mysql.cnf
.DS_Store
.idea/
*.aps
*.cache
cmake-build*
preferences
compile_commands.json

View file

@ -64,10 +64,11 @@ 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()
@ -174,7 +175,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
-Wno-error=sfinae-incomplete # GCC 16+: Qt MOC + protobuf forward decls trigger this
)
foreach(FLAG ${ADDITIONAL_DEBUG_FLAGS})
@ -280,11 +281,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-qttools, qt6-qtsvg, qt6-qtmultimedia, qt6-qtimageformats")
set(CPACK_RPM_PACKAGE_GROUP "Amusements/Games")
set(CPACK_RPM_PACKAGE_URL "http://github.com/Cockatrice/Cockatrice")
# stop directories from making package conflicts
@ -302,12 +299,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-qpa-plugins, qt6-image-formats-plugins")
set(CPACK_DEBIAN_PACKAGE_RECOMMENDS "libqt6sql6-mysql") # for connecting servatrice to a mysql db
endif()
endif()
elseif(WIN32)

View file

@ -349,7 +349,7 @@ OPTIMIZE_OUTPUT_SLICE = NO
#
# Note see also the list of default file extension mappings.
EXTENSION_MAPPING =
EXTENSION_MAPPING = proto=C++
# If the MARKDOWN_SUPPORT tag is enabled then Doxygen pre-processes all comments
# according to the Markdown format, which allows for more readable
@ -1086,7 +1086,8 @@ FILE_PATTERNS = *.cc \
*.h++ \
*.markdown \
*.md \
*.dox
*.dox \
*.proto
# The RECURSIVE tag can be used to specify whether or not subdirectories should
# be searched for input files as well.
@ -1103,6 +1104,7 @@ RECURSIVE = YES
EXCLUDE = build/ \
cmake/ \
cmake-build-debug/ \
doc/doxygen/theme/docs/ \
doc/doxygen/theme/include/ \
vcpkg/
@ -1195,7 +1197,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

View file

@ -158,7 +158,6 @@ The following flags (with their non-default values) can be passed to `cmake`:
| `-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 |
# Run

View file

@ -1,8 +1,7 @@
# Find a compatible Qt version
# Inputs: WITH_SERVER, WITH_CLIENT, WITH_ORACLE, 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
@ -39,69 +38,37 @@ set(REQUIRED_QT_COMPONENTS ${REQUIRED_QT_COMPONENTS} ${_SERVATRICE_NEEDED} ${_CO
)
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.4.2
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.15.2
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}")

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>

View file

@ -117,21 +117,22 @@ ${If} $InstDir == ""
; we need to set a default based on the install mode
StrCpy $InstDir $0
${EndIf}
Call SetModeDestinationFromInstdir
; --- Detect portable install when using /R ---
; --- Detect portable install when using /R (must come BEFORE SetModeDestinationFromInstdir) ---
${If} $ReinstallMode = 1
IfFileExists "$InstDir\portable.dat" 0 not_portable
StrCpy $PortableMode 1
Goto portable_done
not_portable:
StrCpy $PortableMode 0
portable_done:
${EndIf}
; Now that $PortableMode reflects reality, commit InstDir into the correct slot
Call SetModeDestinationFromInstdir
${If} $ReinstallMode = 1
${AndIf} $PortableMode = 0
Call AutoUninstallIfNeeded
${EndIf}

View file

@ -56,56 +56,62 @@ set(cockatrice_SOURCES
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/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/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_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/card_state.cpp
src/game/board/counter_general.cpp
src/game_graphics/board/counter_general.cpp
src/game/board/counter_state.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_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_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_logic.cpp
src/game/zones/hand_zone_logic.cpp
@ -129,6 +135,12 @@ 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/line_edit_completer.cpp
@ -181,6 +193,7 @@ set(cockatrice_SOURCES
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
@ -210,6 +223,9 @@ set(cockatrice_SOURCES
src/interface/widgets/general/layout_containers/flow_widget.cpp
src/interface/widgets/general/layout_containers/overlap_control_widget.cpp
src/interface/widgets/general/layout_containers/overlap_widget.cpp
src/interface/widgets/general/tutorial/tutorial_bubble_widget.cpp
src/interface/widgets/general/tutorial/tutorial_controller.cpp
src/interface/widgets/general/tutorial/tutorial_overlay.cpp
src/interface/widgets/menus/deck_editor_menu.cpp
src/interface/widgets/printing_selector/all_zones_card_amount_widget.cpp
src/interface/widgets/printing_selector/card_amount_widget.cpp
@ -224,7 +240,9 @@ set(cockatrice_SOURCES
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_selector.cpp
@ -233,15 +251,22 @@ set(cockatrice_SOURCES
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_manager.cpp
src/interface/widgets/server/user/user_list_painter.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
@ -280,6 +305,7 @@ set(cockatrice_SOURCES
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
@ -295,6 +321,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
@ -324,6 +357,7 @@ 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_game.cpp
@ -340,12 +374,31 @@ 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/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/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)
@ -387,11 +440,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
@ -403,71 +452,37 @@ 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_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)
@ -487,6 +502,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 ./)
@ -497,7 +529,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}
@ -564,7 +596,7 @@ if(WIN32)
PATTERN "*.ini"
)
# 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}
@ -620,7 +652,7 @@ Data = Resources\")
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(
@ -647,6 +679,4 @@ if(Qt6_FOUND AND Qt6LinguistTools_FOUND)
endif()
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,8 @@
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=true

File diff suppressed because it is too large Load diff

View file

@ -14,6 +14,7 @@
#include <QThread>
#include <libcockatrice/network/client/remote/remote_client.h>
#include <libcockatrice/protocol/pb/response.pb.h>
#include <libcockatrice/settings/servers_settings.h>
ConnectionController::ConnectionController(QWidget *dialogParent, QObject *parent)
: QObject(parent), dialogParent(dialogParent)

View file

@ -6,12 +6,12 @@
#include <QNetworkRequest>
#include <QRegularExpression>
#include <QUrlQuery>
#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);
@ -70,8 +70,8 @@ void DeckStatsInterface::analyzeDeck(const DeckList &deck)
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());
auto copyIfNotAToken = [&destination](const auto node, const auto card) {
CardInfoPtr dbCard = CardDatabaseManager::query()->getCardInfo(card->getName());
if (dbCard && !dbCard->getIsToken()) {
DecklistCardNode *addedCard = destination.addCard(card->getName(), node->getName(), -1);
addedCard->setNumber(card->getNumber());

View file

@ -7,7 +7,6 @@
#ifndef DECKSTATS_INTERFACE_H
#define DECKSTATS_INTERFACE_H
#include <libcockatrice/card/database/card_database.h>
#include <libcockatrice/deck_list/deck_list.h>
class QByteArray;
@ -21,8 +20,6 @@ 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
@ -35,7 +32,7 @@ private slots:
void getAnalyzeRequestData(const DeckList &deck, QByteArray &data);
public:
explicit DeckStatsInterface(CardDatabase &_cardDatabase, QObject *parent = nullptr);
explicit DeckStatsInterface(QObject *parent = nullptr);
void analyzeDeck(const DeckList &deck);
};

View file

@ -6,12 +6,12 @@
#include <QNetworkRequest>
#include <QRegularExpression>
#include <QUrlQuery>
#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);
@ -97,8 +97,8 @@ void TappedOutInterface::analyzeDeck(const DeckList &deck)
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());
auto copyMainOrSide = [&mainboard, &sideboard](const auto node, const auto card) {
CardInfoPtr dbCard = CardDatabaseManager::query()->getCardInfo(card->getName());
if (!dbCard || dbCard->getIsToken()) {
return;
}

View file

@ -7,8 +7,8 @@
#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>
inline Q_LOGGING_CATEGORY(TappedOutInterfaceLog, "tapped_out_interface");
@ -29,14 +29,13 @@ class TappedOutInterface : public QObject
private:
QNetworkAccessManager *manager;
CardDatabase &cardDatabase;
void copyDeckSplitMainAndSide(const DeckList &source, DeckList &mainboard, DeckList &sideboard);
private slots:
void queryFinished(QNetworkReply *reply);
void getAnalyzeRequestData(const DeckList &deck, QByteArray &data);
public:
explicit TappedOutInterface(CardDatabase &_cardDatabase, QObject *parent = nullptr);
explicit TappedOutInterface(QObject *parent = nullptr);
void analyzeDeck(const DeckList &deck);
};

View file

@ -14,6 +14,8 @@
#include <QtConcurrent>
#include <libcockatrice/card/database/card_database.h>
#include <libcockatrice/card/database/card_database_manager.h>
#include <libcockatrice/settings/download_settings.h>
#include <libcockatrice/settings/paths_settings.h>
#include <version_string.h>
#define SPOILERS_STATUS_URL "https://raw.githubusercontent.com/Cockatrice/Magic-Spoiler/files/SpoilerSeasonEnabled"
@ -21,7 +23,7 @@
SpoilerBackgroundUpdater::SpoilerBackgroundUpdater(QObject *apParent) : QObject(apParent), cardUpdateProcess(nullptr)
{
isSpoilerDownloadEnabled = SettingsCache::instance().getDownloadSpoilersStatus();
isSpoilerDownloadEnabled = SettingsCache::instance().downloads().getDownloadSpoilersStatus();
if (isSpoilerDownloadEnabled) {
// Start the process of checking if we're in spoiler season
// File exists means we're in spoiler season
@ -75,7 +77,7 @@ void SpoilerBackgroundUpdater::actDownloadFinishedSpoilersFile()
bool SpoilerBackgroundUpdater::deleteSpoilerFile()
{
QString fileName = SettingsCache::instance().getSpoilerCardDatabasePath();
QString fileName = SettingsCache::instance().paths().getSpoilerCardDatabasePath();
QFileInfo fi(fileName);
QDir fileDir(fi.path());
QFile file(fileName);
@ -126,7 +128,7 @@ void SpoilerBackgroundUpdater::actCheckIfSpoilerSeasonEnabled()
bool SpoilerBackgroundUpdater::saveDownloadedFile(QByteArray data)
{
QString fileName = SettingsCache::instance().getSpoilerCardDatabasePath();
QString fileName = SettingsCache::instance().paths().getSpoilerCardDatabasePath();
QFileInfo fi(fileName);
QDir fileDir(fi.path());

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -5,7 +5,7 @@
#include <QtMath>
CardCounterSettings::CardCounterSettings(const QString &settingsPath, QObject *parent)
: SettingsManager(settingsPath + "global.ini", "cards", "counters", parent)
: SettingsManager(settingsPath + "card_counters.ini", "cards", "counters", parent)
{
}

View file

@ -223,6 +223,10 @@ private:
{"TabDeckEditor/aLoadDeck", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Load Deck..."),
parseSequenceString("Ctrl+O"),
ShortcutGroup::Deck_Editor)},
{"TabDeckEditor/aLoadDeckFromWebsite",
ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Load deck from online service..."),
parseSequenceString("Ctrl+Shift+O"),
ShortcutGroup::Deck_Editor)},
{"TabDeckEditor/aLoadDeckFromClipboard",
ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Load Deck from Clipboard..."),
parseSequenceString("Ctrl+Shift+V"),
@ -283,6 +287,10 @@ private:
ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Load Deck from Clipboard..."),
parseSequenceString("Ctrl+Shift+V"),
ShortcutGroup::Game_Lobby)},
{"DeckViewContainer/loadFromWebsiteButton",
ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Load from website..."),
parseSequenceString("Ctrl+Shift+O"),
ShortcutGroup::Game_Lobby)},
{"DeckViewContainer/unloadDeckButton", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Unload Deck"),
parseSequenceString("Ctrl+Alt+U"),
ShortcutGroup::Game_Lobby)},

View file

@ -2,12 +2,11 @@
#include "settings/cache_settings.h"
#include <QApplication>
#include <QAudioOutput>
#include <QDir>
#include <QMediaPlayer>
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
#include <QAudioOutput>
#endif
#include <libcockatrice/settings/sound_settings.h>
#define DEFAULT_THEME_NAME "Default"
#define TEST_SOUND_FILENAME "player_join"
@ -15,8 +14,10 @@
SoundEngine::SoundEngine(QObject *parent) : QObject(parent), audioOutput(nullptr), player(nullptr)
{
ensureThemeDirectoryExists();
connect(&SettingsCache::instance(), &SettingsCache::soundThemeChanged, this, &SoundEngine::themeChangedSlot);
connect(&SettingsCache::instance(), &SettingsCache::soundEnabledChanged, this, &SoundEngine::soundEnabledChanged);
connect(&SettingsCache::instance().sound(), &SoundSettings::soundThemeChanged, this,
&SoundEngine::themeChangedSlot);
connect(&SettingsCache::instance().sound(), &SoundSettings::soundEnabledChanged, this,
&SoundEngine::soundEnabledChanged);
soundEnabledChanged();
themeChangedSlot();
@ -36,14 +37,12 @@ SoundEngine::~SoundEngine()
void SoundEngine::soundEnabledChanged()
{
if (SettingsCache::instance().getSoundEnabled()) {
if (SettingsCache::instance().sound().getSoundEnabled()) {
qCInfo(SoundEngineLog) << "SoundEngine: enabling sound with" << audioData.size() << "sounds";
if (!player) {
player = new QMediaPlayer;
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
audioOutput = new QAudioOutput(player);
player->setAudioOutput(audioOutput);
#endif
}
} else {
qCInfo(SoundEngineLog) << "SoundEngine: disabling sound";
@ -70,14 +69,9 @@ void SoundEngine::playSound(const QString &fileName)
}
player->stop();
int volumeSliderValue = SettingsCache::instance().getMasterVolume();
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
int volumeSliderValue = SettingsCache::instance().sound().getMasterVolume();
player->audioOutput()->setVolume(qreal(volumeSliderValue) / 100);
player->setSource(QUrl::fromLocalFile(audioData[fileName]));
#else
player->setVolume(volumeSliderValue);
player->setMedia(QUrl::fromLocalFile(audioData[fileName]));
#endif
player->play();
}
@ -88,10 +82,10 @@ void SoundEngine::testSound()
void SoundEngine::ensureThemeDirectoryExists()
{
if (SettingsCache::instance().getSoundThemeName().isEmpty() ||
!getAvailableThemes().contains(SettingsCache::instance().getSoundThemeName())) {
if (SettingsCache::instance().sound().getSoundThemeName().isEmpty() ||
!getAvailableThemes().contains(SettingsCache::instance().sound().getSoundThemeName())) {
qCInfo(SoundEngineLog) << "Sounds theme name not set, setting default value";
SettingsCache::instance().setSoundThemeName(DEFAULT_THEME_NAME);
SettingsCache::instance().sound().setSoundThemeName(DEFAULT_THEME_NAME);
}
}
@ -132,7 +126,7 @@ QStringMap &SoundEngine::getAvailableThemes()
void SoundEngine::themeChangedSlot()
{
QString themeName = SettingsCache::instance().getSoundThemeName();
QString themeName = SettingsCache::instance().sound().getSoundThemeName();
qCInfo(SoundEngineLog) << "Sound theme changed:" << themeName;
QDir dir = getAvailableThemes().value(themeName);

View file

@ -0,0 +1,69 @@
#ifndef COCKATRICE_URL_SCHEME_EVENT_FILTER_H
#define COCKATRICE_URL_SCHEME_EVENT_FILTER_H
#include <QEvent>
#include <QFileOpenEvent>
#include <QObject>
#include <QString>
#include <QUrl>
/**
* @brief Event filter that catches QFileOpenEvent URLs matching a scheme and
* re-emits them as urlReceived().
*
* On macOS, when the application is registered as a URL scheme handler, the
* OS delivers incoming URLs via QFileOpenEvent on the QApplication object.
* Install this filter on QApplication to intercept them:
*
* @code
* UrlSchemeEventFilter filter(QStringList{QStringLiteral("cockatrice")});
* QObject::connect(&filter, &UrlSchemeEventFilter::urlReceived,
* &mainWindow, &MainWindow::handleUrl);
* app.installEventFilter(&filter);
* @endcode
*
* Note: the strings are compared against QUrl::scheme(), so they must be
* written without the "://" suffix (e.g. "cockatrice", not "cockatrice://").
*/
class UrlSchemeEventFilter : public QObject
{
Q_OBJECT
public:
explicit UrlSchemeEventFilter(const QStringList &schemes, QObject *parent = nullptr)
: QObject(parent), prefixes(schemes)
{
}
signals:
void urlReceived(const QString &url);
public:
bool eventFilter(QObject *watched, QEvent *event) override
{
if (event->type() == QEvent::FileOpen) {
auto *fileEvent = static_cast<QFileOpenEvent *>(event);
const QUrl url = fileEvent->url();
for (const auto &prefix : prefixes) {
if (url.scheme() == prefix) {
emit urlReceived(url.toString());
return true;
}
}
if (url.isLocalFile()) {
emit urlReceived(url.toLocalFile());
return true;
}
}
return QObject::eventFilter(watched, event);
}
private:
QStringList prefixes;
};
#endif // COCKATRICE_URL_SCHEME_EVENT_FILTER_H

View file

@ -0,0 +1,41 @@
#ifndef SETTINGS_CARD_DATABASE_PATH_PROVIDER_H
#define SETTINGS_CARD_DATABASE_PATH_PROVIDER_H
#include "../../client/settings/cache_settings.h"
#include <libcockatrice/interfaces/interface_card_database_path_provider.h>
#include <libcockatrice/settings/paths_settings.h>
class SettingsCardDatabasePathProvider : public ICardDatabasePathProvider
{
Q_OBJECT
public:
explicit SettingsCardDatabasePathProvider(QObject *parent = nullptr) : ICardDatabasePathProvider(parent)
{
connect(&SettingsCache::instance().paths(), &PathsSettings::cardDatabasePathChanged, this,
&ICardDatabasePathProvider::cardDatabasePathChanged);
}
[[nodiscard]] QString getCardDatabasePath() const override
{
return SettingsCache::instance().paths().getCardDatabasePath();
}
[[nodiscard]] QString getCustomCardDatabasePath() const override
{
return SettingsCache::instance().paths().getCustomCardDatabasePath();
}
[[nodiscard]] QString getTokenDatabasePath() const override
{
return SettingsCache::instance().paths().getTokenDatabasePath();
}
[[nodiscard]] virtual QString getSpoilerCardDatabasePath() const override
{
return SettingsCache::instance().paths().getSpoilerCardDatabasePath();
}
};
#endif // SETTINGS_CARD_DATABASE_PATH_PROVIDER_H

View file

@ -3,6 +3,8 @@
#include "../../client/settings/cache_settings.h"
#include <libcockatrice/interfaces/interface_card_preference_provider.h>
#include <libcockatrice/settings/card_override_settings.h>
#include <libcockatrice/settings/cards_display_settings.h>
class SettingsCardPreferenceProvider : public ICardPreferenceProvider
{
@ -14,7 +16,7 @@ public:
[[nodiscard]] bool getIncludeRebalancedCards() const override
{
return SettingsCache::instance().getIncludeRebalancedCards();
return SettingsCache::instance().cardsDisplay().getIncludeRebalancedCards();
}
};

View file

@ -3,7 +3,7 @@
#include "../interface/widgets/tabs/tab_game.h"
#include "player/player_logic.h"
AbstractGame::AbstractGame(TabGame *_tab) : QObject(_tab), tab(_tab)
AbstractGame::AbstractGame(QObject *_parent) : QObject(_parent)
{
gameMetaInfo = new GameMetaInfo(this);
gameEventHandler = new GameEventHandler(this);
@ -31,7 +31,7 @@ AbstractClient *AbstractGame::getClientForPlayer(int playerId) const
}
}
void AbstractGame::loadReplay(GameReplay *replay)
void AbstractGame::loadReplay(const GameReplay *replay)
{
gameMetaInfo->setFromProto(replay->game_info());
gameMetaInfo->setSpectatorsOmniscient(true);

View file

@ -16,26 +16,19 @@
#include <libcockatrice/protocol/pb/game_replay.pb.h>
class CardItem;
class TabGame;
class AbstractGame : public QObject
{
Q_OBJECT
public:
explicit AbstractGame(TabGame *tab);
explicit AbstractGame(QObject *parent);
TabGame *tab;
GameMetaInfo *gameMetaInfo;
GameState *gameState;
GameEventHandler *gameEventHandler;
PlayerManager *playerManager;
CardItem *activeCard;
TabGame *getTab() const
{
return tab;
}
GameMetaInfo *getGameMetaInfo()
{
return gameMetaInfo;
@ -60,7 +53,7 @@ public:
AbstractClient *getClientForPlayer(int playerId) const;
void loadReplay(GameReplay *replay);
void loadReplay(const GameReplay *replay);
CardItem *getCard(int playerId, const QString &zoneName, int cardId) const;

View file

@ -0,0 +1,48 @@
#include "arrow_registry.h"
#include "../game_graphics/board/arrow_item.h"
void ArrowRegistry::insert(QSharedPointer<ArrowData> data, ArrowItem *arrow)
{
const ArrowKey key{data->creatorId, data->id};
if (auto *existing = take(data->creatorId, data->id)) {
existing->delArrow();
}
dataStore.insert(key, data);
items.insert(key, arrow);
byPlayer[data->creatorId].insert(data->id);
}
ArrowItem *ArrowRegistry::take(int creatorId, int arrowId)
{
const ArrowKey key{creatorId, arrowId};
dataStore.remove(key);
auto &playerSet = byPlayer[creatorId];
playerSet.remove(arrowId);
if (playerSet.isEmpty()) {
byPlayer.remove(creatorId);
}
return items.take(key);
}
ArrowItem *ArrowRegistry::get(int creatorId, int arrowId) const
{
return items.value(ArrowKey{creatorId, arrowId}, nullptr);
}
bool ArrowRegistry::contains(int creatorId, int arrowId) const
{
return items.contains(ArrowKey{creatorId, arrowId});
}
QSet<int> ArrowRegistry::idsForPlayer(int playerId) const
{
return byPlayer.value(playerId);
}
QList<ArrowItem *> ArrowRegistry::all() const
{
return items.values();
}

View file

@ -0,0 +1,43 @@
#ifndef COCKATRICE_ARROW_REGISTRY_H
#define COCKATRICE_ARROW_REGISTRY_H
#include "board/arrow_data.h"
#include <QMap>
#include <QSet>
#include <QSharedPointer>
class ArrowItem;
struct ArrowKey
{
int creatorId;
int arrowId;
bool operator<(const ArrowKey &other) const
{
if (creatorId != other.creatorId) {
return creatorId < other.creatorId;
}
return arrowId < other.arrowId;
}
};
class ArrowRegistry
{
public:
void insert(QSharedPointer<ArrowData> data, ArrowItem *arrow);
ArrowItem *take(int creatorId, int arrowId);
[[nodiscard]] ArrowItem *get(int creatorId, int arrowId) const;
[[nodiscard]] bool contains(int creatorId, int arrowId) const;
[[nodiscard]] QSet<int> idsForPlayer(int playerId) const;
[[nodiscard]] QList<ArrowItem *> all() const;
private:
QMap<ArrowKey, QSharedPointer<ArrowData>> dataStore;
QMap<ArrowKey, ArrowItem *> items;
QMap<int, QSet<int>> byPlayer;
};
#endif

View file

@ -1,8 +1,10 @@
#include "arrow_data.h"
ArrowData ArrowData::fromProto(const ServerInfo_Arrow &arrow)
ArrowData ArrowData::fromProto(const ServerInfo_Arrow &arrow, int creatorId, bool isLocalCreator)
{
ArrowData data;
data.creatorId = creatorId;
data.isLocalCreator = isLocalCreator;
data.id = arrow.id();
data.startPlayerId = arrow.start_player_id();
data.startZone = QString::fromStdString(arrow.start_zone());

View file

@ -8,16 +8,18 @@
struct ArrowData
{
int id;
int startPlayerId;
QString startZone;
int startCardId;
int targetPlayerId;
QString targetZone; // empty = targeting a player
int targetCardId = -1; // -1 = targeting a player
QColor color;
int creatorId = -1;
bool isLocalCreator = false;
int id = -1;
int startPlayerId = -1;
QString startZone = "";
int startCardId = -1;
int targetPlayerId = -1;
QString targetZone = "";
int targetCardId = -1;
QColor color = "";
static ArrowData fromProto(const ServerInfo_Arrow &arrow);
static ArrowData fromProto(const ServerInfo_Arrow &arrow, int creatorId, bool isLocalCreator);
bool isPlayerTargeted() const
{

View file

@ -1,6 +1,6 @@
#include "card_list.h"
#include "card_item.h"
#include "../../game_graphics/board/card_item.h"
#include <QDebug>
#include <algorithm>

View file

@ -18,7 +18,7 @@ void CardState::setZone(CardZoneLogic *_zone)
}
zone = _zone;
emit zoneChanged(zone);
emit zoneChanged(this, zone);
emit stateChanged();
}

View file

@ -31,7 +31,7 @@ signals:
void doesntUntapChanged(bool newValue);
void destroyOnZoneChangeChanged(bool newValue);
void attachedToChanged(CardItem *newAttachedTo);
void zoneChanged(CardZoneLogic *newZone);
void zoneChanged(CardState *changedCard, CardZoneLogic *newZone);
public:
explicit CardState(QObject *parent, CardZoneLogic *_zone) : QObject(parent), zone(_zone)

View file

@ -4,16 +4,16 @@
#include <libcockatrice/protocol/pb/event_game_joined.pb.h>
Game::Game(TabGame *_tab,
Game::Game(QObject *_parent,
bool isLocalGame,
QList<AbstractClient *> &_clients,
const Event_GameJoined &event,
const QMap<int, QString> &_roomGameTypes)
: AbstractGame(_tab)
: AbstractGame(_parent)
{
gameMetaInfo->setFromProto(event.game_info());
gameMetaInfo->setRoomGameTypes(_roomGameTypes);
gameState = new GameState(this, 0, event.host_id(), tab->getTabSupervisor()->getIsLocalGame(), _clients, false,
event.resuming(), -1, false);
gameState = new GameState(this, 0, event.host_id(), isLocalGame, _clients, false, event.resuming(), -1, false);
connect(gameMetaInfo, &GameMetaInfo::startedChanged, gameState, &GameState::onStartedChanged);
playerManager = new PlayerManager(this, event.player_id(), event.judge(), event.spectator());
gameMetaInfo->setStarted(false);

View file

@ -16,7 +16,8 @@ class Game : public AbstractGame
Q_OBJECT
public:
Game(TabGame *tab,
Game(QObject *parent,
bool isLocalGame,
QList<AbstractClient *> &_clients,
const Event_GameJoined &event,
const QMap<int, QString> &_roomGameTypes);

View file

@ -1,8 +1,8 @@
#include "game_event_handler.h"
#include "../game_graphics/log/message_log_widget.h"
#include "../interface/widgets/tabs/tab_game.h"
#include "abstract_game.h"
#include "log/message_log_widget.h"
#include <libcockatrice/network/client/abstract/abstract_client.h>
#include <libcockatrice/protocol/get_pb_extension.h>
@ -213,11 +213,25 @@ void GameEventHandler::handleChatMessageSent(const QString &chatMessage)
sendGameCommand(cmd);
}
void GameEventHandler::handleArrowDeletion(int arrowId)
void GameEventHandler::handleArrowDeletion(int creatorId, int arrowId)
{
Command_DeleteArrow cmd;
cmd.set_arrow_id(arrowId);
sendGameCommand(cmd);
auto preparedCommand = prepareGameCommand(cmd);
connect(preparedCommand, &PendingCommand::finished, this, [creatorId, arrowId, this](const Response &response) {
handleArrowDeletionFinished(response, creatorId, arrowId);
});
sendGameCommand(preparedCommand);
}
void GameEventHandler::handleArrowDeletionFinished(const Response &response, int creatorId, int arrowId)
{
if (response.response_code() == Response::RespNameNotFound) {
emit arrowDeleted(creatorId, arrowId);
}
}
void GameEventHandler::eventSpectatorSay(const Event_GameSay &event,

View file

@ -1,8 +1,16 @@
/**
* @file game_event_handler.h
* @ingroup GameLogic
* @brief Game-level command sender and event dispatcher.
*
* GameEventHandler sends commands initiated by the local client to the server
* and processes incoming game-wide events. It bridges the networking layer
* (protobuf events received via AbstractClient) with the game model and UI
* (GameState, PlayerManager, logging, widgets).
*
* Player-scoped events are forwarded to PlayerEventHandler instances, while
* spectator and global game events are handled directly here.
*/
//! \todo Document this file.
#ifndef COCKATRICE_GAME_EVENT_HANDLER_H
#define COCKATRICE_GAME_EVENT_HANDLER_H
@ -15,91 +23,310 @@
#include <libcockatrice/protocol/pb/serverinfo_player.pb.h>
class AbstractClient;
class Response;
class AbstractGame;
class CommandContainer;
class GameCommand;
class GameEventContainer;
class GameEventContext;
class GameCommand;
class GameState;
class MessageLogWidget;
class CommandContainer;
class Event_GameJoined;
class PendingCommand;
class PlayerLogic;
class Response;
class Event_GameStateChanged;
class Event_PlayerPropertiesChanged;
class Event_Join;
class Event_Leave;
class Event_GameHostChanged;
class Event_GameClosed;
class Event_GameStart;
class Event_SetActivePlayer;
class Event_SetActivePhase;
class Event_Ping;
class Event_GameSay;
class Event_Kicked;
class Event_ReverseTurn;
class AbstractGame;
class PendingCommand;
class PlayerLogic;
class Event_Ping;
inline Q_LOGGING_CATEGORY(GameEventHandlerLog, "game_event_handler");
/**
* @class GameEventHandler
* @brief Central dispatcher for game-wide commands and events.
*
* This class owns no game state itself. Instead, it:
* - Sends commands to the server on behalf of local players
* - Receives and dispatches server-side game events
* - Updates the game model indirectly via Player, GameState, and PlayerManager
* - Emits high-level signals for UI updates and logging
*/
class GameEventHandler : public QObject
{
Q_OBJECT
private:
/** Pointer to the owning game instance. */
AbstractGame *game;
public:
/** @name Construction
* Lifecycle and ownership.
* @{
*/
/**
* @brief Construct a GameEventHandler.
*
* The handler is owned by the AbstractGame instance and uses it to
* access the game state, players, and network clients.
*
* @param _game Owning game instance (also used as QObject parent).
*/
explicit GameEventHandler(AbstractGame *_game);
/** @} */
/** @name Outgoing game commands
* Commands initiated locally and sent to the server.
*
* These methods construct and send protobuf commands corresponding
* to user actions in the UI.
* @{
*/
/** @brief Request advancing the game to the next turn. */
void handleNextTurn();
/** @brief Request reversing the current turn order. */
void handleReverseTurn();
/** @brief Concede the game for the currently active local player. */
void handleActiveLocalPlayerConceded();
void handleActiveLocalPlayerUnconceded();
void handleActivePhaseChanged(int phase);
void handleGameLeft();
void handleChatMessageSent(const QString &chatMessage);
void handleArrowDeletion(int arrowId);
/** @brief Undo a previous concede for the active local player. */
void handleActiveLocalPlayerUnconceded();
/**
* @brief Set the active phase of the game.
*
* Typically triggered by the active player selecting a new phase.
*
* @param phase Phase identifier.
*/
void handleActivePhaseChanged(int phase);
/** @brief Leave the current game session. */
void handleGameLeft();
/**
* @brief Send a chat message to all players and spectators.
*
* @param chatMessage Message text.
*/
void handleChatMessageSent(const QString &chatMessage);
/**
* @brief Delete an existing arrow.
*
* @param arrowId Unique identifier of the arrow to delete.
*/
void handleArrowDeletion(int creatorId, int arrowId);
void handleArrowDeletionFinished(const Response &response, int creatorId, int arrowId);
/** @} */
/** @name Incoming event processing
* Entry points for server-sent events.
* @{
*/
/**
* @brief Process a container of game events received from the server.
*
* This is the main dispatch function for incoming game events.
* Events are routed to spectator handlers, game-level handlers,
* or forwarded to PlayerEventHandler instances as appropriate.
*
* @param cont Game event container from the server.
* @param client Client that received the container.
* @param options Processing flags (e.g. silent, replay).
*/
void
processGameEventContainer(const GameEventContainer &cont, AbstractClient *client, EventProcessingOptions options);
/** @} */
/** @name Command preparation helpers
* Internal helpers for building command containers.
* @{
*/
/**
* @brief Wrap a single protobuf command in a PendingCommand.
*
* @param cmd Protobuf command message.
* @return Newly allocated PendingCommand (caller takes ownership).
*/
PendingCommand *prepareGameCommand(const ::google::protobuf::Message &cmd);
/**
* @brief Wrap multiple protobuf commands in a single PendingCommand.
*
* Ownership of the messages in cmdList is transferred to the handler.
*
* @param cmdList List of protobuf command messages.
* @return Newly allocated PendingCommand.
*/
PendingCommand *prepareGameCommand(const QList<const ::google::protobuf::Message *> &cmdList);
/** @} */
/** @name Spectator event handlers
* Events originating from spectators.
* @{
*/
/**
* @brief Handle a spectator chat message.
*/
void eventSpectatorSay(const Event_GameSay &event, int eventPlayerId, const GameEventContext &context);
/**
* @brief Handle a spectator leaving the game.
*/
void eventSpectatorLeave(const Event_Leave &event, int eventPlayerId, const GameEventContext &context);
/** @} */
/** @name Game state event handlers
* Events that affect global game state.
* @{
*/
/**
* @brief Handle a full game state update from the server.
*
* Used during game startup, reconnection, and resynchronization.
*/
void eventGameStateChanged(const Event_GameStateChanged &event, int eventPlayerId, const GameEventContext &context);
/**
* @brief Update card attachment relationships for all players.
*
* Called after a game state update to ensure attachments are resolved
* consistently across all zones.
*/
void processCardAttachmentsForPlayers(const Event_GameStateChanged &event);
/** @brief Handle a change in game host. */
void eventGameHostChanged(const Event_GameHostChanged &event, int eventPlayerId, const GameEventContext &context);
/** @brief Handle the game being closed by the server. */
void eventGameClosed(const Event_GameClosed &event, int eventPlayerId, const GameEventContext &context);
/** @brief Handle a change of the active player. */
void eventSetActivePlayer(const Event_SetActivePlayer &event, int eventPlayerId, const GameEventContext &context);
/** @brief Handle a change of the active phase. */
void eventSetActivePhase(const Event_SetActivePhase &event, int eventPlayerId, const GameEventContext &context);
/** @brief Handle a turn reversal event. */
void eventReverseTurn(const Event_ReverseTurn &event, int eventPlayerId, const GameEventContext &context);
/** @brief Handle ping / latency updates. */
void eventPing(const Event_Ping &event, int eventPlayerId, const GameEventContext &context);
/** @} */
/** @name Player lifecycle and property handlers
* Events related to players joining, leaving, or changing state.
* @{
*/
/**
* @brief Handle updates to a player's properties.
*
* Includes readiness, concede state, deck selection, sideboard lock,
* and connection state changes.
*/
void eventPlayerPropertiesChanged(const Event_PlayerPropertiesChanged &event,
int eventPlayerId,
const GameEventContext &context);
/** @brief Handle a player or spectator joining the game. */
void eventJoin(const Event_Join &event, int eventPlayerId, const GameEventContext &context);
/** @brief Handle a player leaving the game. */
void eventLeave(const Event_Leave &event, int eventPlayerId, const GameEventContext &context);
QString getLeaveReason(Event_Leave::LeaveReason reason);
/** @brief Handle the local player being kicked from the game. */
void eventKicked(const Event_Kicked &event, int eventPlayerId, const GameEventContext &context);
void eventGameHostChanged(const Event_GameHostChanged &event, int eventPlayerId, const GameEventContext &context);
void eventGameClosed(const Event_GameClosed &event, int eventPlayerId, const GameEventContext &context);
void eventSetActivePlayer(const Event_SetActivePlayer &event, int eventPlayerId, const GameEventContext &context);
void eventSetActivePhase(const Event_SetActivePhase &event, int eventPlayerId, const GameEventContext &context);
void eventPing(const Event_Ping &event, int eventPlayerId, const GameEventContext &context);
void eventReverseTurn(const Event_ReverseTurn &event, int eventPlayerId, const GameEventContext & /*context*/);
/**
* @brief Convert a leave reason enum to a human-readable string.
*/
QString getLeaveReason(Event_Leave::LeaveReason reason);
void commandFinished(const Response &response);
/** @} */
void
processGameEventContainer(const GameEventContainer &cont, AbstractClient *client, EventProcessingOptions options);
PendingCommand *prepareGameCommand(const ::google::protobuf::Message &cmd);
PendingCommand *prepareGameCommand(const QList<const ::google::protobuf::Message *> &cmdList);
public slots:
/** @name Command dispatch slots
* Low-level command transmission.
* @{
*/
/**
* @brief Send a prepared PendingCommand.
*
* @param pend Pending command to send.
* @param playerId Player whose client should send the command.
*/
void sendGameCommand(PendingCommand *pend, int playerId = -1);
/**
* @brief Send a single protobuf command.
*
* @param command Protobuf command message.
* @param playerId Player whose client should send the command.
*/
void sendGameCommand(const ::google::protobuf::Message &command, int playerId = -1);
/**
* @brief Called when a PendingCommand finishes execution.
*
* Used to detect server-side errors such as chat flood protection.
*/
void commandFinished(const Response &response);
/** @} */
signals:
/** @name Core state signals
* @{
*/
void emitUserEvent();
void containerProcessingStarted(GameEventContext context);
void containerProcessingDone();
void gameFlooded();
void setContextJudgeName(QString judgeName);
/** @} */
/** @name Player and spectator signals
* @{
*/
void addPlayerToAutoCompleteList(QString playerName);
void localPlayerDeckSelected(PlayerLogic *localPlayer, int playerId, ServerInfo_Player playerInfo);
void remotePlayerDeckSelected(QString deckList, int playerId, QString playerName);
void remotePlayersDecksSelected(QVector<QPair<int, QPair<QString, QString>>> opponentDecks);
void localPlayerSideboardLocked(int playerId, bool sideboardLocked);
void localPlayerReadyStateChanged(int playerId, bool ready);
/** @} */
/** @name Game flow signals
* @{
*/
void gameStopped();
void gameClosed();
void playerPropertiesChanged(const ServerInfo_PlayerProperties &prop, int playerId);
@ -108,10 +335,15 @@ signals:
void playerKicked();
void spectatorJoined(const ServerInfo_PlayerProperties &spectatorInfo);
void spectatorLeft(int leavingSpectatorId);
void gameFlooded();
void containerProcessingStarted(GameEventContext context);
void setContextJudgeName(QString judgeName);
void containerProcessingDone();
void arrowDeleted(int creatorId, int arrowId);
/** @} */
/** @name Logging signals
* Signals consumed by MessageLogWidget.
* @{
*/
void logSpectatorSay(ServerInfo_User userInfo, QString message);
void logSpectatorLeave(QString name, QString reason);
void logGameStart();
@ -130,6 +362,8 @@ signals:
void logActivePhaseChanged(int activePhase);
void logConcede(int playerId);
void logUnconcede(int playerId);
/** @} */
};
#endif // COCKATRICE_GAME_EVENT_HANDLER_H

View file

@ -1,188 +0,0 @@
#include "game_view.h"
#include "../client/settings/cache_settings.h"
#include "game_scene.h"
#include <QAction>
#include <QLabel>
#include <QResizeEvent>
#include <QRubberBand>
// QRubberBand calls raise() in showEvent() and changeEvent() to stay on top of siblings.
// This subclass disables that behavior so dragCountLabel can appear above it.
class SelectionRubberBand : public QRubberBand
{
public:
using QRubberBand::QRubberBand;
protected:
void showEvent(QShowEvent *event) override
{
QWidget::showEvent(event); // Skip QRubberBand's raise()
}
void changeEvent(QEvent *event) override
{
if (event->type() == QEvent::ZOrderChange) {
return; // Skip QRubberBand's raise() on z-order changes
}
QRubberBand::changeEvent(event);
}
};
GameView::GameView(GameScene *scene, QWidget *parent) : QGraphicsView(scene, parent), rubberBand(0)
{
setBackgroundBrush(QBrush(QColor(0, 0, 0)));
setRenderHints(QPainter::TextAntialiasing | QPainter::Antialiasing);
setFocusPolicy(Qt::ClickFocus);
setViewportUpdateMode(BoundingRectViewportUpdate);
connect(scene, &GameScene::sceneRectChanged, this, &GameView::updateSceneRect);
connect(scene, &GameScene::sigStartRubberBand, this, &GameView::startRubberBand);
connect(scene, &GameScene::sigResizeRubberBand, this, &GameView::resizeRubberBand);
connect(scene, &GameScene::sigStopRubberBand, this, &GameView::stopRubberBand);
connect(scene, &QGraphicsScene::selectionChanged, this, [this]() { updateTotalSelectionCount(); });
aCloseMostRecentZoneView = new QAction(this);
connect(aCloseMostRecentZoneView, &QAction::triggered, scene, &GameScene::closeMostRecentZoneView);
addAction(aCloseMostRecentZoneView);
connect(&SettingsCache::instance().shortcuts(), &ShortcutsSettings::shortCutChanged, this,
&GameView::refreshShortcuts);
refreshShortcuts();
rubberBand = new SelectionRubberBand(QRubberBand::Rectangle, this);
const QString countLabelStyle = "color: white; "
"font-size: 14px; "
"font-weight: bold; "
"background-color: rgba(0, 0, 0, 160); "
"border-radius: 3px; "
"padding: 1px 2px;";
dragCountLabel = new QLabel(this);
dragCountLabel->setStyleSheet(countLabelStyle);
dragCountLabel->hide();
dragCountLabel->raise();
totalCountLabel = new QLabel(this);
totalCountLabel->setStyleSheet(countLabelStyle);
totalCountLabel->hide();
}
void GameView::resizeEvent(QResizeEvent *event)
{
QGraphicsView::resizeEvent(event);
GameScene *s = dynamic_cast<GameScene *>(scene());
if (s) {
s->processViewSizeChange(event->size());
}
updateSceneRect(scene()->sceneRect());
updateTotalSelectionCount(event->size());
}
void GameView::updateSceneRect(const QRectF &rect)
{
fitInView(rect, Qt::KeepAspectRatio);
}
void GameView::startRubberBand(const QPointF &_selectionOrigin)
{
if (!rubberBand) {
return;
}
selectionOrigin = _selectionOrigin;
rubberBand->setGeometry(QRect(mapFromScene(selectionOrigin), QSize(0, 0)));
rubberBand->show();
}
void GameView::resizeRubberBand(const QPointF &cursorPoint, int selectedCount)
{
if (!rubberBand) {
return;
}
constexpr int kLabelPaddingInPixels = 4;
QPoint cursor = cursorPoint.toPoint();
QRect rect = QRect(mapFromScene(selectionOrigin), cursor).normalized();
rubberBand->setGeometry(rect);
if (!SettingsCache::instance().getShowDragSelectionCount()) {
dragCountLabel->hide();
return;
}
if (selectedCount > 0) {
dragCountLabel->setText(QString::number(selectedCount));
dragCountLabel->adjustSize();
QSize labelSize = dragCountLabel->size();
if (rect.width() < labelSize.width() + 2 * kLabelPaddingInPixels ||
rect.height() < labelSize.height() + 2 * kLabelPaddingInPixels) {
dragCountLabel->hide();
return;
}
const int minX = rect.left() + kLabelPaddingInPixels;
const int minY = rect.top() + kLabelPaddingInPixels;
int x = qMax(minX, cursor.x() - labelSize.width() - kLabelPaddingInPixels);
int y = qMax(minY, cursor.y() - labelSize.height() - kLabelPaddingInPixels);
bool isAtTopLeftCorner = (x == minX) && (y == minY);
if (isAtTopLeftCorner) {
constexpr int kCursorClearanceInPixels = 16;
x = qMin(cursor.x() + kCursorClearanceInPixels, rect.right() - labelSize.width() - kLabelPaddingInPixels);
}
dragCountLabel->move(x, y);
dragCountLabel->show();
} else {
dragCountLabel->hide();
}
}
void GameView::stopRubberBand()
{
if (!rubberBand) {
return;
}
rubberBand->hide();
dragCountLabel->hide();
}
void GameView::refreshShortcuts()
{
aCloseMostRecentZoneView->setShortcuts(
SettingsCache::instance().shortcuts().getShortcut("Player/aCloseMostRecentZoneView"));
}
void GameView::updateTotalSelectionCount(const QSize &viewSize)
{
if (!SettingsCache::instance().getShowTotalSelectionCount()) {
totalCountLabel->hide();
return;
}
int count = scene()->selectedItems().count();
if (count > 1) {
totalCountLabel->setText(QString::number(count));
totalCountLabel->adjustSize();
constexpr int kMarginInPixels = 10;
int availableWidth = viewSize.isValid() ? viewSize.width() : viewport()->width();
int availableHeight = viewSize.isValid() ? viewSize.height() : viewport()->height();
int x = availableWidth - totalCountLabel->width() - kMarginInPixels;
int y = availableHeight - totalCountLabel->height() - kMarginInPixels;
totalCountLabel->move(x, y);
totalCountLabel->show();
} else {
totalCountLabel->hide();
}
}

File diff suppressed because it is too large Load diff

View file

@ -7,8 +7,11 @@
#ifndef COCKATRICE_PLAYER_ACTIONS_H
#define COCKATRICE_PLAYER_ACTIONS_H
#include "../dialogs/dlg_create_token.h"
#include "../dialogs/dlg_move_top_cards_until.h"
#include "../../game_graphics/board/card_item.h"
#include "../../game_graphics/dialogs/dlg_create_token.h"
#include "../../game_graphics/dialogs/dlg_move_top_cards_until.h"
#include "../../game_graphics/player/card_menu_action_type.h"
#include "event_processing_options.h"
#include "player_logic.h"
@ -25,7 +28,6 @@ class Message;
}
} // namespace google
class CardItem;
class Command_MoveCard;
class GameEventContext;
class PendingCommand;
@ -56,30 +58,75 @@ public:
return movingCardsUntil;
}
signals:
void requestViewTopCardsDialog(int defaultNumberTopCards, int deckSize);
void requestViewBottomCardsDialog(int defaultNumberBottomCards, int deckSize);
void requestShuffleTopDialog(int defaultNumberTopCards, int maxCards);
void requestShuffleBottomDialog(int defaultNumberBottomCards, int maxCards);
void requestMulliganDialog(int startSize, int handSize, int deckSize);
void requestDrawCardsDialog(int defaultNumberTopCards, int deckSize);
void requestMoveTopCardsToDialog(int defaultNumberTopCards,
int maxCards,
const QString &targetZone,
const QString &zoneDisplayName,
bool faceDown);
void requestMoveTopCardsUntilDialog(MoveTopCardsUntilOptions options);
void requestMoveBottomCardsToDialog(int defaultNumberBottomCards,
int maxCards,
const QString &targetZone,
const QString &zoneDisplayName,
bool faceDown);
void requestDrawBottomCardsDialog(int defaultNumberBottomCards, int maxCards);
void requestRollDieDialog();
void requestCreateTokenDialog(const QStringList &predefinedTokens);
void requestCreateRelatedFromRelationDialog(const CardItem *sourceCard, const CardRelation *cardRelation);
void requestMoveCardXCardsFromTopDialog(int defaultNumberTopCardsToPlaceBelow, int deckSize);
void requestSetPTDialog(const QString &oldPT);
void requestSetAnnotationDialog(const QString &oldAnnotation);
void requestSetCardCounterDialog(int counterId, const QString &oldValueForDlg);
void requestZoneViewToggle(const QString &zoneName, int numberCards, bool isReversed = false);
void requestSortHand(const QList<CardList::SortOption> &options);
void requestEnableAndSetCreateAnotherTokenAction(const QString &lastTokenName);
void requestSetLastToken(CardInfoPtr lastToken);
public slots:
void setLastToken(CardInfoPtr cardInfo);
void setLastTokenInfo(CardInfoPtr cardInfo);
void playCard(CardItem *c, bool faceDown);
void playCardToTable(const CardItem *c, bool faceDown);
void actUntapAll();
void actRollDie();
void actRequestRollDieDialog();
void actRollDie(int sides, int count);
void actFlipCoin();
void actCreateToken();
void actRequestCreateTokenDialog(const QStringList &predefinedTokens);
void actCreateToken(TokenInfo tokenToCreate);
void actCreateAnotherToken();
void actRequestCreateRelatedFromRelationDialog(const CardItem *sourceCard, const CardRelation *cardRelation);
bool createRelatedFromRelation(const CardItem *sourceCard, const CardRelation *cardRelation, int variableCount);
void onRelatedCardCreated(const CardItem *sourceCard, const CardRelation *cardRelation);
void setLastRelatedCreationSucceeded(bool succeeded)
{
lastRelatedCreationSucceeded = succeeded;
}
void actShuffle();
void actShuffleTop();
void actShuffleBottom();
void actRequestShuffleTopDialog();
void actShuffleTop(int number);
void actRequestShuffleBottomDialog();
void actShuffleBottom(int number);
void actDrawCard();
void actDrawCards();
void actRequestDrawCardsDialog();
void actDrawCards(int number);
void actUndoDraw();
void actMulligan();
void actRequestMulliganDialog();
void actMulligan(int number);
void actMulliganSameSize();
void actMulliganMinusOne();
void doMulligan(int number);
void actPlay();
void actPlayFacedown();
void actHide();
void actPlay(QList<CardItem *> selectedCards);
void actPlayFacedown(QList<CardItem *> selectedCards);
void actHide(QList<CardItem *> selectedCards);
void actMoveTopCardToPlay();
void actMoveTopCardToPlayFaceDown();
@ -89,10 +136,14 @@ public slots:
void actMoveTopCardsToGraveFaceDown();
void actMoveTopCardsToExile();
void actMoveTopCardsToExileFaceDown();
void actMoveTopCardsUntil();
void actRequestMoveTopCardsUntilDialog();
void moveTopCardsUntil(const QString &expr, MoveTopCardsUntilOptions options);
void actMoveTopCardToBottom();
void actRequestMoveTopCardsToDialog(const QString &targetZone, const QString &zoneDisplayName, bool faceDown);
void moveTopCardsTo(int number, const QString &targetZone, bool faceDown);
void actDrawBottomCard();
void actDrawBottomCards();
void actRequestDrawBottomCardsDialog();
void actDrawBottomCards(int number);
void actMoveBottomCardToPlay();
void actMoveBottomCardToPlayFaceDown();
void actMoveBottomCardToGrave();
@ -102,6 +153,8 @@ public slots:
void actMoveBottomCardsToExile();
void actMoveBottomCardsToExileFaceDown();
void actMoveBottomCardToTop();
void actRequestMoveBottomCardsToDialog(const QString &targetZone, const QString &zoneDisplayName, bool faceDown);
void moveBottomCardsTo(int number, const QString &targetZone, bool faceDown);
void actSelectAll();
void actSelectRow();
@ -109,10 +162,12 @@ public slots:
void actViewLibrary();
void actViewHand();
void actViewTopCards();
void actViewBottomCards();
void actAlwaysRevealTopCard();
void actAlwaysLookAtTopCard();
void actRequestViewTopCardsDialog();
void actViewTopCards(int number);
void actRequestViewBottomCardsDialog();
void actViewBottomCards(int number);
void actAlwaysRevealTopCard(bool alwaysRevealTopCard);
void actAlwaysLookAtTopCard(bool alwaysRevealTopCard);
void actViewGraveyard();
void actLendLibrary(int lendToPlayerId);
void actRevealTopCards(int revealToPlayerId, int amount);
@ -127,37 +182,41 @@ public slots:
void actCreateRelatedCard();
void actCreateAllRelatedCards();
void actMoveCardXCardsFromTop();
void actRemoveCardCounter(int counterId);
void actAddCardCounter(int counterId);
void actSetCardCounter(int counterId);
void actIncrementAllCardCounters();
void actRequestMoveCardXCardsFromTopDialog();
void actMoveCardXCardsFromTop(QList<CardItem *> selectedCards, int number);
void actRemoveCardCounter(QList<CardItem *> selectedCards, int counterId);
void actAddCardCounter(QList<CardItem *> selectedCards, int counterId);
void actRequestSetCardCounterDialog(QList<CardItem *> selectedCards, int counterId);
void actSetCardCounter(QList<CardItem *> selectedCards, int counterId, const QString &counterValue);
void actIncrementAllCardCounters(QList<CardItem *> cardsToUpdate);
void actAttach();
void actUnattach();
void actUnattach(QList<CardItem *> selectedCards);
void actDrawArrow();
void actIncPT(int deltaP, int deltaT);
void actResetPT();
void actSetPT();
void actIncP();
void actDecP();
void actIncT();
void actDecT();
void actIncPT();
void actDecPT();
void actFlowP();
void actFlowT();
void actIncPT(QList<CardItem *> selectedCards, int deltaP, int deltaT);
void actResetPT(QList<CardItem *> selectedCards);
void actRequestSetPTDialog(QList<CardItem *> selectedCards);
void actSetPT(QList<CardItem *> selectedCards, const QString &pt);
void actIncP(QList<CardItem *> selectedCards);
void actDecP(QList<CardItem *> selectedCards);
void actIncT(QList<CardItem *> selectedCards);
void actDecT(QList<CardItem *> selectedCards);
void actIncPT(QList<CardItem *> selectedCards);
void actDecPT(QList<CardItem *> selectedCards);
void actFlowP(QList<CardItem *> selectedCards);
void actFlowT(QList<CardItem *> selectedCards);
void actReduceLifeByPower();
void actReduceLifeByPower(QList<CardItem *> selectedCards);
void actSetAnnotation();
void actReveal(QAction *action);
void actRequestSetAnnotationDialog(QList<CardItem *> selectedCards);
void actSetAnnotation(QList<CardItem *> selectedCards, const QString &annotation);
void actReveal(QList<CardItem *> selectedCards, QAction *action);
void actRevealHand(int revealToPlayerId);
void actRevealRandomHandCard(int revealToPlayerId);
void actRevealLibrary(int revealToPlayerId);
void actSortHand();
void cardMenuAction();
void cardMenuAction(QList<CardItem *> selectedCards, CardMenuActionType type);
private:
PlayerLogic *player;
@ -176,21 +235,20 @@ private:
int movingCardsUntilCounter = 0;
MoveTopCardsUntilOptions movingCardsUntilOptions;
void moveTopCardsTo(const QString &targetZone, const QString &zoneDisplayName, bool faceDown);
void moveBottomCardsTo(const QString &targetZone, const QString &zoneDisplayName, bool faceDown);
bool lastRelatedCreationSucceeded = false;
void createCard(const CardItem *sourceCard,
const QString &dbCardName,
CardRelationType attach = CardRelationType::DoesNotAttach,
bool persistent = false);
bool createRelatedFromRelation(const CardItem *sourceCard, const CardRelation *cardRelation);
bool persistent = false,
bool faceDown = false);
void playSelectedCards(bool faceDown = false);
void playSelectedCards(QList<CardItem *> selectedCards, bool faceDown = false);
void cmdSetTopCard(Command_MoveCard &cmd);
void cmdSetBottomCard(Command_MoveCard &cmd);
void offsetCardCounter(int counterId, int offset);
void offsetCardCounter(QList<CardItem *> selectedCards, int counterId, int offset);
};
#endif // COCKATRICE_PLAYER_ACTIONS_H

View file

@ -1,12 +1,11 @@
#include "player_event_handler.h"
#include "../../game_graphics/board/arrow_item.h"
#include "../../game_graphics/board/card_item.h"
#include "../../game_graphics/zones/view_zone.h"
#include "../../interface/widgets/tabs/tab_game.h"
#include "../board/arrow_data.h"
#include "../board/arrow_item.h"
#include "../board/card_item.h"
#include "../board/card_list.h"
#include "libcockatrice/utility/color.h"
#include "player_actions.h"
#include "player_logic.h"
@ -33,10 +32,12 @@
#include <libcockatrice/protocol/pb/event_set_card_counter.pb.h>
#include <libcockatrice/protocol/pb/event_set_counter.pb.h>
#include <libcockatrice/protocol/pb/event_shuffle.pb.h>
#include <libcockatrice/utility/color.h>
#include <libcockatrice/utility/zone_names.h>
PlayerEventHandler::PlayerEventHandler(PlayerLogic *_player) : QObject(_player), player(_player)
{
connect(this, &PlayerEventHandler::requestCardMenuUpdate, player, &PlayerLogic::requestCardMenuUpdate);
}
void PlayerEventHandler::eventGameSay(const Event_GameSay &event)
@ -92,26 +93,24 @@ void PlayerEventHandler::eventRollDie(const Event_RollDie &event)
void PlayerEventHandler::eventCreateArrow(const Event_CreateArrow &event)
{
const ArrowData data = ArrowData::fromProto(event.arrow_info());
auto data = QSharedPointer<ArrowData>::create(ArrowData::fromProto(
event.arrow_info(), player->getPlayerInfo()->getId(), player->getPlayerInfo()->getLocal()));
// Resolve names for logging
const auto &playerList = player->getGame()->getPlayerManager()->getPlayers();
PlayerLogic *startPlayer = playerList.value(data.startPlayerId);
PlayerLogic *targetPlayer = playerList.value(data.targetPlayerId);
PlayerLogic *startPlayer = playerList.value(data->startPlayerId);
PlayerLogic *targetPlayer = playerList.value(data->targetPlayerId);
QString startCardName, targetCardName;
if (startPlayer) {
auto *zone = startPlayer->getZones().value(data.startZone);
if (zone) {
if (auto *card = zone->getCard(data.startCardId)) {
if (auto *zone = startPlayer->getZones().value(data->startZone)) {
if (auto *card = zone->getCard(data->startCardId)) {
startCardName = card->getName();
}
}
}
if (!data.isPlayerTargeted() && targetPlayer) {
auto *zone = targetPlayer->getZones().value(data.targetZone);
if (zone) {
if (auto *card = zone->getCard(data.targetCardId)) {
if (!data->isPlayerTargeted() && targetPlayer) {
if (auto *zone = targetPlayer->getZones().value(data->targetZone)) {
if (auto *card = zone->getCard(data->targetCardId)) {
targetCardName = card->getName();
}
}
@ -119,16 +118,15 @@ void PlayerEventHandler::eventCreateArrow(const Event_CreateArrow &event)
emit player->arrowCreateRequested(data);
const bool validForLogging = !startCardName.isEmpty() && (data.isPlayerTargeted() || !targetCardName.isEmpty());
if (startPlayer && targetPlayer && validForLogging) {
emit logCreateArrow(player, startPlayer, startCardName, targetPlayer, targetCardName, data.isPlayerTargeted());
if (startPlayer && targetPlayer && !startCardName.isEmpty() &&
(data->isPlayerTargeted() || !targetCardName.isEmpty())) {
emit logCreateArrow(player, startPlayer, startCardName, targetPlayer, targetCardName, data->isPlayerTargeted());
}
}
void PlayerEventHandler::eventDeleteArrow(const Event_DeleteArrow &event)
{
emit player->arrowDeleted(event.arrow_id());
emit player->arrowDeleted(player->getPlayerInfo()->getId(), event.arrow_id());
}
void PlayerEventHandler::eventCreateToken(const Event_CreateToken &event)
@ -255,7 +253,7 @@ void PlayerEventHandler::eventSetCardCounter(const Event_SetCardCounter &event)
int oldValue = card->getCounters().value(event.counter_id(), 0);
card->setCounter(event.counter_id(), event.counter_value());
player->getPlayerMenu()->updateCardMenu(card);
emit requestCardMenuUpdate(card);
emit logSetCardCounter(player, card->getName(), event.counter_id(), event.counter_value(), oldValue);
}
@ -373,7 +371,7 @@ void PlayerEventHandler::eventMoveCard(const Event_MoveCard &event, const GameEv
targetZone->addCard(card, true, x, y);
emit cardZoneChanged(card, startZone == targetZone);
player->getPlayerMenu()->updateCardMenu(card);
emit requestCardMenuUpdate(card);
if (player->getPlayerActions()->isMovingCardsUntil() && startZoneString == ZoneNames::DECK &&
targetZone->getName() == ZoneNames::STACK) {
@ -400,7 +398,7 @@ void PlayerEventHandler::eventFlipCard(const Event_FlipCard &event)
emit logFlipCard(player, card->getName(), event.face_down());
card->setFaceDown(event.face_down());
player->getPlayerMenu()->updateCardMenu(card);
emit requestCardMenuUpdate(card);
}
void PlayerEventHandler::eventDestroyCard(const Event_DestroyCard &event)
@ -469,7 +467,7 @@ void PlayerEventHandler::eventAttachCard(const Event_AttachCard &event)
} else {
emit logUnattachCard(player, startCard->getName());
}
player->getPlayerMenu()->updateCardMenu(startCard);
emit requestCardMenuUpdate(startCard);
}
void PlayerEventHandler::eventDrawCards(const Event_DrawCards &event)
@ -555,7 +553,7 @@ void PlayerEventHandler::eventRevealCards(const Event_RevealCards &event, EventP
}
if (!options.testFlag(SKIP_REVEAL_WINDOW) && showZoneView && !cardList.isEmpty()) {
player->getGameScene()->addRevealedZoneView(player, zone, cardList, event.grant_write_access());
emit player->requestRevealedZoneView(player, zone, cardList, event.grant_write_access());
}
emit logRevealCards(player, zone, cardId, cardName, otherPlayer, false,

View file

@ -1,11 +1,24 @@
/**
* @file player_event_handler.h
* @ingroup GameLogicPlayers
* @brief Player-scoped game event handler.
*
* PlayerEventHandler applies game events that affect a single Players
* board state, zones, cards, counters, arrows, and related UI/log output.
*
* It is invoked by GameEventHandler after basic routing and validation.
* Each instance is bound 1:1 to a Player and must never mutate state
* belonging to other players except where explicitly required by events
* (e.g. moving cards between players, attaching cards, arrows).
*
* This class is intentionally stateful and tightly coupled to Player,
* PlayerActions, and the board/zones implementation. It performs both
* model mutation and UI-side bookkeeping (zone views, arrows, menus).
*/
//! \todo Document this file.
#ifndef COCKATRICE_PLAYER_EVENT_HANDLER_H
#define COCKATRICE_PLAYER_EVENT_HANDLER_H
#include "event_processing_options.h"
#include <QObject>
@ -16,6 +29,7 @@
class CardItem;
class CardZoneLogic;
class PlayerLogic;
class Event_AttachCard;
class Event_ChangeZoneProperties;
class Event_CreateArrow;
@ -37,11 +51,176 @@ class Event_SetCounter;
class Event_Shuffle;
class Event_GameLogNotice;
/**
* @class PlayerEventHandler
* @brief Applies player-specific game events and emits corresponding log signals.
*
* Design notes:
* - All event handlers assume events are authoritative and already validated
* by the server.
* - Most handlers mutate both logical state (CardItem, CardZoneLogic, counters)
* and visual/UI state (views, arrows, menus).
* - Logging signals are emitted *after* or *during* state mutation, depending
* on whether later mutations would invalidate log data.
*/
class PlayerEventHandler : public QObject
{
Q_OBJECT
public:
/**
* @brief Construct a PlayerEventHandler bound to a Player.
* @param player Owning player instance.
*/
explicit PlayerEventHandler(PlayerLogic *player);
/** @name Event dispatch
* @{
*/
/**
* @brief Dispatch a generic GameEvent to the appropriate handler.
*
* This is the single entry point used by GameEventHandler. It extracts
* the correct protobuf extension and forwards the event to a typed
* handler method.
*
* @param type Game event type enum.
* @param event Generic protobuf container.
* @param context Additional context (undo, judge, etc.).
* @param options Processing options (UI suppression, reveal behavior).
*/
void processGameEvent(GameEvent::GameEventType type,
const GameEvent &event,
const GameEventContext &context,
EventProcessingOptions options);
/** @} */
/** @name Chat and randomization events
* @{
*/
/// Handle in-game chat messages from this player.
void eventGameSay(const Event_GameSay &event);
/// Handle zone shuffle events (typically libraries).
void eventShuffle(const Event_Shuffle &event);
/// Handle die roll events.
void eventRollDie(const Event_RollDie &event);
/** @} */
/** @name Arrow and targeting events
* @{
*/
/// Create a visual arrow between cards or players.
void eventCreateArrow(const Event_CreateArrow &event);
/// Delete an existing arrow.
void eventDeleteArrow(const Event_DeleteArrow &event);
/** @} */
/** @name Token and card creation
* @{
*/
/// Create a token card in a target zone.
void eventCreateToken(const Event_CreateToken &event);
/** @} */
/** @name Card attribute and counter updates
* @{
*/
/**
* @brief Set a card attribute (tapped, PT, annotation, etc.).
*
* May apply to a single card or all cards in a zone if no card ID
* is provided by the event.
*/
void
eventSetCardAttr(const Event_SetCardAttr &event, const GameEventContext &context, EventProcessingOptions options);
/// Update a counter attached to a card.
void eventSetCardCounter(const Event_SetCardCounter &event);
/// Create a player-level counter.
void eventCreateCounter(const Event_CreateCounter &event);
/// Set a player-level counter value.
void eventSetCounter(const Event_SetCounter &event);
/// Delete a player-level counter.
void eventDelCounter(const Event_DelCounter &event);
/** @} */
/** @name Zone-level operations
* @{
*/
/// Log a zone dump (e.g. reveal graveyard/library contents).
void eventDumpZone(const Event_DumpZone &event);
/**
* @brief Move a card between zones and/or players.
*
* This is one of the most complex handlers:
* - Removes the card from the start zone
* - Updates card identity and ownership if needed
* - Handles attachments and arrows
* - Emits appropriate move or undo-draw logs
* - Inserts the card into the target zone
*/
void eventMoveCard(const Event_MoveCard &event, const GameEventContext &context);
/// Flip a card face up or face down.
void eventFlipCard(const Event_FlipCard &event);
/// Destroy a card and clean up attachments.
void eventDestroyCard(const Event_DestroyCard &event);
/// Attach or detach a card to/from another card.
void eventAttachCard(const Event_AttachCard &event);
/** @} */
/** @name Draw and reveal operations
* @{
*/
/// Draw one or more cards from the deck.
void eventDrawCards(const Event_DrawCards &event);
/**
* @brief Reveal cards from a zone.
*
* Handles peeking, in-place top-card reveals, full reveal windows,
* and write-access granting.
*/
void eventRevealCards(const Event_RevealCards &event, EventProcessingOptions options);
/** @} */
/** @name Zone configuration
* @{
*/
/// Update zone visibility and reveal behavior.
void eventChangeZoneProperties(const Event_ChangeZoneProperties &event);
/** @} */
void eventGameLogNotice(const Event_GameLogNotice &event);
signals:
/** @name Logging signals
* @{
*/
void logSay(PlayerLogic *player, QString message);
void logShuffle(PlayerLogic *player, CardZoneLogic *zone, int start, int end);
void logRollDie(PlayerLogic *player, int sides, const QList<uint> &rolls);
@ -82,39 +261,13 @@ signals:
bool isLentToAnotherPlayer = false);
void logAlwaysRevealTopCard(PlayerLogic *player, CardZoneLogic *zone, bool reveal);
void logAlwaysLookAtTopCard(PlayerLogic *player, CardZoneLogic *zone, bool reveal);
/** @} */
void cardZoneChanged(CardItem *card, bool sameZone);
public:
PlayerEventHandler(PlayerLogic *player);
void processGameEvent(GameEvent::GameEventType type,
const GameEvent &event,
const GameEventContext &context,
EventProcessingOptions options);
void eventGameSay(const Event_GameSay &event);
void eventShuffle(const Event_Shuffle &event);
void eventRollDie(const Event_RollDie &event);
void eventCreateArrow(const Event_CreateArrow &event);
void eventDeleteArrow(const Event_DeleteArrow &event);
void eventCreateToken(const Event_CreateToken &event);
void
eventSetCardAttr(const Event_SetCardAttr &event, const GameEventContext &context, EventProcessingOptions options);
void eventSetCardCounter(const Event_SetCardCounter &event);
void eventCreateCounter(const Event_CreateCounter &event);
void eventSetCounter(const Event_SetCounter &event);
void eventDelCounter(const Event_DelCounter &event);
void eventDumpZone(const Event_DumpZone &event);
void eventMoveCard(const Event_MoveCard &event, const GameEventContext &context);
void eventFlipCard(const Event_FlipCard &event);
void eventDestroyCard(const Event_DestroyCard &event);
void eventAttachCard(const Event_AttachCard &event);
void eventDrawCards(const Event_DrawCards &event);
void eventRevealCards(const Event_RevealCards &event, EventProcessingOptions options);
void eventChangeZoneProperties(const Event_ChangeZoneProperties &event);
void eventGameLogNotice(const Event_GameLogNotice &event);
void requestCardMenuUpdate(const CardItem *card);
private:
/** Owning player instance. */
PlayerLogic *player;
void setCardAttrHelper(const GameEventContext &context,

View file

@ -7,7 +7,7 @@
#ifndef COCKATRICE_PLAYER_INFO_H
#define COCKATRICE_PLAYER_INFO_H
#include "player_target.h"
#include "../../game_graphics/player/player_target.h"
#include <QObject>
#include <libcockatrice/protocol/pb/serverinfo_user.pb.h>

View file

@ -1,18 +1,18 @@
#include "player_logic.h"
#include "../../game_graphics/board/arrow_item.h"
#include "../../game_graphics/board/card_item.h"
#include "../../game_graphics/board/counter_general.h"
#include "../../game_graphics/game_scene.h"
#include "../../game_graphics/player/player_target.h"
#include "../../game_graphics/zones/hand_zone.h"
#include "../../game_graphics/zones/pile_zone.h"
#include "../../game_graphics/zones/stack_zone.h"
#include "../../game_graphics/zones/table_zone.h"
#include "../../interface/theme_manager.h"
#include "../../interface/widgets/tabs/tab_game.h"
#include "../board/arrow_item.h"
#include "../board/card_item.h"
#include "../board/card_list.h"
#include "../board/counter_general.h"
#include "../game_scene.h"
#include "player_actions.h"
#include "player_target.h"
#include <QDebug>
#include <QMenu>
@ -35,14 +35,6 @@ PlayerLogic::PlayerLogic(const ServerInfo_User &info, int _id, bool _local, bool
conceded(false), zoneId(0), dialogSemaphore(false)
{
initializeZones();
playerMenu = new PlayerMenu(this);
graphicsItem = new PlayerGraphicsItem(this);
playerMenu->setMenusForGraphicItems();
connect(this, &PlayerLogic::activeChanged, graphicsItem, &PlayerGraphicsItem::onPlayerActiveChanged);
connect(this, &PlayerLogic::openDeckEditor, game->getTab(), &TabGame::openDeckEditor);
}
void PlayerLogic::initializeZones()
@ -68,13 +60,12 @@ PlayerLogic::~PlayerLogic()
}
zones.clear();
delete playerMenu;
delete getPlayerInfo()->userInfo;
}
void PlayerLogic::clear()
{
emit arrowsCleared();
emit arrowsClearedLocally();
QMapIterator<QString, CardZoneLogic *> i(zones);
while (i.hasNext()) {
@ -115,7 +106,7 @@ void PlayerLogic::processPlayerInfo(const ServerInfo_Player &info)
/* HandZone */
ZoneNames::HAND};
clearCounters();
emit arrowsCleared();
emit arrowsClearedLocally();
QMutableMapIterator<QString, CardZoneLogic *> zoneIt(zones);
while (zoneIt.hasNext()) {
@ -231,7 +222,8 @@ void PlayerLogic::processCardAttachment(const ServerInfo_Player &info)
const int arrowListSize = info.arrow_list_size();
for (int i = 0; i < arrowListSize; ++i) {
emit arrowCreateRequested(ArrowData::fromProto(info.arrow_list(i)));
emit arrowCreateRequested(QSharedPointer<ArrowData>::create(
ArrowData::fromProto(info.arrow_list(i), getPlayerInfo()->getId(), getPlayerInfo()->getLocal())));
}
}
@ -325,22 +317,16 @@ void PlayerLogic::setActive(bool _active)
active = _active;
emit activeChanged(active);
}
void PlayerLogic::onRequestZoneViewToggle(const QString &zoneName, int numberCards, bool isReversed)
{
emit requestZoneViewToggle(this, zoneName, numberCards, isReversed);
}
void PlayerLogic::updateZones()
{
getTableZone()->reorganizeCards();
}
PlayerGraphicsItem *PlayerLogic::getGraphicsItem()
{
return graphicsItem;
}
GameScene *PlayerLogic::getGameScene()
{
return getGraphicsItem()->getGameScene();
}
void PlayerLogic::setGameStarted()
{
if (playerInfo->local) {

View file

@ -7,6 +7,7 @@
#ifndef PLAYER_H
#define PLAYER_H
#include "../../game_graphics/player/player_area.h"
#include "../../interface/widgets/menus/tearoff_menu.h"
#include "../board/arrow_data.h"
#include "../interface/deck_loader/loaded_deck.h"
@ -14,10 +15,7 @@
#include "../zones/pile_zone_logic.h"
#include "../zones/stack_zone_logic.h"
#include "../zones/table_zone_logic.h"
#include "menu/player_menu.h"
#include "player_area.h"
#include "player_event_handler.h"
#include "player_graphics_item.h"
#include "player_info.h"
#include <QInputDialog>
@ -54,6 +52,7 @@ class PlayerMenu;
class QAction;
class QMenu;
class ServerInfo_Arrow;
class ServerInfo_Card;
class ServerInfo_Counter;
class ServerInfo_Player;
class ServerInfo_User;
@ -67,8 +66,14 @@ class PlayerLogic : public QObject
signals:
void openDeckEditor(const LoadedDeck &deck);
void requestZoneViewToggle(PlayerLogic *player, const QString &zoneName, int numberCards, bool isReversed);
void requestRevealedZoneView(PlayerLogic *player,
CardZoneLogic *zone,
const QList<const ServerInfo_Card *> &cardList,
bool withWritePermission);
void deckChanged();
void newCardAdded(AbstractCardItem *card);
void requestCardMenuUpdate(const CardItem *card);
void counterAdded(CounterState *state);
void counterRemoved(int counterId);
void rearrangeCounters();
@ -78,13 +83,14 @@ signals:
void clearCustomZonesMenu();
void addViewCustomZoneActionToCustomZoneMenu(QString zoneName);
void resetTopCardMenuActions();
void arrowCreateRequested(ArrowData data);
void arrowDeleteRequested(int arrowId);
void arrowDeleted(int arrowId);
void arrowsCleared(); // fires on clear() and processPlayerInfo
void arrowCreateRequested(QSharedPointer<ArrowData> data);
void arrowDeleteRequested(int creatorId, int arrowId);
void arrowDeleted(int creatorId, int arrowId);
void arrowsClearedLocally(); // fires on clear() and processPlayerInfo
public slots:
void setActive(bool _active);
void onRequestZoneViewToggle(const QString &zoneName, int numberCards, bool isReversed);
public:
PlayerLogic(const ServerInfo_User &info, int _id, bool _local, bool _judge, AbstractGame *_parent);
@ -112,10 +118,6 @@ public:
return game;
}
GameScene *getGameScene();
[[nodiscard]] PlayerGraphicsItem *getGraphicsItem();
[[nodiscard]] PlayerActions *getPlayerActions() const
{
return playerActions;
@ -131,11 +133,6 @@ public:
return playerInfo;
}
[[nodiscard]] PlayerMenu *getPlayerMenu() const
{
return playerMenu;
}
void setDeck(const DeckList &_deck);
[[nodiscard]] const DeckList &getDeck() const
@ -234,8 +231,6 @@ private:
PlayerInfo *playerInfo;
PlayerEventHandler *playerEventHandler;
PlayerActions *playerActions;
PlayerMenu *playerMenu;
PlayerGraphicsItem *graphicsItem;
bool active;
bool conceded;

View file

@ -2,9 +2,9 @@
#include "../interface/widgets/tabs/tab_game.h"
Replay::Replay(TabGame *_tab, GameReplay *_replay) : AbstractGame(_tab)
Replay::Replay(QObject *_parent, const GameReplay *_replay, bool isLocalGame) : AbstractGame(_parent)
{
gameState = new GameState(this, 0, -1, tab->getTabSupervisor()->getIsLocalGame(), {}, false, false, -1, false);
gameState = new GameState(this, 0, -1, isLocalGame, {}, false, false, -1, false);
connect(gameMetaInfo, &GameMetaInfo::startedChanged, gameState, &GameState::onStartedChanged);
playerManager = new PlayerManager(this, -1, false, true);
loadReplay(_replay);

View file

@ -15,7 +15,7 @@ class Replay : public AbstractGame
Q_OBJECT
public:
explicit Replay(TabGame *_tab, GameReplay *_replay);
explicit Replay(QObject *_parent, const GameReplay *_replay, bool isLocalGame);
};
#endif // COCKATRICE_REPLAY_H

View file

@ -1,7 +1,7 @@
#include "card_zone_logic.h"
#include "../../game_graphics/board/card_item.h"
#include "../../game_graphics/zones/view_zone.h"
#include "../board/card_item.h"
#include "../player/player_actions.h"
#include "../player/player_logic.h"
#include "view_zone_logic.h"

View file

@ -1,6 +1,6 @@
#include "hand_zone_logic.h"
#include "../board/card_item.h"
#include "../../game_graphics/board/card_item.h"
#include "card_zone_algorithms.h"
HandZoneLogic::HandZoneLogic(PlayerLogic *_player,

View file

@ -1,6 +1,6 @@
#include "pile_zone_logic.h"
#include "../board/card_item.h"
#include "../../game_graphics/board/card_item.h"
PileZoneLogic::PileZoneLogic(PlayerLogic *_player,
const QString &_name,

View file

@ -1,6 +1,6 @@
#include "stack_zone_logic.h"
#include "../board/card_item.h"
#include "../../game_graphics/board/card_item.h"
#include "card_zone_algorithms.h"
StackZoneLogic::StackZoneLogic(PlayerLogic *_player,

View file

@ -1,6 +1,6 @@
#include "table_zone_logic.h"
#include "../board/card_item.h"
#include "../../game_graphics/board/card_item.h"
TableZoneLogic::TableZoneLogic(PlayerLogic *_player,
const QString &_name,

View file

@ -1,8 +1,9 @@
#include "view_zone_logic.h"
#include "../../client/settings/cache_settings.h"
#include "../board/card_item.h"
#include "../../game_graphics/board/card_item.h"
#include <libcockatrice/settings/interface_settings.h>
/**
* @param _player the player that the cards are revealed to.
* @param _origZone the zone the cards were revealed from.
@ -57,7 +58,7 @@ bool ZoneViewZoneLogic::prepareAddCard(int x)
// autoclose check is done both here and in removeCard
if (cards.isEmpty() && !doInsert && SettingsCache::instance().getCloseEmptyCardView()) {
if (cards.isEmpty() && !doInsert && SettingsCache::instance().userInterface().getCloseEmptyCardView()) {
emit closeView();
}
@ -144,7 +145,7 @@ void ZoneViewZoneLogic::removeCard(int position, bool toNewZone)
// card gets dragged within the view.
// Another autoclose check is done in prepareAddCard so that the view autocloses if the last card was moved to an
// unrevealed portion of the same zone.
if (cards.isEmpty() && SettingsCache::instance().getCloseEmptyCardView() && toNewZone) {
if (cards.isEmpty() && SettingsCache::instance().userInterface().getCloseEmptyCardView() && toNewZone) {
emit closeView();
return;
}

View file

@ -7,6 +7,7 @@
#include <QDebug>
#include <QGraphicsSceneMouseEvent>
#include <QPainter>
#include <libcockatrice/settings/cards_display_settings.h>
const QColor GHOST_MASK = QColor(255, 255, 255, 50);
@ -34,12 +35,13 @@ AbstractCardDragItem::AbstractCardDragItem(AbstractCardItem *_item,
setCacheMode(DeviceCoordinateCache);
connect(&SettingsCache::instance(), &SettingsCache::roundCardCornersChanged, this, [this](bool _roundCardCorners) {
Q_UNUSED(_roundCardCorners);
connect(&SettingsCache::instance().cardsDisplay(), &CardsDisplaySettings::roundCardCornersChanged, this,
[this](bool _roundCardCorners) {
Q_UNUSED(_roundCardCorners);
prepareGeometryChange();
update();
});
prepareGeometryChange();
update();
});
connect(item, &QObject::destroyed, this, &AbstractCardDragItem::deleteLater);
}
@ -47,7 +49,8 @@ AbstractCardDragItem::AbstractCardDragItem(AbstractCardItem *_item,
QPainterPath AbstractCardDragItem::shape() const
{
QPainterPath shape;
qreal cardCornerRadius = SettingsCache::instance().getRoundCardCorners() ? 0.05 * CardDimensions::WIDTH_F : 0.0;
qreal cardCornerRadius =
SettingsCache::instance().cardsDisplay().getRoundCardCorners() ? 0.05 * CardDimensions::WIDTH_F : 0.0;
shape.addRoundedRect(boundingRect(), cardCornerRadius, cardCornerRadius);
return shape;
}

View file

@ -12,6 +12,9 @@
#include <algorithm>
#include <libcockatrice/card/database/card_database.h>
#include <libcockatrice/card/database/card_database_manager.h>
#include <libcockatrice/settings/appearance_settings.h>
#include <libcockatrice/settings/cards_display_settings.h>
#include <libcockatrice/settings/debug_settings.h>
AbstractCardItem::AbstractCardItem(QGraphicsItem *parent, const CardRef &cardRef, PlayerLogic *_owner, int _id)
: ArrowTarget(_owner, parent), id(_id), cardRef(cardRef), tapped(false), facedown(false), tapAngle(0),
@ -21,15 +24,17 @@ AbstractCardItem::AbstractCardItem(QGraphicsItem *parent, const CardRef &cardRef
setFlag(ItemIsSelectable);
setCacheMode(DeviceCoordinateCache);
connect(&SettingsCache::instance(), &SettingsCache::displayCardNamesChanged, this, [this] { update(); });
connect(&SettingsCache::instance().cardsDisplay(), &CardsDisplaySettings::displayCardNamesChanged, this,
[this] { update(); });
refreshCardInfo();
connect(&SettingsCache::instance(), &SettingsCache::roundCardCornersChanged, this, [this](bool _roundCardCorners) {
Q_UNUSED(_roundCardCorners);
connect(&SettingsCache::instance().cardsDisplay(), &CardsDisplaySettings::roundCardCornersChanged, this,
[this](bool _roundCardCorners) {
Q_UNUSED(_roundCardCorners);
prepareGeometryChange();
update();
});
prepareGeometryChange();
update();
});
}
AbstractCardItem::~AbstractCardItem()
@ -45,7 +50,8 @@ QRectF AbstractCardItem::boundingRect() const
QPainterPath AbstractCardItem::shape() const
{
QPainterPath shape;
qreal cardCornerRadius = SettingsCache::instance().getRoundCardCorners() ? 0.05 * CardDimensions::WIDTH_F : 0.0;
qreal cardCornerRadius =
SettingsCache::instance().cardsDisplay().getRoundCardCorners() ? 0.05 * CardDimensions::WIDTH_F : 0.0;
shape.addRoundedRect(boundingRect(), cardCornerRadius, cardCornerRadius);
return shape;
}
@ -101,7 +107,7 @@ QSizeF AbstractCardItem::getTranslatedSize(QPainter *painter) const
void AbstractCardItem::transformPainter(QPainter *painter, const QSizeF &translatedSize, int angle)
{
const int MAX_FONT_SIZE = SettingsCache::instance().getMaxFontSize();
const int MAX_FONT_SIZE = SettingsCache::instance().appearance().getMaxFontSize();
const int fontSize = std::max(9, MAX_FONT_SIZE);
QRectF totalBoundingRect = painter->combinedTransform().mapRect(boundingRect());
@ -151,7 +157,7 @@ void AbstractCardItem::paintPicture(QPainter *painter, const QSizeF &translatedS
painter->drawPath(shape());
}
if (translatedPixmap.isNull() || SettingsCache::instance().getDisplayCardNames() || facedown) {
if (translatedPixmap.isNull() || SettingsCache::instance().cardsDisplay().getDisplayCardNames() || facedown) {
painter->save();
transformPainter(painter, translatedSize, angle);
painter->setPen(Qt::white);
@ -234,7 +240,7 @@ void AbstractCardItem::setHovered(bool _hovered)
isHovered = _hovered;
setZValue(_hovered ? ZValues::HOVERED_CARD : realZValue);
setScale(_hovered && SettingsCache::instance().getScaleCards() ? 1.1 : 1);
setScale(_hovered && SettingsCache::instance().cardsDisplay().getScaleCards() ? 1.1 : 1);
setTransformOriginPoint(_hovered ? CardDimensions::WIDTH_HALF_F : 0, _hovered ? CardDimensions::HEIGHT_HALF_F : 0);
update();
}
@ -287,7 +293,7 @@ void AbstractCardItem::setTapped(bool _tapped, bool canAnimate)
}
tapped = _tapped;
if (SettingsCache::instance().getTapAnimation() && canAnimate) {
if (SettingsCache::instance().cardsDisplay().getTapAnimation() && canAnimate) {
static_cast<GameScene *>(scene())->registerAnimationItem(this);
} else {
tapAngle = tapped ? 90 : 0;

View file

@ -7,9 +7,9 @@
#ifndef ABSTRACTCARDITEM_H
#define ABSTRACTCARDITEM_H
#include "../../game_graphics/board/graphics_item_type.h"
#include "../card_dimensions.h"
#include "arrow_target.h"
#include "graphics_item_type.h"
#include <libcockatrice/card/printing/exact_card.h>
#include <libcockatrice/utility/card_ref.h>
@ -44,6 +44,11 @@ signals:
void deleteCardInfoPopup(QString cardName);
void sigPixmapUpdated();
void cardShiftClicked(QString cardName);
void rightClicked(AbstractCardItem *card, QPoint screenPos);
void playSelected(AbstractCardItem *card);
void playSelectedFaceDown(AbstractCardItem *card);
void hideSelected(AbstractCardItem *card);
void selectionChanged(AbstractCardItem *card, bool selected);
public:
enum

View file

@ -1,10 +1,11 @@
#include "abstract_counter.h"
#include "../../client/settings/cache_settings.h"
#include "../../client/settings/shortcuts_settings.h"
#include "../../game/player/player_actions.h"
#include "../../game/player/player_logic.h"
#include "../../game_graphics/board/translate_counter_name.h"
#include "../../interface/widgets/tabs/tab_game.h"
#include "../player/player_actions.h"
#include "../player/player_logic.h"
#include "translate_counter_name.h"
#include <QAction>
#include <QApplication>

View file

@ -7,9 +7,9 @@
#ifndef COUNTER_H
#define COUNTER_H
#include "../../game/board/counter_state.h"
#include "../../interface/widgets/menus/tearoff_menu.h"
#include "../player/menu/abstract_player_component.h"
#include "counter_state.h"
#include <QGraphicsItem>
#include <QInputDialog>

View file

@ -2,11 +2,11 @@
#include "arrow_item.h"
#include "../../client/settings/cache_settings.h"
#include "../../game_graphics/zones/card_zone.h"
#include "../player/player_actions.h"
#include "../player/player_logic.h"
#include "../../game/player/player_actions.h"
#include "../../game/player/player_logic.h"
#include "../player/player_target.h"
#include "../z_values.h"
#include "../zones/card_zone.h"
#include "card_item.h"
#include <QDebug>
@ -18,15 +18,12 @@
#include <libcockatrice/protocol/pb/command_attach_card.pb.h>
#include <libcockatrice/protocol/pb/command_create_arrow.pb.h>
#include <libcockatrice/protocol/pb/command_delete_arrow.pb.h>
#include <libcockatrice/settings/interface_settings.h>
#include <libcockatrice/utility/color.h>
#include <libcockatrice/utility/zone_names.h>
ArrowItem::ArrowItem(PlayerLogic *_player,
int _id,
ArrowTarget *_startItem,
ArrowTarget *_targetItem,
const QColor &_color)
: player(_player), id(_id), startItem(_startItem), targetItem(_targetItem), color(_color)
ArrowItem::ArrowItem(QSharedPointer<const ArrowData> _data, ArrowTarget *_startItem, ArrowTarget *_targetItem)
: data(std::move(_data)), startItem(_startItem), targetItem(_targetItem)
{
setZValue(ZValues::ARROWS);
@ -52,7 +49,7 @@ ArrowItem::ArrowItem(PlayerLogic *_player,
void ArrowItem::onTargetDestroyed()
{
emit requestDeletion(id);
emit requestDeletion(data->creatorId, data->id);
}
void ArrowItem::delArrow()
@ -130,7 +127,7 @@ void ArrowItem::updatePath(const QPointF &endPoint)
void ArrowItem::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/)
{
QColor paintColor(color);
QColor paintColor(data->color);
if (fullColor) {
paintColor.setAlpha(200);
} else {
@ -142,7 +139,7 @@ void ArrowItem::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*opti
void ArrowItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
if (!player->getPlayerInfo()->getLocal()) {
if (!data->isLocalCreator) {
event->ignore();
return;
}
@ -156,14 +153,20 @@ void ArrowItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
event->accept();
if (event->button() == Qt::RightButton) {
emit requestDeletion(id);
emit requestDeletion(data->creatorId, data->id);
}
}
// ArrowDragItem
ArrowDragItem::ArrowDragItem(PlayerLogic *_owner, ArrowTarget *_startItem, const QColor &_color, int _deleteInPhase)
: ArrowItem(_owner, -1, _startItem, nullptr, _color), deleteInPhase(_deleteInPhase)
: ArrowItem(QSharedPointer<ArrowData>::create(ArrowData{.creatorId = _owner->getPlayerInfo()->getId(),
.isLocalCreator = true,
.id = -1,
.color = _color}),
_startItem,
nullptr),
player(_owner), deleteInPhase(_deleteInPhase)
{
}
@ -238,7 +241,7 @@ void ArrowDragItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
CardZoneLogic *startZone = startCard->getZone();
Command_CreateArrow cmd;
cmd.mutable_arrow_color()->CopyFrom(convertQColorToColor(color));
cmd.mutable_arrow_color()->CopyFrom(convertQColorToColor(data->color));
cmd.set_start_player_id(startZone->getPlayer()->getPlayerInfo()->getId());
cmd.set_start_zone(startZone->getName().toStdString());
cmd.set_start_card_id(startCard->getId());
@ -259,7 +262,7 @@ void ArrowDragItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
if (startZone->getName() == ZoneNames::HAND) {
startCard->playCard(false);
CardInfoPtr ci = startCard->getCard().getCardPtr();
bool playToStack = SettingsCache::instance().getPlayToStack();
bool playToStack = SettingsCache::instance().userInterface().getPlayToStack();
if (ci && ((!playToStack && ci->getUiAttributes().tableRow == 3) ||
(playToStack && ci->getUiAttributes().tableRow != 0 &&
startCard->getZone()->getName() != ZoneNames::STACK))) {
@ -284,7 +287,14 @@ void ArrowDragItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
// ArrowAttachItem
ArrowAttachItem::ArrowAttachItem(ArrowTarget *_startItem)
: ArrowItem(_startItem->getOwner(), -1, _startItem, nullptr, Qt::green)
: ArrowItem(
QSharedPointer<ArrowData>::create(ArrowData{.creatorId = _startItem->getOwner()->getPlayerInfo()->getId(),
.isLocalCreator = true,
.id = -1,
.color = Qt::green}),
_startItem,
nullptr),
player(_startItem->getOwner())
{
}

View file

@ -1,20 +1,15 @@
/**
* @file arrow_item.h
* @ingroup GameGraphics
*/
//! \todo Document this file.
#ifndef ARROWITEM_H
#define ARROWITEM_H
#include "../../game/board/arrow_data.h"
#include "arrow_target.h"
#include <QGraphicsItem>
#include <QPointer>
#include <QSharedPointer>
class CardItem;
class QGraphicsSceneMouseEvent;
class QMenu;
class PlayerLogic;
class ArrowItem : public QObject, public QGraphicsItem
@ -22,25 +17,27 @@ class ArrowItem : public QObject, public QGraphicsItem
Q_OBJECT
Q_INTERFACES(QGraphicsItem)
signals:
void requestDeletion(int id);
void requestDeletion(int creatorId, int id);
private:
QPainterPath path;
protected:
PlayerLogic *player;
int id;
QSharedPointer<const ArrowData> data;
QPointer<ArrowTarget> startItem;
QPointer<ArrowTarget> targetItem;
bool targetLocked = false;
QColor color;
bool fullColor = true;
void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
public:
ArrowItem(PlayerLogic *_player, int _id, ArrowTarget *_startItem, ArrowTarget *_targetItem, const QColor &_color);
ArrowItem(QSharedPointer<const ArrowData> _data, ArrowTarget *_startItem, ArrowTarget *_targetItem);
void onTargetDestroyed();
void delArrow();
void updatePath();
void updatePath(const QPointF &endPoint);
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
[[nodiscard]] QRectF boundingRect() const override
@ -51,17 +48,13 @@ public:
{
return path;
}
void updatePath();
void updatePath(const QPointF &endPoint);
[[nodiscard]] int getId() const
{
return id;
return data->id;
}
[[nodiscard]] PlayerLogic *getPlayer() const
[[nodiscard]] int getCreatorId() const
{
return player;
return data->creatorId;
}
[[nodiscard]] ArrowTarget *getStartItem() const
{
@ -75,14 +68,13 @@ public:
{
targetLocked = _targetLocked;
}
void delArrow();
};
class ArrowDragItem : public ArrowItem
{
Q_OBJECT
private:
PlayerLogic *player;
int deleteInPhase;
QList<ArrowDragItem *> childArrows;
QMetaObject::Connection positionConnection;
@ -100,6 +92,7 @@ class ArrowAttachItem : public ArrowItem
{
Q_OBJECT
private:
PlayerLogic *player;
QList<ArrowAttachItem *> childArrows;
QMetaObject::Connection positionConnection;
void attachCards(CardItem *startCard, const CardItem *targetCard);
@ -113,4 +106,4 @@ protected:
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
};
#endif // ARROWITEM_H
#endif

View file

@ -1,6 +1,6 @@
#include "arrow_target.h"
#include "../player/player_logic.h"
#include "../../game/player/player_logic.h"
#include "arrow_item.h"
ArrowTarget::ArrowTarget(PlayerLogic *_owner, QGraphicsItem *parent) : AbstractGraphicsItem(parent), owner(_owner)

View file

@ -7,7 +7,7 @@
#ifndef ARROWTARGET_H
#define ARROWTARGET_H
#include "../../game_graphics/board/abstract_graphics_item.h"
#include "abstract_graphics_item.h"
#include <QList>

View file

@ -1,9 +1,9 @@
#include "card_drag_item.h"
#include "../../game_graphics/zones/card_zone.h"
#include "../../game_graphics/zones/table_zone.h"
#include "../../game_graphics/zones/view_zone.h"
#include "../game_scene.h"
#include "../zones/card_zone.h"
#include "../zones/table_zone.h"
#include "../zones/view_zone.h"
#include "card_item.h"
#include <QCursor>

View file

@ -1,14 +1,15 @@
#include "card_item.h"
#include "../../client/settings/cache_settings.h"
#include "../../game_graphics/zones/table_zone.h"
#include "../../game_graphics/zones/view_zone.h"
#include "../../client/settings/card_counter_settings.h"
#include "../../game/phase.h"
#include "../../game/player/player_actions.h"
#include "../../game/player/player_logic.h"
#include "../../game/zones/view_zone_logic.h"
#include "../../interface/widgets/tabs/tab_game.h"
#include "../game_scene.h"
#include "../phase.h"
#include "../player/player_actions.h"
#include "../player/player_logic.h"
#include "../zones/view_zone_logic.h"
#include "../zones/table_zone.h"
#include "../zones/view_zone.h"
#include "arrow_item.h"
#include "card_drag_item.h"
@ -19,6 +20,7 @@
#include <QPainter>
#include <libcockatrice/card/card_info.h>
#include <libcockatrice/protocol/pb/serverinfo_card.pb.h>
#include <libcockatrice/settings/interface_settings.h>
CardItem::CardItem(PlayerLogic *_owner,
QGraphicsItem *parent,
@ -40,7 +42,7 @@ void CardItem::prepareDelete()
{
if (owner != nullptr) {
if (owner->getGame()->getActiveCard() == this) {
owner->getPlayerMenu()->updateCardMenu(nullptr);
emit owner->requestCardMenuUpdate(nullptr);
owner->getGame()->setActiveCard(nullptr);
}
owner = nullptr;
@ -279,7 +281,7 @@ void CardItem::drawArrow(const QColor &arrowColor)
auto *game = owner->getGame();
PlayerLogic *arrowOwner = game->getPlayerManager()->getActiveLocalPlayer(game->getGameState()->getActivePlayer());
int phase = 0; // 0 means to not set the phase
if (SettingsCache::instance().getDoNotDeleteArrowsInSubPhases()) {
if (SettingsCache::instance().userInterface().getDoNotDeleteArrowsInSubPhases()) {
int currentPhase = game->getGameState()->getCurrentPhase();
phase = Phases::getLastSubphase(currentPhase) + 1;
}
@ -398,9 +400,12 @@ void CardItem::playCard(bool faceDown)
if (tz) {
emit tz->toggleTapped();
} else {
if (SettingsCache::instance().getClickPlaysAllSelected()) {
faceDown ? state->getZone()->getPlayer()->getPlayerActions()->actPlayFacedown()
: state->getZone()->getPlayer()->getPlayerActions()->actPlay();
if (SettingsCache::instance().userInterface().getClickPlaysAllSelected()) {
if (faceDown) {
emit playSelectedFaceDown(this);
} else {
emit playSelected(this);
}
} else {
state->getZone()->getPlayer()->getPlayerActions()->playCard(this, faceDown);
}
@ -459,8 +464,8 @@ static bool isUnwritableRevealZone(CardZoneLogic *zone)
void CardItem::handleClickedToPlay(bool shiftHeld)
{
if (isUnwritableRevealZone(state->getZone())) {
if (SettingsCache::instance().getClickPlaysAllSelected()) {
state->getZone()->getPlayer()->getPlayerActions()->actHide();
if (SettingsCache::instance().userInterface().getClickPlaysAllSelected()) {
emit hideSelected(this);
} else {
state->getZone()->removeCard(this);
}
@ -471,21 +476,15 @@ void CardItem::handleClickedToPlay(bool shiftHeld)
void CardItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
if (event->button() == Qt::RightButton) {
if (owner != nullptr) {
owner->getGame()->setActiveCard(this);
if (QMenu *cardMenu = owner->getPlayerMenu()->updateCardMenu(this)) {
cardMenu->popup(event->screenPos());
return;
}
}
} else if ((event->modifiers() != Qt::AltModifier) && (event->button() == Qt::LeftButton) &&
(!SettingsCache::instance().getDoubleClickToPlay())) {
if (event->button() == Qt::RightButton && owner != nullptr) {
emit rightClicked(this, event->screenPos());
return;
}
if ((event->modifiers() != Qt::AltModifier) && (event->button() == Qt::LeftButton) &&
(!SettingsCache::instance().userInterface().getDoubleClickToPlay())) {
handleClickedToPlay(event->modifiers().testFlag(Qt::ShiftModifier));
}
if (owner != nullptr) { // cards without owner will be deleted
if (owner != nullptr) {
setCursor(Qt::OpenHandCursor);
}
AbstractCardItem::mouseReleaseEvent(event);
@ -494,7 +493,7 @@ void CardItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
void CardItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
{
if ((event->modifiers() != Qt::AltModifier) && (event->buttons() == Qt::LeftButton) &&
(SettingsCache::instance().getDoubleClickToPlay())) {
(SettingsCache::instance().userInterface().getDoubleClickToPlay())) {
handleClickedToPlay(event->modifiers().testFlag(Qt::ShiftModifier));
}
event->accept();
@ -531,14 +530,14 @@ bool CardItem::animationEvent()
QVariant CardItem::itemChange(GraphicsItemChange change, const QVariant &value)
{
if ((change == ItemSelectedHasChanged) && owner != nullptr) {
if (value == true) {
owner->getGame()->setActiveCard(this);
owner->getPlayerMenu()->updateCardMenu(this);
} else if (owner->getGameScene()->selectedItems().isEmpty()) {
bool selected = value.toBool();
owner->getGame()->setActiveCard(nullptr);
owner->getPlayerMenu()->updateCardMenu(nullptr);
if (selected) {
owner->getGame()->setActiveCard(this);
}
emit selectionChanged(this, selected);
}
return AbstractCardItem::itemChange(change, value);
}
}

View file

@ -7,12 +7,11 @@
#ifndef CARDITEM_H
#define CARDITEM_H
#include "../zones/card_zone_logic.h"
#include "../../game/board/card_state.h"
#include "../../game/zones/card_zone_logic.h"
#include "abstract_card_item.h"
#include "card_state.h"
#include <libcockatrice/network/server/remote/game/server_card.h>
#include <libcockatrice/utility/trice_limits.h>
class CardDatabase;
class CardDragItem;
@ -55,6 +54,10 @@ public:
CardZoneLogic *_zone = nullptr);
void retranslateUi();
[[nodiscard]] CardState *getState() const
{
return state;
}
[[nodiscard]] CardZoneLogic *getZone() const
{
return state->getZone();

View file

@ -1,7 +1,7 @@
#include "counter_general.h"
#include "../../game_graphics/board/abstract_graphics_item.h"
#include "../../interface/pixel_map_generator.h"
#include "abstract_graphics_item.h"
#include <QPainter>

View file

@ -11,6 +11,7 @@
#include <libcockatrice/card/card_info.h>
#include <libcockatrice/deck_list/deck_list.h>
#include <libcockatrice/deck_list/tree/deck_list_card_node.h>
#include <libcockatrice/settings/cards_display_settings.h>
DeckViewCardDragItem::DeckViewCardDragItem(DeckViewCard *_item,
const QPointF &_hotSpot,
@ -77,11 +78,12 @@ DeckViewCard::DeckViewCard(QGraphicsItem *parent, const CardRef &cardRef, const
{
setAcceptHoverEvents(true);
connect(&SettingsCache::instance(), &SettingsCache::roundCardCornersChanged, this, [this](bool _roundCardCorners) {
Q_UNUSED(_roundCardCorners);
connect(&SettingsCache::instance().cardsDisplay(), &CardsDisplaySettings::roundCardCornersChanged, this,
[this](bool _roundCardCorners) {
Q_UNUSED(_roundCardCorners);
update();
});
update();
});
}
DeckViewCard::~DeckViewCard()
@ -99,7 +101,8 @@ void DeckViewCard::paint(QPainter *painter, const QStyleOptionGraphicsItem *opti
pen.setJoinStyle(Qt::MiterJoin);
pen.setColor(originZone == DECK_ZONE_MAIN ? Qt::green : Qt::red);
painter->setPen(pen);
qreal cardRadius = SettingsCache::instance().getRoundCardCorners() ? 0.05 * (CardDimensions::WIDTH_F - 3) : 0.0;
qreal cardRadius =
SettingsCache::instance().cardsDisplay().getRoundCardCorners() ? 0.05 * (CardDimensions::WIDTH_F - 3) : 0.0;
painter->drawRoundedRect(QRectF(1.5, 1.5, CardDimensions::WIDTH_F - 3, CardDimensions::HEIGHT_F - 3), cardRadius,
cardRadius);
painter->restore();
@ -360,6 +363,16 @@ void DeckViewScene::rebuildTree()
return;
}
QStringList requiredZones = {DECK_ZONE_MAIN, DECK_ZONE_SIDE};
for (const QString &zoneName : requiredZones) {
if (!cardContainers.contains(zoneName)) {
auto *container = new DeckViewCardContainer(zoneName);
cardContainers.insert(zoneName, container);
addItem(container);
}
}
for (auto *currentZone : deck->getZoneNodes()) {
DeckViewCardContainer *container = cardContainers.value(currentZone->getName(), 0);
if (!container) {

View file

@ -1,6 +1,7 @@
#include "deck_view_container.h"
#include "../../client/settings/cache_settings.h"
#include "../../client/settings/shortcuts_settings.h"
#include "../../interface/card_picture_loader/card_picture_loader.h"
#include "../../interface/deck_loader/deck_loader.h"
#include "../../interface/widgets/dialogs/dlg_load_deck.h"
@ -19,7 +20,8 @@
#include <libcockatrice/protocol/pb/command_set_sideboard_plan.pb.h>
#include <libcockatrice/protocol/pb/response_deck_download.pb.h>
#include <libcockatrice/protocol/pending_command.h>
#include <libcockatrice/utility/trice_limits.h>
#include <libcockatrice/settings/visual_deck_storage_settings.h>
#include <libcockatrice/utility/string_limits.h>
ToggleButton::ToggleButton(QWidget *parent) : QPushButton(parent), state(false)
{
@ -95,12 +97,26 @@ DeckViewContainer::DeckViewContainer(int _playerId, TabGame *parent)
&DeckViewContainer::refreshShortcuts);
refreshShortcuts();
connect(&SettingsCache::instance(), &SettingsCache::visualDeckStorageInGameChanged, this,
&DeckViewContainer::setVisualDeckStorageExists);
connect(&SettingsCache::instance().visualDeckStorage(), &VisualDeckStorageSettings::visualDeckStorageInGameChanged,
this, &DeckViewContainer::setVisualDeckStorageExists);
switchToDeckSelectView();
}
TutorialSequence DeckViewContainer::generateTutorialSequence()
{
TutorialSequence deckViewContainerSequence;
deckViewContainerSequence.name = tr("Loading and selecting decks");
deckViewContainerSequence.addStep(
{this, tr("There are multiple ways to select a deck:\n\n- From a local file"
"\n- From the contents of your clipboard\nFrom an external online service")});
visualDeckStorageWidget->generateTutorialSequence(deckViewContainerSequence);
return deckViewContainerSequence;
}
/**
* Creates the VDS widget and inserts it into the layout. No-ops if the widget already exists
*/
@ -138,7 +154,7 @@ static void setVisibility(QPushButton *button, bool visible)
void DeckViewContainer::switchToDeckSelectView()
{
if (SettingsCache::instance().getVisualDeckStorageInGame()) {
if (SettingsCache::instance().visualDeckStorage().getVisualDeckStorageInGame()) {
deckView->setHidden(true);
tryCreateVisualDeckStorageWidget();
@ -209,6 +225,7 @@ void DeckViewContainer::refreshShortcuts()
loadLocalButton->setShortcut(shortcuts.getSingleShortcut("DeckViewContainer/loadLocalButton"));
loadRemoteButton->setShortcut(shortcuts.getSingleShortcut("DeckViewContainer/loadRemoteButton"));
loadFromClipboardButton->setShortcut(shortcuts.getSingleShortcut("DeckViewContainer/loadFromClipboardButton"));
loadFromWebsiteButton->setShortcut(shortcuts.getSingleShortcut("DeckViewContainer/loadFromWebsiteButton"));
unloadDeckButton->setShortcut(shortcuts.getSingleShortcut("DeckViewContainer/unloadDeckButton"));
readyStartButton->setShortcut(shortcuts.getSingleShortcut("DeckViewContainer/readyStartButton"));
sideboardLockButton->setShortcut(shortcuts.getSingleShortcut("DeckViewContainer/sideboardLockButton"));

View file

@ -8,6 +8,7 @@
#define DECK_VIEW_CONTAINER_H
#include "../../interface/deck_loader/deck_loader.h"
#include "../../interface/widgets/general/tutorial/tutorial_controller.h"
#include <QPushButton>
@ -82,6 +83,7 @@ signals:
public:
DeckViewContainer(int _playerId, TabGame *parent);
void retranslateUi();
TutorialSequence generateTutorialSequence();
void setReadyStart(bool ready);
void readyAndUpdate();
void setSideboardLocked(bool locked);

View file

@ -15,6 +15,11 @@ TabbedDeckViewContainer::TabbedDeckViewContainer(int _playerId, TabGame *parent)
updateTabBarVisibility();
}
TutorialSequence TabbedDeckViewContainer::generateTutorialSequence()
{
return playerDeckView->generateTutorialSequence();
}
void TabbedDeckViewContainer::addOpponentDeckView(const DeckList &opponentDeck, int opponentId, QString opponentName)
{
if (opponentDeckViews.contains(opponentId)) {

View file

@ -6,6 +6,7 @@
#ifndef TABBED_DECK_VIEW_CONTAINER_H
#define TABBED_DECK_VIEW_CONTAINER_H
#include "../../interface/widgets/general/tutorial/tutorial_controller.h"
#include "deck_view_container.h"
#include <QTabWidget>
@ -16,6 +17,7 @@ class TabbedDeckViewContainer : public QTabWidget
public:
explicit TabbedDeckViewContainer(int _playerId, TabGame *parent);
TutorialSequence generateTutorialSequence();
void closeTab(int index);
void updateTabBarVisibility();
void addOpponentDeckView(const DeckList &opponentDeck, int opponentId, QString opponentName);

View file

@ -20,7 +20,10 @@
#include <libcockatrice/deck_list/deck_list.h>
#include <libcockatrice/models/database/card_database_model.h>
#include <libcockatrice/models/database/token/token_display_model.h>
#include <libcockatrice/utility/trice_limits.h>
#include <libcockatrice/settings/card_override_settings.h>
#include <libcockatrice/settings/interface_settings.h>
#include <libcockatrice/settings/layouts_settings.h>
#include <libcockatrice/utility/string_limits.h>
DlgCreateToken::DlgCreateToken(const QStringList &_predefinedTokens, QWidget *parent)
: QDialog(parent), predefinedTokens(_predefinedTokens)
@ -186,7 +189,7 @@ void DlgCreateToken::tokenSelectionChanged(const QModelIndex &current, const QMo
const QChar cardColor = cardInfo->getColorChar();
colorEdit->setCurrentIndex(colorEdit->findData(cardColor, Qt::UserRole, Qt::MatchFixedString));
ptEdit->setText(cardInfo->getPowTough());
if (SettingsCache::instance().getAnnotateTokens()) {
if (SettingsCache::instance().userInterface().getAnnotateTokens()) {
annotationEdit->setText(cardInfo->getText());
}
} else {

View file

@ -5,7 +5,7 @@
#include <QSpinBox>
#include <QVBoxLayout>
#include <QWidget>
#include <libcockatrice/utility/trice_limits.h>
#include <libcockatrice/utility/dice_limits.h>
DlgRollDice::DlgRollDice(QWidget *parent) : QDialog(parent)
{

View file

@ -1,13 +1,17 @@
#include "game_scene.h"
#include "../client/settings/cache_settings.h"
#include "../game_graphics/zones/select_zone.h"
#include "../game_graphics/zones/view_zone.h"
#include "../game_graphics/zones/view_zone_widget.h"
#include "../game/abstract_game.h"
#include "../game/player/player_actions.h"
#include "../game/player/player_logic.h"
#include "../game_graphics/player/player_graphics_item.h"
#include "board/card_item.h"
#include "phases_toolbar.h"
#include "player/menu/player_menu.h"
#include "player/player_graphics_item.h"
#include "player/player_logic.h"
#include "zones/select_zone.h"
#include "zones/view_zone.h"
#include "zones/view_zone_widget.h"
#include <QBasicTimer>
#include <QDebug>
@ -15,6 +19,7 @@
#include <QGraphicsView>
#include <QSet>
#include <QtMath>
#include <libcockatrice/settings/interface_settings.h>
#include <libcockatrice/utility/zone_names.h>
#include <numeric>
@ -32,7 +37,7 @@ GameScene::GameScene(PhasesToolbar *_phasesToolbar, QObject *parent)
{
animationTimer = new QBasicTimer;
addItem(phasesToolbar);
connect(&SettingsCache::instance(), &SettingsCache::minPlayersForMultiColumnLayoutChanged, this,
connect(&SettingsCache::instance().userInterface(), &InterfaceSettings::minPlayersForMultiColumnLayoutChanged, this,
&GameScene::rearrange);
rearrange();
@ -42,6 +47,17 @@ GameScene::~GameScene()
{
delete animationTimer;
// Delete all ArrowItems before QGraphicsScene's base destructor runs.
// QGraphicsScene::~QGraphicsScene() destroys items in arbitrary order.
// If a PlayerTarget is destroyed before an ArrowItem pointing to it,
// ArrowItem::onTargetDestroyed fires and emits on the partially-destroyed
// GameScene, causing a segfault.
for (auto *item : items()) {
if (auto *arrow = qgraphicsitem_cast<ArrowItem *>(item)) {
delete arrow;
}
}
// DO NOT call clearViews() here
// clearViews calls close() on the zoneViews, which sends signals; sending signals in destructors leads to segfaults
// deleteLater() deletes the zoneView without allowing it to send signals
@ -72,6 +88,80 @@ QList<CardItem *> GameScene::selectedCards() const
return selectedCards;
}
void GameScene::onCardSelectionChanged(AbstractCardItem *abstractCard, bool selected)
{
CardItem *card = qobject_cast<CardItem *>(abstractCard);
if (!card || !card->getOwner()) {
return;
}
auto *owner = card->getOwner();
if (selected) {
owner->requestCardMenuUpdate(card);
return;
}
if (selectedItems().isEmpty()) {
owner->getGame()->setActiveCard(nullptr);
owner->requestCardMenuUpdate(nullptr);
}
}
void GameScene::onCardRightClicked(AbstractCardItem *abstractCard, QPoint screenPos)
{
auto *card = qobject_cast<CardItem *>(abstractCard);
if (!card) {
return;
}
if (!card->getOwner()) {
return;
}
auto *view = playerViews.value(card->getOwner()->getPlayerInfo()->getId());
if (!view) {
return;
}
card->getOwner()->getGame()->setActiveCard(card);
if (auto *menu = view->getPlayerMenu()->updateCardMenu(card)) {
menu->popup(screenPos);
}
}
void GameScene::playSelected(AbstractCardItem *card)
{
if (!card) {
return;
}
if (!card->getOwner()) {
return;
}
card->getOwner()->getPlayerActions()->actPlay(selectedCards());
}
void GameScene::playSelectedFaceDown(AbstractCardItem *card)
{
if (!card) {
return;
}
if (!card->getOwner()) {
return;
}
card->getOwner()->getPlayerActions()->actPlayFacedown(selectedCards());
}
void GameScene::hideSelected(AbstractCardItem *card)
{
if (!card) {
return;
}
if (!card->getOwner()) {
return;
}
card->getOwner()->getPlayerActions()->actHide(selectedCards());
}
/**
* @brief Adds a player to the scene and stores their graphics item.
* @param player Player to add.
@ -82,9 +172,11 @@ void GameScene::addPlayer(PlayerLogic *player)
{
qCInfo(GameScenePlayerAdditionRemovalLog) << "GameScene::addPlayer name=" << player->getPlayerInfo()->getName();
playerViews.insert(player->getPlayerInfo()->getId(), player->getGraphicsItem());
addItem(player->getGraphicsItem());
connect(player->getGraphicsItem(), &PlayerGraphicsItem::sizeChanged, this, &GameScene::rearrange);
auto *view = new PlayerGraphicsItem(player);
playerViews.insert(player->getPlayerInfo()->getId(), view);
addItem(view);
connect(view, &PlayerGraphicsItem::sizeChanged, this, &GameScene::rearrange);
connect(player, &PlayerLogic::concededChanged, this, [this](int id, bool conceded) {
if (conceded) {
@ -93,11 +185,13 @@ void GameScene::addPlayer(PlayerLogic *player)
rearrange();
});
connect(player, &PlayerLogic::arrowDeleted, this, &GameScene::onArrowDeleted);
connect(player, &PlayerLogic::arrowCreateRequested, this, &GameScene::onArrowCreateRequested);
connect(player, &PlayerLogic::arrowDeleteRequested, this, &GameScene::onArrowDeleteRequested);
connect(player, &PlayerLogic::arrowsCleared, this,
[this, id = player->getPlayerInfo()->getId()]() { clearArrowsForPlayer(id); });
connect(player, &PlayerLogic::requestZoneViewToggle, this, &GameScene::toggleZoneView);
connect(player, &PlayerLogic::requestRevealedZoneView, this, &GameScene::addRevealedZoneView);
connect(player, &PlayerLogic::arrowDeleted, this, &GameScene::deleteArrow);
connect(player, &PlayerLogic::arrowCreateRequested, this, &GameScene::addArrow);
connect(player, &PlayerLogic::arrowDeleteRequested, this, &GameScene::requestArrowDeletion);
connect(player, &PlayerLogic::arrowsClearedLocally, this,
[this, id = player->getPlayerInfo()->getId()]() { clearArrowsForPlayerLocally(id); });
connect(player->getPlayerEventHandler(), &PlayerEventHandler::cardZoneChanged, this, &GameScene::onCardZoneChanged);
@ -123,6 +217,7 @@ void GameScene::removePlayer(PlayerLogic *player)
}
auto *view = playerViews.take(player->getPlayerInfo()->getId());
removeItem(view);
view->deleteLater();
rearrange();
}
@ -204,7 +299,7 @@ QList<PlayerLogic *> GameScene::collectActivePlayers(int &firstPlayerIndex) cons
bool firstPlayerFound = false;
for (auto *pgItem : playerViews.values()) {
PlayerLogic *p = pgItem->getPlayer();
PlayerLogic *p = pgItem->getLogic();
if (p && !p->getConceded()) {
activePlayers.append(p);
if (!firstPlayerFound && p->getPlayerInfo()->getLocal()) {
@ -241,7 +336,7 @@ QList<PlayerLogic *> GameScene::rotatePlayers(const QList<PlayerLogic *> &active
int GameScene::determineColumnCount(int playerCount)
{
return playerCount < SettingsCache::instance().getMinPlayersForMultiColumnLayout() ? 1 : 2;
return playerCount < SettingsCache::instance().userInterface().getMinPlayersForMultiColumnLayout() ? 1 : 2;
}
/**
@ -275,12 +370,12 @@ QSizeF GameScene::computeSceneSizeAndPlayerLayout(const QList<PlayerLogic *> &pl
for (int j = 0; j < rowsInColumn; ++j) {
PlayerLogic *player = playersIter.next();
if (col == 0) {
playersByColumn[col].prepend(player->getGraphicsItem());
playersByColumn[col].prepend(playerViews.value(player->getPlayerInfo()->getId()));
} else {
playersByColumn[col].append(player->getGraphicsItem());
playersByColumn[col].append(playerViews.value(player->getPlayerInfo()->getId()));
}
auto *pgItem = player->getGraphicsItem();
auto *pgItem = playerViews.value(player->getPlayerInfo()->getId());
thisColumnHeight += pgItem->boundingRect().height() + playerAreaSpacing;
columnWidth[col] = std::max(columnWidth[col], (int)pgItem->boundingRect().width());
}
@ -367,62 +462,62 @@ void GameScene::resizeColumnsAndPlayers(const QList<qreal> &minWidthByColumn, qr
}
}
void GameScene::onArrowCreateRequested(const ArrowData &data)
void GameScene::addArrow(QSharedPointer<ArrowData> data)
{
auto *startView = playerViews.value(data.startPlayerId);
auto *targetView = playerViews.value(data.targetPlayerId);
auto *startView = playerViews.value(data->startPlayerId);
auto *targetView = playerViews.value(data->targetPlayerId);
if (!startView || !targetView) {
return;
}
PlayerLogic *startLogic = startView->getPlayer();
auto *startZone = startLogic->getZones().value(data.startZone);
PlayerLogic *startLogic = startView->getLogic();
auto *startZone = startLogic->getZones().value(data->startZone);
if (!startZone) {
return;
}
CardItem *startCard = startZone->getCard(data.startCardId);
CardItem *startCard = startZone->getCard(data->startCardId);
if (!startCard) {
return;
}
ArrowTarget *targetItem = nullptr;
if (data.isPlayerTargeted()) {
if (data->isPlayerTargeted()) {
targetItem = targetView->getPlayerTarget();
} else {
auto *zone = targetView->getPlayer()->getZones().value(data.targetZone);
auto *zone = targetView->getLogic()->getZones().value(data->targetZone);
if (zone) {
targetItem = zone->getCard(data.targetCardId);
targetItem = zone->getCard(data->targetCardId);
}
}
if (!targetItem) {
return;
}
auto *arrow = new ArrowItem(startView->getPlayer(), data.id, startCard, targetItem, data.color);
auto *arrow = new ArrowItem(data, startCard, targetItem);
addItem(arrow);
arrowRegistry.insert(data.id, arrow);
connect(arrow, &ArrowItem::requestDeletion, this, &GameScene::onArrowDeleteRequested);
arrowRegistry.insert(data, arrow);
connect(arrow, &ArrowItem::requestDeletion, this, &GameScene::requestArrowDeletion);
}
void GameScene::onArrowDeleted(int arrowId)
void GameScene::deleteArrow(int playerId, int arrowId)
{
if (arrowRegistry.contains(arrowId)) {
arrowRegistry.take(arrowId)->delArrow();
if (auto *arrow = arrowRegistry.take(playerId, arrowId)) {
arrow->delArrow();
}
}
void GameScene::onArrowDeleteRequested(int arrowId)
void GameScene::requestArrowDeletion(int playerId, int arrowId)
{
if (arrowRegistry.contains(arrowId)) {
emit requestArrowDeletion(arrowId);
if (arrowRegistry.contains(playerId, arrowId)) {
emit arrowDeletionRequested(playerId, arrowId);
}
}
void GameScene::onCardZoneChanged(CardItem *card, bool sameZone)
{
QList<ArrowItem *> toDelete;
for (auto *arrow : arrowRegistry.values()) {
for (auto *arrow : arrowRegistry.all()) {
if (arrow->getStartItem() == card || arrow->getTargetItem() == card) {
if (sameZone) {
arrow->updatePath();
@ -432,15 +527,22 @@ void GameScene::onCardZoneChanged(CardItem *card, bool sameZone)
}
}
for (auto *arrow : toDelete) {
emit requestArrowDeletion(arrow->getId());
deleteArrow(arrow->getCreatorId(), arrow->getId());
}
}
void GameScene::clearArrowsForPlayer(int playerId)
{
for (auto *arrow : arrowRegistry.values()) {
if (arrow->getPlayer()->getPlayerInfo()->getId() == playerId) {
emit requestArrowDeletion(arrow->getId());
for (int arrowId : arrowRegistry.idsForPlayer(playerId)) {
emit requestArrowDeletion(playerId, arrowId);
}
}
void GameScene::clearArrowsForPlayerLocally(int playerId)
{
for (int arrowId : arrowRegistry.idsForPlayer(playerId)) {
if (auto *arrow = arrowRegistry.take(playerId, arrowId)) {
arrow->delArrow();
}
}
}

View file

@ -1,9 +1,10 @@
#ifndef GAMESCENE_H
#define GAMESCENE_H
#include "board/arrow_data.h"
#include "../game/arrow_registry.h"
#include "../game/board/arrow_data.h"
#include "../game/zones/card_zone_logic.h"
#include "board/arrow_item.h"
#include "zones/card_zone_logic.h"
#include <QGraphicsScene>
#include <QList>
@ -45,7 +46,7 @@ private:
PhasesToolbar *phasesToolbar; ///< Toolbar showing game phases
QMap<int, PlayerGraphicsItem *> playerViews; ///< ID lookup for player graphics items
QList<QList<PlayerGraphicsItem *>> playersByColumn; ///< Players organized by column
QMap<int, ArrowItem *> arrowRegistry; ///< ID registry for arrow graphics items
ArrowRegistry arrowRegistry; ///< ID registry for arrow graphics items
QList<ZoneViewWidget *> zoneViews; ///< Active zone view widgets
QSize viewSize; ///< Current view size
QPointer<CardItem> hoveredCard; ///< Currently hovered card
@ -96,6 +97,16 @@ public:
*/
void removePlayer(PlayerLogic *player);
QMap<int, PlayerGraphicsItem *> getPlayers() const
{
return playerViews;
}
PlayerGraphicsItem *viewForPlayer(int playerId)
{
return playerViews.value(playerId);
}
/**
* @brief Adjusts the global rotation offset for player layout.
* @param rotationAdjustment Number of positions to rotate.
@ -181,6 +192,11 @@ public:
void stopRubberBand();
public slots:
void onCardSelectionChanged(AbstractCardItem *card, bool selected);
void onCardRightClicked(AbstractCardItem *card, QPoint screenPos);
void playSelected(AbstractCardItem *card);
void playSelectedFaceDown(AbstractCardItem *card);
void hideSelected(AbstractCardItem *card);
/** @brief Toggles a zone view for a player. */
void toggleZoneView(PlayerLogic *player, const QString &zoneName, int numberCards, bool isReversed = false);
@ -201,11 +217,16 @@ public slots:
QTransform getViewTransform() const;
QTransform getViewportTransform() const;
void onArrowCreateRequested(const ArrowData &data);
void onArrowDeleted(int arrowId);
void onArrowDeleteRequested(int arrowId);
void onCardZoneChanged(CardItem *card, bool sameZone);
/// Directly modifies the scene
void addArrow(QSharedPointer<ArrowData> data);
void deleteArrow(int playerId, int arrowId);
void clearArrowsForPlayer(int playerId);
void clearArrowsForPlayerLocally(int playerId);
/// Queues up arrow deletion but doesn't directly modify the scene
void requestArrowDeletion(int playerId, int arrowId);
void onCardZoneChanged(CardItem *card, bool sameZone);
protected:
/** @brief Handles hover updates. */
@ -218,7 +239,7 @@ signals:
void sigStartRubberBand(const QPointF &selectionOrigin);
void sigResizeRubberBand(const QPointF &cursorPoint, int selectedCount);
void sigStopRubberBand();
void requestArrowDeletion(int arrowId);
void arrowDeletionRequested(int creatorId, int arrowId);
};
#endif

View file

@ -0,0 +1,297 @@
#include "game_view.h"
#include "../client/settings/cache_settings.h"
#include "../client/settings/shortcuts_settings.h"
#include "game_scene.h"
#include <QAction>
#include <QGridLayout>
#include <QLabel>
#include <QLayout>
#include <QResizeEvent>
#include <QRubberBand>
#include <libcockatrice/settings/interface_settings.h>
#include <libcockatrice/utility/qt_utils.h>
// QRubberBand calls raise() in showEvent() and changeEvent() to stay on top of siblings.
// This subclass disables that behavior so dragCountLabel can appear above it.
class SelectionRubberBand : public QRubberBand
{
public:
using QRubberBand::QRubberBand;
protected:
void showEvent(QShowEvent *event) override
{
QWidget::showEvent(event); // Skip QRubberBand's raise()
}
void changeEvent(QEvent *event) override
{
if (event->type() == QEvent::ZOrderChange) {
return; // Skip QRubberBand's raise() on z-order changes
}
QRubberBand::changeEvent(event);
}
};
GameView::GameView(GameScene *scene, QWidget *parent) : QGraphicsView(scene, parent), rubberBand(0)
{
setBackgroundBrush(QBrush(QColor(0, 0, 0)));
setRenderHints(QPainter::TextAntialiasing | QPainter::Antialiasing);
setViewportUpdateMode(BoundingRectViewportUpdate);
connect(scene, &GameScene::sceneRectChanged, this, &GameView::updateSceneRect);
connect(scene, &GameScene::sigStartRubberBand, this, &GameView::startRubberBand);
connect(scene, &GameScene::sigResizeRubberBand, this, &GameView::resizeRubberBand);
connect(scene, &GameScene::sigStopRubberBand, this, &GameView::stopRubberBand);
connect(scene, &QGraphicsScene::selectionChanged, this, [this]() { updateTotalSelectionCount(); });
connect(&SettingsCache::instance().userInterface(), &InterfaceSettings::tallyTypeChanged, this,
[this] { updateTotalSelectionCount(); });
setFocusDisabled(SettingsCache::instance().userInterface().getKeepGameChatFocus());
connect(&SettingsCache::instance().userInterface(), &InterfaceSettings::keepGameChatFocusChanged, this,
&GameView::setFocusDisabled);
aCloseMostRecentZoneView = new QAction(this);
connect(aCloseMostRecentZoneView, &QAction::triggered, scene, &GameScene::closeMostRecentZoneView);
addAction(aCloseMostRecentZoneView);
connect(&SettingsCache::instance().shortcuts(), &ShortcutsSettings::shortCutChanged, this,
&GameView::refreshShortcuts);
refreshShortcuts();
rubberBand = new SelectionRubberBand(QRubberBand::Rectangle, this);
const QString baseProperties = "color: white; "
"font-family: monospace; "
"background-color: rgba(0, 0, 0, 160); "
"border-radius: 3px; "
"padding: 1px 2px; "
"white-space: pre;";
const QString dragCountLabelStyle = baseProperties + "font-size: 14px; font-weight: bold;";
const QString totalCountLabelStyle = baseProperties + "font-size: 16px; font-weight: bold;";
const QString subtypeTallyLabelStyle = baseProperties + "font-size: 12px;";
dragCountLabel = new QLabel(this);
dragCountLabel->setStyleSheet(dragCountLabelStyle);
dragCountLabel->hide();
dragCountLabel->raise();
totalCountLabel = new QLabel(this);
totalCountLabel->setStyleSheet(totalCountLabelStyle);
totalCountLabel->hide();
tallyContainer = new QWidget(this);
tallyContainer->setStyleSheet(subtypeTallyLabelStyle);
tallyLayout = new QGridLayout(tallyContainer);
tallyLayout->setContentsMargins(2, 2, 2, 2);
tallyLayout->setSpacing(2);
tallyContainer->hide();
}
void GameView::resizeEvent(QResizeEvent *event)
{
QGraphicsView::resizeEvent(event);
GameScene *s = static_cast<GameScene *>(scene());
s->processViewSizeChange(event->size());
updateSceneRect(scene()->sceneRect());
updateTotalSelectionCount(event->size());
}
void GameView::updateSceneRect(const QRectF &rect)
{
fitInView(rect, Qt::KeepAspectRatio);
}
void GameView::startRubberBand(const QPointF &_selectionOrigin)
{
if (!rubberBand) {
return;
}
selectionOrigin = _selectionOrigin;
rubberBand->setGeometry(QRect(mapFromScene(selectionOrigin), QSize(0, 0)));
rubberBand->show();
}
void GameView::resizeRubberBand(const QPointF &cursorPoint, int selectedCount)
{
if (!rubberBand) {
return;
}
constexpr int kLabelPaddingInPixels = 4;
QPoint cursor = cursorPoint.toPoint();
QRect rect = QRect(mapFromScene(selectionOrigin), cursor).normalized();
rubberBand->setGeometry(rect);
if (!SettingsCache::instance().userInterface().getShowDragSelectionCount()) {
dragCountLabel->hide();
return;
}
if (selectedCount > 0) {
dragCountLabel->setText(QString::number(selectedCount));
dragCountLabel->adjustSize();
QSize labelSize = dragCountLabel->size();
if (rect.width() < labelSize.width() + 2 * kLabelPaddingInPixels ||
rect.height() < labelSize.height() + 2 * kLabelPaddingInPixels) {
dragCountLabel->hide();
return;
}
const int minX = rect.left() + kLabelPaddingInPixels;
const int minY = rect.top() + kLabelPaddingInPixels;
int x = qMax(minX, cursor.x() - labelSize.width() - kLabelPaddingInPixels);
int y = qMax(minY, cursor.y() - labelSize.height() - kLabelPaddingInPixels);
bool isAtTopLeftCorner = (x == minX) && (y == minY);
if (isAtTopLeftCorner) {
constexpr int kCursorClearanceInPixels = 16;
x = qMin(cursor.x() + kCursorClearanceInPixels, rect.right() - labelSize.width() - kLabelPaddingInPixels);
}
dragCountLabel->move(x, y);
dragCountLabel->show();
} else {
dragCountLabel->hide();
}
}
void GameView::stopRubberBand()
{
if (!rubberBand) {
return;
}
rubberBand->hide();
dragCountLabel->hide();
}
void GameView::refreshShortcuts()
{
aCloseMostRecentZoneView->setShortcuts(
SettingsCache::instance().shortcuts().getShortcut("Player/aCloseMostRecentZoneView"));
}
void GameView::clearTallyLabels()
{
QtUtils::clearLayoutRec(tallyLayout);
}
QSize GameView::rebuildTallyLabels(const QList<TallyRow> &entries)
{
clearTallyLabels();
const QString nameStyle = QStringLiteral("color: white; font-size: 12px; background: transparent;");
const QString countStyle =
QStringLiteral("color: white; font-size: 14px; font-weight: bold; background: transparent;");
int totalHeight = 0;
int maxNameWidth = 0;
int maxCountWidth = 0;
int row = 0;
for (const TallyRow &entry : entries) {
auto *nameLabel = new QLabel(entry.name, tallyContainer);
nameLabel->setStyleSheet(nameStyle);
nameLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
tallyLayout->addWidget(nameLabel, row, 0);
auto *countLabel = new QLabel(entry.value, tallyContainer);
countLabel->setStyleSheet(countStyle);
countLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
tallyLayout->addWidget(countLabel, row, 1);
QSize nameSize = nameLabel->sizeHint();
QSize countSize = countLabel->sizeHint();
maxNameWidth = qMax(maxNameWidth, nameSize.width());
maxCountWidth = qMax(maxCountWidth, countSize.width());
totalHeight += qMax(nameSize.height(), countSize.height());
++row;
}
int spacing = tallyLayout->spacing();
int margins = tallyLayout->contentsMargins().left() + tallyLayout->contentsMargins().right();
int verticalMargins = tallyLayout->contentsMargins().top() + tallyLayout->contentsMargins().bottom();
int width = maxNameWidth + spacing + maxCountWidth + margins;
int height = totalHeight + (row - 1) * spacing + verticalMargins;
return QSize(width, height);
}
void GameView::updateTotalSelectionCount(const QSize &viewSize)
{
constexpr int kMarginInPixels = 10;
constexpr int kSpacingBetweenLabels = 4;
int availableWidth = viewSize.isValid() ? viewSize.width() : viewport()->width();
int availableHeight = viewSize.isValid() ? viewSize.height() : viewport()->height();
int count = scene()->selectedItems().count();
if (!SettingsCache::instance().userInterface().getShowTotalSelectionCount() || count <= 1) {
totalCountLabel->hide();
} else {
totalCountLabel->setText(QString::number(count));
totalCountLabel->adjustSize();
int x = availableWidth - totalCountLabel->width() - kMarginInPixels;
int y = availableHeight - totalCountLabel->height() - kMarginInPixels;
totalCountLabel->move(x, y);
totalCountLabel->show();
}
TallyType tallyType = Tally::intToType(SettingsCache::instance().userInterface().getTallyType());
GameScene *gameScene = static_cast<GameScene *>(scene());
QList<TallyRow> entries = Tally::compute(gameScene->selectedCards(), tallyType);
if (entries.isEmpty()) {
tallyContainer->hide();
cachedTallyRows.clear();
return;
}
// Only rebuild labels if entries changed
QSize containerSize;
if (entries != cachedTallyRows) {
cachedTallyRows = entries;
containerSize = rebuildTallyLabels(entries);
tallyContainer->resize(containerSize);
} else {
containerSize = tallyContainer->size();
}
int x = availableWidth - containerSize.width() - kMarginInPixels;
int y;
if (totalCountLabel->isVisible()) {
y = totalCountLabel->y() - containerSize.height() - kSpacingBetweenLabels;
} else {
y = availableHeight - containerSize.height() - kMarginInPixels;
}
y = qMax(kMarginInPixels, y);
tallyContainer->move(x, y);
tallyContainer->show();
}
/**
* Disabling focus on the game view will allow chat to maintain the autofocusing behavior of pre 2.10.3,
* at the cost of disabling the zone view search bar.
*/
void GameView::setFocusDisabled(bool disabled)
{
setFocusPolicy(disabled ? Qt::NoFocus : Qt::ClickFocus);
}

View file

@ -7,9 +7,12 @@
#ifndef GAMEVIEW_H
#define GAMEVIEW_H
#include "tally/tally.h"
#include <QGraphicsView>
class GameScene;
class QGridLayout;
class QLabel;
class QRubberBand;
@ -21,7 +24,13 @@ private:
QRubberBand *rubberBand;
QLabel *dragCountLabel;
QLabel *totalCountLabel;
QWidget *tallyContainer;
QGridLayout *tallyLayout;
QPointF selectionOrigin;
QList<TallyRow> cachedTallyRows; ///< Cached entries to avoid redundant rebuilds
QSize rebuildTallyLabels(const QList<TallyRow> &entries);
void clearTallyLabels();
protected:
void resizeEvent(QResizeEvent *event) override;
@ -31,6 +40,7 @@ private slots:
void stopRubberBand();
void refreshShortcuts();
void updateTotalSelectionCount(const QSize &viewSize = QSize());
void setFocusDisabled(bool disabled);
public slots:
void updateSceneRect(const QRectF &rect);

View file

@ -1,6 +1,6 @@
#include "hand_counter.h"
#include "../game_graphics/zones/card_zone.h"
#include "zones/card_zone.h"
#include <QGraphicsSceneMouseEvent>
#include <QPainter>

Some files were not shown because too many files have changed in this diff Show more