Compare commits

...

108 commits

Author SHA1 Message Date
tooomm
9d4cf57c70
Merge branch 'master' into tooomm-qt5 2026-05-30 14:49:55 +02:00
RickyRister
6de55e9096
[Game][Arrow] Correctly call clear all arrows for player (#6951) 2026-05-28 23:51:12 -07:00
RickyRister
43c3bf5966
[Game][Arrow] Refactor: Rename arrow methods in GameScene (#6949)
* [Game][Arrow] Rename methods in GameScene

* move stuff around and docs
2026-05-28 02:32:40 -07:00
RickyRister
c4f4cece01
[Game] Show counter color icons in game log (#6948) 2026-05-28 11:15:30 +02:00
RickyRister
0d7047a728
[Game] Show color icons in counters menu (#6947) 2026-05-28 11:05:26 +02:00
RickyRister
7f30728f87
[CardDatabaseModel] Pass CardInfoPtr by const ref (#6940) 2026-05-26 18:53:20 -07:00
RickyRister
1d5d3f2d38
Run formatter on all our files (#6942) 2026-05-26 15:11:38 -07:00
BruebachL
b3c89167c5
[Game][Arrows] Hook up to the state zone change properly. (#6937)
Took 17 minutes

Took 3 seconds

Took 2 minutes

Took 10 minutes

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-05-25 08:19:27 +02:00
BruebachL
90ab663212
[Server][Game][Arrows] Properly notify clients when deleting arrows on card move and transform into (#6936)
* [Server][Game][Arrows] Properly notify clients when deleting arrows on card move and transform into

Took 15 minutes

* Observe "not found" response

Took 18 minutes

Took 4 seconds

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-05-25 08:19:17 +02:00
BruebachL
98c00c55ed
[Game][Arrows] ArrowItem should not send deletion game commands by itself (#6932)
Took 13 minutes

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-05-23 02:20:41 +02:00
BruebachL
d2164c3f08
[Oracle] Change basic lands exception to regex to deal with snow basics. (#6931)
* [Oracle] Change basic lands exception to regex to deal with snow basics.

Took 45 minutes

* Update oracle/src/oracleimporter.cpp

Co-authored-by: ebbit1q <ebbit1q@gmail.com>

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
Co-authored-by: ebbit1q <ebbit1q@gmail.com>
2026-05-22 22:36:53 +02:00
RickyRister
8004d4f2d4
[VDE] Disable filter dock widget (#6924) 2026-05-22 03:26:52 -07:00
BruebachL
8751f0605d
[Game][Arrows] Don't request deletion on inbound arrow deletion signal again but react to it locally (#6927)
* [Game][Arrows] Deleting arrows has no acknowledgement command so we have to delete locally as well.

Took 22 minutes

* Fix properly.

Took 15 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-05-22 10:25:13 +02:00
RickyRister
09d817770e
[Game] Refactor: move proliferate action code to PlayerActions (#6926) 2026-05-22 10:25:00 +02:00
DawnFire42
81a2712b92
Fix card stacking overflow when vertical zone has many cards (#6925)
Previously, minOffset (10px) was enforced unconditionally, causing card
  tops to overflow zone bounds when many cards were stacked. For example,
  50 cards in a 200px zone would place the last card's top at y=490.

  Now offsets compress below minOffset when necessary to keep all card
  tops visible. The constraint is guarded by !allowBottomOverflow to
  preserve future clipping zone semantics.
2026-05-21 22:56:24 -07:00
DawnFire42
8dca14933c
Centralize counter API with server-side bounds and no-op filtering (#6879)
* Refactor server counter API to own overflow protection and filter no-op events

  Counter modifications now clamp to int bounds server-side and return change
  status, allowing command handlers to skip network broadcasts when values
  don't actually change.

* Centralize MAX_COUNTERS_ON_CARD and enforce [0, 999] bounds on server

  - Move MAX_COUNTERS_ON_CARD to trice_limits.h
  - Server clamps values in setCounter() and incrementCounter()
  - Client uses clamped comparison to allow recovery from invalid states
  - Add tests for clamping behavior

* move incrementCount() implementation from header to cpp
2026-05-21 20:39:35 -07:00
tooomm
74102aa1ec
Update external c libs: SFMT & peglib (#6901) 2026-05-22 03:31:59 +02:00
ebbit1q
a9003be30f
update version to 3.1.0 for subsequent beta releases (#6921) 2026-05-22 01:35:01 +02:00
github-actions[bot]
6faa0d54e3
Update translation files (#6920)
Co-authored-by: github-actions <github-actions@github.com>
2026-05-21 19:14:58 -04:00
DawnFire42
33e0f8699b
Standardize Doxygen documentation (#6885) 2026-05-21 22:58:07 +02:00
tooomm
03d54265fe
escape ampersand (#6900) 2026-05-21 22:02:09 +02:00
BruebachL
491d1c9187
[Game][Arrows] Split Arrows into ArrowData and ArrowItem (#6918)
* [Game][Arrows] Split Arrows into ArrowData and ArrowItem

Took 13 minutes

Took 5 seconds

Took 1 minute

Took 26 seconds

* Address comments.

Took 17 minutes

Took 9 seconds


Took 1 minute

* Change check.

Took 3 minutes

* Pass by const reference.

Took 10 minutes

* Remove extra method

Took 2 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-05-21 20:31:14 +02:00
BruebachL
bddf9bd818
[Game][Counters] Split counters into AbstractCounter (graphics) and CounterState (logic) (#6917)
* [Counters] Split counters into graphics and logic states

Took 22 minutes

* Don't have widget hold pointer to state -> Copy what we need and subscribe to changes.

Took 12 minutes

Took 5 seconds

* Sync value too.

Took 3 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-05-21 20:16:28 +02:00
RickyRister
0549892092
[Settings] Regroup card layout settings (#6914) 2026-05-20 14:00:47 -07:00
RickyRister
10b9a65f17
[Server][Game] Make undo draw failure visible in chat (#6889)
* [Server][Game] Make undo draw failure visible in chat

* genericize the proto
2026-05-20 02:23:02 -07:00
BruebachL
5219cffa6b
[Player] Rename player to player logic (#6913)
Took 13 minutes

Took 6 seconds

Took 2 minutes

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-05-19 12:36:31 +02:00
RickyRister
71790d8e10
[Settings] Split printing settings into own group (#6906) 2026-05-19 03:34:42 -07:00
RickyRister
fe31a49f86
[Settings] Split appearance home tab settings into own group (#6905) 2026-05-19 03:33:25 -07:00
RickyRister
55c84ca860
[Settings] Clean up groupings in general settings (#6907) 2026-05-19 03:32:45 -07:00
ebbit1q
40b947c1e7
fix cards attaching/transforming to the wrong card across players (#6909)
* fix cards attaching/transforming to the wrong card across players

* move comment
2026-05-19 12:26:17 +02:00
ebbit1q
40cef0e436
fix resizing the visual card database loading more pages forever (#6884)
* fix resizing the vde loading more pages forever

* make near end of page const
2026-05-19 12:25:35 +02:00
BruebachL
9f1c225b7a
[Player] Stop reaching into graphics_item and emit signals instead for conceded and zoneId (#6912)
* [Player] Stop reaching into graphics_item and emit signals instead for conceded and zoneId

Took 7 minutes

Took 3 seconds

* Add sameValue check.

Took 3 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-05-19 12:22:40 +02:00
BruebachL
af2f888293
[Player] Pull handVisible out of player_info and let graphics_item just determine this on its own. (#6911)
Took 12 minutes


Took 15 seconds

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-05-19 12:10:19 +02:00
BruebachL
7a5b2e9f0e
[Game] Move state fields out of CardItem (#6904)
* [Game] Move state fields out of CardItem

Took 1 hour 2 minutes

* Move stuff into .cpp

Took 14 minutes

* Signals pass changed values as params

Took 2 minutes

* Comments.

Took 23 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-05-18 20:21:57 +02:00
RickyRister
021a9f8383
[DlgSettings] Refactor: split SettingsPage into separate files (#6899) 2026-05-18 01:59:02 -07:00
BruebachL
cba9ce2b2b
[Game/Zones] Simple move refactor to differentiate between logic and graphics for zones (#6903)
* [Game/Zones] Simple move refactor to differentiate between logic and graphics for zones

Took 21 minutes

* Clean up game/zones/logic folder.

Took 6 minutes

* Adjust tests.

Took 3 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-05-18 06:36:18 +02:00
SlightlyCircuitous
bb1a5b33a1
Add generic colorless mana symbol (#6873)
* Add generic colorless mana symbol

* Add generic colorless mana symbol
2026-05-17 19:48:48 +02:00
tooomm
22ee2d76f3
lint 2026-05-17 17:13:40 +02:00
BruebachL
6ac340026f
Update CMakeLists.txt (#6898) 2026-05-17 16:15:15 +02:00
tooomm
49328849f5
Remove 32bit leftovers 2026-05-17 16:11:26 +02:00
BruebachL
059eeebe89
[Settings] Fix typo for schemeComboLabel (#6897)
Took 6 minutes

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-05-17 10:12:34 +02:00
BruebachL
117ea543c5
[App/Theme] Palette Editor (#6877)
* [App/Theme] Palette Editor

Took 1 minute

Took 1 hour 47 minutes

Took 6 seconds

Took 3 minutes

Took 5 minutes


Took 3 minutes

* Add oracle, add palette files and configs.

Took 10 minutes

* Fix a stupid include mistake, thanks IDE

Took 3 minutes

Took 20 seconds

* Includes.

Took 4 minutes

* Fix ampersand not displaying correctly.

Took 14 minutes

* Longer variable names.

Took 10 minutes

Took 5 seconds

* Change ampersand everywhere

Took 23 seconds

* Doxygen properly.

Took 1 minute

* Remove namespace, fold I/O into structs.

Took 12 minutes

* Remove namespace, fold I/O into structs.

Took 33 seconds

* Alphabetize.

Took 35 seconds

* Lint.

Took 49 seconds

* Add a combo box to quick switch settings.

Took 19 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-05-17 10:08:00 +02:00
tooomm
989a5be23b
Enable RemoveSemicolon & update format.sh (#6896)
* Update .clang-format

* Add libs that got added in the meantime

* Use libcockatrice_*
2026-05-16 20:26:37 +02:00
BruebachL
f8ce5c2e39
[PictureLoader] Allow saving downloaded images to local storage and not just the QNetworkManager cache (#6620)
* [PictureLoader] Allow saving downloaded images to local storage and not just the QNetworkManager cache.

Took 1 hour 11 minutes

Took 4 seconds


Took 25 seconds

* Give people options from a dropdown.

Took 1 hour 6 minutes

Took 3 seconds

* Simplify directory removal code.

Took 5 minutes

Took 8 seconds

* Merge pull request #8

* Create new category for new settings

* Split off storage settings

Took 47 minutes

Took 4 seconds

* Allow toggling between caching methods.

Took 1 hour 30 minutes

Took 9 seconds

* Adjust settings dialog.

Took 5 minutes

Took 59 seconds

Took 22 seconds

Took 6 seconds

* tr() strings

Took 1 minute

Took 6 seconds

* Readjust layout, default naming scheme.

Took 5 minutes

* Add stretch.

Took 9 minutes

* Make scrollable.

Took 2 minutes

* Add icon.

Took 7 minutes

* Change naming to be uniform.

Took 3 minutes

Took 3 seconds

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
Co-authored-by: RickyRister <42636155+RickyRister@users.noreply.github.com>
2026-05-16 20:15:10 +02:00
ebbit1q
20cd7ce73d
fix arch compiling (#6895) 2026-05-16 19:39:34 +02:00
DawnFire42
aadee34238
style: Add braces to all control flow statements (#6887)
* style: Add braces to all control flow statements

  Standardize code style by adding explicit braces to all single-statement
  control flow blocks (if, else, for, while) across the entire codebase.

  Also documents the InsertBraces clang-format option (requires v15+) for
  future automated enforcement.

* InsertBraces-check-enabled
2026-05-16 19:19:53 +02:00
BruebachL
7153f7d4c1
[VDD] Fix minimum size by adding a compact mode to quickSettingsButtons (#6890)
* [VDD] Fix minimum size by adding a compact mode to quickSettingsButtons

Took 17 minutes

Took 5 seconds

* Fix and use FlowWidget/FlowLayout

Took 35 minutes

Took 4 seconds

* Set spacings.

Took 12 minutes

* Make VDE tools flow

Took 1 hour 23 minutes

Took 5 seconds

* Squeeze and flow even more.

Took 11 minutes

* Make pushbutton compact.

Took 54 minutes

Took 7 seconds

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-05-14 19:07:15 +02:00
RickyRister
762e742be0
[Game] Implement action to automatically take damage from creatures (#6869)
* [Game] Implement action to automatically take damage from creatures

* cleanup
2026-05-13 15:03:41 -07:00
RickyRister
67f6ab66f0
[DeckLoader] Don't modify text deck files on load (#6888)
* [DeckLoader] Don't modify text deck files on load

* formatter
2026-05-13 11:32:05 -07:00
tooomm
7507103bb2
CI: Update trigger for doc builds (#6871)
* Update trigger

* use `published` trigger

* narrow deploy step filter

* re-add deployments on manual runs
2026-05-13 14:54:47 +02:00
RickyRister
fe12f4cbb9
[VisualDeckEditor] Highlight searchEdit after add card (#6876) 2026-05-11 20:23:44 -07:00
RickyRister
d18f3bce47
[VisualDatabaseDisplayWidget] Refactor: Make all fields private (#6875) 2026-05-11 20:23:29 -07:00
RickyRister
b66743c83c
[DeckEditor] Refactor searchEdit highlighting after add card (#6874)
* [DeckEditor] Refactor searchEdit highlighting after add card

* make searchEdit private
2026-05-11 20:23:12 -07:00
DawnFire42
1a62f82aee
Refactor vertical card stacking with clip containers for variable zone sizes (#6774)
* Refactor vertical card stacking with opt-in overflow for variable zone sizes

Introduce a shared vertical stacking layout system in SelectZone that replaces the old divideCardSpaceInZone() free function with structured layout computation (StackLayoutParams, ZoneLayout, computeZoneLayout).

By default, cards are guaranteed to fit within zone bounds (no overflow). Zones can opt-in to bottom overflow via allowBottomOverflow flag, with sqrt-scaled compression for smooth visual transitions. A clip container mechanism is available for future zones that need visual clipping.

  Key changes:
  - SelectZone: new layout engine with allowBottomOverflow opt-in; clip container infrastructure for future zones needing visual clipping
  - StackZone: uses new layout (no overflow); adds setHeight() for dynamic resizing capabilities
  - HandZone: vertical layout delegates to SelectZone's shared stacking
  - AbstractCardItem: preserves hover z-value during layout passes; invalidates scene rect on hover exit for proper sibling repainting
  - CardZone::onCardAdded made virtual for clip container reparenting
  - Zone widths updated to CardDimensions::WIDTH_F * 1.5

* Changed anonymous namespace for static and braced functions

* CI tests re-run
2026-05-10 19:10:14 -07:00
tooomm
4ee0061154 Update gtest-CMakeLists.txt.in 2026-05-10 22:11:40 +02:00
tooomm
c058642269 lupdate path 2026-05-10 21:48:06 +02:00
tooomm
a034c2e0ad
fix linting 2026-05-10 16:35:05 +02:00
tooomm
e0b3edc9b8 Merge branch 'master' into tooomm-qt5 2026-05-10 16:03:54 +02:00
tooomm
5735a44a9a
Update Dockerfile to 26.04 (#6861) 2026-05-10 14:53:59 +02:00
BruebachL
dbaf5f2e05
[GameSelector] Don't conditionally initialize gamesListProxyModel (#6870)
Took 44 seconds

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-05-10 11:35:17 +02:00
RickyRister
9c53dad4b8
[Game] Refactor: add selectedCards method to GameScene (#6859) 2026-05-09 17:03:48 -07:00
RickyRister
7814204fe2
[Game] Refactor: explicitly pass params from card counter actions (#6780) 2026-05-09 13:20:33 -07:00
RickyRister
cdb171f201
[Game] Refactor: move setCardAttrHelper to PlayerEventHandler (#6772) 2026-05-09 13:10:56 -07:00
RickyRister
48e21aad38
[Game] Refactor: move parsePT to a static method (#6860) 2026-05-09 12:55:11 -07:00
tooomm
f223ff387e
bump vcpkg to 2026.04.27 release (#6855)
* bump vcpkg to 2026.04.27

* remove hint
2026-05-09 14:49:58 +02:00
RickyRister
8845a75627
[GameSelector] Fix bug with hideNotBuddyCreatedGames checkbox (#6858) 2026-05-09 04:08:30 -07:00
BruebachL
caf2bb9ded
Pull client networking out of window_main and into remote_connection_controller (#6796)
* Pull client networking out of window_main and into remote_connection_controller

Took 2 minutes

* Things.

Took 13 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-05-09 12:50:53 +02:00
RickyRister
985936a917
[GameModel] Refactor game filters options into struct (#6856) 2026-05-09 02:45:33 -07:00
ebbit1q
2c51054e77
update version number after update (#6853) 2026-05-08 20:40:39 -04:00
SlightlyCircuitous
f7eeaeddcb
Drop builds for Debian 11 and Ubuntu 22.04 (#6643)
* Delete .ci/Debian11 directory

* Delete .ci/Servatrice_Debian11 directory

* Delete .ci/Ubuntu22.04 directory

* Update desktop-build.yml

* Update release_template.md

* Add servatrice debian 12

* Update desktop-build.yml
2026-05-09 00:28:03 +02:00
SlightlyCircuitous
6cace2a8e6
Bump minimum required cmake to 3.10 in gtest (#6851)
* Bump minimum_required to 3.5 and GoogleTest to 1.12

GoogleTest 1.12 is the oldest version that sets cmake_minimum_required() to 3.5 in the CMakeLists files it provides

* code style changes

* Use 1.17.0

* Set minimum to 3.10 to make top-level CMakeLists

* New hash

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

* Update cmake/gtest-CMakeLists.txt.in

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

---------

Co-authored-by: tooomm <tooomm@users.noreply.github.com>
Co-authored-by: ebbit1q <ebbit1q@gmail.com>
2026-05-09 00:05:10 +02:00
tooomm
6b5f341e10
Fix toc link (#6854) 2026-05-09 00:04:16 +02:00
ZeldaZach
63143f9416
Fix triplet source for macOS13 Intel 2026-05-08 11:02:50 -04:00
ZeldaZach
efe52b5412
Fix windows copying over CMakeFiles to themes 2026-05-08 10:28:14 -04:00
ZeldaZach
0e014c0e5c
Use dash instead of underscore 2026-05-08 10:13:40 -04:00
ZeldaZach
511ccae738
Replace spaces with underscore for release name file names 2026-05-08 10:10:39 -04:00
Zach H
0672603755
Support spaces in release name (#6849) 2026-05-08 09:53:33 -04:00
Zach H
f5f326f65b
Update Repo URL for Webatrice 2026-05-08 09:22:17 -04:00
Jeremy Letto
c5702cc8b6
Remove webclient (extracted to Webatrice repo) (#6848)
The webclient has been extracted to https://github.com/seavor/Webatrice
and the Playwright e2e suite has moved to Sockatrice. Cockatrice keeps
no copy.

Deleted:
- webclient/ (entire tree, 349 files)
- .github/workflows/web-build.yml, web-lint.yml
- .husky/pre-commit (the only tracked husky hook; managed entirely
  from webclient's package.json which no longer exists)

Edited:
- .tx/config: dropped the webclient resource block; Webatrice/.tx/config
  now manages its own translations
- .github/workflows/translations-pull.yml: removed webclient locales
  from add-paths
- .github/workflows/desktop-build.yml, desktop-lint.yml: removed dead
  '!webclient/**' and '!.husky/**' path-filter exclusions
- .github/dependabot.yml: removed commented-out npm/webclient block
- Doxyfile: removed webclient/ from EXCLUDE list
- .ci/release_template.md: dropped Webatrice section (Webatrice now
  cuts its own releases)
- README.md: dropped 'first work on a webclient' line, added Webatrice
  to Related Repositories, updated translation paragraph and build
  badges

History preserved: every webclient commit remains recoverable via
git log on master before this commit.
2026-05-08 09:14:53 -04:00
BruebachL
4f2f942121
Use Qt 6.11.0 (#6846)
* Use Qt 6.10.*

Took 3 hours 1 minute

* Remove workaround.

Took 10 minutes

* Pin to 6.11.0 for now.

Took 3 minutes

* Revert "Remove workaround."

This reverts commit 71584d1e50.


Took 4 seconds

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-05-08 15:10:17 +02:00
BruebachL
a4c2b1411f
Utility method to check if a theme is supposed to be in dark or light mode. (#6785)
* Utility method to check if a theme is supposed to be in dark or light mode.

Took 22 minutes

Took 4 seconds

* Method is public.

Took 3 minutes

* Add a utility method to check if we're using a built-in theme

Took 3 minutes

Took 3 seconds

* Use built-in theme detection for home screen.

Took 6 minutes

* Re-polish on theme change

Took 2 minutes

* Fetch background on theme change.

Took 4 minutes

Took 6 seconds

* No need to double polish.

Took 4 minutes

* No need to repaint.

Took 32 seconds

* Only repolish visible widgets.

Took 5 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-05-08 13:47:14 +02:00
DawnFire42
43bee2316e
Fix Fedora 44 build: suppress -Werror=sfinae-incomplete for GCC 16+ (#6843)
Qt MOC and protobuf forward declarations trigger this warning in GCC 16.
  Re-enable Fedora 44 CI build now that it compiles successfully.
2026-05-06 01:00:13 +02:00
RickyRister
19dbb17fb9
[SettingsManager] Properly handle multithreaded access again (#6844)
* [SettingsManager] Properly handle multithreaded access again

* Add comment

* Add batch write function

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-05-05 21:03:01 +02:00
BruebachL
7c9fbe2be0
Don't queue connection (#6842)
Took 5 minutes

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-05-04 15:44:48 +02:00
BruebachL
d30690236a
Reload card db and notify enabled sets change on "Manage Sets" dialog save (#6837)
* Reload card db and notify enabled sets change on "Manage Sets" dialog save

Took 1 hour 18 minutes

Took 6 seconds

* Extract to method, also notify on "Reload db" and "new sets found"

Took 3 minutes

Took 4 seconds

* Add an "always enable new sets" fuse to "new sets found" dialog

Took 11 minutes

* Always debounce modelDirty() with dirty() timer.

Took 29 minutes

Took 3 minutes

* Performance improvements for settings by not constructing a new settings object on every single set() call (this forced a sync to/from fs but it seems fine to just rely on Qts own periodic sync?)

Took 23 minutes

Took 3 seconds

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-05-03 03:11:10 +02:00
tooomm
ac2e995f15
add annotations level (#6840) 2026-04-30 23:51:53 +02:00
SlightlyCircuitous
dac611f0f1
Add Fedora 44 build and drop Fedora 42 build (#6836)
* Add Fedora 44; Drop Fedora 42

* Delete .ci/Fedora42 directory

* Create Dockerfile

* Update release_template.md

* Skip debug
2026-04-30 21:09:49 +02:00
ebbit1q
45ab2602c6
ensure judges are omniscient when not spectating (#6831) 2026-04-30 14:31:28 +02:00
tooomm
5101cc3d74
CI: Ensure Docker runs + publishes images only on stable releases (#6839)
* Update docker-release.yml

* Update docker-release.yml

* Update docker-release.yml

* Update docker-release.yml

* Update docker-release.yml
2026-04-30 14:30:51 +02:00
BruebachL
9ac9a0c73a
[Game] Add a coinflip shortcut. (#6829)
Took 18 minutes

Took 6 seconds

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-04-29 22:09:30 +02:00
BruebachL
314a577807
Do not allow users to remove VDE functionality by closing tabs (#6838)
* Do not allow users to remove VDE functionality by closing tabs.

Took 6 minutes

* Set filter toolbar visible on delayed initialization.

Took 5 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-04-29 12:37:47 +02:00
tooomm
c5ace60f26
Bump Doxygen to 1.16.1 (#6752)
* Bump Doxygen version to 1.16.1

* Update Doxyfile

* Enable parallel processing
2026-04-29 09:40:18 +02:00
transifex-integration[bot]
8953ae3c67
Updates for project Cockatrice and language de (#6833)
* Translate cockatrice/cockatrice_en@source.ts in de

100% translated source file: 'cockatrice/cockatrice_en@source.ts'
on 'de'.

* Translate cockatrice/cockatrice_en@source.ts in de

100% translated source file: 'cockatrice/cockatrice_en@source.ts'
on 'de'.

---------

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2026-04-27 21:16:10 +02:00
ebbit1q
b1fe4c85d3
fix sending decks to tappedout (#6832)
Some checks failed
Build Desktop / Configure (push) Has been cancelled
Build Docker Image / amd64 & arm64 (push) Has been cancelled
Build Desktop / Debian 11 (push) Has been cancelled
Build Desktop / Debian 13 (push) Has been cancelled
Build Desktop / Debian 12 (push) Has been cancelled
Build Desktop / Fedora 43 (push) Has been cancelled
Build Desktop / Fedora 42 (push) Has been cancelled
Build Desktop / Servatrice_Debian 11 (push) Has been cancelled
Build Desktop / Ubuntu 24.04 (push) Has been cancelled
Build Desktop / Ubuntu 26.04 (push) Has been cancelled
Build Desktop / Ubuntu 22.04 (push) Has been cancelled
Build Desktop / Arch (push) Has been cancelled
Build Desktop / macOS 14 (push) Has been cancelled
Build Desktop / macOS 15 (push) Has been cancelled
Build Desktop / macOS 13 Intel (push) Has been cancelled
Build Desktop / macOS 15 Debug (push) Has been cancelled
Build Desktop / Windows 10 (push) Has been cancelled
2026-04-23 02:28:12 +02:00
Vorliz
a20f3c0fb4
Fix #6659: Correct logging for bottom-of-library card moves (#6764)
Some checks failed
Build Desktop / Configure (push) Has been cancelled
Build Docker Image / amd64 & arm64 (push) Has been cancelled
Build Desktop / Debian 11 (push) Has been cancelled
Build Desktop / Debian 13 (push) Has been cancelled
Build Desktop / Debian 12 (push) Has been cancelled
Build Desktop / Fedora 43 (push) Has been cancelled
Build Desktop / Fedora 42 (push) Has been cancelled
Build Desktop / Servatrice_Debian 11 (push) Has been cancelled
Build Desktop / Ubuntu 24.04 (push) Has been cancelled
Build Desktop / Ubuntu 26.04 (push) Has been cancelled
Build Desktop / Ubuntu 22.04 (push) Has been cancelled
Build Desktop / Arch (push) Has been cancelled
Build Desktop / macOS 14 (push) Has been cancelled
Build Desktop / macOS 15 (push) Has been cancelled
Build Desktop / macOS 13 Intel (push) Has been cancelled
Build Desktop / macOS 15 Debug (push) Has been cancelled
Build Desktop / Windows 10 (push) Has been cancelled
* Fix #6659:  Correct logging for bottom-of-library card moves

Cause:
- This issue happens due to logic of moving the card from the top of the
  deck being reused when moving from the bottom of the deck, in a way
  that makes it impossible to check if the card came from the bottom.

Resolution:
- Updated the logging logic in the client for card moves.
- Added a gRPC parameter ('is_from_bottom') for card moves.
- Updates the server logic to reverse the order of the card move if the
'is_from_bottom' parameter is true.
- Added a test to show the expected behaviour of the fix.

NOTE: While the changes in this patch seem big, this is due to changing
the loop in the moveCard function to a helper function, in order to make
the bug fix change. The only change to the loop was to pass a
variable attribution to the moveCard function because it was redundant
to be in the loop.

* chore: run format on test

* refactor: new way to check if a move is from the bottom of the deck

* refactor: change isFromBottom check to static function

* update comments

Co-authored-by: ebbit1q <ebbit1q@gmail.com>

---------

Co-authored-by: ebbit1q <ebbit1q@gmail.com>
2026-04-21 19:05:31 +02:00
BruebachL
9226bc9ddd
[TabArchidekt] Place sideboard categories into the sideboard. (#6824)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 11 (push) Blocked by required conditions
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Fedora 42 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 11 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 22.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
Took 35 minutes

Took 3 seconds

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-04-21 08:09:39 +02:00
ebbit1q
501c4b96d4
clear all players when closing game tab (#6828)
this prevents issues caused by items in play when using the player
destructor in the wrong order
2026-04-21 08:09:20 +02:00
ebbit1q
6ab947418c
add an isEmpty method to printing info (#6805)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 11 (push) Blocked by required conditions
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Fedora 42 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 11 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 22.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
no reason, I just find it easier to understand
2026-04-21 01:26:08 +02:00
BruebachL
6765831b92
Change button colors to be palette aware. (#6821)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 11 (push) Blocked by required conditions
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Fedora 42 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 11 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 22.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
* Change button colors to be palette aware.

Took 13 minutes


Took 41 seconds

Took 15 seconds

* Change button style.

Took 24 minutes

Took 4 seconds

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-04-20 17:47:15 +02:00
BruebachL
98c4e829f8
[GameSelector/Filters] Properly sync toolbar and dialog. (#6822)
Took 59 minutes

Took 2 minutes

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-04-20 13:19:29 +02:00
BruebachL
ccb9901b28
[DeckAnalytics] Add a checkbox to include/exclude sideboard for calculation (#6823)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 11 (push) Blocked by required conditions
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Fedora 42 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 11 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 22.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
* [DeckAnalytics] Add a checkbox to include/exclude sideboard for calculation

Took 15 minutes

* default to false, actually

Took 2 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-04-19 23:08:56 +02:00
tooomm
58a8c7d3df
CI: Use ubuntu-slim for simple jobs (#6798)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 11 (push) Blocked by required conditions
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Fedora 42 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 11 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 22.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
2026-04-19 02:47:14 +02:00
ebbit1q
655a8e52a1 update release name 2026-04-19 01:15:58 +02:00
ebbit1q
db235ecae8 update version number to 3.0.0 2026-04-19 01:11:00 +02:00
BruebachL
682ac4ed0c
Add -R option in Windows NSIS script for silent upgrade (#6818)
* Add -R option in Windows NSIS script

Took 23 minutes

* Small fix.

Took 3 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-04-19 01:07:38 +02:00
ebbit1q
77978c7178
stop prepending the adventure side (#6819)
the adventure side of adventure cards used to be the first entry in
mtgjson, at some point this changed to the second entry, better
reflecting its secondary nature, however cockatrice has hardcoded the
treatment of the card to assume the second part was the "main" part,
when implementing the treatment of prepare layout cards (#6792) I
copied the behavior over which was then already incorrect, this pr
removes the special treatment of this card layout bringing the result
back in line with expectation: the main part of the card provides the
main type used in cockatrice for sorting and behavior
2026-04-19 01:03:49 +02:00
ebbit1q
e918856fa4
clear player before removing it from the scene (#6817)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 11 (push) Blocked by required conditions
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Fedora 42 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 11 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 22.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
revert #6740
2026-04-18 12:50:53 +02:00
github-actions[bot]
87c4216e80
Update translation files (#6813)
Some checks failed
Build Desktop / Configure (push) Has been cancelled
Build Docker Image / amd64 & arm64 (push) Has been cancelled
Build Desktop / Debian 11 (push) Has been cancelled
Build Desktop / Debian 13 (push) Has been cancelled
Build Desktop / Debian 12 (push) Has been cancelled
Build Desktop / Fedora 43 (push) Has been cancelled
Build Desktop / Fedora 42 (push) Has been cancelled
Build Desktop / Servatrice_Debian 11 (push) Has been cancelled
Build Desktop / Ubuntu 24.04 (push) Has been cancelled
Build Desktop / Ubuntu 26.04 (push) Has been cancelled
Build Desktop / Ubuntu 22.04 (push) Has been cancelled
Build Desktop / Arch (push) Has been cancelled
Build Desktop / macOS 14 (push) Has been cancelled
Build Desktop / macOS 15 (push) Has been cancelled
Build Desktop / macOS 13 Intel (push) Has been cancelled
Build Desktop / macOS 15 Debug (push) Has been cancelled
Build Desktop / Windows 10 (push) Has been cancelled
Co-authored-by: github-actions <github-actions@github.com>
2026-04-15 20:41:45 +02:00
1022 changed files with 40931 additions and 82724 deletions

View file

@ -1,26 +0,0 @@
FROM debian:11
RUN apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
build-essential \
ccache \
clang-format \
cmake \
file \
g++ \
git \
liblzma-dev \
libmariadb-dev-compat \
libprotobuf-dev \
libqt5multimedia5-plugins \
libqt5sql5-mysql \
libqt5svg5-dev \
libqt5websockets5-dev \
ninja-build \
protobuf-compiler \
qt5-image-formats-plugins \
qtmultimedia5-dev \
qttools5-dev \
qttools5-dev-tools \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*

View file

@ -1,4 +1,4 @@
FROM fedora:42
FROM fedora:44
RUN dnf install -y \
ccache \

View file

@ -1,4 +1,4 @@
FROM debian:11
FROM debian:12
RUN apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
@ -11,11 +11,11 @@ RUN apt-get update && \
git \
libmariadb-dev-compat \
libprotobuf-dev \
libqt5sql5-mysql \
libqt5websockets5-dev \
libqt6sql6-mysql \
ninja-build \
protobuf-compiler \
qttools5-dev \
qttools5-dev-tools \
qt6-tools-dev \
qt6-tools-dev-tools \
qt6-websockets-dev \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*

View file

@ -1,26 +0,0 @@
FROM ubuntu:22.04
RUN apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
build-essential \
ccache \
clang-format \
cmake \
file \
g++ \
git \
liblzma-dev \
libmariadb-dev-compat \
libprotobuf-dev \
libqt5multimedia5-plugins \
libqt5sql5-mysql \
libqt5svg5-dev \
libqt5websockets5-dev \
ninja-build \
protobuf-compiler \
qt5-image-formats-plugins \
qtmultimedia5-dev \
qttools5-dev \
qttools5-dev-tools \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*

View file

@ -205,7 +205,11 @@ if [[ $RUNNER_OS == macOS ]]; then
arch="x64"
fi
mkdir -p "$triplets_dir"
cp "../vcpkg/triplets/$arch-osx.cmake" "$triplet_file"
triplet_source="../vcpkg/triplets/$arch-osx.cmake"
if [[ ! -f "$triplet_source" ]]; then
triplet_source="../vcpkg/triplets/community/$arch-osx.cmake"
fi
cp "$triplet_source" "$triplet_file"
echo "set(VCPKG_CMAKE_SYSTEM_VERSION $TARGET_MACOS_VERSION)" >>"$triplet_file"
echo "set(VCPKG_OSX_DEPLOYMENT_TARGET $TARGET_MACOS_VERSION)" >>"$triplet_file"
flags+=("-DVCPKG_OVERLAY_TRIPLETS=$triplets_dir")

View file

@ -89,6 +89,8 @@ else
echo "'$previous' to '$TAG' ($count commits)"
# --> is the markdown comment escape sequence, emojis are way better
generated_list="${generated_list//-->/→}"
# Escape & to preserve it from commit message into markdown output
generated_list="${generated_list//&/\\&}"
body="${body//--REPLACE-WITH-GENERATED-LIST--/$generated_list}"
body="${body//--REPLACE-WITH-COMMIT-COUNT--/$count}"
body="${body//--REPLACE-WITH-PREVIOUS-RELEASE-TAG--/$previous}"

View file

@ -19,12 +19,10 @@ Available pre-compiled binaries for installation:
<b>Linux</b>
<kbd>Ubuntu 26.04 LTS</kbd> <sub><i>Resolute Racoon</i></sub>
<kbd>Ubuntu 24.04 LTS</kbd> <sub><i>Noble Numbat</i></sub>
<kbd>Ubuntu 22.04 LTS</kbd> <sub><i>Jammy Jellyfish</i></sub>
<kbd>Debian 13</kbd> <sub><i>Trixie</i></sub>
<kbd>Debian 12</kbd> <sub><i>Bookworm</i></sub>
<kbd>Debian 11</kbd> <sub><i>Bullseye</i></sub>
<kbd>Fedora 44</kbd>
<kbd>Fedora 43</kbd>
<kbd>Fedora 42</kbd>
<sub>We are also packaged in <kbd>Arch Linux</kbd>'s <a href="https://archlinux.org/packages/extra/x86_64/cockatrice">official extra repository</a>, courtesy of @FFY00.</sub>
<sub>General Linux support is available via a <kbd>flatpak</kbd> package at <a href="https://flathub.org/apps/io.github.Cockatrice.cockatrice">Flathub</a>!</sub>
@ -85,7 +83,6 @@ Remove empty headers when done.
### Under the Hood
### Oracle
### Servatrice
### Webatrice
</details>

View file

@ -31,7 +31,7 @@ if [[ ! -e $FILE ]]; then
fi
# print version
if ! lupdate -version; then
if ! /usr/lib/qt6/bin/lupdate -version; then
echo "failed to run lupdate" >&2
exit 4;
fi
@ -39,7 +39,7 @@ fi
# run lupdate, duplicating the output in stderr and saving it
# for convenience we ignore that $DIRS will be split on spaces
# shellcheck disable=SC2086
if ! got="$(lupdate $DIRS -ts "$FILE" | tee /dev/stderr)"; then
if ! got="$(/usr/lib/qt6/bin/lupdate $DIRS -ts "$FILE" | tee /dev/stderr)"; then
echo "failed to update $FILE with $DIRS" >&2
exit 4;
fi

View file

@ -3,7 +3,9 @@ AccessModifierOffset: -4
ColumnLimit: 120
---
Language: Cpp
BreakBeforeBraces: Custom
AllowAllParametersOfDeclarationOnNextLine: false
AllowShortFunctionsOnASingleLine: None
BinPackParameters: false
BraceWrapping:
AfterClass: true
AfterControlStatement: false
@ -18,16 +20,14 @@ BraceWrapping:
SplitEmptyFunction: true
SplitEmptyRecord: true
SplitEmptyNamespace: true
AllowShortFunctionsOnASingleLine: None
BinPackParameters: false
AllowAllParametersOfDeclarationOnNextLine: false
IndentCaseLabels: true
PointerAlignment: Right
SortIncludes: true
BreakBeforeBraces: Custom
IncludeBlocks: Regroup
IndentCaseLabels: true
InsertBraces: true
PointerAlignment: Right
RemoveSemicolon: true
SortIncludes: true
StatementAttributeLikeMacros: [emit]
# requires clang-format 16
# RemoveSemicolon: true
---
Language: Proto
AllowShortFunctionsOnASingleLine: None

View file

@ -209,6 +209,16 @@ nowadays and clean it up for you.
Lines should be 120 characters or less. Please break up lines that are too long
into smaller parts, for example at spaces or after opening a brace.
### Documentation Comments ###
Use [Doxygen](https://www.doxygen.nl/) for code documentation:
- **Doc blocks**: Use `/** @brief Description */` (Javadoc-style), not `///`
- **Member comments**: Use trailing `///<` for inline member documentation
- **TODOs**: Use `//! \todo Description` (Qt-style), Doxygen collects them into a Todo List
(uses [Qt-style comments](https://www.doxygen.nl/manual/docblocks.html) with
Doxygen's [\todo command](https://www.doxygen.nl/manual/commands.html#cmdtodo))
### Memory Management ###
New code should be written using references over pointers and stack allocation

View file

@ -10,7 +10,7 @@ updates:
# Look for `.gitmodules` in the `root` directory
directory: "/"
ignore:
# Ignore updates for vcpkg (Bump to latest tag not working (no SemVer used) & macOS Intel triplet broken with newer releases)
# Ignore updates for vcpkg (Bump to latest tag not working (no SemVer used)
- dependency-name: "vcpkg"
# Check for updates once a month
schedule:
@ -39,13 +39,3 @@ updates:
interval: "weekly"
# Limit the amout of open PR's (default = 5, disabled = 0, security updates are not impacted)
open-pull-requests-limit: 2
# # Enable version updates for npm
# - package-ecosystem: "npm"
# # Look for `package.json` and `lock` files in the `webclient` subdirectory
# directory: "/webclient"
# # Check the npm registry for updates once a week
# schedule:
# interval: "weekly"
# # Limit the amout of open PR's (default = 5, disabled = 0, security updates are not impacted)
# open-pull-requests-limit: 5

View file

@ -14,10 +14,8 @@ on:
- '*/**' # matches all files not in root
- '!**.md'
- '!.github/**'
- '!.husky/**'
- '!.tx/**'
- '!doc/**'
- '!webclient/**'
- '.github/workflows/desktop-build.yml'
- 'CMakeLists.txt'
- 'vcpkg.json'
@ -29,10 +27,8 @@ on:
- '*/**' # matches all files not in root
- '!**.md'
- '!.github/**'
- '!.husky/**'
- '!.tx/**'
- '!doc/**'
- '!webclient/**'
- '.github/workflows/desktop-build.yml'
- 'CMakeLists.txt'
- 'vcpkg.json'
@ -46,7 +42,7 @@ concurrency:
jobs:
configure:
name: Configure
runs-on: ubuntu-latest
runs-on: ubuntu-slim
outputs:
tag: ${{steps.configure.outputs.tag}}
sha: ${{steps.configure.outputs.sha}}
@ -111,12 +107,8 @@ jobs:
package: skip # We are packaged in Arch already
allow-failure: yes
- distro: Debian
version: 11
package: DEB
- distro: Servatrice_Debian
version: 11
version: 12
package: DEB
test: skip
server_only: yes
@ -131,22 +123,18 @@ jobs:
package: DEB
- distro: Fedora
version: 42
version: 43
package: RPM
test: skip # Running tests on all distros is superfluous
- distro: Fedora
version: 43
version: 44
package: RPM
- distro: Ubuntu
version: 22.04
package: DEB
test: skip # Running tests on all distros is superfluous
- distro: Ubuntu
version: 24.04
package: DEB
test: skip # Running tests on all distros is superfluous
- distro: Ubuntu
version: 26.04
@ -265,7 +253,7 @@ jobs:
shell: bash
env:
GH_TOKEN: ${{github.token}}
run: gh attestation verify ${{steps.build.outputs.path}} --repo Cockatrice/Cockatrice
run: gh attestation verify "${{steps.build.outputs.path}}" --repo Cockatrice/Cockatrice
build-vcpkg:
strategy:
@ -281,7 +269,7 @@ jobs:
override_target: 13
make_package: 1
package_suffix: "-macOS13_Intel"
qt_version: 6.11.*
qt_version: 6.11.0
qt_arch: clang_64
qt_modules: qtimageformats qtmultimedia qtwebsockets
cmake_generator: Ninja
@ -296,7 +284,7 @@ jobs:
type: Release
make_package: 1
package_suffix: "-macOS14"
qt_version: 6.11.*
qt_version: 6.11.0
qt_arch: clang_64
qt_modules: qtimageformats qtmultimedia qtwebsockets
cmake_generator: Ninja
@ -311,7 +299,7 @@ jobs:
type: Release
make_package: 1
package_suffix: "-macOS15"
qt_version: 6.11.*
qt_version: 6.11.0
qt_arch: clang_64
qt_modules: qtimageformats qtmultimedia qtwebsockets
cmake_generator: Ninja
@ -324,7 +312,7 @@ jobs:
soc: Apple
xcode: "16.4"
type: Debug
qt_version: 6.11.*
qt_version: 6.11.0
qt_arch: clang_64
qt_modules: qtimageformats qtmultimedia qtwebsockets
cmake_generator: Ninja
@ -337,7 +325,7 @@ jobs:
type: Release
make_package: 1
package_suffix: "-Win10"
qt_version: 6.11.*
qt_version: 6.11.0
qt_arch: win64_msvc2022_64
qt_modules: qtimageformats qtmultimedia qtwebsockets
cmake_generator: "Visual Studio 17 2022"
@ -494,7 +482,7 @@ jobs:
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 "${{steps.build.outputs.path}}"
fi
- name: Notarize app bundle
@ -514,7 +502,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 "${{steps.build.outputs.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
@ -526,7 +514,7 @@ 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 "${{steps.build.outputs.path}}"
fi
- name: Upload artifact
@ -573,4 +561,4 @@ jobs:
shell: bash
env:
GH_TOKEN: ${{github.token}}
run: gh attestation verify ${{steps.build.outputs.path}} --repo Cockatrice/Cockatrice
run: gh attestation verify "${{steps.build.outputs.path}}" --repo Cockatrice/Cockatrice

View file

@ -8,10 +8,8 @@ on:
- '!**.md'
- '!.ci/**'
- '!.github/**'
- '!.husky/**'
- '!.tx/**'
- '!doc/**'
- '!webclient/**'
- '.ci/lint_cpp.sh'
- '.github/workflows/desktop-lint.yml'
- '.clang-format'
@ -20,13 +18,13 @@ on:
jobs:
format:
runs-on: ubuntu-22.04
runs-on: ubuntu-slim
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 20 # should be enough to find merge base
fetch-depth: 20 # should be enough to find merge base
- name: Install dependencies
shell: bash

View file

@ -1,9 +1,10 @@
name: Build Docker Image
on:
release:
types:
- released # publishing of stable releases
push:
tags:
- '*Release*'
branches:
- master
pull_request:
@ -16,16 +17,18 @@ on:
# Cancel earlier, unfinished runs of this workflow on the same branch (unless on release)
concurrency:
group: "${{ github.workflow }} @ ${{ github.ref_name }}"
cancel-in-progress: ${{ github.ref_type != 'tag' }}
cancel-in-progress: ${{ github.event_name != 'release' }}
jobs:
docker:
name: amd64 & arm64
if: ${{ github.repository_owner == 'Cockatrice' }}
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout
uses: actions/checkout@v6
@ -33,6 +36,8 @@ jobs:
- name: Docker metadata
id: metadata
uses: docker/metadata-action@v6
env:
DOCKER_METADATA_ANNOTATIONS_LEVELS: index # needed for GHCR
with:
images: |
ghcr.io/cockatrice/servatrice
@ -52,7 +57,7 @@ jobs:
uses: docker/setup-buildx-action@v4
- name: Login to GitHub Container Registry
if: github.ref_type == 'tag'
if: contains(github.event.release.tag_name, 'Release') && github.event.release.target_commitish == 'master'
uses: docker/login-action@v4
with:
registry: ghcr.io

View file

@ -1,9 +1,9 @@
name: Generate Docs
on:
push:
tags:
- '*' # Only re-generate docs when a new tagged version is pushed
release:
types:
- published # publishing of stable releases and pre-releases
pull_request:
paths:
- 'doc/doxygen/**'
@ -33,7 +33,7 @@ jobs:
- name: Install Doxygen
uses: ssciwr/doxygen-install@v2
with:
version: "1.14.0"
version: "1.16.1"
- name: Update Doxygen Configuration
run: |
@ -53,11 +53,11 @@ jobs:
run: doxygen Doxyfile
- name: Deploy to cockatrice.github.io
if: github.event_name != 'pull_request'
if: github.event_name == 'release' || github.event_name == 'workflow_dispatch'
uses: peaceiris/actions-gh-pages@v4
with:
deploy_key: ${{ secrets.DOCS_DEPLOY_KEY }}
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/
destination_dir: docs # Docs will live under https://cockatrice.github.io/docs/

View file

@ -16,7 +16,7 @@ jobs:
if: github.event_name != 'schedule' || github.repository_owner == 'Cockatrice'
name: Pull languages
runs-on: ubuntu-latest
runs-on: ubuntu-slim
steps:
- name: Checkout repo
@ -38,7 +38,6 @@ jobs:
add-paths: |
cockatrice/translations/*.ts
oracle/translations/*.ts
webclient/public/locales/*/translation.json
commit-message: Update translation files
# author is the owner of the commit
author: github-actions <github-actions@github.com>

View file

@ -16,7 +16,7 @@ jobs:
if: github.event_name != 'schedule' || github.repository_owner == 'Cockatrice'
name: Push strings
runs-on: ubuntu-latest
runs-on: ubuntu-slim
steps:
- name: Checkout repo
@ -27,7 +27,6 @@ jobs:
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends qt6-l10n-tools
lupdate -version
- name: Update Cockatrice translation source
id: cockatrice
@ -49,7 +48,7 @@ jobs:
- name: Render template
id: template
uses: chuhlomin/render-template@v1
uses: chuhlomin/render-template/binary@v1
with:
template: .ci/update_translation_source_strings_template.md
vars: |

View file

@ -1,54 +0,0 @@
name: Build Web
on:
push:
branches:
- master
paths:
- '.husky/**'
- 'webclient/**'
- '!**.md'
- '.github/workflows/web-build.yml'
pull_request:
paths:
- '.husky/**'
- 'webclient/**'
- '!**.md'
- '.github/workflows/web-build.yml'
jobs:
build-web:
name: React (Node ${{matrix.node_version}})
runs-on: ubuntu-latest
defaults:
run:
working-directory: webclient
strategy:
fail-fast: false
matrix:
node_version:
- 16
- lts/*
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: ${{matrix.node_version}}
cache: 'npm'
cache-dependency-path: 'webclient/package-lock.json'
- name: Install dependencies
run: npm clean-install
- name: Build app
run: npm run build
- name: Test app
run: npm run test

View file

@ -1,33 +0,0 @@
name: Code Style (TypeScript)
on:
# push trigger not needed for linting, we do not allow direct pushes to master
pull_request:
paths:
- 'webclient/**'
- '!**.md'
- '.github/workflows/web-lint.yml'
jobs:
ESLint:
runs-on: ubuntu-latest
defaults:
run:
working-directory: webclient
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v6
with:
cache: 'npm'
cache-dependency-path: 'webclient/package-lock.json'
- name: Install ESLint
run: npm clean-install --ignore-scripts
- name: Run ESLint
run: npm run lint

View file

@ -1,7 +0,0 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
cd webclient
npm run translate
git add src/i18n-default.json

View file

@ -16,11 +16,3 @@ source_file = oracle/oracle_en@source.ts
file_filter = oracle/translations/oracle_<lang>.ts
type = QT
minimum_perc = 10
[o:cockatrice:p:cockatrice:r:webclient-src-i18n-default-json--master]
resource_name = Webclient
source_lang = en
source_file = webclient/src/i18n-default.json
file_filter = webclient/public/locales/<lang>/translation.json
type = KEYVALUEJSON
minimum_perc = 10

View file

@ -72,11 +72,11 @@ endif()
# A project name is needed for CPack
# Version can be overriden by git tags, see cmake/getversion.cmake
project("Cockatrice" VERSION 2.11.0)
project("Cockatrice" VERSION 3.1.0)
# Set release name if not provided via env/cmake var
if(NOT DEFINED GIT_TAG_RELEASENAME)
set(GIT_TAG_RELEASENAME "Omenpath")
set(GIT_TAG_RELEASENAME "Graduation Day")
endif()
# Use c++20 for all targets
@ -172,6 +172,7 @@ elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
-Wno-error=delete-non-virtual-dtor
-Wno-error=sign-compare
-Wno-error=missing-declarations
-Wno-error=sfinae-incomplete # GCC 16+: Qt MOC + protobuf forward decls trigger this
)
foreach(FLAG ${ADDITIONAL_DEBUG_FLAGS})

View file

@ -1,5 +1,5 @@
# -------- Build Stage --------
FROM ubuntu:24.04 AS build
FROM ubuntu:26.04 AS build
ARG DEBIAN_FRONTEND=noninteractive
@ -26,7 +26,7 @@ RUN mkdir build && cd build && \
# -------- Runtime Stage (clean) --------
FROM ubuntu:24.04
FROM ubuntu:26.04
RUN apt-get update && apt-get install -y --no-install-recommends \
libprotobuf32t64 \

View file

@ -1,4 +1,4 @@
# Doxyfile 1.14.0
# Doxyfile 1.16.1
# This file describes the settings to be used by the documentation system
# Doxygen (www.doxygen.org) for a project.
@ -361,6 +361,20 @@ EXTENSION_MAPPING =
MARKDOWN_SUPPORT = YES
# If the MARKDOWN_STRICT tag is enabled then Doxygen treats text in comments as
# Markdown formatted also in cases where Doxygen's native markup format
# conflicts with that of Markdown. This is only relevant in cases where
# backticks are used. Doxygen's native markup style allows a single quote to end
# a text fragment started with a backtick and then treat it as a piece of quoted
# text, whereas in Markdown such text fragment is treated as verbatim and only
# ends when a second matching backtick is found. Also, Doxygen's native markup
# format requires double quotes to be escaped when they appear in a backtick
# section, whereas this is not needed for Markdown.
# The default value is: YES.
# This tag requires that the tag MARKDOWN_SUPPORT is set to YES.
MARKDOWN_STRICT = YES
# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up
# to that level are automatically included in the table of contents, even if
# they do not have an id attribute.
@ -392,8 +406,8 @@ AUTOLINK_SUPPORT = YES
# This tag specifies a list of words that, when matching the start of a word in
# the documentation, will suppress auto links generation, if it is enabled via
# AUTOLINK_SUPPORT. This list does not affect links explicitly created using \#
# or the \link or commands.
# AUTOLINK_SUPPORT. This list does not affect links explicitly created using #
# or the \link or \ref commands.
# This tag requires that the tag AUTOLINK_SUPPORT is set to YES.
AUTOLINK_IGNORE_WORDS =
@ -510,9 +524,9 @@ LOOKUP_CACHE_SIZE = 0
# which effectively disables parallel processing. Please report any issues you
# encounter. Generating dot graphs in parallel is controlled by the
# DOT_NUM_THREADS setting.
# Minimum value: 0, maximum value: 32, default value: 1.
# Minimum value: 0, maximum value: 512, default value: 1.
NUM_PROC_THREADS = 1
NUM_PROC_THREADS = 0
# If the TIMESTAMP tag is set different from NO then each generated page will
# contain the date or date and time when the page was generated. Setting this to
@ -779,6 +793,27 @@ GENERATE_BUGLIST = YES
GENERATE_DEPRECATEDLIST= YES
# The GENERATE_REQUIREMENTS tag can be used to enable (YES) or disable (NO) the
# requirements page. When enabled, this page is automatically created when at
# least one comment block with a \requirement command appears in the input.
# The default value is: YES.
GENERATE_REQUIREMENTS = YES
# The REQ_TRACEABILITY_INFO tag controls if traceability information is shown on
# the requirements page (only relevant when using \requirement comment blocks).
# The setting NO will disable the traceablility information altogether. The
# setting UNSATISFIED_ONLY will show a list of requirements that are missing a
# satisfies relation (through the command: \satisfies). Similarly the setting
# UNVERIFIED_ONLY will show a list of requirements that are missing a verifies
# relation (through the command: \verifies). Setting the tag to YES (the
# default) will show both lists if applicable.
# Possible values are: YES, NO, UNSATISFIED_ONLY and UNVERIFIED_ONLY.
# The default value is: YES.
# This tag requires that the tag GENERATE_REQUIREMENTS is set to YES.
REQ_TRACEABILITY_INFO = YES
# The ENABLED_SECTIONS tag can be used to enable conditional documentation
# sections, marked by \if <section_label> ... \endif and \cond <section_label>
# ... \endcond blocks.
@ -1070,8 +1105,7 @@ EXCLUDE = build/ \
cmake/ \
doc/doxygen/theme/docs/ \
doc/doxygen/theme/include/ \
vcpkg/ \
webclient/
vcpkg/
# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
# directories that are symbolic links (a Unix file system feature) are excluded
@ -1887,7 +1921,7 @@ USE_MATHJAX = NO
# regards to the different settings, so it is possible that also other MathJax
# settings have to be changed when switching between the different MathJax
# versions.
# Possible values are: MathJax_2 and MathJax_3.
# Possible values are: MathJax_2, MathJax_3 and MathJax_4.
# The default value is: MathJax_2.
# This tag requires that the tag USE_MATHJAX is set to YES.
@ -1896,9 +1930,10 @@ MATHJAX_VERSION = MathJax_2
# When MathJax is enabled you can set the default output format to be used for
# the MathJax output. For more details about the output format see MathJax
# version 2 (see:
# http://docs.mathjax.org/en/v2.7-latest/output.html) and MathJax version 3
# https://docs.mathjax.org/en/v2.7/output.html), MathJax version 3 (see:
# https://docs.mathjax.org/en/v3.2/output/index.html) and MathJax version 4
# (see:
# http://docs.mathjax.org/en/latest/web/components/output.html).
# https://docs.mathjax.org/en/v4.0/output/index.htm).
# Possible values are: HTML-CSS (which is slower, but has the best
# compatibility. This is the name for Mathjax version 2, for MathJax version 3
# this will be translated into chtml), NativeMML (i.e. MathML. Only supported
@ -1911,36 +1946,50 @@ MATHJAX_VERSION = MathJax_2
MATHJAX_FORMAT = HTML-CSS
# When MathJax is enabled you need to specify the location relative to the HTML
# output directory using the MATHJAX_RELPATH option. The destination directory
# should contain the MathJax.js script. For instance, if the mathjax directory
# is located at the same level as the HTML output directory, then
# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax
# Content Delivery Network so you can quickly see the result without installing
# MathJax. However, it is strongly recommended to install a local copy of
# MathJax from https://www.mathjax.org before deployment. The default value is:
# output directory using the MATHJAX_RELPATH option. For Mathjax version 2 the
# destination directory should contain the MathJax.js script. For instance, if
# the mathjax directory is located at the same level as the HTML output
# directory, then MATHJAX_RELPATH should be ../mathjax.s For Mathjax versions 3
# and 4 the destination directory should contain the tex-<format>.js script
# (where <format> is either chtml or svg). The default value points to the
# MathJax Content Delivery Network so you can quickly see the result without
# installing MathJax. However, it is strongly recommended to install a local
# copy of MathJax from https://www.mathjax.org before deployment. The default
# value is:
# - in case of MathJax version 2: https://cdn.jsdelivr.net/npm/mathjax@2
# - in case of MathJax version 3: https://cdn.jsdelivr.net/npm/mathjax@3
# - in case of MathJax version 4: https://cdn.jsdelivr.net/npm/mathjax@4
# This tag requires that the tag USE_MATHJAX is set to YES.
MATHJAX_RELPATH =
# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax
# extension names that should be enabled during MathJax rendering. For example
# for MathJax version 2 (see
# https://docs.mathjax.org/en/v2.7-latest/tex.html#tex-and-latex-extensions):
# for MathJax version 2 (see https://docs.mathjax.org/en/v2.7/tex.html):
# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols
# For example for MathJax version 3 (see
# http://docs.mathjax.org/en/latest/input/tex/extensions/index.html):
# https://docs.mathjax.org/en/v3.2/input/tex/extensions/):
# MATHJAX_EXTENSIONS = ams
# For example for MathJax version 4 (see
# https://docs.mathjax.org/en/v4.0/input/tex/extensions/):
# MATHJAX_EXTENSIONS = units
# Note that for Mathjax version 4 quite a few extensions are already
# automatically loaded. To disable a package in Mathjax version 4 one can use
# the package name prepended with a minus sign (- like MATHJAX_EXTENSIONS +=
# -textmacros)
# This tag requires that the tag USE_MATHJAX is set to YES.
MATHJAX_EXTENSIONS =
# The MATHJAX_CODEFILE tag can be used to specify a file with JavaScript pieces
# of code that will be used on startup of the MathJax code. See the MathJax site
# (see:
# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an
# example see the documentation.
# of code that will be used on startup of the MathJax code. See the Mathjax site
# for more details:
# - MathJax version 2 (see:
# https://docs.mathjax.org/en/v2.7/)
# - MathJax version 3 (see:
# https://docs.mathjax.org/en/v3.2/)
# - MathJax version 4 (see:
# https://docs.mathjax.org/en/v4.0/) For an example see the documentation.
# This tag requires that the tag USE_MATHJAX is set to YES.
MATHJAX_CODEFILE =
@ -2601,7 +2650,7 @@ HAVE_DOT = YES
# processors available in the system. You can set it explicitly to a value
# larger than 0 to get control over the balance between CPU load and processing
# speed.
# Minimum value: 0, maximum value: 32, default value: 0.
# Minimum value: 0, maximum value: 512, default value: 0.
# This tag requires that the tag HAVE_DOT is set to YES.
DOT_NUM_THREADS = 0

View file

@ -8,7 +8,7 @@
<a href="#related-repositories">Related</a> <b>|</b>
<a href="#community-resources-">Community</a> <b>|</b>
<a href="#contribute">Contribute</a> <b>|</b>
<a href="#build---">Build</a> <b>|</b>
<a href="#build--">Build</a> <b>|</b>
<a href="#run">Run</a>
</p>
@ -25,7 +25,6 @@
Cockatrice is an open-source, multiplatform application for playing tabletop card games over a network. The program's server design prevents users from manipulating the game for unfair advantage. The client also provides a single-player mode, which allows users to brew while offline.<br><br>
This project uses <kbd>C++</kbd> and the <kbd>Qt</kbd> libraries.<br>
First work on a webclient with <kbd>Typescript</kbd> was started as well.<br>
# Download [![Cockatrice Eternal Download Count](https://img.shields.io/github/downloads/cockatrice/cockatrice/total.svg)](https://tooomm.github.io/github-release-stats/?username=Cockatrice&repository=Cockatrice&search=0)
@ -48,6 +47,7 @@ Latest <kbd>beta</kbd> version:
- [Magic-Spoiler](https://github.com/Cockatrice/Magic-Spoiler): Code to generate MtG spoiler data from [MTGJSON](https://github.com/mtgjson/mtgjson) for use in Cockatrice
- [cockatrice.github.io](https://github.com/Cockatrice/cockatrice.github.io): Code of the official Cockatrice webpage
- [io.github.Cockatrice.cockatrice](https://github.com/flathub/io.github.Cockatrice.cockatrice): Configuration of our Linux `flatpak` package hosted at [Flathub](https://flathub.org/en/apps/io.github.Cockatrice.cockatrice)
- [Webatrice](https://github.com/Cockatrice/Webatrice): Web client for Cockatrice servers (TypeScript / React)
# Community Resources [![Discord](https://img.shields.io/discord/314987288398659595?label=Discord&logo=discord&logoColor=white)](https://discord.gg/3Z9yzmA)
@ -107,12 +107,12 @@ Cockatrice tries to use the [Google Developer Documentation Style Guide](https:/
### Translation [![Transifex Project](https://img.shields.io/badge/translate-on%20transifex-brightgreen)](https://explore.transifex.com/cockatrice/cockatrice/)
Cockatrice uses Transifex to manage translations. You can help us bring <kbd>Cockatrice</kbd>, <kbd>Oracle</kbd> and <kbd>Webatrice</kbd> to your language and just adjust single wordings right from within your browser by visiting our [Transifex project page](https://explore.transifex.com/cockatrice/cockatrice/).<br>
Cockatrice uses Transifex to manage translations. You can help us bring <kbd>Cockatrice</kbd> and <kbd>Oracle</kbd> to your language and just adjust single wordings right from within your browser by visiting our [Transifex project page](https://explore.transifex.com/cockatrice/cockatrice/). The [Webatrice](https://github.com/seavor/Webatrice) web client manages its own translations in its repo.<br>
Check out our [Translator FAQ](https://github.com/Cockatrice/Cockatrice/wiki/Translation-FAQ) for more information about getting involved, and join a group of hundreds of others!<br>
# Build [![CI Desktop](https://github.com/Cockatrice/Cockatrice/actions/workflows/desktop-build.yml/badge.svg?branch=master&event=push)](https://github.com/Cockatrice/Cockatrice/actions/workflows/desktop-build.yml?query=branch%3Amaster+event%3Apush) [![CI Docker](https://github.com/Cockatrice/Cockatrice/actions/workflows/docker-release.yml/badge.svg?branch=master&event=push)](https://github.com/Cockatrice/Cockatrice/actions/workflows/docker-release.yml?query=branch%3Amaster+event%3Apush) [![CI Web](https://github.com/Cockatrice/Cockatrice/actions/workflows/web-build.yml/badge.svg?branch=master&event=push)](https://github.com/Cockatrice/Cockatrice/actions/workflows/web-build.yml?query=branch%3Amaster+event%3Apush)
# Build [![CI Desktop](https://github.com/Cockatrice/Cockatrice/actions/workflows/desktop-build.yml/badge.svg?branch=master&event=push)](https://github.com/Cockatrice/Cockatrice/actions/workflows/desktop-build.yml?query=branch%3Amaster+event%3Apush) [![CI Docker](https://github.com/Cockatrice/Cockatrice/actions/workflows/docker-release.yml/badge.svg?branch=master&event=push)](https://github.com/Cockatrice/Cockatrice/actions/workflows/docker-release.yml?query=branch%3Amaster+event%3Apush)
Dependencies: *(for minimum versions search our [CMake file](https://github.com/Cockatrice/Cockatrice/blob/master/CMakeLists.txt))*
- [Qt](https://www.qt.io/developers/)

View file

@ -11,6 +11,7 @@ SetCompressor LZMA
Var NormalDestDir
Var PortableDestDir
Var PortableMode
Var ReinstallMode
!include LogicLib.nsh
!include FileFunc.nsh
@ -28,13 +29,23 @@ Var PortableMode
!define MUI_FINISHPAGE_RUN_TEXT "Run 'Cockatrice' now"
!define MUI_ICON "${NSIS_SOURCE_PATH}\cockatrice\resources\appicon.ico"
!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfReinstall
!insertmacro MUI_PAGE_WELCOME
!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfReinstall
!insertmacro MUI_PAGE_LICENSE "${NSIS_SOURCE_PATH}\LICENSE"
Page Custom PortableModePageCreate PortableModePageLeave
!define MUI_PAGE_CUSTOMFUNCTION_PRE componentsPagePre
!insertmacro MUI_PAGE_COMPONENTS
!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfReinstall
!insertmacro MUI_PAGE_DIRECTORY
!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfReinstall
!insertmacro MUI_PAGE_INSTFILES
!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfReinstall
!insertmacro MUI_PAGE_FINISH
!insertmacro MUI_UNPAGE_CONFIRM
@ -53,14 +64,6 @@ ${If} ${NSIS_IS_64_BIT} == 1 #NSIS 64bit
${EndIf}
StrCpy $NormalDestDir "$ProgramFiles64\Cockatrice"
SetRegView 64
${Else} #NSIS 32bit
${If} ${RunningX64}
MessageBox MB_OK|MB_ICONEXCLAMATION \
"You are about to install a 32-bit version of Cockatrice on a 64-bit Windows system.$\n\
We advise you to use the correct 64-bit installer instead to get around potential issues.$\n$\n\
Download from our webpage: https://cockatrice.github.io"
${EndIf}
StrCpy $NormalDestDir "$ProgramFiles\Cockatrice"
${EndIf}
StrCpy $PortableDestDir "$Desktop\CockatricePortable"
@ -73,6 +76,7 @@ ${IfNot} ${Errors}
MessageBox MB_ICONINFORMATION|MB_SETFOREGROUND "\
/PORTABLE : Install in portable mode$\n\
/S : Silent install$\n\
/R : Silent upgrade$\n\
/D=%directory% : Specify destination directory$\n"
Quit
${EndIf}
@ -90,6 +94,16 @@ ${Else}
${EndIf}
${EndIf}
ClearErrors
${GetOptions} $9 "/R" $8
${IfNot} ${Errors}
StrCpy $ReinstallMode 1
SetSilent silent
SetAutoClose true
${Else}
StrCpy $ReinstallMode 0
${EndIf}
${If} $InstDir == ""
; User did not use /D to specify a directory,
; we need to set a default based on the install mode
@ -97,6 +111,22 @@ ${If} $InstDir == ""
${EndIf}
Call SetModeDestinationFromInstdir
; --- Detect portable install when using /R ---
${If} $ReinstallMode = 1
IfFileExists "$InstDir\portable.dat" 0 not_portable
StrCpy $PortableMode 1
Goto portable_done
not_portable:
StrCpy $PortableMode 0
portable_done:
${EndIf}
${If} $ReinstallMode = 1
Call AutoUninstallIfNeeded
${EndIf}
FunctionEnd
Function un.onInit
@ -126,8 +156,33 @@ ${Else}
${EndIf}
FunctionEnd
Function SkipIfReinstall
${If} $ReinstallMode = 1
Abort
${EndIf}
FunctionEnd
Function AutoUninstallIfNeeded
SetShellVarContext all
SetRegView 64
ReadRegStr $R0 HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Cockatrice" "QuietUninstallString"
StrCmp $R0 "" done64
DetailPrint "Removing previous version (64-bit)..."
ExecWait '$R0'
done64:
FunctionEnd
Function PortableModePageCreate
${If} $ReinstallMode = 1
Abort
${EndIf}
Call SetModeDestinationFromInstdir ; If the user clicks BACK on the directory page we will remember their mode specific directory
!insertmacro MUI_HEADER_TEXT "Install Mode" "Choose how you want to install Cockatrice."
nsDialogs::Create 1018
@ -159,40 +214,31 @@ ${EndIf}
FunctionEnd
Function componentsPagePre
${If} $ReinstallMode = 1
Return
${EndIf}
${If} $PortableMode = 0
SetShellVarContext all
# uninstall 32bit version
SetRegView 32
SetRegView 64
ReadRegStr $R0 HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Cockatrice" "UninstallString"
StrCmp $R0 "" done32
ReadRegStr $R0 HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Cockatrice" "UninstallString"
StrCmp $R0 "" done64
MessageBox MB_OKCANCEL|MB_ICONEXCLAMATION "A previous version of Cockatrice must be uninstalled before installing the new one." IDOK uninst32
Abort
${If} $ReinstallMode = 0
MessageBox MB_OKCANCEL|MB_ICONEXCLAMATION "A previous version of Cockatrice must be uninstalled before installing the new one." IDOK uninst64
Abort
${Else}
Goto uninst64
${EndIf}
uninst32:
ClearErrors
ExecWait "$R0"
uninst64:
ClearErrors
ExecWait "$R0"
done32:
# uninstall 64bit version
${If} ${NSIS_IS_64_BIT} == 1
SetRegView 64
ReadRegStr $R0 HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Cockatrice" "UninstallString"
StrCmp $R0 "" done64
MessageBox MB_OKCANCEL|MB_ICONEXCLAMATION "A previous version of Cockatrice must be uninstalled before installing the new one." IDOK uninst64
Abort
uninst64:
ClearErrors
ExecWait "$R0"
done64:
${EndIf}
done64:
${Else}
Abort
@ -277,6 +323,12 @@ ${Else}
FileWrite $0 "PORTABLE"
FileClose $0
${EndIf}
${If} $ReinstallMode = 1
IfFileExists "$INSTDIR\cockatrice.exe" 0 +2
Exec '"$INSTDIR\cockatrice.exe"'
${EndIf}
SectionEnd
Section "Start menu item" SecStartMenu

View file

@ -213,7 +213,8 @@ set(PROJECT_VERSION_FRIENDLY "${PROJECT_VERSION} (${GIT_COMMIT_DATE_FRIENDLY})")
# Format: <program name>[-ReleaseName]-MAJ.MIN.PATCH[-prerelease_label]
set(PROJECT_VERSION_FILENAME "${PROJECT_NAME}")
if(PROJECT_VERSION_RELEASENAME)
set(PROJECT_VERSION_FILENAME "${PROJECT_VERSION_FILENAME}-${PROJECT_VERSION_RELEASENAME}")
string(REPLACE " " "-" PROJECT_VERSION_RELEASENAME_SAFE "${PROJECT_VERSION_RELEASENAME}")
set(PROJECT_VERSION_FILENAME "${PROJECT_VERSION_FILENAME}-${PROJECT_VERSION_RELEASENAME_SAFE}")
endif()
set(PROJECT_VERSION_FILENAME "${PROJECT_VERSION_FILENAME}-${PROJECT_VERSION}")

View file

@ -1,15 +1,16 @@
cmake_minimum_required(VERSION 3.2)
cmake_minimum_required(VERSION 3.16)
project(gtest-download LANGUAGES NONE)
include(ExternalProject)
ExternalProject_Add(googletest
URL https://github.com/google/googletest/archive/release-1.11.0.zip
URL_HASH SHA1=9ffb7b5923f4a8fcdabf2f42c6540cce299f44c0
externalproject_add(
googletest
URL https://github.com/google/googletest/archive/refs/tags/v1.17.0.zip
URL_HASH SHA1=f638fa0e724760e2ba07ff8cfba32cd644e1ce28
SOURCE_DIR "${CMAKE_BINARY_DIR}/gtest-src"
BINARY_DIR "${CMAKE_BINARY_DIR}/gtest-build"
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND ""
TEST_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND ""
TEST_COMMAND ""
)

View file

@ -7,6 +7,7 @@ project(Cockatrice VERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${
set(cockatrice_SOURCES
${VERSION_STRING_CPP}
# sort by alphabetical order, so that there is no debate about where to add new sources to the list
src/client/network/connection_controller/remote_connection_controller.cpp
src/client/network/update/client/update_downloader.cpp
src/client/network/interfaces/deck_stats_interface.cpp
src/client/network/interfaces/tapped_out_interface.cpp
@ -58,12 +59,15 @@ set(cockatrice_SOURCES
src/game/board/abstract_card_drag_item.cpp
src/game/board/abstract_card_item.cpp
src/game/board/abstract_counter.cpp
src/game/board/arrow_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/board/card_list.cpp
src/game/board/card_state.cpp
src/game/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
@ -93,30 +97,30 @@ set(cockatrice_SOURCES
src/game/player/menu/say_menu.cpp
src/game/player/menu/sideboard_menu.cpp
src/game/player/menu/utility_menu.cpp
src/game/player/player.cpp
src/game/player/player_actions.cpp
src/game/player/player_area.cpp
src/game/player/player_event_handler.cpp
src/game/player/player_graphics_item.cpp
src/game/player/player_info.cpp
src/game/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/replay.cpp
src/game/zones/card_zone.cpp
src/game/zones/hand_zone.cpp
src/game/zones/logic/card_zone_logic.cpp
src/game/zones/logic/hand_zone_logic.cpp
src/game/zones/logic/pile_zone_logic.cpp
src/game/zones/logic/stack_zone_logic.cpp
src/game/zones/logic/table_zone_logic.cpp
src/game/zones/logic/view_zone_logic.cpp
src/game/zones/pile_zone.cpp
src/game/zones/select_zone.cpp
src/game/zones/stack_zone.cpp
src/game/zones/table_zone.cpp
src/game/zones/view_zone.cpp
src/game/zones/view_zone_widget.cpp
src/game/zones/card_zone_logic.cpp
src/game/zones/hand_zone_logic.cpp
src/game/zones/pile_zone_logic.cpp
src/game/zones/stack_zone_logic.cpp
src/game/zones/table_zone_logic.cpp
src/game/zones/view_zone_logic.cpp
src/game_graphics/zones/card_zone.cpp
src/game_graphics/zones/hand_zone.cpp
src/game_graphics/zones/pile_zone.cpp
src/game_graphics/zones/select_zone.cpp
src/game_graphics/zones/stack_zone.cpp
src/game_graphics/zones/table_zone.cpp
src/game_graphics/zones/view_zone.cpp
src/game_graphics/zones/view_zone_widget.cpp
src/game_graphics/board/abstract_graphics_item.cpp
src/interface/card_picture_loader/card_picture_loader.cpp
src/interface/card_picture_loader/card_picture_loader_local.cpp
@ -129,7 +133,13 @@ set(cockatrice_SOURCES
src/interface/layouts/overlap_layout.cpp
src/interface/widgets/utility/line_edit_completer.cpp
src/interface/pixel_map_generator.cpp
src/interface/theme_config.cpp
src/interface/theme_manager.cpp
src/interface/palette_editor/color_button.cpp
src/interface/palette_editor/palette_generator.cpp
src/interface/palette_editor/quick_setup_panel.cpp
src/interface/palette_editor/palette_grid_widget.cpp
src/interface/palette_editor/palette_editor_dialog.cpp
src/interface/widgets/cards/additional_info/color_identity_widget.cpp
src/interface/widgets/cards/additional_info/mana_cost_widget.cpp
src/interface/widgets/cards/additional_info/mana_symbol_widget.cpp
@ -216,6 +226,7 @@ set(cockatrice_SOURCES
src/interface/widgets/replay/replay_manager.cpp
src/interface/widgets/replay/replay_timeline_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
src/interface/widgets/server/game_selector_quick_filter_toolbar.cpp
src/interface/widgets/server/games_model.cpp
@ -227,6 +238,14 @@ set(cockatrice_SOURCES
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_widget.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/shortcut_settings_page.cpp
src/interface/widgets/settings_page/sound_settings_page.cpp
src/interface/widgets/settings_page/storage_settings_page.cpp
src/interface/widgets/settings_page/user_interface_settings_page.cpp
src/interface/widgets/utility/custom_line_edit.cpp
src/interface/widgets/utility/get_text_with_max.cpp
src/interface/widgets/utility/sequence_edit.cpp
@ -325,6 +344,8 @@ set(cockatrice_SOURCES
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
)
add_subdirectory(sounds)
@ -498,6 +519,7 @@ if(WIN32)
DIRECTORY "${CMAKE_BINARY_DIR}/cockatrice/"
DESTINATION ./
FILES_MATCHING
PATTERN "CMakeFiles" EXCLUDE
PATTERN "*.ini"
)

View file

@ -55,6 +55,7 @@
<file>resources/icons/view.svg</file>
<file>resources/icons/mana/B.svg</file>
<file>resources/icons/mana/C.svg</file>
<file>resources/icons/mana/G.svg</file>
<file>resources/icons/mana/R.svg</file>
<file>resources/icons/mana/U.svg</file>
@ -69,6 +70,7 @@
<file>resources/config/interface.svg</file>
<file>resources/config/messages.svg</file>
<file>resources/config/deckeditor.svg</file>
<file>resources/config/storage.svg</file>
<file>resources/config/shorcuts.svg</file>
<file>resources/config/sound.svg</file>
<file>resources/config/debug.ini</file>

View file

@ -28,6 +28,8 @@
#dlg_tip_of_the_day = true
#dlg_update = true
#general_settings_page = true
#settings_cache = true
#servers_settings = true
#shortcuts_settings = true

View file

@ -0,0 +1,799 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/"
width="62.636364"
height="62.090908"
id="svg2"
sodipodi:version="0.32"
inkscape:version="1.4.3 (0d15f75042, 2025-12-25)"
sodipodi:docname="storage.svg"
inkscape:output_extension="org.inkscape.output.svg.inkscape"
version="1.0"
xml:space="preserve"
xmlns="http://www.w3.org/2000/svg"><defs
id="defs4"><linearGradient
id="linearGradient3169"><stop
style="stop-color:#0000ff;stop-opacity:1;"
offset="0"
id="stop3171" /><stop
style="stop-color:#000067;stop-opacity:1;"
offset="1"
id="stop3173" /></linearGradient><linearGradient
id="linearGradient4766"><stop
style="stop-color:#784421;stop-opacity:1;"
offset="0"
id="stop4768" /><stop
style="stop-color:#3d2210;stop-opacity:0;"
offset="1"
id="stop4770" /></linearGradient><linearGradient
id="linearGradient4758"><stop
style="stop-color:#a05a2c;stop-opacity:1;"
offset="0"
id="stop4760" /><stop
style="stop-color:#3d2210;stop-opacity:1;"
offset="1"
id="stop4762" /></linearGradient><inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 526.18109 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="744.09448 : 526.18109 : 1"
inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
id="perspective10" /><inkscape:perspective
id="perspective2484"
inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
inkscape:vp_z="744.09448 : 526.18109 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 526.18109 : 1"
sodipodi:type="inkscape:persp3d" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient4758"
id="linearGradient4764"
x1="466.09601"
y1="485.96021"
x2="715.14801"
y2="485.96021"
gradientUnits="userSpaceOnUse" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient4766"
id="linearGradient4772"
x1="496.548"
y1="485.26816"
x2="683.31201"
y2="485.26816"
gradientUnits="userSpaceOnUse" /><radialGradient
inkscape:collect="always"
xlink:href="#linearGradient3169"
id="radialGradient3175"
cx="120.07376"
cy="56.138123"
fx="120.07376"
fy="56.138123"
r="82.790039"
gradientTransform="matrix(1,0,0,0.2116376,0,44.257186)"
gradientUnits="userSpaceOnUse" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient6482"
id="linearGradient6488"
x1="32.18182"
y1="3.2835093"
x2="32.18182"
y2="13.02554"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.0281354,0,0,1.0429299,85.21874,131.0326)" /><linearGradient
id="linearGradient6482"><stop
style="stop-color:#ffffff;stop-opacity:1;"
offset="0"
id="stop6484" /><stop
style="stop-color:#00ff00;stop-opacity:0;"
offset="1"
id="stop6486" /></linearGradient><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient6464"
id="linearGradient6470"
x1="32.090908"
y1="1.8181819"
x2="31.09091"
y2="62.909088"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(0,-0.1818182)" /><linearGradient
id="linearGradient6464"><stop
style="stop-color:#0061ff;stop-opacity:1;"
offset="0"
id="stop6466" /><stop
style="stop-color:#001c4c;stop-opacity:1;"
offset="1"
id="stop6468" /></linearGradient><linearGradient
y2="62.909088"
x2="31.09091"
y1="1.8181819"
x1="32.090908"
gradientTransform="translate(86.2151,131.5372)"
gradientUnits="userSpaceOnUse"
id="linearGradient4477"
xlink:href="#linearGradient6464"
inkscape:collect="always" /><linearGradient
inkscape:collect="always"
id="linearGradient2916"><stop
style="stop-color:white;stop-opacity:1;"
offset="0"
id="stop2918" /><stop
style="stop-color:white;stop-opacity:0;"
offset="1"
id="stop2920" /></linearGradient><linearGradient
inkscape:collect="always"
id="linearGradient2902"><stop
style="stop-color:black;stop-opacity:1;"
offset="0"
id="stop2905" /><stop
style="stop-color:black;stop-opacity:0;"
offset="1"
id="stop2907" /></linearGradient><linearGradient
id="linearGradient2064"><stop
id="stop2066"
offset="0"
style="stop-color:white;stop-opacity:1;" /><stop
style="stop-color:#555753;stop-opacity:0.60000002;"
offset="0.5"
id="stop2070" /><stop
id="stop2068"
offset="1"
style="stop-color:#555753;stop-opacity:0;" /></linearGradient><linearGradient
id="linearGradient9641"><stop
style="stop-color:white;stop-opacity:1"
offset="0"
id="stop9643" /><stop
style="stop-color:#888a85;stop-opacity:1"
offset="1"
id="stop9645" /></linearGradient><linearGradient
id="linearGradient9633"><stop
style="stop-color:#eeeeec;stop-opacity:1"
offset="0"
id="stop9635" /><stop
style="stop-color:#888a85;stop-opacity:1"
offset="1"
id="stop9639" /></linearGradient><linearGradient
id="linearGradient9613"><stop
style="stop-color:white;stop-opacity:1"
offset="0"
id="stop9615" /><stop
id="stop9619"
offset="0.5"
style="stop-color:white;stop-opacity:1;" /><stop
style="stop-color:#cccfca;stop-opacity:1"
offset="1"
id="stop9617" /></linearGradient><linearGradient
id="linearGradient8710"><stop
style="stop-color:black;stop-opacity:1;"
offset="0"
id="stop8712" /><stop
style="stop-color:white;stop-opacity:1;"
offset="1"
id="stop8714" /></linearGradient><linearGradient
id="linearGradient8631"><stop
id="stop8633"
offset="0"
style="stop-color:#eeeeec;stop-opacity:1" /><stop
style="stop-color:#eeeeec;stop-opacity:1;"
offset="0.2"
id="stop8637" /><stop
id="stop8635"
offset="1"
style="stop-color:#babdb6;stop-opacity:1" /></linearGradient><linearGradient
id="linearGradient8625"><stop
id="stop8627"
offset="0"
style="stop-color:white;stop-opacity:1" /><stop
id="stop8629"
offset="1"
style="stop-color:#babdb6;stop-opacity:1" /></linearGradient><linearGradient
id="linearGradient8613"><stop
style="stop-color:#babdb6;stop-opacity:1"
offset="0"
id="stop8615" /><stop
style="stop-color:#2e3436;stop-opacity:1"
offset="1"
id="stop8617" /></linearGradient><linearGradient
id="linearGradient5740"><stop
style="stop-color:#d0d0cb;stop-opacity:1;"
offset="0"
id="stop5742" /><stop
style="stop-color:#babdb6;stop-opacity:1"
offset="1"
id="stop5744" /></linearGradient><linearGradient
id="linearGradient5690"><stop
style="stop-color:white;stop-opacity:1;"
offset="0"
id="stop5692" /><stop
style="stop-color:#888a85;stop-opacity:0.59848487"
offset="1"
id="stop5694" /></linearGradient><linearGradient
id="linearGradient2899"><stop
id="stop2901"
offset="0"
style="stop-color:#555753;stop-opacity:1" /><stop
id="stop2903"
offset="1"
style="stop-color:#2e3436;stop-opacity:1" /></linearGradient><linearGradient
id="linearGradient3468"><stop
style="stop-color:#fdfdfc;stop-opacity:1"
offset="0"
id="stop3470" /><stop
style="stop-color:white;stop-opacity:0.37121212"
offset="1"
id="stop3472" /></linearGradient><linearGradient
id="linearGradient2909"><stop
style="stop-color:white;stop-opacity:0;"
offset="0"
id="stop2911" /><stop
id="stop2917"
offset="0.5"
style="stop-color:white;stop-opacity:1;" /><stop
style="stop-color:white;stop-opacity:0;"
offset="1"
id="stop2913" /></linearGradient><linearGradient
id="linearGradient2839"><stop
style="stop-color:white;stop-opacity:0.25773194;"
offset="0"
id="stop2841" /><stop
id="stop2847"
offset="0.5472973"
style="stop-color:white;stop-opacity:1;" /><stop
style="stop-color:white;stop-opacity:0.24705882;"
offset="0.66243607"
id="stop2849" /><stop
id="stop2851"
offset="0.875"
style="stop-color:white;stop-opacity:0.83505154;" /><stop
style="stop-color:white;stop-opacity:0;"
offset="1"
id="stop2843" /></linearGradient><linearGradient
id="linearGradient2900"><stop
style="stop-color:black;stop-opacity:0;"
offset="0"
id="stop2902" /><stop
id="stop2908"
offset="0.5"
style="stop-color:black;stop-opacity:1;" /><stop
style="stop-color:black;stop-opacity:0;"
offset="1"
id="stop2904" /></linearGradient><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3468"
id="linearGradient3474"
x1="24.748737"
y1="35.354588"
x2="24.998737"
y2="14.997767"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1,0,0,0.995556,0,-3.931113)" /><radialGradient
inkscape:collect="always"
xlink:href="#linearGradient2902"
id="radialGradient4700"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(4.095822,0,0,3.101282,-9.53921,-94.5433)"
cx="0"
cy="17"
fx="0"
fy="17"
r="2" /><radialGradient
inkscape:collect="always"
xlink:href="#linearGradient2902"
id="radialGradient4702"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(4.095822,0,0,3.101282,38.20996,-10.90025)"
cx="0"
cy="17"
fx="0"
fy="17"
r="2" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2900"
id="linearGradient4704"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(2.047911,0,0,2.067521,1.347566,6.673675)"
x1="9.8994951"
y1="20"
x2="9.8994951"
y2="13.979153" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2909"
id="linearGradient4711"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1,0,0,1.42294,10.5,-14.95703)"
x1="15.335379"
y1="33.06237"
x2="20.329321"
y2="36.37693" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2909"
id="linearGradient4713"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1,0,0,1.42294,-0.875,-15.04578)"
x1="15.335379"
y1="33.06237"
x2="20.329321"
y2="36.37693" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2909"
id="linearGradient4715"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.459833,0,-0.391165,1.370105,40.62503,-13.29892)"
x1="15.335379"
y1="33.06237"
x2="20.329321"
y2="36.37693" /><radialGradient
inkscape:collect="always"
xlink:href="#linearGradient5740"
id="radialGradient5748"
cx="25.251999"
cy="16.47991"
fx="25.251999"
fy="16.47991"
r="21.980215"
gradientTransform="matrix(1.032991,-0.596398,0.575121,0.99614,-12.23456,11.55448)"
gradientUnits="userSpaceOnUse" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2064"
id="linearGradient5790"
gradientUnits="userSpaceOnUse"
x1="18.048874"
y1="25.461344"
x2="22.211937"
y2="12.143078"
gradientTransform="matrix(0.940224,0,0,0.931632,1.331811,1.401537)" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient8631"
id="linearGradient5865"
x1="24"
y1="36.638382"
x2="25.818018"
y2="6.8314762"
gradientUnits="userSpaceOnUse" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2839"
id="linearGradient7658"
gradientUnits="userSpaceOnUse"
x1="27.057796"
y1="12.669416"
x2="32.042896"
y2="31.219666" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient5690"
id="linearGradient8603"
x1="20.304037"
y1="24.035707"
x2="18.498415"
y2="40.647167"
gradientUnits="userSpaceOnUse" /><radialGradient
inkscape:collect="always"
xlink:href="#linearGradient8613"
id="radialGradient8619"
cx="7.5177727"
cy="30.573555"
fx="7.5177727"
fy="30.573555"
r="0.53125"
gradientTransform="matrix(1.662477,0,0,1.61358,-4.989175,-18.65647)"
gradientUnits="userSpaceOnUse" /><radialGradient
inkscape:collect="always"
xlink:href="#linearGradient9613"
id="radialGradient8623"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.389748,0,0,1.348872,-2.91982,-10.63815)"
cx="7.5191436"
cy="30.304251"
fx="7.5191436"
fy="30.304251"
r="0.53125" /><radialGradient
inkscape:collect="always"
xlink:href="#linearGradient9633"
id="radialGradient8664"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.569487,0,0,1.523325,-4.288627,-15.92107)"
cx="7.5336008"
cy="30.307562"
fx="7.5336008"
fy="30.307562"
r="0.53125" /><radialGradient
inkscape:collect="always"
xlink:href="#linearGradient8613"
id="radialGradient8666"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.662477,0,0,1.61358,-4.989175,-18.65647)"
cx="7.5177727"
cy="30.573555"
fx="7.5177727"
fy="30.573555"
r="0.53125" /><radialGradient
inkscape:collect="always"
xlink:href="#linearGradient8625"
id="radialGradient8676"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.662477,0,0,1.61358,-4.989175,-18.65647)"
cx="7.4792061"
cy="30.36071"
fx="7.4792061"
fy="30.36071"
r="0.53125" /><radialGradient
inkscape:collect="always"
xlink:href="#linearGradient8613"
id="radialGradient8678"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.662477,0,0,1.61358,-4.989175,-18.65647)"
cx="7.5177727"
cy="30.573555"
fx="7.5177727"
fy="30.573555"
r="0.53125" /><radialGradient
inkscape:collect="always"
xlink:href="#linearGradient9641"
id="radialGradient8680"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.662477,0,0,1.61358,-4.989175,-18.65647)"
cx="7.4893188"
cy="30.337601"
fx="7.4893188"
fy="30.337601"
r="0.53125" /><radialGradient
inkscape:collect="always"
xlink:href="#linearGradient8613"
id="radialGradient8682"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.662477,0,0,1.61358,-4.989175,-18.65647)"
cx="7.5177727"
cy="30.573555"
fx="7.5177727"
fy="30.573555"
r="0.53125" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient8710"
id="linearGradient8716"
x1="40.617188"
y1="30.554688"
x2="40.710938"
y2="30.359375"
gradientUnits="userSpaceOnUse" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient8710"
id="linearGradient9605"
gradientUnits="userSpaceOnUse"
x1="40.617188"
y1="30.554688"
x2="40.710938"
y2="30.359375"
gradientTransform="matrix(0.602867,-0.797841,0.797841,0.602867,-41.12611,44.62773)" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient8710"
id="linearGradient9649"
gradientUnits="userSpaceOnUse"
x1="40.617188"
y1="30.554688"
x2="40.710938"
y2="30.359375"
gradientTransform="rotate(-30.000012,-5.5813167,76.089146)" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient8710"
id="linearGradient9654"
gradientUnits="userSpaceOnUse"
x1="40.617188"
y1="30.554688"
x2="40.710938"
y2="30.359375"
gradientTransform="matrix(0.707107,0.527555,-0.707107,0.527555,29.0058,-24.09196)" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2916"
id="linearGradient2973"
x1="12.5"
y1="43.1875"
x2="12.5"
y2="34.045513"
gradientUnits="userSpaceOnUse" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2899"
id="linearGradient5655"
gradientUnits="userSpaceOnUse"
x1="53.812813"
y1="43.573235"
x2="-2.8138931"
y2="35.500015"
gradientTransform="translate(0,50)" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2902"
id="linearGradient2992"
x1="21.9375"
y1="39"
x2="21.9375"
y2="37.995617"
gradientUnits="userSpaceOnUse" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2902"
id="linearGradient2910"
x1="22.101398"
y1="27.658131"
x2="22.971142"
y2="20.903238"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(0,2)" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2916"
id="linearGradient2922"
x1="24.847851"
y1="28.908398"
x2="24.847851"
y2="25.757175"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(0,2)" /></defs><sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="5.8830488"
inkscape:cx="-3.9945275"
inkscape:cy="-14.363301"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="2560"
inkscape:window-height="1408"
inkscape:window-x="0"
inkscape:window-y="32"
inkscape:window-maximized="1"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:showpageshadow="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#505050" /><metadata
id="metadata7"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><g
inkscape:label="Ebene 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-86.987816,-132.85536)"><rect
style="fill:url(#linearGradient4477);fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-opacity:1"
id="rect6462"
width="61.636364"
height="61.090908"
x="87.487816"
y="133.35536"
ry="5.6363635" /><rect
style="fill:url(#linearGradient6488);fill-opacity:1;fill-rule:nonzero;stroke:none"
id="rect6472"
width="59.796619"
height="13.251164"
x="88.407707"
y="134.45705"
ry="4.7325583" /><g
inkscape:label="Livello 1"
id="layer1-3"
style="display:inline"
transform="matrix(1.1537183,0,0,1.1537183,91.003924,136.40297)"><g
id="g3519"
style="opacity:0.7"
transform="matrix(1.030831,0,0,1.151147,-0.73609,-12.57431)"
inkscape:export-filename="/home/lapo/Desktop/uhm.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90"><rect
transform="scale(-1)"
y="-48.024086"
x="-9.5392103"
height="12.405126"
width="8.1916437"
id="rect2884"
style="opacity:1;fill:url(#radialGradient4700);fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /><rect
y="35.618961"
x="38.209965"
height="12.405126"
width="8.1916437"
id="rect2894"
style="opacity:1;fill:url(#radialGradient4702);fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /><rect
y="35.618961"
x="9.5392103"
height="12.405126"
width="28.670753"
id="rect2898"
style="opacity:1;fill:url(#linearGradient4704);fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /></g><g
id="g5672"
transform="translate(0,-48.99747)"><path
sodipodi:nodetypes="ccccccccccccc"
id="rect2010"
d="M 4.5182287,80.500013 H 43.481768 c 0.564099,0 1.018229,0.45413 1.018229,1.018229 v 2.963543 c 0,1.315584 -0.450231,3.018228 -2.455729,3.018228 L 40.5,87.5 v 1 h -33 v -1 l -1.8567713,1.3e-5 c -1.2712053,0 -2.1432282,-0.884627 -2.1432282,-2.255665 v -3.726106 c 0,-0.564099 0.4541297,-1.018229 1.0182282,-1.018229 z"
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:url(#linearGradient5655);fill-opacity:1;fill-rule:nonzero;stroke:#2e3436;stroke-width:1;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.4;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none" /><path
transform="translate(0,50)"
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.1;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:url(#linearGradient2973);stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dashoffset:1.4;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none"
d="m 4.59375,31.59375 v 3.729743 c 0,0.599619 0.3756505,1.104854 0.8863276,1.104854 H 42.426407 c 0.512469,0 0.979843,-0.507235 0.979843,-1.016466 V 31.59375 Z"
id="path2076"
sodipodi:nodetypes="ccccccc" /><g
transform="translate(0,50)"
style="opacity:0.5"
id="g4706"><path
style="opacity:0.109524;fill:url(#linearGradient4711);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 26.144738,32.088747 c 0,0 -1.502602,5.533939 -3.226175,5.911253 0,0 6.231378,-0.125771 6.231378,-0.125771 1.387072,-0.317461 3.358758,-5.785482 3.358758,-5.785482 z"
id="path2907"
sodipodi:nodetypes="ccccc"
inkscape:export-filename="/home/lapo/Desktop/uhm.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90" /><path
inkscape:export-ydpi="90"
inkscape:export-xdpi="90"
inkscape:export-filename="/home/lapo/Desktop/uhm.png"
sodipodi:nodetypes="ccccc"
id="path2892"
d="m 14.769738,32 c 0,0 -1.502602,5.533939 -3.226175,5.911253 0,0 6.231378,-0.125771 6.231378,-0.125771 C 19.162013,37.468021 21.133699,32 21.133699,32 Z"
style="opacity:0.109524;fill:url(#linearGradient4713);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /><path
inkscape:export-ydpi="90"
inkscape:export-xdpi="90"
inkscape:export-filename="/home/lapo/Desktop/uhm.png"
sodipodi:nodetypes="ccccc"
id="path2896"
d="m 34.886139,32 c 0,0 -2.212224,5.328458 -3.108503,5.691761 0,0 2.899969,-0.121101 2.899969,-0.121101 C 35.402697,37.264987 37.8125,32 37.8125,32 Z"
style="opacity:0.109524;fill:url(#linearGradient4715);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /></g></g><path
style="fill:url(#radialGradient5748);fill-opacity:1;stroke:#888a85;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1"
d="m 11.693127,10.498788 h 24.572566 c 1.68417,0 2.396517,0.117479 3.040019,2.385005 l 5.074491,17.881119 c 0.501024,1.765471 -1.355848,2.735101 -3.040018,2.735101 H 6.6186312 c -1.868408,0 -3.4893833,-1.181417 -3.0400182,-2.735101 L 8.8290448,12.611497 c 0.5683008,-1.964905 1.1799122,-2.112709 2.8640822,-2.112709 z"
id="rect1879"
sodipodi:nodetypes="cczzcczzc"
inkscape:export-filename="/home/lapo/Desktop/uhm.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90" /><path
sodipodi:type="inkscape:offset"
inkscape:radius="-0.5"
inkscape:original="M 11.6875 10.5 C 10.00333 10.5 9.4120513 10.660095 8.84375 12.625 L 3.59375 30.75 C 3.1443849 32.303684 4.7565918 33.500002 6.625 33.5 L 41.34375 33.5 C 43.02792 33.5 44.876024 32.515471 44.375 30.75 L 39.3125 12.875 C 38.668998 10.607474 37.965419 10.5 36.28125 10.5 L 11.6875 10.5 z "
style="display:inline;opacity:0.462406;fill:url(#linearGradient7658);fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1"
id="path5806"
d="m 11.6875,11 c -0.826242,0 -1.28475,0.05742 -1.5625,0.242188 -0.2777497,0.184768 -0.5284825,0.580009 -0.8007812,1.521484 l -5.2500001,18.125 c -0.1708248,0.590628 0.021709,1.039316 0.4902344,1.4375 C 5.0329784,32.724356 5.7975106,33.000001 6.625,33 h 34.71875 c 0.744655,0 1.538941,-0.232575 2.03125,-0.609375 0.492309,-0.3768 0.719298,-0.799984 0.519531,-1.503906 l -5.0625,-17.875 C 38.52278,11.922001 38.224454,11.462814 37.910156,11.253906 37.595859,11.044998 37.112699,11 36.28125,11 Z" /><ellipse
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:url(#radialGradient8623);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.4;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none"
id="path8621"
transform="matrix(-2.628602,0,0,1.777765,27.79309,-23.77739)"
cx="7.625"
cy="30.578125"
rx="0.53125"
ry="0.515625" /><path
style="fill:none;fill-opacity:1;stroke:url(#linearGradient5790);stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
d="m 16.110953,12.552805 c -0.573581,0 -1.02837,0.431821 -1.02837,0.989859 l -0.940223,3.230801 c -2.859962,1.276514 -4.6423552,3.099073 -4.6423552,5.123976 0,3.856957 6.4790242,6.987239 14.4853222,6.98724 8.006296,0 14.514705,-3.130284 14.514704,-6.98724 0,-2.039034 -1.835591,-3.875388 -4.730501,-5.153089 l -0.940224,-3.201688 c 0,-0.558038 -0.454788,-0.989859 -1.02837,-0.989859 z"
id="path2784"
sodipodi:nodetypes="cccssscccc" /><path
style="display:inline;fill:none;fill-opacity:1;stroke:url(#linearGradient3474);stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1"
d="m 11.6875,11.500005 c -0.803124,0 -1.097168,0.07051 -1.21875,0.155556 -0.121582,0.08504 -0.357707,0.40212 -0.6875,1.306667 l -5.25,18.137786 c -0.1337204,0.366765 -0.054827,0.533865 0.3125,0.84 0.3673267,0.306136 1.066693,0.56 1.78125,0.560001 h 34.71875 c 0.639793,0 1.393345,-0.237954 1.78125,-0.52889 0.387905,-0.290935 0.488311,-0.382809 0.3125,-0.871111 L 38.375,13.242228 c -0.377206,-1.04766 -0.68208,-1.439297 -0.84375,-1.555556 -0.16167,-0.116259 -0.443711,-0.186667 -1.25,-0.186667 z"
id="path3394"
sodipodi:nodetypes="csccsccsccscc" /><g
id="g5657"
transform="translate(7,-1)"
style="opacity:0.302857"><rect
ry="0.74712253"
rx="0.75130093"
y="35.500008"
x="18.499996"
height="1.9999924"
width="14.000004"
id="rect5641"
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#eeeeec;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.4;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none" /><rect
y="36"
x="19"
height="1"
width="1"
id="rect5645"
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.4;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none" /><rect
y="36"
x="22"
height="1"
width="1"
id="rect5647"
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.4;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none" /><rect
y="36"
x="24"
height="1"
width="1"
id="rect5649"
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.4;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none" /><rect
y="36"
x="26"
height="1"
width="1"
id="rect5651"
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.4;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none" /><rect
y="36"
x="29"
height="1"
width="2"
id="rect5653"
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.4;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none" /></g><path
sodipodi:type="inkscape:offset"
inkscape:radius="-0.44194174"
inkscape:original="M 16.125 12.5625 C 15.55142 12.5625 15.09375 12.973212 15.09375 13.53125 L 14.15625 16.78125 C 11.296288 18.057765 9.5 19.881347 9.5 21.90625 C 9.5 25.763206 15.993702 28.874999 24 28.875 C 32.006296 28.874999 38.500001 25.763206 38.5 21.90625 C 38.5 19.867215 36.67616 18.027701 33.78125 16.75 L 32.84375 13.53125 C 32.843748 12.973212 32.386082 12.5625 31.8125 12.5625 L 16.125 12.5625 z "
style="display:inline;fill:url(#linearGradient5865);fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="path5857"
d="m 16.125,13.003906 c -0.362612,0 -0.589844,0.210942 -0.589844,0.527344 a 0.44198593,0.44198593 0 0 1 -0.01758,0.123047 l -0.9375,3.25 a 0.44198593,0.44198593 0 0 1 -0.24414,0.28125 c -1.389903,0.620369 -2.504368,1.368471 -3.25586,2.177734 -0.751491,0.809263 -1.1386718,1.661199 -1.1386717,2.542969 0,1.680455 1.4530107,3.311153 3.9980467,4.533203 2.545037,1.22205 6.114654,1.99414 10.060547,1.994141 3.945892,-1e-6 7.51551,-0.772091 10.060547,-1.994141 2.545037,-1.22205 3.998047,-2.852748 3.998047,-4.533203 0,-0.887751 -0.391823,-1.747213 -1.154297,-2.5625 -0.762474,-0.815287 -1.893636,-1.568394 -3.300781,-2.189453 a 0.44198593,0.44198593 0 0 1 -0.246094,-0.28125 l -0.9375,-3.21875 a 0.44198593,0.44198593 0 0 1 -0.01758,-0.123047 c -10e-7,-0.316404 -0.22723,-0.527344 -0.589844,-0.527344 z" /><ellipse
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.9;fill:#000000;fill-opacity:0.0530303;fill-rule:nonzero;stroke:url(#linearGradient8603);stroke-width:2.52015;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.4;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none"
id="path8595"
transform="matrix(0.449978,0,0,0.349909,16.36363,12.21469)"
cx="16.970562"
cy="25.107418"
rx="7.7781744"
ry="4.2868347" /><ellipse
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:url(#radialGradient8619);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.4;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none"
id="path8611"
transform="matrix(1.411772,0,0,0.969697,-3.014767,0.848485)"
cx="7.625"
cy="30.578125"
rx="0.53125"
ry="0.515625" /><ellipse
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:url(#radialGradient8664);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.462594;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.4;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none"
id="path8660"
transform="matrix(-2.628602,0,0,1.777765,60.79309,-23.77739)"
cx="7.625"
cy="30.578125"
rx="0.53125"
ry="0.515625" /><ellipse
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:url(#radialGradient8666);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.4;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none"
id="path8662"
transform="matrix(1.411772,0,0,0.969697,29.98523,0.848485)"
cx="7.625"
cy="30.578125"
rx="0.53125"
ry="0.515625" /><ellipse
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:url(#radialGradient8676);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.4;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none"
id="path8668"
transform="matrix(-2.628602,0,0,1.777765,31.79309,-40.77739)"
cx="7.625"
cy="30.578125"
rx="0.53125"
ry="0.515625" /><ellipse
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:url(#radialGradient8678);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.4;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none"
id="path8670"
transform="matrix(1.411772,0,0,0.969697,0.985233,-16.15152)"
cx="7.625"
cy="30.578125"
rx="0.53125"
ry="0.515625" /><ellipse
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:url(#radialGradient8680);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.4;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none"
id="path8672"
transform="matrix(-2.628602,0,0,1.777765,56.3029,-40.77739)"
cx="7.625"
cy="30.578125"
rx="0.53125"
ry="0.515625" /><ellipse
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:url(#radialGradient8682);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.4;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none"
id="path8674"
transform="matrix(1.411772,0,0,0.969697,25.49504,-16.15152)"
cx="7.625"
cy="30.578125"
rx="0.53125"
ry="0.515625" /><path
style="opacity:0.4;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient8716);stroke-width:0.3;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 40.328109,30.261401 0.874999,0.430332"
id="path8700" /><path
style="display:inline;opacity:0.4;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient9605);stroke-width:0.3;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 7.330186,30.695906 8.201031,30.257228"
id="path9603" /><path
style="display:inline;opacity:0.4;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient9649);stroke-width:0.3;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 11.263531,13.446473 0.972937,-0.06482"
id="path9647" /><path
style="display:inline;opacity:0.4;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient9654);stroke-width:0.3;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 36.124038,13.147874 0.314427,0.688634"
id="path9652" /><rect
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.12;fill:url(#linearGradient2992);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.681836;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none"
id="rect2984"
width="32.03125"
height="1"
x="8"
y="38" /><path
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.12;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:url(#linearGradient2910);stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none"
d="M 10.460155,15.082355 6.8513979,27.675762 C 8.2982685,28.375511 10.625,29.167061 10.429825,31.533131 H 37.299883 C 37.869398,29.640915 39.875,28.375 41.34614,28.25 L 37.498106,15.082355 32.350135,12.523347 H 14.318912 Z"
id="path1997"
sodipodi:nodetypes="ccccccccc" /><path
sodipodi:nodetypes="ccccc"
id="path2912"
d="m 7.9763979,27.050762 c 1.4468706,0.699749 3.1789321,1.433241 3.4256991,3.357369 H 36.857941 C 37.427456,28.515915 38.875,27.5 40.34614,27.375 Z"
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.834286;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:url(#linearGradient2922);stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none" /></g></g></svg>

After

Width:  |  Height:  |  Size: 40 KiB

View file

@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="169.33115mm"
height="169.59981mm"
viewBox="0 0 169.33115 169.59981"
version="1.1"
id="svg1"
inkscape:export-filename="C.svg"
inkscape:export-xdpi="96.000015"
inkscape:export-ydpi="96.000015"
inkscape:version="1.4.3 (0d15f75042, 2025-12-25)"
sodipodi:docname="colorless.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
inkscape:zoom="0.71535494"
inkscape:cx="397.00572"
inkscape:cy="536.09751"
inkscape:window-width="1853"
inkscape:window-height="1011"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer1">
<inkscape:page
x="0"
y="0"
width="169.33115"
height="169.59981"
id="page2"
margin="0"
bleed="0" />
</sodipodi:namedview>
<defs
id="defs1" />
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-20.334425,-63.700102)">
<ellipse
style="fill:#cccccc;stroke:#000000;stroke-width:0.165333;stroke-linecap:round"
id="path1"
cx="-30.617094"
cy="179.22736"
transform="matrix(0.70654605,-0.70766707,0.70654608,0.70766704,0,0)"
rx="84.650612"
ry="84.65062" />
<rect
style="fill:none;stroke:#000000;stroke-width:14.5003;stroke-linecap:round;stroke-linejoin:bevel;stroke-dasharray:none"
id="rect1"
width="84.683701"
height="84.683769"
x="-221.60611"
y="-73.174698"
ry="0.084683768"
transform="matrix(-0.7073973,-0.70681615,0.7073973,-0.70681615,0,0)" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View file

@ -0,0 +1,587 @@
#include "remote_connection_controller.h"
#include "../../settings/cache_settings.h"
#include "../interface/widgets/dialogs/dlg_connect.h"
#include "../interface/widgets/dialogs/dlg_forgot_password_challenge.h"
#include "../interface/widgets/dialogs/dlg_forgot_password_request.h"
#include "../interface/widgets/dialogs/dlg_forgot_password_reset.h"
#include "../interface/widgets/dialogs/dlg_register.h"
#include "../interface/widgets/utility/get_text_with_max.h"
#include <QDateTime>
#include <QLineEdit>
#include <QMessageBox>
#include <QThread>
#include <libcockatrice/network/client/remote/remote_client.h>
#include <libcockatrice/protocol/pb/response.pb.h>
ConnectionController::ConnectionController(QWidget *dialogParent, QObject *parent)
: QObject(parent), dialogParent(dialogParent)
{
remoteClient = new RemoteClient(nullptr, &SettingsCache::instance());
clientThread = new QThread(this);
remoteClient->moveToThread(clientThread);
clientThread->start();
wireClientSignals();
}
ConnectionController::~ConnectionController()
{
remoteClient->deleteLater();
clientThread->wait();
}
void ConnectionController::wireClientSignals()
{
connect(remoteClient, &RemoteClient::connectionClosedEventReceived, this,
&ConnectionController::onConnectionClosedEvent);
connect(remoteClient, &RemoteClient::serverShutdownEventReceived, this,
&ConnectionController::onServerShutdownEvent);
connect(remoteClient, &RemoteClient::statusChanged, this, &ConnectionController::onStatusChanged);
connect(remoteClient, &RemoteClient::userInfoChanged, this, &ConnectionController::onUserInfoReceived,
Qt::BlockingQueuedConnection);
connect(remoteClient, &RemoteClient::loginError, this,
[this](Response::ResponseCode r, QString rs, quint32 et, QList<QString> mf) {
onLoginError(static_cast<int>(r), rs, et, mf);
});
connect(remoteClient, &RemoteClient::registerError, this,
[this](Response::ResponseCode r, QString rs, quint32 et) { onRegisterError(static_cast<int>(r), rs, et); });
connect(remoteClient, &RemoteClient::activateError, this, &ConnectionController::onActivateError);
connect(remoteClient, &RemoteClient::socketError, this, &ConnectionController::onSocketError);
connect(remoteClient, &RemoteClient::serverTimeout, this, &ConnectionController::onServerTimeout);
connect(remoteClient, &RemoteClient::protocolVersionMismatch, this,
&ConnectionController::onProtocolVersionMismatch);
connect(remoteClient, &RemoteClient::registerAccepted, this, &ConnectionController::onRegisterAccepted);
connect(remoteClient, &RemoteClient::registerAcceptedNeedsActivate, this,
&ConnectionController::onRegisterAcceptedNeedsActivate);
connect(remoteClient, &RemoteClient::activateAccepted, this, &ConnectionController::onActivateAccepted);
connect(remoteClient, &RemoteClient::notifyUserAboutUpdate, this, &ConnectionController::onNotifyUserAboutUpdate);
connect(remoteClient, &RemoteClient::sigForgotPasswordSuccess, this,
&ConnectionController::onForgotPasswordSuccess);
connect(remoteClient, &RemoteClient::sigForgotPasswordError, this, &ConnectionController::onForgotPasswordError);
connect(remoteClient, &RemoteClient::sigPromptForForgotPasswordReset, this,
&ConnectionController::onPromptForgotPasswordReset);
connect(remoteClient, &RemoteClient::sigPromptForForgotPasswordChallenge, this,
&ConnectionController::onPromptForgotPasswordChallenge);
}
void ConnectionController::connectToServer()
{
dlgConnect = new DlgConnect(dialogParent);
connect(dlgConnect, &DlgConnect::sigStartForgotPasswordRequest, this, &ConnectionController::forgotPasswordRequest);
if (dlgConnect->exec()) {
remoteClient->connectToServer(dlgConnect->getHost(), static_cast<unsigned int>(dlgConnect->getPort()),
dlgConnect->getPlayerName(), dlgConnect->getPassword());
}
}
void ConnectionController::connectToServerDirect(const QString &host,
unsigned int port,
const QString &playerName,
const QString &password)
{
remoteClient->connectToServer(host, port, playerName, password);
}
void ConnectionController::disconnectFromServer()
{
remoteClient->disconnectFromServer();
}
void ConnectionController::registerToServer()
{
DlgRegister dlg(dialogParent);
if (dlg.exec()) {
remoteClient->registerToServer(dlg.getHost(), static_cast<unsigned int>(dlg.getPort()), dlg.getPlayerName(),
dlg.getPassword(), dlg.getEmail(), dlg.getCountry(), dlg.getRealName());
}
}
void ConnectionController::forgotPasswordRequest()
{
DlgForgotPasswordRequest dlg(dialogParent);
if (dlg.exec()) {
remoteClient->requestForgotPasswordToServer(dlg.getHost(), static_cast<unsigned int>(dlg.getPort()),
dlg.getPlayerName());
}
}
void ConnectionController::onConnectionClosedEvent(const Event_ConnectionClosed &event)
{
remoteClient->disconnectFromServer();
QString reasonStr;
switch (event.reason()) {
case Event_ConnectionClosed::USER_LIMIT_REACHED: {
reasonStr = tr("The server has reached its maximum user capacity, please check back later.");
break;
}
case Event_ConnectionClosed::TOO_MANY_CONNECTIONS: {
reasonStr = tr("There are too many concurrent connections from your address.");
break;
}
case Event_ConnectionClosed::BANNED: {
reasonStr = tr("Banned by moderator");
if (event.has_end_time()) {
reasonStr.append(
"\n" + tr("Expected end time: %1").arg(QDateTime::fromSecsSinceEpoch(event.end_time()).toString()));
} else {
reasonStr.append("\n" + tr("This ban lasts indefinitely."));
}
if (event.has_reason_str()) {
reasonStr.append("\n\n" + QString::fromStdString(event.reason_str()));
}
break;
}
case Event_ConnectionClosed::SERVER_SHUTDOWN: {
reasonStr = tr("Scheduled server shutdown.");
break;
}
case Event_ConnectionClosed::USERNAMEINVALID: {
reasonStr = tr("Invalid username.");
break;
}
case Event_ConnectionClosed::LOGGEDINELSEWERE: {
reasonStr = tr("You have been logged out due to logging in at another location.");
break;
}
default:
reasonStr = QString::fromStdString(event.reason_str());
}
QMessageBox::critical(dialogParent, tr("Connection closed"),
tr("The server has terminated your connection.\nReason: %1").arg(reasonStr));
}
void ConnectionController::onServerShutdownEvent(const Event_ServerShutdown &event)
{
serverShutdownMessageBox.setInformativeText(tr("The server is going to be restarted in %n minute(s).\nAll running "
"games will be lost.\nReason for shutdown: %1",
"", event.minutes())
.arg(QString::fromStdString(event.reason())));
serverShutdownMessageBox.setIconPixmap(QPixmap("theme:cockatrice").scaled(64, 64));
serverShutdownMessageBox.setText(tr("Scheduled server shutdown"));
serverShutdownMessageBox.setWindowModality(Qt::ApplicationModal);
serverShutdownMessageBox.setVisible(true);
}
void ConnectionController::onStatusChanged(ClientStatus status)
{
// Update the window title first, then let MainWindow handle its own UI
// state via the forwarded signal
updateWindowTitle();
emit statusChanged(status);
// TabSupervisor::stop() needs calling on disconnect; start() is driven by
// onUserInfoReceived → tabSupervisorStartRequested.
if (status == StatusDisconnected) {
emit tabSupervisorStopRequested();
}
}
void ConnectionController::onUserInfoReceived(const ServerInfo_User &info)
{
emit tabSupervisorStartRequested(info);
}
void ConnectionController::onLoginError(int r,
QString reasonStr,
quint32 endTime,
const QList<QString> &missingFeatures)
{
switch (static_cast<Response::ResponseCode>(r)) {
case Response::RespClientUpdateRequired: {
QString formatted = "Missing Features: ";
for (int i = 0; i < missingFeatures.size(); ++i) {
formatted.append(QString("\n %1").arg(QChar(0x2022)) + " " + missingFeatures.value(i));
}
QMessageBox msgBox(dialogParent);
msgBox.setIcon(QMessageBox::Critical);
msgBox.setWindowTitle(tr("Failed Login"));
msgBox.setText(tr("Your client seems to be missing features this server requires for connection.") +
"\n\n" + tr("To update your client, go to 'Help -> Check for Client Updates'."));
msgBox.setDetailedText(formatted);
msgBox.exec();
break;
}
case Response::RespWrongPassword: {
QMessageBox::critical(dialogParent, tr("Error"),
tr("Incorrect username or password. "
"Please check your authentication information and try again."));
break;
}
case Response::RespWouldOverwriteOldSession: {
QMessageBox::critical(dialogParent, tr("Error"),
tr("There is already an active session using this user name.\n"
"Please close that session first and re-login."));
break;
}
case Response::RespUserIsBanned: {
QString bannedStr =
endTime ? tr("You are banned until %1.").arg(QDateTime::fromSecsSinceEpoch(endTime).toString())
: tr("You are banned indefinitely.");
if (!reasonStr.isEmpty()) {
bannedStr.append("\n\n" + reasonStr);
}
QMessageBox::critical(dialogParent, tr("Error"), bannedStr);
break;
}
case Response::RespUsernameInvalid: {
QMessageBox::critical(dialogParent, tr("Error"), extractInvalidUsernameMessage(reasonStr));
break;
}
case Response::RespRegistrationRequired: {
if (QMessageBox::question(dialogParent, tr("Error"),
tr("This server requires user registration. Do you want to register now?"),
QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
registerToServer();
}
return; // don't re-prompt connect
}
case Response::RespClientIdRequired: {
QMessageBox::critical(dialogParent, tr("Error"),
tr("This server requires client IDs. Your client is either failing to generate an "
"ID or you are running a modified client.\n"
"Please close and reopen your client to try again."));
break;
}
case Response::RespContextError: {
QMessageBox::critical(dialogParent, tr("Error"),
tr("An internal error has occurred, please close and reopen Cockatrice before "
"trying again.\nIf the error persists, ensure you are running the latest "
"version of the software and if needed contact the software developers."));
break;
}
case Response::RespAccountNotActivated: {
bool ok = false;
QString token =
getTextWithMax(dialogParent, tr("Account activation"),
tr("Your account has not been activated yet.\n"
"You need to provide the activation token received in the activation email."),
QLineEdit::Normal, QString(), &ok);
if (ok && !token.isEmpty()) {
remoteClient->activateToServer(token);
return;
}
remoteClient->disconnectFromServer();
return;
}
case Response::RespServerFull: {
QMessageBox::critical(dialogParent, tr("Server Full"),
tr("The server has reached its maximum user capacity, please check back later."));
break;
}
default: {
QMessageBox::critical(dialogParent, tr("Error"),
tr("Unknown login error: %1").arg(r) +
tr("\nThis usually means that your client version is out of date, and the server "
"sent a reply your client doesn't understand."));
break;
}
}
// Re-open the connect dialog after any handled error
connectToServer();
}
void ConnectionController::onRegisterError(int r, QString reasonStr, quint32 endTime)
{
switch (static_cast<Response::ResponseCode>(r)) {
case Response::RespRegistrationDisabled: {
QMessageBox::critical(dialogParent, tr("Registration denied"),
tr("Registration is currently disabled on this server"));
break;
}
case Response::RespUserAlreadyExists: {
QMessageBox::critical(dialogParent, tr("Registration denied"),
tr("There is already an existing account with the same user name."));
break;
}
case Response::RespEmailRequiredToRegister: {
QMessageBox::critical(dialogParent, tr("Registration denied"),
tr("It's mandatory to specify a valid email address when registering."));
break;
}
case Response::RespEmailBlackListed: {
if (reasonStr.isEmpty()) {
reasonStr =
"The email address provider used during registration has been blocked from use on this server.";
}
QMessageBox::critical(dialogParent, tr("Registration denied"), reasonStr);
break;
}
case Response::RespTooManyRequests: {
QMessageBox::critical(dialogParent, tr("Registration denied"),
tr("It appears you are attempting to register a new account on this server yet you "
"already have an account registered with the email provided. This server "
"restricts the number of accounts a user can register per address. Please "
"contact the server operator for further assistance or to obtain your "
"credential information."));
break;
}
case Response::RespPasswordTooShort: {
QMessageBox::critical(dialogParent, tr("Registration denied"), tr("Password too short."));
break;
}
case Response::RespUserIsBanned: {
QString bannedStr =
endTime ? tr("You are banned until %1.").arg(QDateTime::fromSecsSinceEpoch(endTime).toString())
: tr("You are banned indefinitely.");
if (!reasonStr.isEmpty()) {
bannedStr.append("\n\n" + reasonStr);
}
QMessageBox::critical(dialogParent, tr("Error"), bannedStr);
break;
}
case Response::RespUsernameInvalid: {
QMessageBox::critical(dialogParent, tr("Error"), extractInvalidUsernameMessage(reasonStr));
break;
}
case Response::RespRegistrationFailed: {
QMessageBox::critical(dialogParent, tr("Error"),
tr("Registration failed for a technical problem on the server."));
break;
}
case Response::RespNotConnected: {
QMessageBox::critical(dialogParent, tr("Error"), tr("The connection to the server has been lost."));
break;
}
default: {
QMessageBox::critical(dialogParent, tr("Error"),
tr("Unknown registration error: %1").arg(r) +
tr("\nThis usually means that your client version is out of date, and the server "
"sent a reply your client doesn't understand."));
break;
}
}
registerToServer();
}
void ConnectionController::onActivateError()
{
QMessageBox::critical(dialogParent, tr("Error"), tr("Account activation failed"));
remoteClient->disconnectFromServer();
connectToServer();
}
void ConnectionController::onSocketError(const QString &errorStr)
{
QMessageBox::critical(dialogParent, tr("Error"), tr("Socket error: %1").arg(errorStr));
connectToServer();
}
void ConnectionController::onServerTimeout()
{
QMessageBox::critical(dialogParent, tr("Error"), tr("Server timeout"));
connectToServer();
}
void ConnectionController::onProtocolVersionMismatch(int localVersion, int remoteVersion)
{
if (localVersion > remoteVersion) {
QMessageBox::critical(dialogParent, tr("Error"),
tr("You are trying to connect to an obsolete server. Please downgrade your Cockatrice "
"version or connect to a suitable server.\n"
"Local version is %1, remote version is %2.")
.arg(localVersion)
.arg(remoteVersion));
} else {
QMessageBox::critical(dialogParent, tr("Error"),
tr("Your Cockatrice client is obsolete. Please update your Cockatrice version.\n"
"Local version is %1, remote version is %2.")
.arg(localVersion)
.arg(remoteVersion));
}
}
void ConnectionController::onRegisterAccepted()
{
QMessageBox::information(dialogParent, tr("Success"), tr("Registration accepted.\nWill now login."));
}
void ConnectionController::onRegisterAcceptedNeedsActivate()
{
// Server will send activation email; nothing to display here.
}
void ConnectionController::onActivateAccepted()
{
QMessageBox::information(dialogParent, tr("Success"), tr("Account activation accepted.\nWill now login."));
}
void ConnectionController::onNotifyUserAboutUpdate()
{
QMessageBox::information(
dialogParent, tr("Information"),
tr("This server supports additional features that your client doesn't have.\n"
"This is most likely not a problem, but this message might mean there is a new version of "
"Cockatrice available or this server is running a custom or pre-release version.\n\n"
"To update your client, go to Help -> Check for Updates."));
}
void ConnectionController::onForgotPasswordSuccess()
{
QMessageBox::information(
dialogParent, tr("Reset Password"),
tr("Your password has been reset successfully, you can now log in using the new credentials."));
SettingsCache::instance().servers().setFPHostName("");
SettingsCache::instance().servers().setFPPort("");
SettingsCache::instance().servers().setFPPlayerName("");
}
void ConnectionController::onForgotPasswordError()
{
QMessageBox::warning(
dialogParent, tr("Reset Password"),
tr("Failed to reset user account password, please contact the server operator to reset your password."));
SettingsCache::instance().servers().setFPHostName("");
SettingsCache::instance().servers().setFPPort("");
SettingsCache::instance().servers().setFPPlayerName("");
}
void ConnectionController::onPromptForgotPasswordReset()
{
QMessageBox::information(dialogParent, tr("Reset Password"),
tr("Activation request received, please check your email for an activation token."));
DlgForgotPasswordReset dlg(dialogParent);
if (dlg.exec()) {
remoteClient->submitForgotPasswordResetToServer(dlg.getHost(), static_cast<unsigned int>(dlg.getPort()),
dlg.getPlayerName(), dlg.getToken(), dlg.getPassword());
}
}
void ConnectionController::onPromptForgotPasswordChallenge()
{
DlgForgotPasswordChallenge dlg(dialogParent);
if (dlg.exec()) {
remoteClient->submitForgotPasswordChallengeToServer(dlg.getHost(), static_cast<unsigned int>(dlg.getPort()),
dlg.getPlayerName(), dlg.getEmail());
}
}
void ConnectionController::updateWindowTitle()
{
const QString appName = QStringLiteral("Cockatrice");
QString title;
switch (remoteClient->getStatus()) {
case StatusConnecting: {
title = appName + " - " + tr("Connecting to %1...").arg(remoteClient->peerName());
break;
}
case StatusRegistering: {
title = appName + " - " +
tr("Registering to %1 as %2...").arg(remoteClient->peerName()).arg(remoteClient->getUserName());
break;
}
case StatusDisconnected: {
title = appName + " - " + tr("Disconnected");
break;
}
case StatusLoggingIn: {
title = appName + " - " + tr("Connected, logging in at %1").arg(remoteClient->peerName());
break;
}
case StatusLoggedIn: {
title = remoteClient->getUserName() + "@" + remoteClient->peerName();
break;
}
case StatusRequestingForgotPassword:
case StatusSubmitForgotPasswordChallenge:
case StatusSubmitForgotPasswordReset:
title = appName + " - " +
tr("Requesting forgotten password to %1 as %2...")
.arg(remoteClient->peerName())
.arg(remoteClient->getUserName());
break;
default:
title = appName;
}
emit windowTitleChanged(title);
}
// static
QString ConnectionController::extractInvalidUsernameMessage(QString &in)
{
QString out = tr("Invalid username.") + "<br/>";
QStringList rules = in.split(QChar('|'));
if (rules.size() == 7 || rules.size() == 9) {
out += tr("Your username must respect these rules:") + "<ul>";
out += "<li>" + tr("is %1 - %2 characters long").arg(rules.at(0)).arg(rules.at(1)) + "</li>";
out += "<li>" + tr("can %1 contain lowercase characters").arg((rules.at(2).toInt() > 0) ? "" : tr("NOT")) +
"</li>";
out += "<li>" + tr("can %1 contain uppercase characters").arg((rules.at(3).toInt() > 0) ? "" : tr("NOT")) +
"</li>";
out +=
"<li>" + tr("can %1 contain numeric characters").arg((rules.at(4).toInt() > 0) ? "" : tr("NOT")) + "</li>";
if (rules.at(6).size() > 0) {
out += "<li>" + tr("can contain the following punctuation: %1").arg(rules.at(6).toHtmlEscaped()) + "</li>";
}
out += "<li>" +
tr("first character can %1 be a punctuation mark").arg((rules.at(5).toInt() > 0) ? "" : tr("NOT")) +
"</li>";
if (rules.size() == 9) {
if (rules.at(7).size() > 0) {
QString words = rules.at(7).toHtmlEscaped();
if (words.startsWith("\n")) {
out += tr("no unacceptable language as specified by these server rules:",
"note that the following lines will not be translated");
for (QString &line : words.split("\n", Qt::SkipEmptyParts)) {
out += "<li>" + line + "</li>";
}
} else {
out += "<li>" + tr("can not contain any of the following words: %1").arg(words) + "</li>";
}
}
if (rules.at(8).size() > 0) {
out += "<li>" +
tr("can not match any of the following expressions: %1").arg(rules.at(8).toHtmlEscaped()) +
"</li>";
}
}
out += "</ul>";
} else {
out += tr("You may only use A-Z, a-z, 0-9, _, ., and - in your username.");
}
return out;
}

View file

@ -0,0 +1,98 @@
#ifndef COCKATRICE_REMOTE_CONNECTION_CONTROLLER_H
#define COCKATRICE_REMOTE_CONNECTION_CONTROLLER_H
#include "abstract_client.h"
#include <QMessageBox>
#include <QObject>
#include <QString>
#include <QThread>
#include <libcockatrice/protocol/pb/event_connection_closed.pb.h>
#include <libcockatrice/protocol/pb/event_server_shutdown.pb.h>
class RemoteClient;
class ServerInfo_User;
class DlgConnect;
/**
* Owns the RemoteClient and its worker thread.
* Encapsulates all connection, authentication, and registration logic so that
* MainWindow only needs to react to high-level signals.
*/
class ConnectionController : public QObject
{
Q_OBJECT
public:
explicit ConnectionController(QWidget *dialogParent, QObject *parent = nullptr);
~ConnectionController() override;
RemoteClient *client() const
{
return remoteClient;
}
void registerToServer();
void forgotPasswordRequest();
void connectToServer();
void
connectToServerDirect(const QString &host, unsigned int port, const QString &playerName, const QString &password);
void disconnectFromServer();
void refreshWindowTitle()
{
updateWindowTitle();
}
signals:
void windowTitleChanged(const QString &title);
void tabSupervisorStartRequested(const ServerInfo_User &info);
void tabSupervisorStopRequested();
// Passes the raw ClientStatus through so MainWindow can drive its own
// action enable/disable logic
void statusChanged(ClientStatus status);
private slots:
// Slots wired directly to RemoteClient signals
void onStatusChanged(ClientStatus status);
void onUserInfoReceived(const ServerInfo_User &info);
void onLoginError(int r, QString reasonStr, quint32 endTime, const QList<QString> &missingFeatures);
void onRegisterAccepted();
void onRegisterAcceptedNeedsActivate();
void onRegisterError(int r, QString reasonStr, quint32 endTime);
void onActivateAccepted();
void onActivateError();
void onProtocolVersionMismatch(int localVersion, int remoteVersion);
void onNotifyUserAboutUpdate();
void onConnectionClosedEvent(const Event_ConnectionClosed &event);
void onServerShutdownEvent(const Event_ServerShutdown &event);
void onSocketError(const QString &errorStr);
void onServerTimeout();
// Forgot-password flow
void onForgotPasswordSuccess();
void onForgotPasswordError();
void onPromptForgotPasswordReset();
void onPromptForgotPasswordChallenge();
private:
void wireClientSignals();
void updateWindowTitle();
/** Parse the server's pipe-delimited username-rule string into HTML. */
static QString extractInvalidUsernameMessage(QString &in);
RemoteClient *remoteClient{nullptr};
QThread *clientThread{nullptr};
QWidget *dialogParent{nullptr}; // used as parent for QMessageBox / dialog calls
// Persistent so it can be updated in-place by onServerShutdownEvent
QMessageBox serverShutdownMessageBox;
// Kept as a member so the forgot-password signal can be wired to it
DlgConnect *dlgConnect{nullptr};
};
#endif // COCKATRICE_REMOTE_CONNECTION_CONTROLLER_H

View file

@ -1,8 +1,8 @@
/**
* @file deck_stats_interface.h
* @ingroup ApiInterfaces
* @brief TODO: Document this.
*/
//! \todo Document this file.
#ifndef DECKSTATS_INTERFACE_H
#define DECKSTATS_INTERFACE_H

View file

@ -89,6 +89,8 @@ void TappedOutInterface::analyzeDeck(const DeckList &deck)
QNetworkRequest request(QUrl("https://tappedout.net/mtg-decks/paste/"));
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
request.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING));
// we interpret the redirect and open it in the browser instead, do not follow redirects
request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::ManualRedirectPolicy);
manager->post(request, data);
}
@ -97,14 +99,16 @@ void TappedOutInterface::copyDeckSplitMainAndSide(const DeckList &source, DeckLi
{
auto copyMainOrSide = [this, &mainboard, &sideboard](const auto node, const auto card) {
CardInfoPtr dbCard = cardDatabase.query()->getCardInfo(card->getName());
if (!dbCard || dbCard->getIsToken())
if (!dbCard || dbCard->getIsToken()) {
return;
}
DecklistCardNode *addedCard;
if (node->getName() == DECK_ZONE_SIDE)
if (node->getName() == DECK_ZONE_SIDE) {
addedCard = sideboard.addCard(card->getName(), node->getName(), -1);
else
} else {
addedCard = mainboard.addCard(card->getName(), node->getName(), -1);
}
addedCard->setNumber(card->getNumber());
};

View file

@ -1,8 +1,8 @@
/**
* @file tapped_out_interface.h
* @ingroup ApiInterfaces
* @brief TODO: Document this.
*/
//! \todo Document this file.
#ifndef TAPPEDOUT_INTERFACE_H
#define TAPPEDOUT_INTERFACE_H

View file

@ -1,8 +1,8 @@
/**
* @file deck_link_to_api_transformer.h
* @ingroup ApiInterfaces
* @brief TODO: Document this.
*/
//! \todo Document this file.
#ifndef DECK_LINK_TO_API_TRANSFORMER_H
#define DECK_LINK_TO_API_TRANSFORMER_H

View file

@ -1,8 +1,8 @@
/**
* @file interface_json_deck_parser.h
* @ingroup ApiInterfaces
* @brief TODO: Document this.
*/
//! \todo Document this file.
#ifndef INTERFACE_JSON_DECK_PARSER_H
#define INTERFACE_JSON_DECK_PARSER_H

View file

@ -1,8 +1,8 @@
/**
* @file spoiler_background_updater.h
* @ingroup Client
* @brief TODO: Document this.
*/
//! \todo Document this file.
#ifndef COCKATRICE_SPOILER_DOWNLOADER_H
#define COCKATRICE_SPOILER_DOWNLOADER_H

View file

@ -1,8 +1,8 @@
/**
* @file client_update_checker.h
* @ingroup ClientUpdate
* @brief TODO: Document this.
*/
//! \todo Document this file.
#ifndef CLIENT_UPDATE_CHECKER_H
#define CLIENT_UPDATE_CHECKER_H

View file

@ -129,8 +129,9 @@ void StableReleaseChannel::releaseListFinished()
return;
}
if (!lastRelease)
if (!lastRelease) {
lastRelease = new Release;
}
lastRelease->setName(resultMap["name"].toString());
lastRelease->setDescriptionUrl(resultMap["html_url"].toString());
@ -246,8 +247,9 @@ void BetaReleaseChannel::releaseListFinished()
return;
}
if (lastRelease == nullptr)
if (lastRelease == nullptr) {
lastRelease = new Release;
}
lastRelease->setCommitHash(resultMap["target_commitish"].toString());
lastRelease->setPublishDate(resultMap["published_at"].toDate());

View file

@ -1,8 +1,8 @@
/**
* @file release_channel.h
* @ingroup ClientUpdate
* @brief TODO: Document this.
*/
//! \todo Document this file.
#ifndef RELEASECHANNEL_H
#define RELEASECHANNEL_H

View file

@ -10,8 +10,9 @@ UpdateDownloader::UpdateDownloader(QObject *parent) : QObject(parent), response(
void UpdateDownloader::beginDownload(QUrl downloadUrl)
{
// Save the original URL because we need it for the filename
if (originalUrl.isEmpty())
if (originalUrl.isEmpty()) {
originalUrl = downloadUrl;
}
response = netMan->get(QNetworkRequest(downloadUrl));
connect(response, &QNetworkReply::finished, this, &UpdateDownloader::fileFinished);
@ -21,8 +22,9 @@ void UpdateDownloader::beginDownload(QUrl downloadUrl)
void UpdateDownloader::downloadError(QNetworkReply::NetworkError)
{
if (response == nullptr)
if (response == nullptr) {
return;
}
emit error(response->errorString().toUtf8());
}

View file

@ -1,8 +1,8 @@
/**
* @file update_downloader.h
* @ingroup ClientUpdate
* @brief TODO: Document this.
*/
//! \todo Document this file.
#ifndef COCKATRICE_UPDATEDOWNLOADER_H
#define COCKATRICE_UPDATEDOWNLOADER_H

View file

@ -1,5 +1,7 @@
#include "cache_settings.h"
#include "../../interface/card_picture_loader/card_picture_loader_cache_method.h"
#include "../../interface/card_picture_loader/card_picture_loader_local_schemes.h"
#include "../network/update/client/release_channel.h"
#include "card_counter_settings.h"
#include "version_string.h"
@ -24,10 +26,11 @@ SettingsCache &SettingsCache::instance()
QString SettingsCache::getDataPath()
{
if (isPortableBuild)
if (isPortableBuild) {
return qApp->applicationDirPath() + "/data";
else
} else {
return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation);
}
}
QString SettingsCache::getSettingsPath()
@ -37,10 +40,11 @@ QString SettingsCache::getSettingsPath()
QString SettingsCache::getCachePath() const
{
if (isPortableBuild)
if (isPortableBuild) {
return qApp->applicationDirPath() + "/cache";
else
} else {
return QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
}
}
QString SettingsCache::getNetworkCachePath() const
@ -50,14 +54,17 @@ QString SettingsCache::getNetworkCachePath() const
void SettingsCache::translateLegacySettings()
{
if (isPortableBuild)
if (isPortableBuild) {
return;
}
// Layouts
QFile layoutFile(getSettingsPath() + "layouts/deckLayout.ini");
if (layoutFile.exists())
if (layoutFile.copy(getSettingsPath() + "layouts.ini"))
if (layoutFile.exists()) {
if (layoutFile.copy(getSettingsPath() + "layouts.ini")) {
layoutFile.remove();
}
}
QStringList usedKeys;
QSettings legacySetting;
@ -116,10 +123,11 @@ void SettingsCache::translateLegacySettings()
gameFilters().setHideIgnoredUserGames(legacySetting.value("hide_ignored_user_games").toBool());
gameFilters().setMinPlayers(legacySetting.value("min_players").toInt());
if (legacySetting.value("max_players").toInt() > 1)
if (legacySetting.value("max_players").toInt() > 1) {
gameFilters().setMaxPlayers(legacySetting.value("max_players").toInt());
else
} else {
gameFilters().setMaxPlayers(99); // This prevents a bug where no games will show if max was not set before
}
QStringList allFilters = legacySetting.allKeys();
for (int i = 0; i < allFilters.size(); ++i) {
@ -135,8 +143,9 @@ void SettingsCache::translateLegacySettings()
QStringList allLegacyKeys = legacySetting.allKeys();
for (int i = 0; i < allLegacyKeys.size(); ++i) {
if (usedKeys.contains(allLegacyKeys.at(i)))
if (usedKeys.contains(allLegacyKeys.at(i))) {
continue;
}
settings->setValue(allLegacyKeys.at(i), legacySetting.value(allLegacyKeys.at(i)));
}
}
@ -147,8 +156,9 @@ QString SettingsCache::getSafeConfigPath(QString configEntry, QString defaultPat
// if the config settings is empty or refers to a not-existing folder,
// ensure that the defaut path exists and return it
if (tmp.isEmpty() || !QDir(tmp).exists()) {
if (!QDir().mkpath(defaultPath))
if (!QDir().mkpath(defaultPath)) {
qCInfo(SettingsCacheLog) << "[SettingsCache] Could not create folder:" << defaultPath;
}
tmp = defaultPath;
}
return tmp;
@ -159,8 +169,9 @@ QString SettingsCache::getSafeConfigFilePath(QString configEntry, QString defaul
QString tmp = settings->value(configEntry).toString();
// if the config settings is empty or refers to a not-existing file,
// return the default Path
if (!QFile::exists(tmp) || tmp.isEmpty())
if (!QFile::exists(tmp) || tmp.isEmpty()) {
tmp = std::move(defaultPath);
}
return tmp;
}
@ -168,8 +179,9 @@ SettingsCache::SettingsCache()
{
// first, figure out if we are running in portable mode
isPortableBuild = QFile::exists(qApp->applicationDirPath() + "/portable.dat");
if (isPortableBuild)
if (isPortableBuild) {
qCInfo(SettingsCacheLog) << "Portable mode enabled";
}
// define a dummy context that will be used where needed
QString dummy = QT_TRANSLATE_NOOP("i18n", "English");
@ -189,8 +201,9 @@ SettingsCache::SettingsCache()
cardCounterSettings = new CardCounterSettings(settingsPath, this);
if (!QFile(settingsPath + "global.ini").exists())
if (!QFile(settingsPath + "global.ini").exists()) {
translateLegacySettings();
}
// updates - don't reorder them or their index in the settings won't match
// append channels one by one, or msvc will add them in the wrong order.
@ -211,6 +224,7 @@ SettingsCache::SettingsCache()
startupCardUpdateCheckAlwaysUpdate = settings->value("personal/startupCardUpdateCheckAlwaysUpdate", false).toBool();
cardUpdateCheckInterval = settings->value("personal/cardUpdateCheckInterval", 7).toInt();
lastCardUpdateCheck = settings->value("personal/lastCardUpdateCheck", QDateTime::currentDateTime().date()).toDate();
alwaysEnableNewSets = settings->value("personal/alwaysEnableNewSets", false).toBool();
notifyAboutUpdates = settings->value("personal/updatenotification", true).toBool();
notifyAboutNewVersion = settings->value("personal/newversionnotification", true).toBool();
@ -256,14 +270,26 @@ SettingsCache::SettingsCache()
settings->setValue("personal/pixmapCacheSize", pixmapCacheSize);
settings->setValue("personal/picturedownloadhq", false);
settings->setValue("revert/pixmapCacheSize", true);
} else
} else {
pixmapCacheSize = settings->value("personal/pixmapCacheSize", PIXMAPCACHE_SIZE_DEFAULT).toInt();
}
// sanity check
if (pixmapCacheSize < PIXMAPCACHE_SIZE_MIN || pixmapCacheSize > PIXMAPCACHE_SIZE_MAX)
if (pixmapCacheSize < PIXMAPCACHE_SIZE_MIN || pixmapCacheSize > PIXMAPCACHE_SIZE_MAX) {
pixmapCacheSize = PIXMAPCACHE_SIZE_DEFAULT;
}
networkCacheSize = settings->value("personal/networkCacheSize", NETWORK_CACHE_SIZE_DEFAULT).toInt();
redirectCacheTtl = settings->value("personal/redirectCacheTtl", NETWORK_REDIRECT_CACHE_TTL_DEFAULT).toInt();
cardPictureLoaderCacheMethod =
settings
->value("personal/cardPictureLoaderCacheMethod",
static_cast<int>(CardPictureLoaderCacheMethod::CacheMethod::NETWORK_CACHE))
.toInt();
localCardImageStorageNamingScheme =
settings
->value("personal/localCardImageStorageNamingScheme",
static_cast<int>(CardPictureLoaderLocalSchemes::NamingScheme::Set_Folder_Name_Set_Collector))
.toInt();
picDownload = settings->value("personal/picturedownload", true).toBool();
showStatusBar = settings->value("personal/showStatusBar", false).toBool();
@ -769,8 +795,9 @@ void SettingsCache::setPrintingSelectorCardSize(int _printingSelectorCardSize)
void SettingsCache::setIncludeRebalancedCards(bool _includeRebalancedCards)
{
if (includeRebalancedCards == _includeRebalancedCards)
if (includeRebalancedCards == _includeRebalancedCards) {
return;
}
includeRebalancedCards = _includeRebalancedCards;
settings->setValue("cards/includerebalancedcards", includeRebalancedCards);
@ -1097,6 +1124,13 @@ void SettingsCache::setPixmapCacheSize(const int _pixmapCacheSize)
emit pixmapCacheSizeChanged(pixmapCacheSize);
}
void SettingsCache::setCardImageCacheMethod(const CardPictureLoaderCacheMethod::CacheMethod _cardImageCachingMethod)
{
cardPictureLoaderCacheMethod = static_cast<int>(_cardImageCachingMethod);
settings->setValue("personal/cardPictureLoaderCacheMethod", cardPictureLoaderCacheMethod);
emit cardPictureLoaderCacheMethodChanged(cardPictureLoaderCacheMethod);
}
void SettingsCache::setNetworkCacheSizeInMB(const int _networkCacheSize)
{
networkCacheSize = _networkCacheSize;
@ -1111,6 +1145,14 @@ void SettingsCache::setNetworkRedirectCacheTtl(const int _redirectCacheTtl)
emit redirectCacheTtlChanged(redirectCacheTtl);
}
void SettingsCache::setLocalCardImageStorageNamingScheme(
const CardPictureLoaderLocalSchemes::NamingScheme _localCardImageStorageNamingScheme)
{
localCardImageStorageNamingScheme = static_cast<int>(_localCardImageStorageNamingScheme);
settings->setValue("personal/localCardImageStorageNamingScheme", localCardImageStorageNamingScheme);
emit localCardImageStorageNamingSchemeChanged(localCardImageStorageNamingScheme);
}
void SettingsCache::setClientID(const QString &_clientID)
{
clientID = _clientID;
@ -1246,6 +1288,12 @@ void SettingsCache::setLastCardUpdateCheck(QDate value)
settings->setValue("personal/lastCardUpdateCheck", lastCardUpdateCheck);
}
void SettingsCache::setAlwaysEnableNewSets(bool value)
{
alwaysEnableNewSets = value;
settings->setValue("personal/alwaysEnableNewSets", alwaysEnableNewSets);
}
void SettingsCache::setRememberGameSettings(const bool _rememberGameSettings)
{
rememberGameSettings = _rememberGameSettings;
@ -1303,8 +1351,9 @@ void SettingsCache::setMaxFontSize(int _max)
void SettingsCache::setRoundCardCorners(bool _roundCardCorners)
{
if (_roundCardCorners == roundCardCorners)
if (_roundCardCorners == roundCardCorners) {
return;
}
roundCardCorners = _roundCardCorners;
settings->setValue("cards/roundcardcorners", _roundCardCorners);

View file

@ -1,12 +1,14 @@
/**
* @file cache_settings.h
* @ingroup Settings
* @brief TODO: Document this.
*/
//! \todo Document this file.
#ifndef SETTINGSCACHE_H
#define SETTINGSCACHE_H
#include "../../interface/card_picture_loader/card_picture_loader_cache_method.h"
#include "../../interface/card_picture_loader/card_picture_loader_local_schemes.h"
#include "shortcuts_settings.h"
#include <QDate>
@ -184,6 +186,8 @@ signals:
void pixmapCacheSizeChanged(int newSizeInMBs);
void networkCacheSizeChanged(int newSizeInMBs);
void redirectCacheTtlChanged(int newTtl);
void cardPictureLoaderCacheMethodChanged(int cardPictureLoaderCacheMethod);
void localCardImageStorageNamingSchemeChanged(int localCardImageStorageNamingScheme);
void masterVolumeChanged(int value);
void chatMentionCompleterChanged();
void downloadSpoilerTimeIndexChanged();
@ -216,6 +220,7 @@ private:
bool checkCardUpdatesOnStartup;
int cardUpdateCheckInterval;
QDate lastCardUpdateCheck;
bool alwaysEnableNewSets;
bool notifyAboutUpdates;
bool notifyAboutNewVersion;
bool showTipsOnStartup;
@ -302,6 +307,8 @@ private:
int pixmapCacheSize;
int networkCacheSize;
int redirectCacheTtl;
int cardPictureLoaderCacheMethod;
int localCardImageStorageNamingScheme;
bool scaleCards;
int verticalCardOverlapPercent;
bool showMessagePopups;
@ -502,6 +509,10 @@ public:
return getLastCardUpdateCheck().daysTo(QDateTime::currentDateTime().date()) >= getCardUpdateCheckInterval() &&
getLastCardUpdateCheck() != QDateTime::currentDateTime().date();
}
[[nodiscard]] bool getAlwaysEnableNewSets() const
{
return alwaysEnableNewSets;
}
[[nodiscard]] bool getNotifyAboutUpdates() const override
{
return notifyAboutUpdates;
@ -781,6 +792,10 @@ public:
{
return pixmapCacheSize;
}
[[nodiscard]] CardPictureLoaderCacheMethod::CacheMethod getCardPictureLoaderCacheMethod() const
{
return static_cast<CardPictureLoaderCacheMethod::CacheMethod>(cardPictureLoaderCacheMethod);
}
[[nodiscard]] int getNetworkCacheSizeInMB() const
{
return networkCacheSize;
@ -789,6 +804,10 @@ public:
{
return redirectCacheTtl;
}
[[nodiscard]] CardPictureLoaderLocalSchemes::NamingScheme getLocalCardImageStorageNamingScheme() const
{
return static_cast<CardPictureLoaderLocalSchemes::NamingScheme>(localCardImageStorageNamingScheme);
}
[[nodiscard]] bool getScaleCards() const
{
return scaleCards;
@ -1093,8 +1112,11 @@ public slots:
void setIgnoreUnregisteredUsers(QT_STATE_CHANGED_T _ignoreUnregisteredUsers);
void setIgnoreUnregisteredUserMessages(QT_STATE_CHANGED_T _ignoreUnregisteredUserMessages);
void setPixmapCacheSize(const int _pixmapCacheSize);
void setCardImageCacheMethod(CardPictureLoaderCacheMethod::CacheMethod _cardImageCachingMethod);
void setNetworkCacheSizeInMB(const int _networkCacheSize);
void setNetworkRedirectCacheTtl(const int _redirectCacheTtl);
void setLocalCardImageStorageNamingScheme(
const CardPictureLoaderLocalSchemes::NamingScheme _localCardImageStorageNamingScheme);
void setCardScaling(const QT_STATE_CHANGED_T _scaleCards);
void setStackCardOverlapPercent(const int _verticalCardOverlapPercent);
void setShowMessagePopups(const QT_STATE_CHANGED_T _showMessagePopups);
@ -1125,6 +1147,7 @@ public slots:
void setStartupCardUpdateCheckAlwaysUpdate(bool value);
void setCardUpdateCheckInterval(int value);
void setLastCardUpdateCheck(QDate value);
void setAlwaysEnableNewSets(bool value);
void setNotifyAboutUpdate(QT_STATE_CHANGED_T _notifyaboutupdate);
void setNotifyAboutNewVersion(QT_STATE_CHANGED_T _notifyaboutnewversion);
void setUpdateReleaseChannelIndex(int value);

View file

@ -15,8 +15,9 @@ void CardCounterSettings::setColor(int counterId, const QColor &color)
QString key = QString("cards/counters/%1/color").arg(counterId);
if (settings.value(key).value<QColor>() == color)
if (settings.value(key).value<QColor>() == color) {
return;
}
settings.setValue(key, color);
emit colorChanged(counterId, color);

View file

@ -1,8 +1,8 @@
/**
* @file card_counter_settings.h
* @ingroup GameSettings
* @brief TODO: Document this.
*/
//! \todo Document this file.
#ifndef CARD_COUNTER_SETTINGS_H
#define CARD_COUNTER_SETTINGS_H

View file

@ -1,8 +1,8 @@
/**
* @file shortcut_treeview.h
* @ingroup CoreSettings
* @brief TODO: Document this.
*/
//! \todo Document this file.
#ifndef SHORTCUT_TREEVIEW_H
#define SHORTCUT_TREEVIEW_H

View file

@ -64,8 +64,13 @@ ShortcutsSettings::ShortcutsSettings(const QString &settingsPath, QObject *paren
}
}
/// PR 5079 changes Textbox/unfocusTextBox to Player/unfocusTextBox and tab_game/aFocusChat to Player/aFocusChat.
/// A migration is necessary to let players keep their already configured shortcuts.
/**
* @brief Migrates legacy shortcut key names to current naming scheme.
*
* PR 5079 changed Textbox/unfocusTextBox to Player/unfocusTextBox and
* tab_game/aFocusChat to Player/aFocusChat. This migration allows players
* to keep their already configured shortcuts.
*/
void ShortcutsSettings::migrateShortcuts()
{
if (QFile(settingsFilePath).exists()) {
@ -236,9 +241,7 @@ bool ShortcutsSettings::isValid(const QString &name, const QString &sequences) c
return findOverlaps(name, sequences).isEmpty();
}
/**
* Checks if the shortcut is a shortcut that is active in all windows
*/
/** @brief Checks if the shortcut is a shortcut that is active in all windows. */
static bool isAlwaysActiveShortcut(const QString &shortcutName)
{
return shortcutName.startsWith("MainWindow") || shortcutName.startsWith("Tabs");

View file

@ -1,8 +1,8 @@
/**
* @file shortcuts_settings.h
* @ingroup CoreSettings
* @brief TODO: Document this.
*/
//! \todo Document this file.
#ifndef SHORTCUTSSETTINGS_H
#define SHORTCUTSSETTINGS_H
@ -537,6 +537,9 @@ private:
{"Player/aSetAnnotation", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Set Annotation..."),
parseSequenceString("Alt+N"),
ShortcutGroup::Playing_Area)},
{"Player/aReduceLifeByPower", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Reduce Life by Power"),
parseSequenceString("Ctrl+Shift+L"),
ShortcutGroup::Playing_Area)},
{"Player/aSelectAll", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Select All Cards in Zone"),
parseSequenceString("Ctrl+A"),
ShortcutGroup::Playing_Area)},
@ -664,6 +667,9 @@ private:
{"Player/aRollDie", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Roll Dice..."),
parseSequenceString("Ctrl+I"),
ShortcutGroup::Gameplay)},
{"Player/aFlipCoin", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Flip Coin"),
parseSequenceString("Ctrl+Shift+I"),
ShortcutGroup::Gameplay)},
{"Player/aShuffle", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Shuffle Library"),
parseSequenceString("Ctrl+S"),
ShortcutGroup::Gameplay)},

View file

@ -95,8 +95,9 @@ QStringMap &SoundEngine::getAvailableThemes()
dir.setPath(SettingsCache::instance().getDataPath() + "/sounds");
for (const QString &themeName : dir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot, QDir::Name)) {
if (!availableThemes.contains(themeName))
if (!availableThemes.contains(themeName)) {
availableThemes.insert(themeName, dir.absoluteFilePath(themeName));
}
}
// load themes from cockatrice system dir
@ -111,8 +112,9 @@ QStringMap &SoundEngine::getAvailableThemes()
);
for (const QString &themeName : dir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot, QDir::Name)) {
if (!availableThemes.contains(themeName))
if (!availableThemes.contains(themeName)) {
availableThemes.insert(themeName, dir.absoluteFilePath(themeName));
}
}
return availableThemes;

View file

@ -1,8 +1,8 @@
/**
* @file sound_engine.h
* @ingroup Core
* @brief TODO: Document this.
*/
//! \todo Document this file.
#ifndef SOUNDENGINE_H
#define SOUNDENGINE_H

View file

@ -88,20 +88,27 @@ static void setupParserRules()
const auto arg = std::any_cast<int>(sv[1]);
const auto op = std::any_cast<QString>(sv[0]);
if (op == ">")
if (op == ">") {
return [=](const int s) { return s > arg; };
if (op == ">=")
}
if (op == ">=") {
return [=](const int s) { return s >= arg; };
if (op == "<")
}
if (op == "<") {
return [=](const int s) { return s < arg; };
if (op == "<=")
}
if (op == "<=") {
return [=](const int s) { return s <= arg; };
if (op == "=")
}
if (op == "=") {
return [=](const int s) { return s == arg; };
if (op == ":")
}
if (op == ":") {
return [=](const int s) { return s == arg; };
if (op == "!=")
}
if (op == "!=") {
return [=](const int s) { return s != arg; };
}
return [](int) { return false; };
};

View file

@ -1,8 +1,8 @@
/**
* @file deck_filter_string.h
* @ingroup DeckStorageWidgets
* @brief TODO: Document this.
*/
//! \todo Document this file.
#ifndef DECK_FILTER_STRING_H
#define DECK_FILTER_STRING_H

View file

@ -11,13 +11,15 @@ FilterBuilder::FilterBuilder(QWidget *parent) : QWidget(parent)
{
filterCombo = new QComboBox;
filterCombo->setObjectName("filterCombo");
for (int i = 0; i < CardFilter::AttrEnd; i++)
for (int i = 0; i < CardFilter::AttrEnd; i++) {
filterCombo->addItem(CardFilter::attrName(static_cast<CardFilter::Attr>(i)), QVariant(i));
}
typeCombo = new QComboBox;
typeCombo->setObjectName("typeCombo");
for (int i = 0; i < CardFilter::TypeEnd; i++)
for (int i = 0; i < CardFilter::TypeEnd; i++) {
typeCombo->addItem(CardFilter::typeName(static_cast<CardFilter::Type>(i)), QVariant(i));
}
QPushButton *ok = new QPushButton(QPixmap("theme:icons/increment"), QString());
ok->setObjectName("ok");
@ -53,8 +55,9 @@ FilterBuilder::~FilterBuilder()
void FilterBuilder::destroyFilter()
{
if (fltr)
if (fltr) {
delete fltr;
}
}
static int comboCurrentIntData(const QComboBox *combo)
@ -67,8 +70,9 @@ void FilterBuilder::emit_add()
QString txt;
txt = edit->text();
if (txt.length() < 1)
if (txt.length() < 1) {
return;
}
destroyFilter();
fltr = new CardFilter(txt, static_cast<CardFilter::Type>(comboCurrentIntData(typeCombo)),

View file

@ -1,8 +1,8 @@
/**
* @file filter_builder.h
* @ingroup CardDatabaseModelFilters
* @brief TODO: Document this.
*/
//! \todo Document this file.
#ifndef FILTERBUILDER_H
#define FILTERBUILDER_H

View file

@ -23,8 +23,9 @@ void FilterTreeModel::proxyBeginInsertRow(const FilterTreeNode *node, int i)
int idx;
idx = node->index();
if (idx >= 0)
if (idx >= 0) {
beginInsertRows(createIndex(idx, 0, (void *)node), i, i);
}
}
void FilterTreeModel::proxyEndInsertRow(const FilterTreeNode *node, int)
@ -32,8 +33,9 @@ void FilterTreeModel::proxyEndInsertRow(const FilterTreeNode *node, int)
int idx;
idx = node->index();
if (idx >= 0)
if (idx >= 0) {
endInsertRows();
}
}
void FilterTreeModel::proxyBeginRemoveRow(const FilterTreeNode *node, int i)
@ -41,8 +43,9 @@ void FilterTreeModel::proxyBeginRemoveRow(const FilterTreeNode *node, int i)
int idx;
idx = node->index();
if (idx >= 0)
if (idx >= 0) {
beginRemoveRows(createIndex(idx, 0, (void *)node), i, i);
}
}
void FilterTreeModel::proxyEndRemoveRow(const FilterTreeNode *node, int)
@ -50,8 +53,9 @@ void FilterTreeModel::proxyEndRemoveRow(const FilterTreeNode *node, int)
int idx;
idx = node->index();
if (idx >= 0)
if (idx >= 0) {
endRemoveRows();
}
}
FilterTreeNode *FilterTreeModel::indexToNode(const QModelIndex &idx) const
@ -59,12 +63,14 @@ FilterTreeNode *FilterTreeModel::indexToNode(const QModelIndex &idx) const
void *ip;
FilterTreeNode *node;
if (!idx.isValid())
if (!idx.isValid()) {
return fTree;
}
ip = idx.internalPointer();
if (ip == NULL)
if (ip == NULL) {
return fTree;
}
node = static_cast<FilterTreeNode *>(ip);
return node;
@ -145,14 +151,16 @@ int FilterTreeModel::rowCount(const QModelIndex &parent) const
const FilterTreeNode *node;
int result;
if (parent.column() > 0)
if (parent.column() > 0) {
return 0;
}
node = indexToNode(parent);
if (node)
if (node) {
result = node->childCount();
else
} else {
result = 0;
}
return result;
}
@ -166,14 +174,17 @@ QVariant FilterTreeModel::data(const QModelIndex &index, int role) const
{
const FilterTreeNode *node;
if (!index.isValid())
if (!index.isValid()) {
return QVariant();
if (index.column() >= columnCount())
}
if (index.column() >= columnCount()) {
return QVariant();
}
node = indexToNode(index);
if (node == NULL)
if (node == NULL) {
return QVariant();
}
switch (role) {
case Qt::FontRole:
@ -190,10 +201,11 @@ QVariant FilterTreeModel::data(const QModelIndex &index, int role) const
case Qt::WhatsThisRole:
return node->text();
case Qt::CheckStateRole:
if (node->isEnabled())
if (node->isEnabled()) {
return Qt::Checked;
else
} else {
return Qt::Unchecked;
}
default:
return QVariant();
}
@ -205,22 +217,27 @@ bool FilterTreeModel::setData(const QModelIndex &index, const QVariant &value, i
{
FilterTreeNode *node;
if (!index.isValid())
if (!index.isValid()) {
return false;
if (index.column() >= columnCount())
}
if (index.column() >= columnCount()) {
return false;
if (role != Qt::CheckStateRole)
}
if (role != Qt::CheckStateRole) {
return false;
}
node = indexToNode(index);
if (node == NULL || node == fTree)
if (node == NULL || node == fTree) {
return false;
}
Qt::CheckState state = static_cast<Qt::CheckState>(value.toInt());
if (state == Qt::Checked)
if (state == Qt::Checked) {
node->enable();
else
} else {
node->disable();
}
emit dataChanged(index, index);
return true;
@ -231,16 +248,19 @@ Qt::ItemFlags FilterTreeModel::flags(const QModelIndex &index) const
const FilterTreeNode *node;
Qt::ItemFlags result;
if (!index.isValid())
if (!index.isValid()) {
return Qt::NoItemFlags;
}
node = indexToNode(index);
if (node == NULL)
if (node == NULL) {
return Qt::NoItemFlags;
}
result = Qt::ItemIsEnabled;
if (node == fTree)
if (node == fTree) {
return result;
}
result |= Qt::ItemIsSelectable;
result |= Qt::ItemIsUserCheckable;
@ -252,8 +272,9 @@ QModelIndex FilterTreeModel::nodeIndex(const FilterTreeNode *node, int row, int
{
FilterTreeNode *child;
if (column > 0 || row >= node->childCount())
if (column > 0 || row >= node->childCount()) {
return QModelIndex();
}
child = node->nodeAt(row);
return createIndex(row, column, child);
@ -263,12 +284,14 @@ QModelIndex FilterTreeModel::index(int row, int column, const QModelIndex &paren
{
const FilterTreeNode *node;
if (!hasIndex(row, column, parent))
if (!hasIndex(row, column, parent)) {
return QModelIndex();
}
node = indexToNode(parent);
if (node == NULL)
if (node == NULL) {
return QModelIndex();
}
return nodeIndex(node, row, column);
}
@ -279,18 +302,21 @@ QModelIndex FilterTreeModel::parent(const QModelIndex &ind) const
FilterTreeNode *parent;
QModelIndex idx;
if (!ind.isValid())
if (!ind.isValid()) {
return QModelIndex();
}
node = indexToNode(ind);
if (node == NULL || node == fTree)
if (node == NULL || node == fTree) {
return QModelIndex();
}
parent = node->parent();
if (parent) {
int row = parent->index();
if (row < 0)
if (row < 0) {
return QModelIndex();
}
idx = createIndex(row, 0, parent);
return idx;
}
@ -304,18 +330,22 @@ bool FilterTreeModel::removeRows(int row, int count, const QModelIndex &parent)
int i, last;
last = row + count - 1;
if (!parent.isValid() || count < 1 || row < 0)
if (!parent.isValid() || count < 1 || row < 0) {
return false;
}
node = indexToNode(parent);
if (node == NULL || last >= node->childCount())
if (node == NULL || last >= node->childCount()) {
return false;
}
for (i = 0; i < count; i++)
for (i = 0; i < count; i++) {
node->deleteAt(row);
}
if (node != fTree && node->childCount() < 1)
if (node != fTree && node->childCount() < 1) {
return removeRow(parent.row(), parent.parent());
}
return true;
}

View file

@ -1,8 +1,8 @@
/**
* @file filter_tree_model.h
* @ingroup CardDatabaseModelFilters
* @brief TODO: Document this.
*/
//! \todo Document this file.
#ifndef FILTERTREEMODEL_H
#define FILTERTREEMODEL_H

View file

@ -2,8 +2,8 @@
* @file syntax_help.h
* @ingroup CardDatabaseModelFilters
* @ingroup DeckStorageWidgets
* @brief TODO: Document this.
*/
//! \todo Document this file.
#ifndef SEARCH_SYNTAX_HELP_H
#define SEARCH_SYNTAX_HELP_H

View file

@ -1,7 +1,7 @@
#include "abstract_game.h"
#include "../interface/widgets/tabs/tab_game.h"
#include "player/player.h"
#include "player/player_logic.h"
AbstractGame::AbstractGame(TabGame *_tab) : QObject(_tab), tab(_tab)
{
@ -24,10 +24,11 @@ AbstractClient *AbstractGame::getClientForPlayer(int playerId) const
}
return gameState->getClients().at(playerId);
} else if (gameState->getClients().isEmpty())
} else if (gameState->getClients().isEmpty()) {
return nullptr;
else
} else {
return gameState->getClients().first();
}
}
void AbstractGame::loadReplay(GameReplay *replay)
@ -43,13 +44,15 @@ void AbstractGame::setActiveCard(CardItem *card)
CardItem *AbstractGame::getCard(int playerId, const QString &zoneName, int cardId) const
{
Player *player = playerManager->getPlayer(playerId);
if (!player)
PlayerLogic *player = playerManager->getPlayer(playerId);
if (!player) {
return nullptr;
}
CardZoneLogic *zone = player->getZones().value(zoneName, 0);
if (!zone)
if (!zone) {
return nullptr;
}
return zone->getCard(cardId);
}

View file

@ -1,8 +1,8 @@
/**
* @file abstract_game.h
* @ingroup GameLogic
* @brief TODO: Document this.
*/
//! \todo Document this file.
#ifndef COCKATRICE_ABSTRACT_GAME_H
#define COCKATRICE_ABSTRACT_GAME_H

View file

@ -25,11 +25,12 @@ AbstractCardDragItem::AbstractCardDragItem(AbstractCardItem *_item,
setCursor(Qt::ClosedHandCursor);
setZValue(ZValues::DRAG_ITEM);
}
if (item->getTapped())
if (item->getTapped()) {
setTransform(QTransform()
.translate(CardDimensions::WIDTH_HALF_F, CardDimensions::HEIGHT_HALF_F)
.rotate(90)
.translate(-CardDimensions::WIDTH_HALF_F, -CardDimensions::HEIGHT_HALF_F));
}
setCacheMode(DeviceCoordinateCache);

View file

@ -1,8 +1,8 @@
/**
* @file abstract_card_drag_item.h
* @ingroup GameGraphicsCards
* @brief TODO: Document this.
*/
//! \todo Document this file.
#ifndef ABSTRACTCARDDRAGITEM_H
#define ABSTRACTCARDDRAGITEM_H

View file

@ -13,7 +13,7 @@
#include <libcockatrice/card/database/card_database.h>
#include <libcockatrice/card/database/card_database_manager.h>
AbstractCardItem::AbstractCardItem(QGraphicsItem *parent, const CardRef &cardRef, Player *_owner, int _id)
AbstractCardItem::AbstractCardItem(QGraphicsItem *parent, const CardRef &cardRef, PlayerLogic *_owner, int _id)
: ArrowTarget(_owner, parent), id(_id), cardRef(cardRef), tapped(false), facedown(false), tapAngle(0),
bgColor(Qt::transparent), isHovered(false), realZValue(0)
{
@ -85,7 +85,12 @@ const CardInfo &AbstractCardItem::getCardInfo() const
void AbstractCardItem::setRealZValue(qreal _zValue)
{
realZValue = _zValue;
setZValue(_zValue);
// During hover, zValue is overridden to HOVERED_CARD. Layout operations
// like reorganizeCards() call setRealZValue() on all cards including the
// hovered one — skip setZValue() here to avoid clobbering the override.
if (!isHovered) {
setZValue(_zValue);
}
}
QSizeF AbstractCardItem::getTranslatedSize(QPainter *painter) const
@ -126,8 +131,9 @@ void AbstractCardItem::paintPicture(QPainter *painter, const QSizeF &translatedS
// don't even spend time trying to load the picture if our size is too small
if (translatedSize.width() > 10) {
CardPictureLoader::getPixmap(translatedPixmap, exactCard, translatedSize.toSize());
if (translatedPixmap.isNull())
if (translatedPixmap.isNull()) {
paintImage = false;
}
} else {
paintImage = false;
}
@ -152,9 +158,9 @@ void AbstractCardItem::paintPicture(QPainter *painter, const QSizeF &translatedS
painter->setBackground(Qt::black);
painter->setBackgroundMode(Qt::OpaqueMode);
QString nameStr;
if (facedown)
if (facedown) {
nameStr = "# " + QString::number(id);
else {
} else {
QString prefix = "";
if (SettingsCache::instance().debug().getShowCardId()) {
prefix = "#" + QString::number(id) + " ";
@ -181,10 +187,12 @@ void AbstractCardItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *
if (isSelected() || isHovered) {
QPen pen;
if (isHovered)
if (isHovered) {
pen.setColor(Qt::yellow);
if (isSelected())
}
if (isSelected()) {
pen.setColor(Qt::red);
}
pen.setWidth(0); // Cosmetic pen
painter->setPen(pen);
painter->drawPath(shape());
@ -210,11 +218,20 @@ void AbstractCardItem::setCardRef(const CardRef &_cardRef)
void AbstractCardItem::setHovered(bool _hovered)
{
if (isHovered == _hovered)
if (isHovered == _hovered) {
return;
}
if (_hovered)
if (_hovered) {
processHoverEvent();
} else {
// Mark the hovered card's current scene footprint dirty so overlapped
// sibling zones (e.g. StackZone) repaint after the card moves away.
if (scene()) {
scene()->update(sceneBoundingRect());
}
}
isHovered = _hovered;
setZValue(_hovered ? ZValues::HOVERED_CARD : realZValue);
setScale(_hovered && SettingsCache::instance().getScaleCards() ? 1.1 : 1);
@ -265,13 +282,14 @@ void AbstractCardItem::cacheBgColor()
void AbstractCardItem::setTapped(bool _tapped, bool canAnimate)
{
if (tapped == _tapped)
if (tapped == _tapped) {
return;
}
tapped = _tapped;
if (SettingsCache::instance().getTapAnimation() && canAnimate)
if (SettingsCache::instance().getTapAnimation() && canAnimate) {
static_cast<GameScene *>(scene())->registerAnimationItem(this);
else {
} else {
tapAngle = tapped ? 90 : 0;
setTransform(QTransform()
.translate(CardDimensions::WIDTH_HALF_F, CardDimensions::HEIGHT_HALF_F)
@ -297,17 +315,19 @@ void AbstractCardItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
scene()->clearSelection();
setSelected(true);
}
if (event->button() == Qt::LeftButton)
if (event->button() == Qt::LeftButton) {
setCursor(Qt::ClosedHandCursor);
else if (event->button() == Qt::MiddleButton)
} else if (event->button() == Qt::MiddleButton) {
emit showCardInfoPopup(event->screenPos(), cardRef);
}
event->accept();
}
void AbstractCardItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
if (event->button() == Qt::MiddleButton)
if (event->button() == Qt::MiddleButton) {
emit deleteCardInfoPopup(cardRef.name);
}
// This function ensures the parent function doesn't mess around with our selection.
event->accept();
@ -323,6 +343,7 @@ QVariant AbstractCardItem::itemChange(QGraphicsItem::GraphicsItemChange change,
if (change == ItemSelectedHasChanged) {
update();
return value;
} else
} else {
return ArrowTarget::itemChange(change, value);
}
}

View file

@ -1,7 +1,7 @@
/**
* @file abstract_card_item.h
* @ingroup GameGraphicsCards
* @brief TODO: Document this.
* @brief Base class for graphical card items, providing shared rendering, identity, and interaction logic.
*/
#ifndef ABSTRACTCARDITEM_H
@ -14,7 +14,7 @@
#include <libcockatrice/card/printing/exact_card.h>
#include <libcockatrice/utility/card_ref.h>
class Player;
class PlayerLogic;
class AbstractCardItem : public ArrowTarget
{
@ -56,7 +56,7 @@ public:
}
explicit AbstractCardItem(QGraphicsItem *parent = nullptr,
const CardRef &cardRef = {},
Player *_owner = nullptr,
PlayerLogic *_owner = nullptr,
int _id = -1);
~AbstractCardItem() override;
QRectF boundingRect() const override;
@ -96,6 +96,10 @@ public:
}
void setRealZValue(qreal _zValue);
void setHovered(bool _hovered);
bool getIsHovered() const
{
return isHovered;
}
QString getColor() const
{
return color;

View file

@ -2,13 +2,14 @@
#include "../../client/settings/cache_settings.h"
#include "../../interface/widgets/tabs/tab_game.h"
#include "../player/player.h"
#include "../player/player_actions.h"
#include "../player/player_logic.h"
#include "translate_counter_name.h"
#include <QAction>
#include <QApplication>
#include <QGraphicsSceneMouseEvent>
#include <QGraphicsView>
#include <QKeyEvent>
#include <QMenu>
#include <QString>
@ -16,24 +17,24 @@
#include <libcockatrice/protocol/pb/command_set_counter.pb.h>
#include <libcockatrice/utility/expression.h>
AbstractCounter::AbstractCounter(Player *_player,
int _id,
const QString &_name,
AbstractCounter::AbstractCounter(CounterState *state,
PlayerLogic *_player,
bool _shownInCounterArea,
int _value,
bool _useNameForShortcut,
QGraphicsItem *parent)
: QGraphicsItem(parent), player(_player), id(_id), name(_name), value(_value),
useNameForShortcut(_useNameForShortcut), hovered(false), aDec(nullptr), aInc(nullptr), dialogSemaphore(false),
deleteAfterDialog(false), shownInCounterArea(_shownInCounterArea)
: QGraphicsItem(parent), player(_player), id(state->getId()), name(state->getName()), value(state->getValue()),
color(state->getColor()), radius(state->getRadius()), useNameForShortcut(_useNameForShortcut),
shownInCounterArea(_shownInCounterArea)
{
setAcceptHoverEvents(true);
shortcutActive = false;
connect(state, &CounterState::valueChanged, this, [this](int, int newValue) {
value = newValue;
update();
});
if (player->getPlayerInfo()->getLocalOrJudge()) {
QString displayName = TranslateCounterName::getDisplayName(_name);
menu = new TearOffMenu(displayName);
menu = new TearOffMenu(TranslateCounterName::getDisplayName(state->getName()));
aSet = new QAction(this);
connect(aSet, &QAction::triggered, this, &AbstractCounter::setCounter);
menu->addAction(aSet);
@ -41,16 +42,18 @@ AbstractCounter::AbstractCounter(Player *_player,
for (int i = 10; i >= -10; --i) {
if (i == 0) {
menu->addSeparator();
} else {
QAction *aIncrement = new QAction(QString(i < 0 ? "%1" : "+%1").arg(i), this);
if (i == -1)
aDec = aIncrement;
else if (i == 1)
aInc = aIncrement;
aIncrement->setData(i);
connect(aIncrement, &QAction::triggered, this, &AbstractCounter::incrementCounter);
menu->addAction(aIncrement);
continue;
}
auto *a = new QAction(QString(i < 0 ? "%1" : "+%1").arg(i), this);
if (i == -1) {
aDec = a;
}
if (i == 1) {
aInc = a;
}
a->setData(i);
connect(a, &QAction::triggered, this, &AbstractCounter::incrementCounter);
menu->addAction(a);
}
} else {
menu = nullptr;
@ -69,39 +72,35 @@ AbstractCounter::~AbstractCounter()
void AbstractCounter::delCounter()
{
if (dialogSemaphore)
if (dialogSemaphore) {
deleteAfterDialog = true;
else
} else {
deleteLater();
}
}
void AbstractCounter::retranslateUi()
{
if (menu) {
if (aSet) {
aSet->setText(tr("&Set counter..."));
}
}
void AbstractCounter::setShortcutsActive()
{
if (!menu) {
if (!menu || !player->getPlayerInfo()->getLocal()) {
return;
}
if (!player->getPlayerInfo()->getLocal()) {
return;
}
ShortcutsSettings &shortcuts = SettingsCache::instance().shortcuts();
ShortcutsSettings &sc = SettingsCache::instance().shortcuts();
shortcutActive = true;
if (name == "life") {
shortcutActive = true;
aSet->setShortcuts(shortcuts.getShortcut("Player/aSet"));
aDec->setShortcuts(shortcuts.getShortcut("Player/aDec"));
aInc->setShortcuts(shortcuts.getShortcut("Player/aInc"));
aSet->setShortcuts(sc.getShortcut("Player/aSet"));
aDec->setShortcuts(sc.getShortcut("Player/aDec"));
aInc->setShortcuts(sc.getShortcut("Player/aInc"));
} else if (useNameForShortcut) {
shortcutActive = true;
aSet->setShortcuts(shortcuts.getShortcut("Player/aSetCounter_" + name));
aDec->setShortcuts(shortcuts.getShortcut("Player/aDecCounter_" + name));
aInc->setShortcuts(shortcuts.getShortcut("Player/aIncCounter_" + name));
aSet->setShortcuts(sc.getShortcut("Player/aSetCounter_" + name));
aDec->setShortcuts(sc.getShortcut("Player/aDecCounter_" + name));
aInc->setShortcuts(sc.getShortcut("Player/aIncCounter_" + name));
}
}
@ -126,43 +125,32 @@ void AbstractCounter::refreshShortcuts()
}
}
void AbstractCounter::setValue(int _value)
{
value = _value;
update();
}
void AbstractCounter::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
if (isUnderMouse() && player->getPlayerInfo()->getLocalOrJudge()) {
if (event->button() == Qt::MiddleButton || (QApplication::keyboardModifiers() & Qt::ShiftModifier)) {
if (menu)
menu->exec(event->screenPos());
event->accept();
} else if (event->button() == Qt::LeftButton) {
Command_IncCounter cmd;
cmd.set_counter_id(id);
cmd.set_delta(1);
player->getPlayerActions()->sendGameCommand(cmd);
event->accept();
} else if (event->button() == Qt::RightButton) {
Command_IncCounter cmd;
cmd.set_counter_id(id);
cmd.set_delta(-1);
player->getPlayerActions()->sendGameCommand(cmd);
event->accept();
}
} else
if (!isUnderMouse() || !player->getPlayerInfo()->getLocalOrJudge()) {
event->ignore();
return;
}
if (event->button() == Qt::MiddleButton || QApplication::keyboardModifiers() & Qt::ShiftModifier) {
if (menu) {
menu->exec(event->screenPos());
}
} else {
Command_IncCounter cmd;
cmd.set_counter_id(id);
cmd.set_delta(event->button() == Qt::LeftButton ? 1 : -1);
player->getPlayerActions()->sendGameCommand(cmd);
}
event->accept();
}
void AbstractCounter::hoverEnterEvent(QGraphicsSceneHoverEvent * /*event*/)
void AbstractCounter::hoverEnterEvent(QGraphicsSceneHoverEvent *)
{
hovered = true;
update();
}
void AbstractCounter::hoverLeaveEvent(QGraphicsSceneHoverEvent * /*event*/)
void AbstractCounter::hoverLeaveEvent(QGraphicsSceneHoverEvent *)
{
hovered = false;
update();
@ -170,34 +158,36 @@ void AbstractCounter::hoverLeaveEvent(QGraphicsSceneHoverEvent * /*event*/)
void AbstractCounter::incrementCounter()
{
const int delta = static_cast<QAction *>(sender())->data().toInt();
Command_IncCounter cmd;
cmd.set_counter_id(id);
cmd.set_delta(delta);
cmd.set_delta(static_cast<QAction *>(sender())->data().toInt());
player->getPlayerActions()->sendGameCommand(cmd);
}
void AbstractCounter::setCounter()
{
QWidget *parent = nullptr;
if (auto *view = scene() ? scene()->views().value(0) : nullptr) {
parent = view->window();
}
dialogSemaphore = true;
AbstractCounterDialog dialog(name, QString::number(value), player->getGame()->getTab());
const int ok = dialog.exec();
AbstractCounterDialog dlg(name, QString::number(value), parent);
const int ok = dlg.exec();
dialogSemaphore = false;
if (deleteAfterDialog) {
deleteLater();
return;
}
dialogSemaphore = false;
if (!ok)
if (!ok) {
return;
}
Expression exp(value);
int newValue = static_cast<int>(exp.parse(dialog.textValue()));
Command_SetCounter cmd;
cmd.set_counter_id(id);
cmd.set_value(newValue);
cmd.set_value(static_cast<int>(exp.parse(dlg.textValue())));
player->getPlayerActions()->sendGameCommand(cmd);
}
@ -231,8 +221,9 @@ void AbstractCounterDialog::changeValue(int diff)
{
bool ok;
int curValue = textValue().toInt(&ok);
if (!ok)
if (!ok) {
return;
}
curValue += diff;
setTextValue(QString::number(curValue));
}

View file

@ -1,19 +1,20 @@
/**
* @file abstract_counter.h
* @ingroup GameGraphicsPlayers
* @brief TODO: Document this.
*/
//! \todo Document this file.
#ifndef COUNTER_H
#define COUNTER_H
#include "../../interface/widgets/menus/tearoff_menu.h"
#include "../player/menu/abstract_player_component.h"
#include "counter_state.h"
#include <QGraphicsItem>
#include <QInputDialog>
class Player;
class PlayerLogic;
class QAction;
class QKeyEvent;
class QMenu;
@ -25,22 +26,26 @@ class AbstractCounter : public QObject, public QGraphicsItem, public AbstractPla
Q_INTERFACES(QGraphicsItem)
protected:
Player *player;
PlayerLogic *player;
int id;
QString name;
int value;
bool useNameForShortcut, hovered;
QColor color;
int radius;
bool hovered = false;
bool useNameForShortcut;
void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
void hoverEnterEvent(QGraphicsSceneHoverEvent *event) override;
void hoverLeaveEvent(QGraphicsSceneHoverEvent *event) override;
private:
QAction *aSet, *aDec, *aInc;
TearOffMenu *menu;
bool dialogSemaphore, deleteAfterDialog;
QAction *aSet = nullptr, *aDec = nullptr, *aInc = nullptr;
TearOffMenu *menu = nullptr;
bool dialogSemaphore = false;
bool deleteAfterDialog = false;
bool shownInCounterArea;
bool shortcutActive;
bool shortcutActive = false;
private slots:
void refreshShortcuts();
@ -48,17 +53,14 @@ private slots:
void setCounter();
public:
AbstractCounter(Player *_player,
int _id,
const QString &_name,
bool _shownInCounterArea,
int _value,
bool _useNameForShortcut = false,
AbstractCounter(CounterState *state,
PlayerLogic *player,
bool shownInCounterArea,
bool useNameForShortcut = false,
QGraphicsItem *parent = nullptr);
~AbstractCounter() override;
void retranslateUi() override;
void setValue(int _value);
void setShortcutsActive() override;
void setShortcutsInactive() override;
void delCounter();
@ -67,7 +69,6 @@ public:
{
return menu;
}
int getId() const
{
return id;
@ -76,14 +77,22 @@ public:
{
return name;
}
bool getShownInCounterArea() const
QColor getColor() const
{
return shownInCounterArea;
return color;
}
int getRadius() const
{
return radius;
}
int getValue() const
{
return value;
}
bool getShownInCounterArea() const
{
return shownInCounterArea;
}
};
class AbstractCounterDialog : public QInputDialog

View file

@ -0,0 +1,19 @@
#include "arrow_data.h"
ArrowData ArrowData::fromProto(const ServerInfo_Arrow &arrow)
{
ArrowData data;
data.id = arrow.id();
data.startPlayerId = arrow.start_player_id();
data.startZone = QString::fromStdString(arrow.start_zone());
data.startCardId = arrow.start_card_id();
data.targetPlayerId = arrow.target_player_id();
data.color = convertColorToQColor(arrow.arrow_color());
if (arrow.has_target_zone()) {
data.targetZone = QString::fromStdString(arrow.target_zone());
data.targetCardId = arrow.target_card_id();
}
return data;
}

View file

@ -0,0 +1,28 @@
#ifndef COCKATRICE_ARROW_DATA_H
#define COCKATRICE_ARROW_DATA_H
#include <QColor>
#include <QString>
#include <libcockatrice/protocol/pb/serverinfo_arrow.pb.h>
#include <libcockatrice/utility/color.h>
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;
static ArrowData fromProto(const ServerInfo_Arrow &arrow);
bool isPlayerTargeted() const
{
return targetZone.isEmpty();
}
};
#endif // COCKATRICE_ARROW_DATA_H

View file

@ -2,11 +2,11 @@
#include "arrow_item.h"
#include "../../client/settings/cache_settings.h"
#include "../player/player.h"
#include "../../game_graphics/zones/card_zone.h"
#include "../player/player_actions.h"
#include "../player/player_logic.h"
#include "../player/player_target.h"
#include "../z_values.h"
#include "../zones/card_zone.h"
#include "card_item.h"
#include <QDebug>
@ -21,46 +21,53 @@
#include <libcockatrice/utility/color.h>
#include <libcockatrice/utility/zone_names.h>
ArrowItem::ArrowItem(Player *_player, int _id, ArrowTarget *_startItem, ArrowTarget *_targetItem, const QColor &_color)
: QGraphicsItem(), player(_player), id(_id), startItem(_startItem), targetItem(_targetItem), targetLocked(false),
color(_color), fullColor(true)
ArrowItem::ArrowItem(PlayerLogic *_player,
int _id,
ArrowTarget *_startItem,
ArrowTarget *_targetItem,
const QColor &_color)
: player(_player), id(_id), startItem(_startItem), targetItem(_targetItem), color(_color)
{
setZValue(ZValues::ARROWS);
if (startItem)
startItem->addArrowFrom(this);
if (targetItem)
targetItem->addArrowTo(this);
auto doUpdate = [this]() {
if (startItem && targetItem) {
updatePath();
}
};
if (startItem && targetItem)
if (startItem) {
connect(startItem, &ArrowTarget::scenePositionChanged, this, doUpdate);
connect(startItem, &QObject::destroyed, this, &ArrowItem::onTargetDestroyed);
}
if (targetItem) {
connect(targetItem, &ArrowTarget::scenePositionChanged, this, doUpdate);
connect(targetItem, &QObject::destroyed, this, &ArrowItem::onTargetDestroyed);
}
if (startItem && targetItem) {
updatePath();
}
}
ArrowItem::~ArrowItem()
void ArrowItem::onTargetDestroyed()
{
emit requestDeletion(id);
}
void ArrowItem::delArrow()
{
if (startItem) {
startItem->removeArrowFrom(this);
startItem = 0;
}
if (targetItem) {
targetItem->setBeingPointedAt(false);
targetItem->removeArrowTo(this);
targetItem = 0;
}
player->removeArrow(this);
deleteLater();
}
void ArrowItem::updatePath()
{
if (!targetItem)
if (!targetItem) {
return;
}
QPointF endPoint = targetItem->mapToScene(
QPointF(targetItem->boundingRect().width() / 2, targetItem->boundingRect().height() / 2));
@ -75,8 +82,9 @@ void ArrowItem::updatePath(const QPointF &endPoint)
headWidth / qPow(2, 0.5); // aka headWidth / sqrt (2) but this produces a compile error with MSVC++
const double phi = 15;
if (!startItem)
if (!startItem) {
return;
}
QPointF startPoint =
startItem->mapToScene(QPointF(startItem->boundingRect().width() / 2, startItem->boundingRect().height() / 2));
@ -84,9 +92,9 @@ void ArrowItem::updatePath(const QPointF &endPoint)
qreal lineLength = line.length();
prepareGeometryChange();
if (lineLength < 30)
if (lineLength < 30) {
path = QPainterPath();
else {
} else {
QPointF c(lineLength / 2, qTan(phi * M_PI / 180) * lineLength);
QPainterPath centerLine;
@ -123,10 +131,11 @@ void ArrowItem::updatePath(const QPointF &endPoint)
void ArrowItem::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/)
{
QColor paintColor(color);
if (fullColor)
if (fullColor) {
paintColor.setAlpha(200);
else
} else {
paintColor.setAlpha(150);
}
painter->setBrush(paintColor);
painter->drawPath(path);
}
@ -138,8 +147,7 @@ void ArrowItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
return;
}
QList<QGraphicsItem *> colliding = scene()->items(event->scenePos());
for (QGraphicsItem *item : colliding) {
for (auto *item : scene()->items(event->scenePos())) {
if (qgraphicsitem_cast<CardItem *>(item)) {
event->ignore();
return;
@ -148,80 +156,86 @@ void ArrowItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
event->accept();
if (event->button() == Qt::RightButton) {
Command_DeleteArrow cmd;
cmd.set_arrow_id(id);
player->getPlayerActions()->sendGameCommand(cmd);
emit requestDeletion(id);
}
}
ArrowDragItem::ArrowDragItem(Player *_owner, ArrowTarget *_startItem, const QColor &_color, int _deleteInPhase)
: ArrowItem(_owner, -1, _startItem, 0, _color), deleteInPhase(_deleteInPhase)
// ArrowDragItem
ArrowDragItem::ArrowDragItem(PlayerLogic *_owner, ArrowTarget *_startItem, const QColor &_color, int _deleteInPhase)
: ArrowItem(_owner, -1, _startItem, nullptr, _color), deleteInPhase(_deleteInPhase)
{
}
void ArrowDragItem::addChildArrow(ArrowDragItem *childArrow)
void ArrowDragItem::addChildArrow(ArrowDragItem *child)
{
childArrows.append(childArrow);
childArrows.append(child);
}
void ArrowDragItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
// This ensures that if a mouse move event happens after a call to delArrow(),
// the event will be discarded as it would create some stray pointers.
if (targetLocked || !startItem)
if (targetLocked || !startItem) {
return;
}
QPointF endPos = event->scenePos();
const QPointF endPos = event->scenePos();
QList<QGraphicsItem *> colliding = scene()->items(endPos);
ArrowTarget *cursorItem = 0;
ArrowTarget *cursorItem = nullptr;
qreal cursorItemZ = -1;
for (int i = colliding.size() - 1; i >= 0; i--) {
if (qgraphicsitem_cast<PlayerTarget *>(colliding.at(i)) || qgraphicsitem_cast<CardItem *>(colliding.at(i))) {
if (colliding.at(i)->zValue() > cursorItemZ) {
cursorItem = static_cast<ArrowTarget *>(colliding.at(i));
cursorItemZ = cursorItem->zValue();
}
for (auto *item : scene()->items(endPos)) {
ArrowTarget *candidate = nullptr;
if (auto *card = qgraphicsitem_cast<CardItem *>(item)) {
candidate = card;
} else if (auto *pt = qgraphicsitem_cast<PlayerTarget *>(item)) {
candidate = pt;
}
if (candidate && candidate->zValue() > cursorItemZ) {
cursorItem = candidate;
cursorItemZ = candidate->zValue();
}
}
if ((cursorItem != targetItem) && targetItem) {
targetItem->setBeingPointedAt(false);
targetItem->removeArrowTo(this);
}
if (!cursorItem) {
fullColor = false;
targetItem = 0;
updatePath(endPos);
} else {
if (cursorItem != targetItem) {
fullColor = true;
if (cursorItem != startItem) {
cursorItem->setBeingPointedAt(true);
cursorItem->addArrowTo(this);
}
targetItem = cursorItem;
if (cursorItem != targetItem) {
if (targetItem) {
disconnect(positionConnection);
targetItem->setBeingPointedAt(false);
}
targetItem = cursorItem;
fullColor = (cursorItem != nullptr);
if (cursorItem && cursorItem != startItem) {
cursorItem->setBeingPointedAt(true);
positionConnection =
connect(cursorItem, &ArrowTarget::scenePositionChanged, this, [this]() { updatePath(); });
}
updatePath();
}
targetItem ? updatePath() : updatePath(endPos);
update();
for (ArrowDragItem *child : childArrows) {
for (auto *child : childArrows) {
child->mouseMoveEvent(event);
}
}
void ArrowDragItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
if (!startItem)
if (!startItem) {
return;
}
if (targetItem && (targetItem != startItem)) {
CardZoneLogic *startZone = static_cast<CardItem *>(startItem)->getZone();
if (targetItem && targetItem != startItem) {
CardItem *startCard = qgraphicsitem_cast<CardItem *>(startItem);
// For now, we can safely assume that the start item is always a card.
// The target item can be a player as well.
CardItem *startCard = qgraphicsitem_cast<CardItem *>(startItem);
CardItem *targetCard = qgraphicsitem_cast<CardItem *>(targetItem);
if (!startCard) {
delArrow();
return;
}
CardZoneLogic *startZone = startCard->getZone();
Command_CreateArrow cmd;
cmd.mutable_arrow_color()->CopyFrom(convertQColorToColor(color));
@ -229,14 +243,16 @@ void ArrowDragItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
cmd.set_start_zone(startZone->getName().toStdString());
cmd.set_start_card_id(startCard->getId());
if (targetCard) {
if (auto *targetCard = qgraphicsitem_cast<CardItem *>(targetItem)) {
CardZoneLogic *targetZone = targetCard->getZone();
cmd.set_target_player_id(targetZone->getPlayer()->getPlayerInfo()->getId());
cmd.set_target_zone(targetZone->getName().toStdString());
cmd.set_target_card_id(targetCard->getId());
} else { // failed to cast target to card, this means it's a player
PlayerTarget *targetPlayer = qgraphicsitem_cast<PlayerTarget *>(targetItem);
} else if (auto *targetPlayer = qgraphicsitem_cast<PlayerTarget *>(targetItem)) {
cmd.set_target_player_id(targetPlayer->getOwner()->getPlayerInfo()->getId());
} else {
delArrow();
return;
}
// if the card is in hand then we will move the card to stack or table as part of drawing the arrow
@ -246,10 +262,11 @@ void ArrowDragItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
bool playToStack = SettingsCache::instance().getPlayToStack();
if (ci && ((!playToStack && ci->getUiAttributes().tableRow == 3) ||
(playToStack && ci->getUiAttributes().tableRow != 0 &&
startCard->getZone()->getName() != ZoneNames::STACK)))
startCard->getZone()->getName() != ZoneNames::STACK))) {
cmd.set_start_zone(ZoneNames::STACK);
else
} else {
cmd.set_start_zone(playToStack ? ZoneNames::STACK : ZoneNames::TABLE);
}
}
if (deleteInPhase != 0) {
@ -258,111 +275,109 @@ void ArrowDragItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
player->getPlayerActions()->sendGameCommand(cmd);
}
delArrow();
for (ArrowDragItem *child : childArrows) {
delArrow();
for (auto *child : childArrows) {
child->mouseReleaseEvent(event);
}
}
// ArrowAttachItem
ArrowAttachItem::ArrowAttachItem(ArrowTarget *_startItem)
: ArrowItem(_startItem->getOwner(), -1, _startItem, 0, Qt::green)
: ArrowItem(_startItem->getOwner(), -1, _startItem, nullptr, Qt::green)
{
}
void ArrowAttachItem::addChildArrow(ArrowAttachItem *childArrow)
void ArrowAttachItem::addChildArrow(ArrowAttachItem *child)
{
childArrows.append(childArrow);
childArrows.append(child);
}
void ArrowAttachItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
if (targetLocked || !startItem)
if (targetLocked || !startItem) {
return;
}
QPointF endPos = event->scenePos();
const QPointF endPos = event->scenePos();
QList<QGraphicsItem *> colliding = scene()->items(endPos);
ArrowTarget *cursorItem = 0;
ArrowTarget *cursorItem = nullptr;
qreal cursorItemZ = -1;
for (int i = colliding.size() - 1; i >= 0; i--) {
if (qgraphicsitem_cast<CardItem *>(colliding.at(i))) {
if (colliding.at(i)->zValue() > cursorItemZ) {
cursorItem = static_cast<ArrowTarget *>(colliding.at(i));
cursorItemZ = cursorItem->zValue();
for (auto *item : scene()->items(endPos)) {
if (auto *card = qgraphicsitem_cast<CardItem *>(item)) {
if (card->zValue() > cursorItemZ) {
cursorItem = card;
cursorItemZ = card->zValue();
}
}
}
if ((cursorItem != targetItem) && targetItem) {
targetItem->setBeingPointedAt(false);
}
if (!cursorItem) {
fullColor = false;
targetItem = 0;
updatePath(endPos);
} else {
fullColor = true;
if (cursorItem != startItem) {
cursorItem->setBeingPointedAt(true);
if (cursorItem != targetItem) {
if (targetItem) {
disconnect(positionConnection);
targetItem->setBeingPointedAt(false);
}
targetItem = cursorItem;
updatePath();
fullColor = (cursorItem != nullptr);
if (cursorItem && cursorItem != startItem) {
cursorItem->setBeingPointedAt(true);
positionConnection =
connect(cursorItem, &ArrowTarget::scenePositionChanged, this, [this]() { updatePath(); });
}
}
targetItem ? updatePath() : updatePath(endPos);
update();
for (ArrowAttachItem *child : childArrows) {
for (auto *child : childArrows) {
child->mouseMoveEvent(event);
}
}
void ArrowAttachItem::attachCards(CardItem *startCard, const CardItem *targetCard)
{
// do nothing if target is already attached to another card or is not in play
if (targetCard->getAttachedTo() || targetCard->getZone()->getName() != ZoneNames::TABLE) {
return;
}
CardZoneLogic *startZone = startCard->getZone();
CardZoneLogic *targetZone = targetCard->getZone();
// move card onto table first if attaching from some other zone
if (startZone->getName() != ZoneNames::TABLE) {
player->getPlayerActions()->playCardToTable(startCard, false);
}
Command_AttachCard cmd;
cmd.set_start_zone(ZoneNames::TABLE);
cmd.set_card_id(startCard->getId());
cmd.set_target_player_id(targetZone->getPlayer()->getPlayerInfo()->getId());
cmd.set_target_zone(targetZone->getName().toStdString());
cmd.set_target_card_id(targetCard->getId());
player->getPlayerActions()->sendGameCommand(cmd);
}
void ArrowAttachItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
if (!startItem)
if (!startItem) {
return;
}
// Attaching could move startItem under the current cursor position, causing all children to retarget to it right
// before they are processed. Prevent that.
for (ArrowAttachItem *child : childArrows) {
for (auto *child : childArrows) {
child->setTargetLocked(true);
}
if (targetItem && (targetItem != startItem)) {
auto startCard = qgraphicsitem_cast<CardItem *>(startItem);
auto targetCard = qgraphicsitem_cast<CardItem *>(targetItem);
if (targetItem && targetItem != startItem) {
auto *startCard = qgraphicsitem_cast<CardItem *>(startItem);
auto *targetCard = qgraphicsitem_cast<CardItem *>(targetItem);
if (startCard && targetCard) {
attachCards(startCard, targetCard);
}
}
delArrow();
for (ArrowAttachItem *child : childArrows) {
for (auto *child : childArrows) {
child->mouseReleaseEvent(event);
}
}
void ArrowAttachItem::attachCards(CardItem *startCard, const CardItem *targetCard)
{
if (targetCard->getAttachedTo() || targetCard->getZone()->getName() != ZoneNames::TABLE) {
return;
}
// move card onto table first if attaching from some other zone
if (startCard->getZone()->getName() != ZoneNames::TABLE) {
player->getPlayerActions()->playCardToTable(startCard, false);
}
Command_AttachCard cmd;
cmd.set_start_zone(ZoneNames::TABLE);
cmd.set_card_id(startCard->getId());
cmd.set_target_player_id(targetCard->getZone()->getPlayer()->getPlayerInfo()->getId());
cmd.set_target_zone(targetCard->getZone()->getName().toStdString());
cmd.set_target_card_id(targetCard->getId());
player->getPlayerActions()->sendGameCommand(cmd);
}

View file

@ -1,40 +1,47 @@
/**
* @file arrow_item.h
* @ingroup GameGraphics
* @brief TODO: Document this.
*/
//! \todo Document this file.
#ifndef ARROWITEM_H
#define ARROWITEM_H
#include "arrow_target.h"
#include <QGraphicsItem>
#include <QPointer>
class CardItem;
class QGraphicsSceneMouseEvent;
class QMenu;
class Player;
class ArrowTarget;
class PlayerLogic;
class ArrowItem : public QObject, public QGraphicsItem
{
Q_OBJECT
Q_INTERFACES(QGraphicsItem)
signals:
void requestDeletion(int id);
private:
QPainterPath path;
QMenu *menu;
protected:
Player *player;
PlayerLogic *player;
int id;
ArrowTarget *startItem, *targetItem;
bool targetLocked;
QPointer<ArrowTarget> startItem;
QPointer<ArrowTarget> targetItem;
bool targetLocked = false;
QColor color;
bool fullColor;
bool fullColor = true;
void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
public:
ArrowItem(Player *_player, int _id, ArrowTarget *_startItem, ArrowTarget *_targetItem, const QColor &color);
~ArrowItem() override;
ArrowItem(PlayerLogic *_player, int _id, ArrowTarget *_startItem, ArrowTarget *_targetItem, const QColor &_color);
void onTargetDestroyed();
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
[[nodiscard]] QRectF boundingRect() const override
{
@ -44,6 +51,7 @@ public:
{
return path;
}
void updatePath();
void updatePath(const QPointF &endPoint);
@ -51,18 +59,10 @@ public:
{
return id;
}
[[nodiscard]] Player *getPlayer() const
[[nodiscard]] PlayerLogic *getPlayer() const
{
return player;
}
void setStartItem(ArrowTarget *_item)
{
startItem = _item;
}
void setTargetItem(ArrowTarget *_item)
{
targetItem = _item;
}
[[nodiscard]] ArrowTarget *getStartItem() const
{
return startItem;
@ -75,6 +75,7 @@ public:
{
targetLocked = _targetLocked;
}
void delArrow();
};
@ -84,10 +85,11 @@ class ArrowDragItem : public ArrowItem
private:
int deleteInPhase;
QList<ArrowDragItem *> childArrows;
QMetaObject::Connection positionConnection;
public:
ArrowDragItem(Player *_owner, ArrowTarget *_startItem, const QColor &_color, int _deleteInPhase);
void addChildArrow(ArrowDragItem *childArrow);
ArrowDragItem(PlayerLogic *_owner, ArrowTarget *_startItem, const QColor &_color, int _deleteInPhase);
void addChildArrow(ArrowDragItem *child);
protected:
void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;
@ -99,12 +101,12 @@ class ArrowAttachItem : public ArrowItem
Q_OBJECT
private:
QList<ArrowAttachItem *> childArrows;
QMetaObject::Connection positionConnection;
void attachCards(CardItem *startCard, const CardItem *targetCard);
public:
explicit ArrowAttachItem(ArrowTarget *_startItem);
void addChildArrow(ArrowAttachItem *childArrow);
void addChildArrow(ArrowAttachItem *child);
protected:
void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;

View file

@ -1,41 +1,23 @@
#include "arrow_target.h"
#include "../player/player.h"
#include "../player/player_logic.h"
#include "arrow_item.h"
ArrowTarget::ArrowTarget(Player *_owner, QGraphicsItem *parent)
: AbstractGraphicsItem(parent), owner(_owner), beingPointedAt(false)
ArrowTarget::ArrowTarget(PlayerLogic *_owner, QGraphicsItem *parent) : AbstractGraphicsItem(parent), owner(_owner)
{
setFlag(ItemSendsScenePositionChanges);
}
ArrowTarget::~ArrowTarget()
{
for (int i = 0; i < arrowsFrom.size(); ++i) {
arrowsFrom[i]->setStartItem(0);
arrowsFrom[i]->delArrow();
}
for (int i = 0; i < arrowsTo.size(); ++i) {
arrowsTo[i]->setTargetItem(0);
arrowsTo[i]->delArrow();
}
}
void ArrowTarget::setBeingPointedAt(bool _beingPointedAt)
{
beingPointedAt = _beingPointedAt;
update();
}
QVariant ArrowTarget::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value)
QVariant ArrowTarget::itemChange(GraphicsItemChange change, const QVariant &value)
{
if (change == ItemScenePositionHasChanged && scene()) {
for (auto *arrow : arrowsFrom)
arrow->updatePath();
for (auto *arrow : arrowsTo)
arrow->updatePath();
if (change == ItemScenePositionHasChanged) {
emit scenePositionChanged();
}
return QGraphicsItem::itemChange(change, value);
}
return AbstractGraphicsItem::itemChange(change, value);
}

View file

@ -1,8 +1,8 @@
/**
* @file arrow_target.h
* @ingroup GameGraphics
* @brief TODO: Document this.
*/
//! \todo Document this file.
#ifndef ARROWTARGET_H
#define ARROWTARGET_H
@ -11,24 +11,26 @@
#include <QList>
class Player;
class PlayerLogic;
class ArrowItem;
class ArrowTarget : public AbstractGraphicsItem
{
Q_OBJECT
protected:
Player *owner;
PlayerLogic *owner;
private:
bool beingPointedAt;
QList<ArrowItem *> arrowsFrom, arrowsTo;
bool beingPointedAt = false;
signals:
void scenePositionChanged();
public:
explicit ArrowTarget(Player *_owner, QGraphicsItem *parent = nullptr);
~ArrowTarget() override;
explicit ArrowTarget(PlayerLogic *_owner, QGraphicsItem *parent = nullptr);
~ArrowTarget() override = default;
[[nodiscard]] Player *getOwner() const
[[nodiscard]] PlayerLogic *getOwner() const
{
return owner;
}
@ -39,32 +41,7 @@ public:
return beingPointedAt;
}
[[nodiscard]] const QList<ArrowItem *> &getArrowsFrom() const
{
return arrowsFrom;
}
void addArrowFrom(ArrowItem *arrow)
{
arrowsFrom.append(arrow);
}
void removeArrowFrom(ArrowItem *arrow)
{
arrowsFrom.removeOne(arrow);
}
[[nodiscard]] const QList<ArrowItem *> &getArrowsTo() const
{
return arrowsTo;
}
void addArrowTo(ArrowItem *arrow)
{
arrowsTo.append(arrow);
}
void removeArrowTo(ArrowItem *arrow)
{
arrowsTo.removeOne(arrow);
}
protected:
QVariant itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) override;
QVariant itemChange(GraphicsItemChange change, const QVariant &value) override;
};
#endif

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>
@ -24,8 +24,9 @@ void CardDragItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *opti
{
AbstractCardDragItem::paint(painter, option, widget);
if (occupied)
if (occupied) {
painter->fillPath(shape(), QColor(200, 0, 0, 100));
}
}
void CardDragItem::updatePosition(const QPointF &cursorScenePos)
@ -38,16 +39,19 @@ void CardDragItem::updatePosition(const QPointF &cursorScenePos)
ZoneViewZone *zoneViewZone = 0;
for (int i = colliding.size() - 1; i >= 0; i--) {
CardZone *temp = qgraphicsitem_cast<CardZone *>(colliding.at(i));
if (!cardZone)
if (!cardZone) {
cardZone = temp;
if (!zoneViewZone)
}
if (!zoneViewZone) {
zoneViewZone = qobject_cast<ZoneViewZone *>(temp);
}
}
CardZone *cursorZone = 0;
if (zoneViewZone)
if (zoneViewZone) {
cursorZone = zoneViewZone;
else if (cardZone)
} else if (cardZone) {
cursorZone = cardZone;
}
// Always update the current zone, even if its null, to cancel the drag
// instead of dropping cards into an non-intuitive location.
@ -59,8 +63,9 @@ void CardDragItem::updatePosition(const QPointF &cursorScenePos)
QPointF newPos = cursorScenePos - hotSpot;
if (newPos != pos()) {
for (int i = 0; i < childDrags.size(); i++)
for (int i = 0; i < childDrags.size(); i++) {
childDrags[i]->setPos(newPos + childDrags[i]->getHotSpot());
}
setPos(newPos);
}
@ -78,23 +83,27 @@ void CardDragItem::updatePosition(const QPointF &cursorScenePos)
// position.
TableZone *tableZone = qobject_cast<TableZone *>(cursorZone);
QPointF closestGridPoint;
if (tableZone)
if (tableZone) {
closestGridPoint = tableZone->closestGridPoint(cursorPosInZone);
else
} else {
closestGridPoint = cursorPosInZone - hotSpot;
}
QPointF newPos = zonePos + closestGridPoint;
if (newPos != pos()) {
for (int i = 0; i < childDrags.size(); i++)
for (int i = 0; i < childDrags.size(); i++) {
childDrags[i]->setPos(newPos + childDrags[i]->getHotSpot());
}
setPos(newPos);
bool newOccupied = false;
TableZone *table = qobject_cast<TableZone *>(cursorZone);
if (table)
if (table->getCardFromCoords(closestGridPoint))
if (table) {
if (table->getCardFromCoords(closestGridPoint)) {
newOccupied = true;
}
}
if (newOccupied != occupied) {
occupied = newOccupied;
update();

View file

@ -1,8 +1,8 @@
/**
* @file card_drag_item.h
* @ingroup GameGraphicsCards
* @brief TODO: Document this.
*/
//! \todo Document this file.
#ifndef CARDDRAGITEM_H
#define CARDDRAGITEM_H

View file

@ -1,14 +1,14 @@
#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 "../../interface/widgets/tabs/tab_game.h"
#include "../game_scene.h"
#include "../phase.h"
#include "../player/player.h"
#include "../player/player_actions.h"
#include "../zones/logic/view_zone_logic.h"
#include "../zones/table_zone.h"
#include "../zones/view_zone.h"
#include "../player/player_logic.h"
#include "../zones/view_zone_logic.h"
#include "arrow_item.h"
#include "card_drag_item.h"
@ -20,15 +20,19 @@
#include <libcockatrice/card/card_info.h>
#include <libcockatrice/protocol/pb/serverinfo_card.pb.h>
CardItem::CardItem(Player *_owner, QGraphicsItem *parent, const CardRef &cardRef, int _cardid, CardZoneLogic *_zone)
: AbstractCardItem(parent, cardRef, _owner, _cardid), zone(_zone), attacking(false), destroyOnZoneChange(false),
doesntUntap(false), dragItem(nullptr), attachedTo(nullptr)
CardItem::CardItem(PlayerLogic *_owner,
QGraphicsItem *parent,
const CardRef &cardRef,
int _cardid,
CardZoneLogic *_zone)
: AbstractCardItem(parent, cardRef, _owner, _cardid), state(new CardState(this, _zone)), dragItem(nullptr)
{
owner->addCard(this);
connect(&SettingsCache::instance().cardCounters(), &CardCounterSettings::colorChanged, this, [this](int counterId) {
if (counters.contains(counterId))
if (state->getCounters().contains(counterId)) {
update();
}
});
}
@ -47,23 +51,24 @@ void CardItem::prepareDelete()
attachedCards.first()->setAttachedTo(nullptr);
}
if (attachedTo != nullptr) {
attachedTo->removeAttachedCard(this);
attachedTo = nullptr;
if (state->getAttachedTo() != nullptr) {
state->getAttachedTo()->removeAttachedCard(this);
state->setAttachedTo(nullptr);
}
}
void CardItem::deleteLater()
{
prepareDelete();
if (scene())
if (scene()) {
static_cast<GameScene *>(scene())->unregisterAnimationItem(this);
}
AbstractCardItem::deleteLater();
}
void CardItem::setZone(CardZoneLogic *_zone)
{
zone = _zone;
state->setZone(_zone);
}
void CardItem::retranslateUi()
@ -78,23 +83,23 @@ void CardItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
AbstractCardItem::paint(painter, option, widget);
int i = 0;
QMapIterator<int, int> counterIterator(counters);
QMapIterator<int, int> counterIterator(state->getCounters());
while (counterIterator.hasNext()) {
counterIterator.next();
QColor _color = cardCounterSettings.color(counterIterator.key());
paintNumberEllipse(counterIterator.value(), 14, _color, i, counters.size(), painter);
paintNumberEllipse(counterIterator.value(), 14, _color, i, state->getCounters().size(), painter);
++i;
}
QSizeF translatedSize = getTranslatedSize(painter);
qreal scaleFactor = translatedSize.width() / boundingRect().width();
if (!pt.isEmpty()) {
if (!state->getPT().isEmpty()) {
painter->save();
transformPainter(painter, translatedSize, tapAngle);
if (!getFaceDown() && pt == exactCard.getInfo().getPowTough()) {
if (!getFaceDown() && state->getPT() == exactCard.getInfo().getPowTough()) {
painter->setPen(Qt::white);
} else {
painter->setPen(QColor(255, 150, 0)); // dark orange
@ -105,11 +110,11 @@ void CardItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
painter->drawText(QRectF(4 * scaleFactor, 4 * scaleFactor, translatedSize.width() - 10 * scaleFactor,
translatedSize.height() - 8 * scaleFactor),
Qt::AlignRight | Qt::AlignBottom, pt);
Qt::AlignRight | Qt::AlignBottom, state->getPT());
painter->restore();
}
if (!annotation.isEmpty()) {
if (!state->getAnnotation().isEmpty()) {
painter->save();
transformPainter(painter, translatedSize, tapAngle);
@ -119,7 +124,7 @@ void CardItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
painter->drawText(QRectF(4 * scaleFactor, 4 * scaleFactor, translatedSize.width() - 8 * scaleFactor,
translatedSize.height() - 8 * scaleFactor),
Qt::AlignCenter | Qt::TextWrapAnywhere, annotation);
Qt::AlignCenter | Qt::TextWrapAnywhere, state->getAnnotation());
painter->restore();
}
@ -127,7 +132,7 @@ void CardItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
painter->fillPath(shape(), QBrush(QColor(255, 0, 0, 100)));
}
if (doesntUntap) {
if (state->getDoesntUntap()) {
painter->save();
painter->setRenderHint(QPainter::Antialiasing, false);
@ -146,69 +151,66 @@ void CardItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
void CardItem::setAttacking(bool _attacking)
{
attacking = _attacking;
state->setAttacking(_attacking);
update();
}
void CardItem::setCounter(int _id, int _value)
{
if (_value)
counters.insert(_id, _value);
else
counters.remove(_id);
state->setCounter(_id, _value);
update();
}
void CardItem::setAnnotation(const QString &_annotation)
{
annotation = _annotation;
state->setAnnotation(_annotation);
update();
}
void CardItem::setDoesntUntap(bool _doesntUntap)
{
doesntUntap = _doesntUntap;
state->setDoesntUntap(_doesntUntap);
update();
}
void CardItem::setPT(const QString &_pt)
{
pt = _pt;
state->setPT(_pt);
update();
}
void CardItem::setAttachedTo(CardItem *_attachedTo)
{
if (attachedTo != nullptr) {
attachedTo->removeAttachedCard(this);
if (state->getAttachedTo() != nullptr) {
state->getAttachedTo()->removeAttachedCard(this);
}
gridPoint.setX(-1);
attachedTo = _attachedTo;
if (attachedTo != nullptr) {
state->setAttachedTo(_attachedTo);
if (state->getAttachedTo() != nullptr) {
// If the zone is being torn down, it might already be null by the time a card tries to un-attach all its
// attached cards
if (attachedTo->zone == nullptr) {
if (state->getAttachedTo()->getZone() == nullptr) {
deleteLater();
} else {
emit attachedTo->zone->cardAdded(this);
attachedTo->addAttachedCard(this);
if (zone != attachedTo->getZone()) {
attachedTo->getZone()->reorganizeCards();
emit state->getAttachedTo()->getZone()->cardAdded(this);
state->getAttachedTo()->addAttachedCard(this);
if (state->getZone() != state->getAttachedTo()->getZone()) {
state->getAttachedTo()->getZone()->reorganizeCards();
}
}
} else {
// If the zone is being torn down, it might already be null by the time a card tries to un-attach all its
// attached cards
if (zone == nullptr) {
if (state->getZone() == nullptr) {
deleteLater();
} else {
emit zone->cardAdded(this);
emit state->getZone()->cardAdded(this);
}
}
if (zone != nullptr) {
zone->reorganizeCards();
if (state->getZone() != nullptr) {
state->getZone()->reorganizeCards();
}
}
@ -217,28 +219,23 @@ void CardItem::setAttachedTo(CardItem *_attachedTo)
*/
void CardItem::resetState(bool keepAnnotations)
{
attacking = false;
counters.clear();
pt.clear();
if (!keepAnnotations) {
annotation.clear();
}
attachedTo = 0;
state->resetState(keepAnnotations);
attachedCards.clear();
setTapped(false, false);
setDoesntUntap(false);
if (scene())
if (scene()) {
static_cast<GameScene *>(scene())->unregisterAnimationItem(this);
}
update();
}
void CardItem::processCardInfo(const ServerInfo_Card &_info)
{
counters.clear();
state->clearCounters();
const int counterListSize = _info.counter_list_size();
for (int i = 0; i < counterListSize; ++i) {
const ServerInfo_CardCounter &counterInfo = _info.counter_list(i);
counters.insert(counterInfo.id(), counterInfo.value());
state->insertCounter(counterInfo.id(), counterInfo.value());
}
setId(_info.id());
@ -275,11 +272,12 @@ void CardItem::deleteDragItem()
void CardItem::drawArrow(const QColor &arrowColor)
{
if (owner->getGame()->getPlayerManager()->isSpectator())
if (owner->getGame()->getPlayerManager()->isSpectator()) {
return;
}
auto *game = owner->getGame();
Player *arrowOwner = game->getPlayerManager()->getActiveLocalPlayer(game->getGameState()->getActivePlayer());
PlayerLogic *arrowOwner = game->getPlayerManager()->getActiveLocalPlayer(game->getGameState()->getActivePlayer());
int phase = 0; // 0 means to not set the phase
if (SettingsCache::instance().getDoNotDeleteArrowsInSubPhases()) {
int currentPhase = game->getGameState()->getCurrentPhase();
@ -291,10 +289,12 @@ void CardItem::drawArrow(const QColor &arrowColor)
for (const auto &item : scene()->selectedItems()) {
CardItem *card = qgraphicsitem_cast<CardItem *>(item);
if (card == nullptr || card == this)
if (card == nullptr || card == this) {
continue;
if (card->getZone() != zone)
}
if (card->getZone() != state->getZone()) {
continue;
}
ArrowDragItem *childArrow = new ArrowDragItem(arrowOwner, card, arrowColor, phase);
scene()->addItem(childArrow);
@ -304,8 +304,9 @@ void CardItem::drawArrow(const QColor &arrowColor)
void CardItem::drawAttachArrow()
{
if (owner->getGame()->getPlayerManager()->isSpectator())
if (owner->getGame()->getPlayerManager()->isSpectator()) {
return;
}
auto *arrow = new ArrowAttachItem(this);
scene()->addItem(arrow);
@ -313,10 +314,12 @@ void CardItem::drawAttachArrow()
for (const auto &item : scene()->selectedItems()) {
CardItem *card = qgraphicsitem_cast<CardItem *>(item);
if (card == nullptr)
if (card == nullptr) {
continue;
if (card->getZone() != zone)
}
if (card->getZone() != state->getZone()) {
continue;
}
ArrowAttachItem *childArrow = new ArrowAttachItem(card);
scene()->addItem(childArrow);
@ -328,27 +331,32 @@ void CardItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
if (event->buttons().testFlag(Qt::RightButton)) {
if ((event->screenPos() - event->buttonDownScreenPos(Qt::RightButton)).manhattanLength() <
2 * QApplication::startDragDistance())
2 * QApplication::startDragDistance()) {
return;
}
QColor arrowColor = Qt::red;
if (event->modifiers().testFlag(Qt::ControlModifier))
if (event->modifiers().testFlag(Qt::ControlModifier)) {
arrowColor = Qt::yellow;
else if (event->modifiers().testFlag(Qt::AltModifier))
} else if (event->modifiers().testFlag(Qt::AltModifier)) {
arrowColor = Qt::blue;
else if (event->modifiers().testFlag(Qt::ShiftModifier))
} else if (event->modifiers().testFlag(Qt::ShiftModifier)) {
arrowColor = Qt::green;
}
drawArrow(arrowColor);
} else if (event->buttons().testFlag(Qt::LeftButton)) {
if ((event->screenPos() - event->buttonDownScreenPos(Qt::LeftButton)).manhattanLength() <
2 * QApplication::startDragDistance())
2 * QApplication::startDragDistance()) {
return;
if (const ZoneViewZoneLogic *view = qobject_cast<const ZoneViewZoneLogic *>(zone)) {
if (view->getRevealZone() && !view->getWriteableRevealZone())
}
if (const ZoneViewZoneLogic *view = qobject_cast<const ZoneViewZoneLogic *>(state->getZone())) {
if (view->getRevealZone() && !view->getWriteableRevealZone()) {
return;
} else if (!owner->getPlayerInfo()->getLocalOrJudge())
}
} else if (!owner->getPlayerInfo()->getLocalOrJudge()) {
return;
}
bool forceFaceDown = event->modifiers().testFlag(Qt::ShiftModifier);
@ -360,14 +368,16 @@ void CardItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
int childIndex = 0;
for (const auto &item : scene()->selectedItems()) {
CardItem *card = static_cast<CardItem *>(item);
if ((card == this) || (card->getZone() != zone))
if ((card == this) || (card->getZone() != state->getZone())) {
continue;
}
++childIndex;
QPointF childPos;
if (zone->getHasCardAttr())
if (state->getZone()->getHasCardAttr()) {
childPos = card->pos() - pos();
else
} else {
childPos = QPointF(childIndex * CardDimensions::WIDTH_HALF_F, 0);
}
CardDragItem *drag =
new CardDragItem(card, card->getId(), childPos, card->getFaceDown() || forceFaceDown, dragItem);
drag->setPos(dragItem->pos() + childPos);
@ -380,22 +390,54 @@ void CardItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
void CardItem::playCard(bool faceDown)
{
// Do nothing if the card belongs to another player
if (!owner->getPlayerInfo()->getLocalOrJudge())
if (!owner->getPlayerInfo()->getLocalOrJudge()) {
return;
}
TableZoneLogic *tz = qobject_cast<TableZoneLogic *>(zone);
if (tz)
TableZoneLogic *tz = qobject_cast<TableZoneLogic *>(state->getZone());
if (tz) {
emit tz->toggleTapped();
else {
} else {
if (SettingsCache::instance().getClickPlaysAllSelected()) {
faceDown ? zone->getPlayer()->getPlayerActions()->actPlayFacedown()
: zone->getPlayer()->getPlayerActions()->actPlay();
faceDown ? state->getZone()->getPlayer()->getPlayerActions()->actPlayFacedown()
: state->getZone()->getPlayer()->getPlayerActions()->actPlay();
} else {
zone->getPlayer()->getPlayerActions()->playCard(this, faceDown);
state->getZone()->getPlayer()->getPlayerActions()->playCard(this, faceDown);
}
}
}
QVariantList CardItem::parsePT(const QString &pt)
{
QVariantList ptList = QVariantList();
if (!pt.isEmpty()) {
int sep = pt.indexOf('/');
if (sep == 0) {
ptList.append(QVariant(pt.mid(1))); // cut off starting '/' and take full string
} else {
int start = 0;
for (;;) {
QString item = pt.mid(start, sep - start);
if (item.isEmpty()) {
ptList.append(QVariant(QString()));
} else if (item[0] == '+') {
ptList.append(QVariant(item.mid(1).toInt())); // add as int
} else if (item[0] == '-') {
ptList.append(QVariant(item.toInt())); // add as int
} else {
ptList.append(QVariant(item)); // add as qstring
}
if (sep == -1) {
break;
}
start = sep + 1;
sep = pt.indexOf('/', start);
}
}
}
return ptList;
}
/**
* @brief returns true if the zone is a unwritable reveal zone view (eg a card reveal window). Will return false if zone
* is nullptr.
@ -416,11 +458,11 @@ static bool isUnwritableRevealZone(CardZoneLogic *zone)
*/
void CardItem::handleClickedToPlay(bool shiftHeld)
{
if (isUnwritableRevealZone(zone)) {
if (isUnwritableRevealZone(state->getZone())) {
if (SettingsCache::instance().getClickPlaysAllSelected()) {
zone->getPlayer()->getPlayerActions()->actHide();
state->getZone()->getPlayer()->getPlayerActions()->actHide();
} else {
zone->removeCard(this);
state->getZone()->removeCard(this);
}
} else {
playCard(shiftHeld);
@ -460,13 +502,11 @@ void CardItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
bool CardItem::animationEvent()
{
if (owner == nullptr) {
return false;
}
int rotation = ROTATION_DEGREES_PER_FRAME;
bool animationIncomplete = true;
if (!tapped)
if (!tapped) {
rotation *= -1;
}
tapAngle += rotation;
if (tapped && (tapAngle > 90)) {

View file

@ -1,42 +1,37 @@
/**
* @file card_item.h
* @ingroup GameGraphicsCards
* @brief TODO: Document this.
*/
//! \todo Document this file.
#ifndef CARDITEM_H
#define CARDITEM_H
#include "../zones/logic/card_zone_logic.h"
#include "../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;
class CardZone;
class ServerInfo_Card;
class Player;
class PlayerLogic;
class QAction;
class QColor;
const int MAX_COUNTERS_ON_CARD = 999;
const int ROTATION_DEGREES_PER_FRAME = 10;
class CardItem : public AbstractCardItem
{
Q_OBJECT
private:
CardZoneLogic *zone;
bool attacking;
QMap<int, int> counters;
QString annotation;
QString pt;
bool destroyOnZoneChange;
bool doesntUntap;
CardState *state;
QPoint gridPoint;
CardDragItem *dragItem;
CardItem *attachedTo;
QList<CardItem *> attachedCards;
void prepareDelete();
@ -53,16 +48,20 @@ public:
{
return Type;
}
explicit CardItem(Player *_owner,
explicit CardItem(PlayerLogic *_owner,
QGraphicsItem *parent = nullptr,
const CardRef &cardRef = {},
int _cardid = -1,
CardZoneLogic *_zone = nullptr);
void retranslateUi();
[[nodiscard]] CardState *getState() const
{
return state;
}
[[nodiscard]] CardZoneLogic *getZone() const
{
return zone;
return state->getZone();
}
void setZone(CardZoneLogic *_zone);
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
@ -78,50 +77,50 @@ public:
{
return gridPoint;
}
[[nodiscard]] Player *getOwner() const
[[nodiscard]] PlayerLogic *getOwner() const
{
return owner;
}
void setOwner(Player *_owner)
void setOwner(PlayerLogic *_owner)
{
owner = _owner;
}
[[nodiscard]] bool getAttacking() const
{
return attacking;
return state->getAttacking();
}
void setAttacking(bool _attacking);
[[nodiscard]] const QMap<int, int> &getCounters() const
{
return counters;
return state->getCounters();
}
void setCounter(int _id, int _value);
[[nodiscard]] QString getAnnotation() const
{
return annotation;
return state->getAnnotation();
}
void setAnnotation(const QString &_annotation);
[[nodiscard]] bool getDoesntUntap() const
{
return doesntUntap;
return state->getDoesntUntap();
}
void setDoesntUntap(bool _doesntUntap);
[[nodiscard]] QString getPT() const
{
return pt;
return state->getPT();
}
void setPT(const QString &_pt);
[[nodiscard]] bool getDestroyOnZoneChange() const
{
return destroyOnZoneChange;
return state->getDestroyOnZoneChange();
}
void setDestroyOnZoneChange(bool _destroy)
{
destroyOnZoneChange = _destroy;
state->setDestroyOnZoneChange(_destroy);
}
[[nodiscard]] CardItem *getAttachedTo() const
{
return attachedTo;
return state->getAttachedTo();
}
void setAttachedTo(CardItem *_attachedTo);
void addAttachedCard(CardItem *card)
@ -146,6 +145,26 @@ public:
void drawAttachArrow();
void playCard(bool faceDown);
/**
* @brief Parses a string representing a p/t in order to extract the values from it.
*
* If the string contains '/', the string will be split at the '/' and each side will be parsed separately,
* which means the result list will have two elements.
*
* If '/' is not found, then the entire string is parsed together, which means the result list will
* have a single element.
*
* If either side of the split is empty, there will also only be a single element in the result list.
*
* This function will attempt to parse each substring as an int first, handling plus and minus prefixes.
* If successful, it will put the parsed value into the QVariant as an int.
* If failed, it will just put the substring into the QVariant as a QString.
*
* @param pt The p/t string
* @return A QVariantList that can contain one or two elements, where each QVariant can be either int or QString
*/
static QVariantList parsePT(const QString &pt);
protected:
void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;

View file

@ -1,8 +1,8 @@
/**
* @file card_list.h
* @ingroup GameLogicCards
* @brief TODO: Document this.
*/
//! \todo Document this file.
#ifndef CARDLIST_H
#define CARDLIST_H

View file

@ -0,0 +1,111 @@
#include "card_state.h"
void CardState::resetState(bool keepAnnotations)
{
attacking = false;
counters.clear();
pt.clear();
if (!keepAnnotations) {
annotation.clear();
}
attachedTo = nullptr;
}
void CardState::setZone(CardZoneLogic *_zone)
{
if (zone == _zone) {
return;
}
zone = _zone;
emit zoneChanged(this, zone);
emit stateChanged();
}
void CardState::setAttacking(bool _attacking)
{
if (attacking == _attacking) {
return;
}
attacking = _attacking;
emit attackingChanged(_attacking);
emit stateChanged();
}
void CardState::insertCounter(int id, int value)
{
counters.insert(id, value);
emit countersChanged(counters);
emit stateChanged();
}
void CardState::setCounter(int id, int value)
{
if (value) {
counters[id] = value;
} else {
counters.remove(id);
}
emit countersChanged(counters);
emit stateChanged();
}
void CardState::clearCounters()
{
counters.clear();
emit countersChanged(counters);
emit stateChanged();
}
void CardState::setAnnotation(const QString &_annotation)
{
if (annotation == _annotation) {
return;
}
annotation = _annotation;
emit annotationChanged(annotation);
emit stateChanged();
}
void CardState::setPT(const QString &_pt)
{
if (pt == _pt) {
return;
}
pt = _pt;
emit ptChanged(pt);
emit stateChanged();
}
void CardState::setDoesntUntap(bool _doesntUntap)
{
if (doesntUntap == _doesntUntap) {
return;
}
doesntUntap = _doesntUntap;
emit doesntUntapChanged(_doesntUntap);
emit stateChanged();
}
void CardState::setDestroyOnZoneChange(bool _destroyOnZoneChange)
{
if (destroyOnZoneChange == _destroyOnZoneChange) {
return;
}
destroyOnZoneChange = _destroyOnZoneChange;
emit destroyOnZoneChangeChanged(_destroyOnZoneChange);
emit stateChanged();
}
void CardState::setAttachedTo(CardItem *_attachedTo)
{
if (attachedTo == _attachedTo) {
return;
}
attachedTo = _attachedTo;
emit attachedToChanged(_attachedTo);
emit stateChanged();
}

View file

@ -0,0 +1,103 @@
#ifndef COCKATRICE_CARD_STATE_H
#define COCKATRICE_CARD_STATE_H
#include <QMap>
#include <QObject>
class CardZoneLogic;
class CardItem;
class CardState : public QObject
{
Q_OBJECT
private:
bool attacking = false;
QMap<int, int> counters;
QString annotation;
QString pt;
bool doesntUntap = false;
bool destroyOnZoneChange = false;
CardItem *attachedTo = nullptr;
CardZoneLogic *zone = nullptr;
signals:
void stateChanged();
void attackingChanged(bool newValue);
void countersChanged(const QMap<int, int> &newCounters);
void annotationChanged(const QString &newAnnotation);
void ptChanged(const QString &newPt);
void doesntUntapChanged(bool newValue);
void destroyOnZoneChangeChanged(bool newValue);
void attachedToChanged(CardItem *newAttachedTo);
void zoneChanged(CardState *changedCard, CardZoneLogic *newZone);
public:
explicit CardState(QObject *parent, CardZoneLogic *_zone) : QObject(parent), zone(_zone)
{
}
void resetState(bool keepAnnotations);
CardZoneLogic *getZone() const
{
return zone;
}
void setZone(CardZoneLogic *_zone);
bool getAttacking() const
{
return attacking;
}
void setAttacking(bool _attacking);
const QMap<int, int> &getCounters() const
{
return counters;
}
void insertCounter(int id, int value);
void setCounter(int id, int value);
void clearCounters();
QString getAnnotation() const
{
return annotation;
}
void setAnnotation(const QString &_annotation);
QString getPT() const
{
return pt;
}
void setPT(const QString &_pt);
bool getDoesntUntap() const
{
return doesntUntap;
}
void setDoesntUntap(bool _doesntUntap);
bool getDestroyOnZoneChange() const
{
return destroyOnZoneChange;
}
void setDestroyOnZoneChange(bool _destroyOnZoneChange);
CardItem *getAttachedTo() const
{
return attachedTo;
}
void setAttachedTo(CardItem *_attachedTo);
};
#endif // COCKATRICE_CARD_STATE_H

View file

@ -5,15 +5,8 @@
#include <QPainter>
GeneralCounter::GeneralCounter(Player *_player,
int _id,
const QString &_name,
const QColor &_color,
int _radius,
int _value,
bool useNameForShortcut,
QGraphicsItem *parent)
: AbstractCounter(_player, _id, _name, true, _value, useNameForShortcut, parent), color(_color), radius(_radius)
GeneralCounter::GeneralCounter(CounterState *state, PlayerLogic *player, bool useNameForShortcut, QGraphicsItem *parent)
: AbstractCounter(state, player, true, useNameForShortcut, parent)
{
setCacheMode(DeviceCoordinateCache);
}

View file

@ -1,8 +1,8 @@
/**
* @file counter_general.h
* @ingroup GameGraphicsPlayers
* @brief TODO: Document this.
*/
//! \todo Document this file.
#ifndef COUNTER_GENERAL_H
#define COUNTER_GENERAL_H
@ -12,17 +12,10 @@
class GeneralCounter : public AbstractCounter
{
Q_OBJECT
private:
QColor color;
int radius;
public:
GeneralCounter(Player *_player,
int _id,
const QString &_name,
const QColor &_color,
int _radius,
int _value,
GeneralCounter(CounterState *state,
PlayerLogic *player,
bool useNameForShortcut = false,
QGraphicsItem *parent = nullptr);
QRectF boundingRect() const override;

View file

@ -0,0 +1,24 @@
#include "counter_state.h"
#include <libcockatrice/utility/color.h>
CounterState::CounterState(int id, const QString &name, const QColor &color, int radius, int value, QObject *parent)
: QObject(parent), id(id), name(name), color(color), radius(radius), value(value)
{
}
CounterState *CounterState::fromProto(const ServerInfo_Counter &counter, QObject *parent)
{
return new CounterState(counter.id(), QString::fromStdString(counter.name()),
convertColorToQColor(counter.counter_color()), counter.radius(), counter.count(), parent);
}
void CounterState::setValue(int newValue)
{
if (newValue == value) {
return;
}
int old = value;
value = newValue;
emit valueChanged(old, newValue);
}

View file

@ -0,0 +1,51 @@
#ifndef COCKATRICE_COUNTER_STATE_H
#define COCKATRICE_COUNTER_STATE_H
#include <QColor>
#include <QObject>
#include <QString>
#include <libcockatrice/protocol/pb/serverinfo_counter.pb.h>
class CounterState : public QObject
{
Q_OBJECT
public:
CounterState(int id, const QString &name, const QColor &color, int radius, int value, QObject *parent = nullptr);
static CounterState *fromProto(const ServerInfo_Counter &counter, QObject *parent = nullptr);
int getId() const
{
return id;
}
QString getName() const
{
return name;
}
QColor getColor() const
{
return color;
}
int getRadius() const
{
return radius;
}
int getValue() const
{
return value;
}
void setValue(int newValue);
signals:
void valueChanged(int oldValue, int newValue);
private:
int id;
QString name;
QColor color;
int radius;
int value;
};
#endif // COCKATRICE_COUNTER_STATE_H

View file

@ -1,8 +1,8 @@
/**
* @file translate_counter_name.h
* @ingroup GameGraphicsPlayers
* @brief TODO: Document this.
*/
//! \todo Document this file.
#ifndef TRANSLATECOUNTERNAME_H
#define TRANSLATECOUNTERNAME_H

View file

@ -12,16 +12,16 @@
*/
namespace CardDimensions
{
/// Card width in pixels
/** @brief Card width in pixels. */
constexpr int WIDTH = 72;
/// Card height in pixels
/** @brief Card height in pixels. */
constexpr int HEIGHT = 102;
/// Pre-converted for floating-point contexts (Z-value calculations)
/** @brief Pre-converted for floating-point contexts (Z-value calculations). */
constexpr qreal WIDTH_F = static_cast<qreal>(WIDTH);
constexpr qreal HEIGHT_F = static_cast<qreal>(HEIGHT);
/// Half-dimensions for centering and rotation transforms
/** @brief Half-dimensions for centering and rotation transforms. */
constexpr qreal WIDTH_HALF_F = WIDTH_F / 2;
constexpr qreal HEIGHT_HALF_F = HEIGHT_F / 2;
} // namespace CardDimensions

View file

@ -24,17 +24,21 @@ void DeckViewCardDragItem::updatePosition(const QPointF &cursorScenePos)
QList<QGraphicsItem *> colliding = scene()->items(cursorScenePos);
DeckViewCardContainer *cursorZone = 0;
for (int i = colliding.size() - 1; i >= 0; i--)
if ((cursorZone = qgraphicsitem_cast<DeckViewCardContainer *>(colliding.at(i))))
for (int i = colliding.size() - 1; i >= 0; i--) {
if ((cursorZone = qgraphicsitem_cast<DeckViewCardContainer *>(colliding.at(i)))) {
break;
if (!cursorZone)
}
}
if (!cursorZone) {
return;
}
currentZone = cursorZone;
QPointF newPos = cursorScenePos;
if (newPos != pos()) {
for (int i = 0; i < childDrags.size(); i++)
for (int i = 0; i < childDrags.size(); i++) {
childDrags[i]->setPos(newPos + childDrags[i]->getHotSpot());
}
setPos(newPos);
}
}
@ -104,11 +108,13 @@ void DeckViewCard::paint(QPainter *painter, const QStyleOptionGraphicsItem *opti
void DeckViewCard::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
if ((event->screenPos() - event->buttonDownScreenPos(Qt::LeftButton)).manhattanLength() <
2 * QApplication::startDragDistance())
2 * QApplication::startDragDistance()) {
return;
}
if (static_cast<DeckViewScene *>(scene())->getLocked())
if (static_cast<DeckViewScene *>(scene())->getLocked()) {
return;
}
delete dragItem;
dragItem = new DeckViewCardDragItem(this, event->pos());
@ -120,8 +126,9 @@ void DeckViewCard::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
int j = 0;
for (int i = 0; i < sel.size(); i++) {
auto *c = static_cast<DeckViewCard *>(sel.at(i));
if (c == this)
if (c == this) {
continue;
}
++j;
auto childPos = QPointF(j * CardDimensions::WIDTH_HALF_F, 0);
auto *drag = new DeckViewCardDragItem(c, childPos, dragItem);
@ -133,8 +140,9 @@ void DeckViewCard::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
void DeckView::mouseDoubleClickEvent(QMouseEvent *event)
{
if (static_cast<DeckViewScene *>(scene())->getLocked())
if (static_cast<DeckViewScene *>(scene())->getLocked()) {
return;
}
if (event->button() == Qt::LeftButton) {
QList<MoveCard_ToZone> result;
@ -147,12 +155,13 @@ void DeckView::mouseDoubleClickEvent(QMouseEvent *event)
m.set_card_name(c->getName().toStdString());
m.set_start_zone(zone->getName().toStdString());
if (zone->getName() == DECK_ZONE_MAIN)
if (zone->getName() == DECK_ZONE_MAIN) {
m.set_target_zone(DECK_ZONE_SIDE);
else if (zone->getName() == DECK_ZONE_SIDE)
} else if (zone->getName() == DECK_ZONE_SIDE) {
m.set_target_zone(DECK_ZONE_MAIN);
else // Trying to move from another zone
} else { // Trying to move from another zone
m.set_target_zone(zone->getName().toStdString());
}
result.append(m);
}
@ -232,8 +241,9 @@ QList<QPair<int, int>> DeckViewCardContainer::getRowsAndCols() const
{
QList<QPair<int, int>> result;
QList<QString> cardTypeList = cardsByType.uniqueKeys();
for (int i = 0; i < cardTypeList.size(); ++i)
for (int i = 0; i < cardTypeList.size(); ++i) {
result.append(QPair<int, int>(1, cardsByType.count(cardTypeList[i])));
}
return result;
}
@ -262,8 +272,9 @@ QSizeF DeckViewCardContainer::calculateBoundingRect(const QList<QPair<int, int>>
// Calculate space needed for cards
for (int i = 0; i < rowsAndCols.size(); ++i) {
totalHeight += CardDimensions::HEIGHT_F * rowsAndCols[i].first + paddingY;
if (CardDimensions::WIDTH_F * rowsAndCols[i].second > totalWidth)
if (CardDimensions::WIDTH_F * rowsAndCols[i].second > totalWidth) {
totalWidth = CardDimensions::WIDTH_F * rowsAndCols[i].second;
}
}
return QSizeF(getCardTypeTextWidth() + totalWidth, totalHeight + separatorY + paddingY);
@ -271,8 +282,9 @@ QSizeF DeckViewCardContainer::calculateBoundingRect(const QList<QPair<int, int>>
bool DeckViewCardContainer::sortCardsByName(DeckViewCard *c1, DeckViewCard *c2)
{
if (c1 && c2)
if (c1 && c2) {
return c1->getName() < c2->getName();
}
return false;
}
@ -322,15 +334,17 @@ DeckViewScene::~DeckViewScene()
void DeckViewScene::clearContents()
{
QMapIterator<QString, DeckViewCardContainer *> i(cardContainers);
while (i.hasNext())
while (i.hasNext()) {
delete i.next().value();
}
cardContainers.clear();
}
void DeckViewScene::setDeck(const DeckList &_deck)
{
if (deck)
if (deck) {
delete deck;
}
deck = new DeckList(_deck.writeToString_Native());
rebuildTree();
@ -342,8 +356,9 @@ void DeckViewScene::rebuildTree()
{
clearContents();
if (!deck)
if (!deck) {
return;
}
for (auto *currentZone : deck->getZoneNodes()) {
DeckViewCardContainer *container = cardContainers.value(currentZone->getName(), 0);
@ -355,8 +370,9 @@ void DeckViewScene::rebuildTree()
for (int j = 0; j < currentZone->size(); j++) {
auto *currentCard = dynamic_cast<DecklistCardNode *>(currentZone->at(j));
if (!currentCard)
if (!currentCard) {
continue;
}
for (int k = 0; k < currentCard->getNumber(); ++k) {
auto *newCard = new DeckViewCard(container, currentCard->toCardRef(), currentZone->getName());
@ -373,18 +389,21 @@ void DeckViewScene::applySideboardPlan(const QList<MoveCard_ToZone> &plan)
const MoveCard_ToZone &m = plan[i];
DeckViewCardContainer *start = cardContainers.value(QString::fromStdString(m.start_zone()));
DeckViewCardContainer *target = cardContainers.value(QString::fromStdString(m.target_zone()));
if (!start || !target)
if (!start || !target) {
continue;
}
DeckViewCard *card = 0;
const QList<DeckViewCard *> &cardList = start->getCards();
for (int j = 0; j < cardList.size(); ++j)
for (int j = 0; j < cardList.size(); ++j) {
if (cardList[j]->getName() == QString::fromStdString(m.card_name())) {
card = cardList[j];
break;
}
if (!card)
}
if (!card) {
continue;
}
start->removeCard(card);
target->addCard(card);
@ -405,8 +424,9 @@ void DeckViewScene::rearrangeItems()
rowsAndColsList.append(rowsAndCols);
cardCountList.append(QList<int>());
for (int j = 0; j < rowsAndCols.size(); ++j)
for (int j = 0; j < rowsAndCols.size(); ++j) {
cardCountList[i].append(rowsAndCols[j].second);
}
}
qreal totalHeight, totalWidth;
@ -417,23 +437,27 @@ void DeckViewScene::rearrangeItems()
for (int i = 0; i < contList.size(); ++i) {
QSizeF contSize = contList[i]->calculateBoundingRect(rowsAndColsList[i]);
totalHeight += contSize.height() + spacing;
if (contSize.width() > totalWidth)
if (contSize.width() > totalWidth) {
totalWidth = contSize.width();
}
}
// We're done when the aspect ratio shifts from too high to too low.
if (totalWidth / totalHeight <= optimalAspectRatio)
if (totalWidth / totalHeight <= optimalAspectRatio) {
break;
}
// Find category with highest column count
int maxIndex1 = -1, maxIndex2 = -1, maxCols = 0;
for (int i = 0; i < rowsAndColsList.size(); ++i)
for (int j = 0; j < rowsAndColsList[i].size(); ++j)
for (int i = 0; i < rowsAndColsList.size(); ++i) {
for (int j = 0; j < rowsAndColsList[i].size(); ++j) {
if (rowsAndColsList[i][j].second > maxCols) {
maxIndex1 = i;
maxIndex2 = j;
maxCols = rowsAndColsList[i][j].second;
}
}
}
// Add row to category
const int maxRows = rowsAndColsList[maxIndex1][maxIndex2].first;
@ -451,8 +475,9 @@ void DeckViewScene::rearrangeItems()
}
totalWidth = totalHeight * optimalAspectRatio;
for (int i = 0; i < contList.size(); ++i)
for (int i = 0; i < contList.size(); ++i) {
contList[i]->setWidth(totalWidth);
}
setSceneRect(QRectF(0, 0, totalWidth, totalHeight));
}
@ -470,7 +495,7 @@ QList<MoveCard_ToZone> DeckViewScene::getSideboardPlan() const
while (containerIterator.hasNext()) {
DeckViewCardContainer *cont = containerIterator.next().value();
const QList<DeckViewCard *> cardList = cont->getCards();
for (int i = 0; i < cardList.size(); ++i)
for (int i = 0; i < cardList.size(); ++i) {
if (cardList[i]->getOriginZone() != cont->getName()) {
MoveCard_ToZone m;
m.set_card_name(cardList[i]->getName().toStdString());
@ -478,6 +503,7 @@ QList<MoveCard_ToZone> DeckViewScene::getSideboardPlan() const
m.set_target_zone(cont->getName().toStdString());
result.append(m);
}
}
}
return result;
}

View file

@ -1,8 +1,8 @@
/**
* @file deck_view.h
* @ingroup Lobby
* @brief TODO: Document this.
*/
//! \todo Document this file.
#ifndef DECKVIEW_H
#define DECKVIEW_H

View file

@ -251,8 +251,9 @@ void DeckViewContainer::unloadDeck()
void DeckViewContainer::loadLocalDeck()
{
DlgLoadDeck dialog(this);
if (!dialog.exec())
if (!dialog.exec()) {
return;
}
loadDeckFromFile(dialog.selectedFiles().at(0));
}
@ -364,8 +365,9 @@ void DeckViewContainer::sideboardPlanChanged()
{
Command_SetSideboardPlan cmd;
const QList<MoveCard_ToZone> &newPlan = deckView->getSideboardPlan();
for (const auto &i : newPlan)
for (const auto &i : newPlan) {
cmd.add_move_list()->CopyFrom(i);
}
parentGame->getGame()->getGameEventHandler()->sendGameCommand(cmd, playerId);
}
@ -404,8 +406,9 @@ void DeckViewContainer::setSideboardLocked(bool locked)
{
sideboardLockButton->setState(!locked);
deckView->setLocked(readyStartButton->getState() || !sideboardLockButton->getState());
if (locked)
if (locked) {
deckView->resetSideboardPlan();
}
}
void DeckViewContainer::setDeck(const DeckList &deck)

View file

@ -1,8 +1,8 @@
/**
* @file deck_view_container.h
* @ingroup Lobby
* @brief TODO: Document this.
*/
//! \todo Document this file.
#ifndef DECK_VIEW_CONTAINER_H
#define DECK_VIEW_CONTAINER_H

View file

@ -1,8 +1,8 @@
/**
* @file tabbed_deck_view_container.h
* @ingroup Lobby
* @brief TODO: Document this.
*/
//! \todo Document this file.
#ifndef TABBED_DECK_VIEW_CONTAINER_H
#define TABBED_DECK_VIEW_CONTAINER_H

View file

@ -101,8 +101,9 @@ DlgCreateToken::DlgCreateToken(const QStringList &_predefinedTokens, QWidget *pa
chooseTokenView->resizeColumnToContents(0);
chooseTokenView->setWordWrap(true);
if (!deckHeaderState.isNull())
if (!deckHeaderState.isNull()) {
chooseTokenView->header()->restoreState(deckHeaderState);
}
chooseTokenView->header()->setStretchLastSection(false);
chooseTokenView->header()->hideSection(1); // Sets
@ -185,8 +186,9 @@ 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().getAnnotateTokens()) {
annotationEdit->setText(cardInfo->getText());
}
} else {
nameEdit->setText("");
colorEdit->setCurrentIndex(colorEdit->findData(QString(), Qt::UserRole, Qt::MatchFixedString));

View file

@ -1,8 +1,8 @@
/**
* @file dlg_create_token.h
* @ingroup GameDialogs
* @brief TODO: Document this.
*/
//! \todo Document this file.
#ifndef DLG_CREATETOKEN_H
#define DLG_CREATETOKEN_H

View file

@ -1,8 +1,8 @@
/**
* @file dlg_move_top_cards_until.h
* @ingroup GameDialogs
* @brief TODO: Document this.
*/
//! \todo Document this file.
#ifndef DLG_MOVE_TOP_CARDS_UNTIL_H
#define DLG_MOVE_TOP_CARDS_UNTIL_H

View file

@ -1,8 +1,8 @@
/**
* @file dlg_roll_dice.h
* @ingroup GameDialogs
* @brief TODO: Document this.
*/
//! \todo Document this file.
#ifndef DLG_ROLL_DICE_H
#define DLG_ROLL_DICE_H

View file

@ -1,8 +1,8 @@
/**
* @file game.h
* @ingroup GameLogic
* @brief TODO: Document this.
*/
//! \todo Document this file.
#ifndef COCKATRICE_GAME_H
#define COCKATRICE_GAME_H

View file

@ -36,8 +36,9 @@ GameEventHandler::GameEventHandler(AbstractGame *_game) : QObject(_game), game(_
void GameEventHandler::sendGameCommand(PendingCommand *pend, int playerId)
{
AbstractClient *client = game->getClientForPlayer(playerId);
if (!client)
if (!client) {
return;
}
connect(pend, &PendingCommand::finished, this, &GameEventHandler::commandFinished);
client->sendCommand(pend);
@ -46,8 +47,9 @@ void GameEventHandler::sendGameCommand(PendingCommand *pend, int playerId)
void GameEventHandler::sendGameCommand(const google::protobuf::Message &command, int playerId)
{
AbstractClient *client = game->getClientForPlayer(playerId);
if (!client)
if (!client) {
return;
}
PendingCommand *pend = prepareGameCommand(command);
connect(pend, &PendingCommand::finished, this, &GameEventHandler::commandFinished);
@ -56,8 +58,9 @@ void GameEventHandler::sendGameCommand(const google::protobuf::Message &command,
void GameEventHandler::commandFinished(const Response &response)
{
if (response.response_code() == Response::RespChatFlood)
if (response.response_code() == Response::RespChatFlood) {
emit gameFlooded();
}
}
PendingCommand *GameEventHandler::prepareGameCommand(const ::google::protobuf::Message &cmd)
@ -96,7 +99,7 @@ void GameEventHandler::processGameEventContainer(const GameEventContainer &cont,
if (cont.has_forced_by_judge()) {
auto id = cont.forced_by_judge();
Player *judgep = game->getPlayerManager()->getPlayers().value(id, nullptr);
PlayerLogic *judgep = game->getPlayerManager()->getPlayers().value(id, nullptr);
if (judgep) {
emit setContextJudgeName(judgep->getPlayerInfo()->getName());
} else if (game->getPlayerManager()->getSpectators().contains(id)) {
@ -117,9 +120,11 @@ void GameEventHandler::processGameEventContainer(const GameEventContainer &cont,
break;
}
} else {
if ((game->getGameState()->getClients().size() > 1) && (playerId != -1))
if (game->getGameState()->getClients().at(playerId) != client)
if ((game->getGameState()->getClients().size() > 1) && (playerId != -1)) {
if (game->getGameState()->getClients().at(playerId) != client) {
continue;
}
}
switch (eventType) {
case GameEvent::GAME_STATE_CHANGED:
@ -155,7 +160,7 @@ void GameEventHandler::processGameEventContainer(const GameEventContainer &cont,
break;
default: {
Player *player = game->getPlayerManager()->getPlayers().value(playerId, 0);
PlayerLogic *player = game->getPlayerManager()->getPlayers().value(playerId, 0);
if (!player) {
qCWarning(GameEventHandlerLog) << "unhandled game event: invalid player id";
break;
@ -212,7 +217,20 @@ void GameEventHandler::handleArrowDeletion(int arrowId)
{
Command_DeleteArrow cmd;
cmd.set_arrow_id(arrowId);
sendGameCommand(cmd);
auto preparedCommand = prepareGameCommand(cmd);
connect(preparedCommand, &PendingCommand::finished, this,
[arrowId, this](const Response &response) { handleArrowDeletionFinished(response, arrowId); });
sendGameCommand(preparedCommand);
}
void GameEventHandler::handleArrowDeletionFinished(const Response &response, int arrowId)
{
if (response.response_code() == Response::RespNameNotFound) {
emit arrowDeleted(arrowId);
}
}
void GameEventHandler::eventSpectatorSay(const Event_GameSay &event,
@ -256,7 +274,7 @@ void GameEventHandler::eventGameStateChanged(const Event_GameStateChanged &event
emit spectatorJoined(prop);
}
} else {
Player *player = game->getPlayerManager()->getPlayers().value(playerId, 0);
PlayerLogic *player = game->getPlayerManager()->getPlayers().value(playerId, 0);
if (!player) {
player = game->getPlayerManager()->addPlayer(playerId, prop.user_info());
emit playerJoined(prop);
@ -284,8 +302,9 @@ void GameEventHandler::eventGameStateChanged(const Event_GameStateChanged &event
if (event.game_started() && !game->getGameMetaInfo()->started()) {
game->getGameState()->setResuming(!game->getGameState()->isGameStateKnown());
game->getGameMetaInfo()->setStarted(event.game_started());
if (game->getGameState()->isGameStateKnown())
if (game->getGameState()->isGameStateKnown()) {
emit logGameStart();
}
game->getGameState()->setActivePlayer(event.active_player_id());
game->getGameState()->setCurrentPhase(event.active_phase());
} else if (!event.game_started() && game->getGameMetaInfo()->started()) {
@ -304,9 +323,10 @@ void GameEventHandler::processCardAttachmentsForPlayers(const Event_GameStateCha
const ServerInfo_Player &playerInfo = event.player_list(i);
const ServerInfo_PlayerProperties &prop = playerInfo.properties();
if (!prop.spectator()) {
Player *player = game->getPlayerManager()->getPlayers().value(prop.player_id(), 0);
if (!player)
PlayerLogic *player = game->getPlayerManager()->getPlayers().value(prop.player_id(), 0);
if (!player) {
continue;
}
player->processCardAttachment(playerInfo);
}
}
@ -316,9 +336,10 @@ void GameEventHandler::eventPlayerPropertiesChanged(const Event_PlayerProperties
int eventPlayerId,
const GameEventContext &context)
{
Player *player = game->getPlayerManager()->getPlayers().value(eventPlayerId, 0);
if (!player)
PlayerLogic *player = game->getPlayerManager()->getPlayers().value(eventPlayerId, 0);
if (!player) {
return;
}
const ServerInfo_PlayerProperties &prop = event.player_properties();
emit playerPropertiesChanged(prop, eventPlayerId);
@ -326,8 +347,9 @@ void GameEventHandler::eventPlayerPropertiesChanged(const Event_PlayerProperties
switch (contextType) {
case GameEventContext::READY_START: {
bool ready = prop.ready_start();
if (player->getPlayerInfo()->getLocal())
if (player->getPlayerInfo()->getLocal()) {
emit localPlayerReadyStateChanged(player->getPlayerInfo()->getId(), ready);
}
if (ready) {
emit logReadyStart(player);
} else {
@ -338,9 +360,10 @@ void GameEventHandler::eventPlayerPropertiesChanged(const Event_PlayerProperties
case GameEventContext::CONCEDE: {
player->setConceded(true);
QMapIterator<int, Player *> playerIterator(game->getPlayerManager()->getPlayers());
while (playerIterator.hasNext())
QMapIterator<int, PlayerLogic *> playerIterator(game->getPlayerManager()->getPlayers());
while (playerIterator.hasNext()) {
playerIterator.next().value()->updateZones();
}
emit logConcede(eventPlayerId);
@ -349,9 +372,10 @@ void GameEventHandler::eventPlayerPropertiesChanged(const Event_PlayerProperties
case GameEventContext::UNCONCEDE: {
player->setConceded(false);
QMapIterator<int, Player *> playerIterator(game->getPlayerManager()->getPlayers());
while (playerIterator.hasNext())
QMapIterator<int, PlayerLogic *> playerIterator(game->getPlayerManager()->getPlayers());
while (playerIterator.hasNext()) {
playerIterator.next().value()->updateZones();
}
emit logUnconcede(eventPlayerId);
@ -389,15 +413,16 @@ void GameEventHandler::eventJoin(const Event_Join &event, int /*eventPlayerId*/,
QString playerName = QString::fromStdString(playerInfo.user_info().name());
emit addPlayerToAutoCompleteList(playerName);
if (game->getPlayerManager()->getPlayers().contains(playerId))
if (game->getPlayerManager()->getPlayers().contains(playerId)) {
return;
}
if (playerInfo.spectator()) {
game->getPlayerManager()->addSpectator(playerId, playerInfo);
emit logJoinSpectator(playerName);
emit spectatorJoined(playerInfo);
} else {
Player *newPlayer = game->getPlayerManager()->addPlayer(playerId, playerInfo.user_info());
PlayerLogic *newPlayer = game->getPlayerManager()->addPlayer(playerId, playerInfo.user_info());
emit logJoinPlayer(newPlayer);
emit playerJoined(playerInfo);
}
@ -425,23 +450,25 @@ QString GameEventHandler::getLeaveReason(Event_Leave::LeaveReason reason)
}
void GameEventHandler::eventLeave(const Event_Leave &event, int eventPlayerId, const GameEventContext & /*context*/)
{
Player *player = game->getPlayerManager()->getPlayers().value(eventPlayerId, 0);
if (!player)
PlayerLogic *player = game->getPlayerManager()->getPlayers().value(eventPlayerId, 0);
if (!player) {
return;
}
player->clear();
emit playerLeft(eventPlayerId);
emit logLeave(player, getLeaveReason(event.reason()));
game->getPlayerManager()->removePlayer(eventPlayerId);
player->clear();
player->deleteLater();
// Rearrange all remaining zones so that attachment relationship updates take place
QMapIterator<int, Player *> playerIterator(game->getPlayerManager()->getPlayers());
while (playerIterator.hasNext())
QMapIterator<int, PlayerLogic *> playerIterator(game->getPlayerManager()->getPlayers());
while (playerIterator.hasNext()) {
playerIterator.next().value()->updateZones();
}
emitUserEvent();
}
@ -460,9 +487,10 @@ void GameEventHandler::eventReverseTurn(const Event_ReverseTurn &event,
int eventPlayerId,
const GameEventContext & /*context*/)
{
Player *player = game->getPlayerManager()->getPlayers().value(eventPlayerId, 0);
if (!player)
PlayerLogic *player = game->getPlayerManager()->getPlayers().value(eventPlayerId, 0);
if (!player) {
return;
}
emit logTurnReversed(player, event.reversed());
}
@ -490,9 +518,10 @@ void GameEventHandler::eventSetActivePlayer(const Event_SetActivePlayer &event,
const GameEventContext & /*context*/)
{
game->getGameState()->setActivePlayer(event.active_player_id());
Player *player = game->getPlayerManager()->getPlayer(event.active_player_id());
if (!player)
PlayerLogic *player = game->getPlayerManager()->getPlayer(event.active_player_id());
if (!player) {
return;
}
emit logActivePlayer(player);
emitUserEvent();
}

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