Compare commits

...

42 commits

Author SHA1 Message Date
transifex-integration[bot]
e28f31c93e
Translate oracle/oracle_en@source.ts in es (#7006)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
2026-06-17 22:16:26 +02:00
DawnFire42
0c140d866f
[Card] Respect disabled sets when loading v3 card databases (#6882) (#7004)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
2026-06-17 14:09:05 +02:00
RickyRister
45d0cedb5c
[Game] Setting to restore old chat autofocus behavior (#6992)
Some checks failed
Build Desktop / Configure (push) Has been cancelled
Build Docker Image / amd64 & arm64 (push) Has been cancelled
Build Desktop / Debian 13 (push) Has been cancelled
Build Desktop / Debian 12 (push) Has been cancelled
Build Desktop / Fedora 44 (push) Has been cancelled
Build Desktop / Fedora 43 (push) Has been cancelled
Build Desktop / Servatrice_Debian 12 (push) Has been cancelled
Build Desktop / Ubuntu 26.04 (push) Has been cancelled
Build Desktop / Ubuntu 24.04 (push) Has been cancelled
Build Desktop / Arch (push) Has been cancelled
Build Desktop / macOS 14 (push) Has been cancelled
Build Desktop / macOS 15 (push) Has been cancelled
Build Desktop / macOS 13 Intel (push) Has been cancelled
Build Desktop / macOS 15 Debug (push) Has been cancelled
Build Desktop / Windows 10 (push) Has been cancelled
* [Game] Setting to restore old chat autofocus behavior

* fixes
2026-06-15 17:04:30 +02:00
BruebachL
309e4730a3
[Lobby][DeckView] Always at minimum create main and side deck zone containers. (#7001)
Took 12 minutes

Took 3 seconds

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-06-15 15:50:04 +02:00
BruebachL
0c4cc3f824
[DeckView][DeckEditor] Implement shortcut for load deck from website (default: Ctrl+Shift+O) (#7002)
Took 12 minutes

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-06-15 15:49:47 +02:00
BruebachL
dfbe944c31
[App][Windows][NSIS] Use QProcess::setNativeArguments on Windows, properly order portable detection (#6989)
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-06-15 15:23:18 +02:00
RickyRister
0f3e6fbe26
[CardDatabase] Pass CardInfoPtr by const ref (#6998)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
* [CardDatabase] Pass CardInfoPtr by const ref

* trailing newline
2026-06-14 04:46:18 -07:00
RickyRister
5ffe344779
[Game] Fix facedown predefined tokens leaking tablerow (#7000) 2026-06-14 03:21:17 -07:00
kongwu
f28ede7ae3
[UserContextMenu] Add confirmation dialog before kicking a player (#6987)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
2026-06-13 02:42:55 -07:00
BruebachL
7aaacbf347
[Update][NSIS] Use single string shell invocation (#6986)
Some checks failed
Build Desktop / Configure (push) Has been cancelled
Build Docker Image / amd64 & arm64 (push) Has been cancelled
Build Desktop / Debian 13 (push) Has been cancelled
Build Desktop / Debian 12 (push) Has been cancelled
Build Desktop / Fedora 44 (push) Has been cancelled
Build Desktop / Fedora 43 (push) Has been cancelled
Build Desktop / Servatrice_Debian 12 (push) Has been cancelled
Build Desktop / Ubuntu 26.04 (push) Has been cancelled
Build Desktop / Ubuntu 24.04 (push) Has been cancelled
Build Desktop / Arch (push) Has been cancelled
Build Desktop / macOS 14 (push) Has been cancelled
Build Desktop / macOS 15 (push) Has been cancelled
Build Desktop / macOS 13 Intel (push) Has been cancelled
Build Desktop / macOS 15 Debug (push) Has been cancelled
Build Desktop / Windows 10 (push) Has been cancelled
Took 18 minutes


Took 2 minutes

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-06-11 21:17:28 +02:00
tooomm
694adc9e64
Use Visual Studio 2026 (#6985)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
2026-06-11 10:45:17 +02:00
BruebachL
b17d879da8
[Game][Graphics][Player] Add named zone lookup-map to player graphics. (#6984)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
Took 16 minutes

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-06-10 10:58:28 +02:00
ebbit1q
6be9cec6e2
do not save a const reference to the user data in the info dialog (#6974)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
* do not save a const reference to the user data in the info dialog

* cmake format
2026-06-09 23:35:00 -07:00
kongwu
6d0a423dcf
[Messages] Add option to ignore private messages from non-buddy users (#6966)
* [Messages] Add option to ignore private messages from non-buddy users

* [Messages] Exclude Moderator/Admin from non-buddy ignore filter

Moderator and Admin messages should not be filtered out when the
'Ignore private messages from non-buddies' setting is enabled, to
ensure that important warnings from server staff reach users.
2026-06-09 20:49:29 -07:00
kongwu
f72c82d0f9
[DeckEditor] Replace mainboard/sideboard with tokensboard for tokens (#6971)
* [DeckEditor] Replace mainboard/sideboard with tokensboard for token cards (#6546)

* [PrintingSelector] Replace std::tuple with ZoneCounts struct for readability (#6546)
2026-06-09 20:46:43 -07:00
BruebachL
da4ba222c0
[Game] Move graphics out of game and into game_graphics (#6928)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
* [Game][Player] Pull out graphics_items out of player_logic

Took 25 seconds


Took 9 minutes

* [Game] Move graphics files into game_graphics

Took 1 minute

Took 2 minutes

Took 23 seconds

Took 1 minute

Took 2 seconds

* Include.

Took 4 minutes

Took 3 minutes

Took 4 minutes

Took 1 minute

Took 3 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-06-09 09:51:13 +02:00
BruebachL
cbfd286908
[Game][Player] Move dialog creation out of player_actions and into player_dialogs (#6946)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
* [Game][Player] Split Player into PlayerLogic/PlayerGraphicsItem

Took 4 minutes

Took 48 seconds

Took 2 minutes

* Drop early return.

Took 1 hour 13 minutes


Took 2 minutes

Took 1 minute

Took 24 seconds

* [Game][Player] Split Player into PlayerLogic/PlayerGraphicsItem

Took 4 minutes

Took 58 seconds

* [Game][Menus] Make Menus accept PlayerGraphicsItem instead of PlayerLogic

Took 7 minutes

Took 4 minutes

Took 9 seconds

Took 2 minutes


Took 5 minutes

Took 58 seconds

* [Game][Player] Split Player into PlayerLogic/PlayerGraphicsItem

Took 4 minutes

Took 2 minutes

* [Game][Menus] Make Menus accept PlayerGraphicsItem instead of PlayerLogic

Took 7 minutes


Took 1 minute

Took 57 seconds

* [Game][Player] Move dialog creation out of player_actions and into player_dialogs

Took 3 minutes

Took 1 second

* Fix typo.

Took 5 minutes

* Addressed comments.

Took 16 minutes

Took 11 seconds

* Reintroduce clearCardsToDelete check.

Took 3 minutes

* Capture cards before semaphore.

Took 1 minute

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-06-09 08:22:59 +02:00
BruebachL
487bb84b6f
[Game][Menus] Make Menus accept PlayerGraphicsItem instead of PlayerLogic (#6945)
* [Game][Player] Split Player into PlayerLogic/PlayerGraphicsItem

Took 4 minutes

Took 58 seconds


Took 2 minutes

* [Game][Menus] Make Menus accept PlayerGraphicsItem instead of PlayerLogic

Took 7 minutes

Took 4 minutes

Took 9 seconds

Took 2 minutes


Took 5 minutes

Took 58 seconds

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-06-09 08:07:06 +02:00
BruebachL
9e03f82616
[Game][Player] Split Player into PlayerLogic/PlayerGraphicsItem (#6944)
* [Game][Player] Split Player into PlayerLogic/PlayerGraphicsItem

Took 4 minutes

Took 48 seconds

* Drop early return.

Took 1 hour 13 minutes


Took 2 minutes

Took 1 minute

* Delete player view.

Took 37 seconds

* Restore card counter color in menu.

Took 5 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-06-09 08:05:39 +02:00
Christo
e674a39b87
Fix #6952: prevent deck loss when saving to a full disk (#6978)
* Fix #6952: prevent deck loss when saving to a full disk

DeckLoader::saveToFile() opened the target with QFile in WriteOnly mode,
which truncates the existing file to 0 bytes the moment it is opened. The
serializers (DeckList::saveToFile_Native/_Plain) always return true and the
result of flush() was ignored, so a write that failed part-way -- e.g.
because the disk was full -- left a 0-byte file behind yet was still
reported (and logged) as a successful save. The same truncate-then-write
pattern in updateLastLoadedTimestamp() could destroy a deck on load.

Switch both paths to QSaveFile, which writes to a temporary file and only
atomically replaces the target if commit() succeeds. On any write or flush
failure commit() returns false, the original deck is left untouched, and
the failure is logged instead of being reported as success.

* Use QSaveFile in convertToCockatriceFormat() too

convertToCockatriceFormat() had the same data-loss pattern: QFile WriteOnly
truncated the .cod, saveToFile_Native() always returns true, and the original
file was then removed unconditionally -- so a full disk during conversion wrote
a 0-byte .cod and then deleted the source deck.

Switch to QSaveFile (write + atomic commit), remove the original only after a
successful commit, and move the format check ahead of the file open so an
already-Cockatrice or unsupported deck never truncates or deletes anything.

Raised in review by ZeldaZach.
2026-06-09 07:54:01 +02:00
tooomm
1efc382c05
CI: Cleanup (#6959)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
* Label & variables

* fix bracket

* other workflows

* fix trailing whitespace

* fixes
2026-06-08 19:37:50 +02:00
tooomm
dc152e89f7
CI: Print colored diff for lint check (#6975)
* print colored diff in gha

* use spaces
2026-06-08 19:19:28 +02:00
RickyRister
20cdcdb382
[ReplayManager] Refactor to send replayed events through signal (#6979)
* [ReplayManager] Refactor to send replayed events through signal

* remove blank

* pass by const auto ref
2026-06-07 19:39:31 -07:00
BruebachL
23da49ee5b
[Game] [Arrows] Use arrowData/registry and generate unique server-side ids (#6973)
* [Game] [Arrows] Track creatorId, use arrowData in arrowItem, use registry, generate unique arrow id's on server side and delete-on-exist inserts.

Took 2 minutes

Took 1 minute

* Fix emitting slot instead of signal.

Took 15 minutes

* Clear arrows locally in special circumstances i.e. teardown.

Took 28 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-06-07 21:11:02 +02:00
RickyRister
c14a008080
[TabDeckEditor] Refactor card database view into own class (#6967)
* rename method

* [TabDeckEditor] Refactor card database view into own class

* fix include guard

* directly get key signals for eventFilter

* fix includes
2026-06-05 10:20:46 -07:00
RickyRister
0da2ac4087
[TabDeckEditor] Refactor: pass nullable deck model into filter widget (#6969) 2026-06-05 09:21:28 -07:00
RickyRister
29cc622ce3
[TabDeckEditor] Refactor: Create shared CardDatabaseModel for tab (#6968) 2026-06-05 09:21:13 -07:00
RickyRister
86256602ff
[TabDeckEditor] Refactor to use signal instead of calling tab (#6965)
* [TabDeckEditor] Refactor to use signal instead of calling tab

* update docs

* fix cardInfoRequest
2026-06-03 10:41:55 -07:00
RickyRister
f37c418865
[TabDeckEditor] Refactor: Remove cardDatabase field from analysis interfaces (#6963)
* [TabDeckEditor] Refactor: Remove cardDatabase field from analysis interfaces

* update includes
2026-06-03 10:08:57 -07:00
RickyRister
46d3b820db
[TabDeckEditor] Refactor: pull up showPrintingSelector (#6964)
* [TabDeckEditor] Refactor: pull up showPrintingSelector

* trailing newline
2026-06-03 09:35:48 -07:00
RickyRister
e0cbb7f06c
[TabDeckEditor] Refactor: pass ExactCard in signal instead of widget (#6962)
* [TabDeckEditor] Refactor: pass ExactCard in signal instead of widget

* address comments
2026-06-02 21:22:06 -07:00
RickyRister
f52dc6dda8
[TabDeckEditor] Refactor: consolidate add/decrement card signals (#6961) 2026-06-02 20:13:39 -07:00
RickyRister
3fa377a11c
[TabDeckEditor] Refactor check ctrl to be on click (#6956) 2026-05-31 03:44:40 -07:00
RickyRister
c5372a9e92
[DeckEditor] Refactor: clean up addCardHelper (#6939)
* [DeckEditor] Refactor: clean up addCardHelper

* remove setSaveStatus
2026-05-31 03:14:21 -07:00
RickyRister
6de55e9096
[Game][Arrow] Correctly call clear all arrows for player (#6951) 2026-05-28 23:51:12 -07:00
RickyRister
43c3bf5966
[Game][Arrow] Refactor: Rename arrow methods in GameScene (#6949)
* [Game][Arrow] Rename methods in GameScene

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

Took 3 seconds

Took 2 minutes

Took 10 minutes

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

Took 15 minutes

* Observe "not found" response

Took 18 minutes

Took 4 seconds

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-05-25 08:19:17 +02:00
224 changed files with 3151 additions and 1957 deletions

View file

@ -13,17 +13,9 @@ fi
# Check formatting using format.sh # Check formatting using format.sh
echo "Checking your code using format.sh..." echo "Checking your code using format.sh..."
diff="$(./format.sh --diff --cmake --shell --print-version --branch origin/master)" ./format.sh --color-diff --cmake --shell --print-version --branch origin/master
err=$? err=$?
sep="
----------
"
used_version="${diff%%"$sep"*}"
diff="${diff#*"$sep"}"
changes_to_make="${diff%%"$sep"*}"
files_to_edit="${diff#*"$sep"}"
case $err in case $err in
1) 1)
cat <<EOM cat <<EOM
@ -40,15 +32,6 @@ case $err in
*** *** *** ***
*********************************************************** ***********************************************************
Used version:
$used_version
Affected files:
$files_to_edit
The following changes should be made:
$changes_to_make
Exiting... Exiting...
EOM EOM
exit 2 exit 2
@ -65,9 +48,6 @@ EOM
*** *** *** ***
*********************************************************** ***********************************************************
Used version:
$used_version
Exiting... Exiting...
EOM EOM
exit 0 exit 0

View file

@ -1,10 +1,10 @@
name: Build Desktop name: Build Desktop
permissions: permissions:
actions: write # needed to delete entries in GHA cache (update ccache)
attestations: write # needed to persist the attestation.
contents: write contents: write
id-token: write id-token: write # needed for signing certificate in attestation
attestations: write
actions: write # needed for ccache action to be able to delete gha caches
on: on:
push: push:
@ -19,7 +19,7 @@ on:
- '.github/workflows/desktop-build.yml' - '.github/workflows/desktop-build.yml'
- 'CMakeLists.txt' - 'CMakeLists.txt'
- 'vcpkg.json' - 'vcpkg.json'
- 'vcpkg' - 'vcpkg' # needed to match submodule bumps (gitlink)
tags: tags:
- '*' - '*'
pull_request: pull_request:
@ -32,7 +32,7 @@ on:
- '.github/workflows/desktop-build.yml' - '.github/workflows/desktop-build.yml'
- 'CMakeLists.txt' - 'CMakeLists.txt'
- 'vcpkg.json' - 'vcpkg.json'
- 'vcpkg' - 'vcpkg' # needed to match submodule bumps (gitlink)
# Cancel earlier, unfinished runs of this workflow on the same branch (unless on release) # Cancel earlier, unfinished runs of this workflow on the same branch (unless on release)
concurrency: concurrency:
@ -48,7 +48,7 @@ jobs:
sha: ${{ steps.configure.outputs.sha }} sha: ${{ steps.configure.outputs.sha }}
steps: steps:
- name: Configure - name: "Configure"
id: configure id: configure
shell: bash shell: bash
run: | run: |
@ -64,13 +64,13 @@ jobs:
fi fi
echo "sha=$sha" >>"$GITHUB_OUTPUT" echo "sha=$sha" >>"$GITHUB_OUTPUT"
- name: Checkout - name: "Checkout"
if: steps.configure.outputs.tag != null if: steps.configure.outputs.tag != null
uses: actions/checkout@v6 uses: actions/checkout@v6
with: with:
fetch-depth: 0 fetch-depth: 0 # fetch all history for all branches and tags
- name: Prepare release parameters - name: "Prepare release parameters"
id: prepare id: prepare
if: steps.configure.outputs.tag != null if: steps.configure.outputs.tag != null
shell: bash shell: bash
@ -78,7 +78,7 @@ jobs:
TAG: ${{ steps.configure.outputs.tag }} TAG: ${{ steps.configure.outputs.tag }}
run: .ci/prep_release.sh run: .ci/prep_release.sh
- name: Create release - name: "Create release"
if: steps.configure.outputs.tag != null if: steps.configure.outputs.tag != null
id: create_release id: create_release
shell: bash shell: bash
@ -90,54 +90,63 @@ jobs:
body_path: ${{ steps.prepare.outputs.body_path }} body_path: ${{ steps.prepare.outputs.body_path }}
prerelease: ${{ steps.prepare.outputs.is_beta }} prerelease: ${{ steps.prepare.outputs.is_beta }}
run: | run: |
if [[ $prerelease == yes ]]; then args=()
args="--prerelease" [[ $prerelease == yes ]] && args+=(--prerelease)
fi
gh release create "$tag_name" --draft --verify-tag $args \ gh release create "$tag_name" --verify-tag --draft "${args[@]}" \
--target "$target" --title "$release_name" \ --target "$target" \
--title "$release_name" \
--notes-file "$body_path" --notes-file "$body_path"
build-linux: build-linux:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
# These names correspond to the files in ".ci/$distro$version" # The files in ".ci/$distro$version" correspond to the values given here
include: include:
- distro: Arch - distro: Arch
package: skip # We are packaged in Arch already
allow-failure: yes allow-failure: yes
package: skip # We are packaged in Arch already
- distro: Servatrice_Debian - distro: Servatrice_Debian
version: 12 version: 12
package: DEB package: DEB
test: skip
server_only: yes server_only: yes
test: skip
- distro: Debian - distro: Debian
version: 12 version: 12
package: DEB package: DEB
test: skip # Running tests on all distros is superfluous test: skip # Running tests on all distros is superfluous
- distro: Debian - distro: Debian
version: 13 version: 13
package: DEB package: DEB
- distro: Fedora - distro: Fedora
version: 43 version: 43
package: RPM package: RPM
test: skip # Running tests on all distros is superfluous test: skip # Running tests on all distros is superfluous
- distro: Fedora - distro: Fedora
version: 44 version: 44
package: RPM package: RPM
- distro: Ubuntu - distro: Ubuntu
version: 24.04 version: 24.04
package: DEB package: DEB
test: skip # Running tests on all distros is superfluous test: skip # Running tests on all distros is superfluous
- distro: Ubuntu - distro: Ubuntu
version: 26.04 version: 26.04
package: DEB package: DEB
name: ${{ matrix.distro }} ${{ matrix.version }} name: ${{ matrix.distro }} ${{ matrix.version }}
@ -146,33 +155,31 @@ jobs:
continue-on-error: ${{ matrix.allow-failure == 'yes' }} continue-on-error: ${{ matrix.allow-failure == 'yes' }}
timeout-minutes: 70 timeout-minutes: 70
env: env:
NAME: ${{matrix.distro}}${{matrix.version}}
CACHE: ${{ github.workspace }}/.cache/${{ matrix.distro }}${{ matrix.version }} # directory for caching docker image and ccache CACHE: ${{ github.workspace }}/.cache/${{ matrix.distro }}${{ matrix.version }} # directory for caching docker image and ccache
# Cache size over the entire repo is 10Gi:
# https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#usage-limits-and-eviction-policy
CCACHE_SIZE: 550M
CCACHE_EVICTION_AGE: 7d CCACHE_EVICTION_AGE: 7d
CCACHE_SIZE: 550M # space of all repo is 10Gi: https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#usage-limits-and-eviction-policy
CMAKE_GENERATOR: 'Ninja' CMAKE_GENERATOR: 'Ninja'
NAME: ${{ matrix.distro }}${{ matrix.version }}
steps: steps:
- name: Checkout - name: "Checkout"
uses: actions/checkout@v6 uses: actions/checkout@v6
- name: Restore compiler cache (ccache) - name: "Restore compiler cache (ccache)"
id: ccache_restore id: ccache_restore
uses: actions/cache/restore@v5 uses: actions/cache/restore@v5
env: env:
BRANCH_NAME: ${{ github.head_ref || github.ref_name }} BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
with: with:
path: ${{env.CACHE}}
key: ccache-${{ matrix.distro }}${{ matrix.version }}-${{ env.BRANCH_NAME }} key: ccache-${{ matrix.distro }}${{ matrix.version }}-${{ env.BRANCH_NAME }}
path: ${{ env.CACHE }}
restore-keys: ccache-${{ matrix.distro }}${{ matrix.version }}- restore-keys: ccache-${{ matrix.distro }}${{ matrix.version }}-
- name: Build ${{matrix.distro}} ${{matrix.version}} Docker image - name: "Build ${{ matrix.distro }} ${{ matrix.version }} Docker image"
shell: bash shell: bash
run: source .ci/docker.sh --build run: source .ci/docker.sh --build
- name: Build debug and test - name: "Build debug and test"
if: matrix.test != 'skip' if: matrix.test != 'skip'
shell: bash shell: bash
run: | run: |
@ -180,7 +187,7 @@ jobs:
RUN --server --debug --test --ccache "$CCACHE_SIZE" \ RUN --server --debug --test --ccache "$CCACHE_SIZE" \
--cmake-generator "$CMAKE_GENERATOR" --cmake-generator "$CMAKE_GENERATOR"
- name: Build release package - name: "Build release package"
id: build id: build
if: matrix.package != 'skip' if: matrix.package != 'skip'
shell: bash shell: bash
@ -191,19 +198,16 @@ jobs:
run: | run: |
source .ci/docker.sh source .ci/docker.sh
args=() args=()
if [[ $server_only == yes ]]; then [[ $server_only == yes ]] && args+=(--no-client)
args+=(--no-client) [[ $GITHUB_REF == "refs/heads/master" ]] && args+=(--evict-ccache "$CCACHE_EVICTION_AGE")
fi
if [[ $GITHUB_REF == "refs/heads/master" ]]; then
args+=(--evict-ccache "$CCACHE_EVICTION_AGE")
fi
args+=(--ccache "$CCACHE_SIZE") args+=(--ccache "$CCACHE_SIZE")
args+=(--cmake-generator "$CMAKE_GENERATOR") args+=(--cmake-generator "$CMAKE_GENERATOR")
args+=(--suffix "$SUFFIX") args+=(--suffix "$SUFFIX")
RUN --server --release --package "$package" "${args[@]}" RUN --server --release --package "$package" "${args[@]}"
# Delete used cache to emulate a ccache update. See https://github.com/actions/cache/issues/342 # Delete used cache to emulate a ccache update. See https://github.com/actions/cache/issues/342
- name: Delete remote compiler cache (ccache) - name: "Delete remote compiler cache (ccache)"
if: github.ref == 'refs/heads/master' && steps.ccache_restore.outputs.cache-hit if: github.ref == 'refs/heads/master' && steps.ccache_restore.outputs.cache-hit
continue-on-error: true continue-on-error: true
env: env:
@ -213,42 +217,42 @@ jobs:
echo "Cache deleted successfully" echo "Cache deleted successfully"
fi fi
- name: Save updated compiler cache (ccache) - name: "Save updated compiler cache (ccache)"
if: github.ref == 'refs/heads/master' if: github.ref == 'refs/heads/master'
uses: actions/cache/save@v5 uses: actions/cache/save@v5
with: with:
path: ${{env.CACHE}}
key: ${{ steps.ccache_restore.outputs.cache-primary-key }} key: ${{ steps.ccache_restore.outputs.cache-primary-key }}
path: ${{ env.CACHE }}
- name: Upload artifact - name: "Upload artifact"
id: upload_artifact id: upload_artifact
if: matrix.package != 'skip' if: matrix.package != 'skip'
uses: actions/upload-artifact@v7 uses: actions/upload-artifact@v7
with: with:
path: ${{steps.build.outputs.path}}
archive: false archive: false
if-no-files-found: error if-no-files-found: error
path: ${{ steps.build.outputs.path }}
- name: Upload to release - name: "Upload to release"
id: upload_release id: upload_release
if: matrix.package != 'skip' && needs.configure.outputs.tag != null if: matrix.package != 'skip' && needs.configure.outputs.tag != null
shell: bash shell: bash
env: env:
GH_TOKEN: ${{github.token}}
tag_name: ${{needs.configure.outputs.tag}}
asset_name: ${{ steps.build.outputs.fullname }} asset_name: ${{ steps.build.outputs.fullname }}
asset_path: ${{ steps.build.outputs.path }} asset_path: ${{ steps.build.outputs.path }}
GH_TOKEN: ${{ github.token }}
tag_name: ${{ needs.configure.outputs.tag }}
run: gh release upload "$tag_name" "$asset_path#$asset_name" run: gh release upload "$tag_name" "$asset_path#$asset_name"
- name: Attest binary provenance - name: "Attest binary provenance"
id: attestation id: attestation
if: steps.upload_release.outcome == 'success' if: steps.upload_release.outcome == 'success'
uses: actions/attest@v4 uses: actions/attest@v4
with: with:
subject-path: ${{steps.build.outputs.path}}
show-summary: false show-summary: false
subject-path: ${{ steps.build.outputs.path }}
- name: Verify binary attestation - name: "Verify binary attestation"
if: steps.attestation.outcome == 'success' if: steps.attestation.outcome == 'success'
shell: bash shell: bash
env: env:
@ -263,73 +267,78 @@ jobs:
- os: macOS - os: macOS
target: 13 target: 13
runner: macos-15-intel runner: macos-15-intel
soc: Intel
xcode: "16.4" ccache_eviction_age: 7d
type: Release cmake_generator: Ninja
override_target: 13
make_package: 1 make_package: 1
override_target: 13
package_suffix: "-macOS13_Intel" package_suffix: "-macOS13_Intel"
qt_version: 6.11.0 qt_version: 6.11.0
qt_arch: clang_64 qt_arch: clang_64
qt_modules: qtimageformats qtmultimedia qtwebsockets qt_modules: qtimageformats qtmultimedia qtwebsockets
cmake_generator: Ninja soc: Intel
type: Release
use_ccache: 1 use_ccache: 1
ccache_eviction_age: 7d xcode: "16.4"
- os: macOS - os: macOS
target: 14 target: 14
runner: macos-14 runner: macos-14
soc: Apple
xcode: "15.4" ccache_eviction_age: 7d
type: Release cmake_generator: Ninja
make_package: 1 make_package: 1
package_suffix: "-macOS14" package_suffix: "-macOS14"
qt_version: 6.11.0 qt_version: 6.11.0
qt_arch: clang_64 qt_arch: clang_64
qt_modules: qtimageformats qtmultimedia qtwebsockets qt_modules: qtimageformats qtmultimedia qtwebsockets
cmake_generator: Ninja soc: Apple
type: Release
use_ccache: 1 use_ccache: 1
ccache_eviction_age: 7d xcode: "15.4"
- os: macOS - os: macOS
target: 15 target: 15
runner: macos-15 runner: macos-15
soc: Apple
xcode: "16.4" ccache_eviction_age: 7d
type: Release cmake_generator: Ninja
make_package: 1 make_package: 1
package_suffix: "-macOS15" package_suffix: "-macOS15"
qt_version: 6.11.0 qt_version: 6.11.0
qt_arch: clang_64 qt_arch: clang_64
qt_modules: qtimageformats qtmultimedia qtwebsockets qt_modules: qtimageformats qtmultimedia qtwebsockets
cmake_generator: Ninja soc: Apple
type: Release
use_ccache: 1 use_ccache: 1
ccache_eviction_age: 7d xcode: "16.4"
- os: macOS - os: macOS
target: 15 target: 15
runner: macos-15 runner: macos-15
soc: Apple
xcode: "16.4" ccache_eviction_age: 7d
type: Debug cmake_generator: Ninja
qt_version: 6.11.0 qt_version: 6.11.0
qt_arch: clang_64 qt_arch: clang_64
qt_modules: qtimageformats qtmultimedia qtwebsockets qt_modules: qtimageformats qtmultimedia qtwebsockets
cmake_generator: Ninja soc: Apple
type: Debug
use_ccache: 1 use_ccache: 1
ccache_eviction_age: 7d xcode: "16.4"
- os: Windows - os: Windows
target: 10 target: 10
runner: windows-2025 runner: windows-2025
type: Release
cmake_generator: "Visual Studio 18 2026"
cmake_generator_platform: x64
make_package: 1 make_package: 1
package_suffix: "-Win10" package_suffix: "-Win10"
qt_version: 6.11.0 qt_version: 6.11.0
qt_arch: win64_msvc2022_64 qt_arch: win64_msvc2022_64
qt_modules: qtimageformats qtmultimedia qtwebsockets qt_modules: qtimageformats qtmultimedia qtwebsockets
cmake_generator: "Visual Studio 17 2022" type: Release
cmake_generator_platform: x64
name: ${{ matrix.os }} ${{ matrix.target }}${{ matrix.soc == 'Intel' && ' Intel' || '' }}${{ matrix.type == 'Debug' && ' Debug' || '' }} name: ${{ matrix.os }} ${{ matrix.target }}${{ matrix.soc == 'Intel' && ' Intel' || '' }}${{ matrix.type == 'Debug' && ' Debug' || '' }}
needs: configure needs: configure
@ -337,126 +346,123 @@ jobs:
timeout-minutes: 100 timeout-minutes: 100
env: env:
CCACHE_DIR: ${{ github.workspace }}/.cache/ CCACHE_DIR: ${{ github.workspace }}/.cache/
# Cache size over the entire repo is 10Gi: CCACHE_SIZE: 550M # space of all repo is 10Gi: https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#usage-limits-and-eviction-policy
# https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#usage-limits-and-eviction-policy
CCACHE_SIZE: 550M
steps: steps:
- name: Checkout - name: "Checkout"
uses: actions/checkout@v6 uses: actions/checkout@v6
with: with:
submodules: recursive submodules: recursive
- name: Add msbuild to PATH - name: "[Windows] Add msbuild to PATH"
if: matrix.os == 'Windows' if: matrix.os == 'Windows'
id: add-msbuild id: add-msbuild
uses: microsoft/setup-msbuild@v3 uses: microsoft/setup-msbuild@v3
with: with:
msbuild-architecture: x64 msbuild-architecture: x64
- name: Setup ccache - name: "[macOS] Setup ccache"
if: matrix.use_ccache == 1 && matrix.os == 'macOS' if: matrix.os == 'macOS' && matrix.use_ccache == 1
run: brew install ccache run: brew install ccache
- name: Restore compiler cache (ccache) - name: "[macOS] Restore compiler cache (ccache)"
if: matrix.use_ccache == 1 if: matrix.os == 'macOS' && matrix.use_ccache == 1
id: ccache_restore id: ccache_restore
uses: actions/cache/restore@v5 uses: actions/cache/restore@v5
env: env:
BRANCH_NAME: ${{ github.head_ref || github.ref_name }} BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
with: with:
path: ${{env.CCACHE_DIR}}
key: ccache-${{ matrix.runner }}-${{ matrix.soc }}-${{ matrix.type }}-${{ env.BRANCH_NAME }} key: ccache-${{ matrix.runner }}-${{ matrix.soc }}-${{ matrix.type }}-${{ env.BRANCH_NAME }}
path: ${{ env.CCACHE_DIR }}
restore-keys: ccache-${{ matrix.runner }}-${{ matrix.soc }}-${{ matrix.type }}- restore-keys: ccache-${{ matrix.runner }}-${{ matrix.soc }}-${{ matrix.type }}-
- name: Install aqtinstall - name: "Install aqtinstall"
run: pipx install aqtinstall run: pipx install aqtinstall
# Resolve given wildcard versions (e.g. Qt 6.6.*) to latest version via aqtinstall to avoid stale caches on new releases # Resolve given wildcard versions (e.g. Qt 6.6.*) to latest version via aqtinstall to avoid stale caches on new releases
- name: Resolve latest Qt patch version - name: "Resolve latest Qt patch version"
id: resolve_qt_version id: resolve_qt_version
shell: bash shell: bash
run: .ci/resolve_latest_aqt_qt_version.sh "${{ matrix.qt_version }}" run: .ci/resolve_latest_aqt_qt_version.sh "${{ matrix.qt_version }}"
- name: Restore thin Qt ${{ steps.resolve_qt_version.outputs.version }} libraries (${{ matrix.soc }} macOS) - name: "[macOS] Restore thin Qt ${{ steps.resolve_qt_version.outputs.version }} libraries"
if: matrix.os == 'macOS' if: matrix.os == 'macOS'
id: restore_qt id: restore_qt
uses: actions/cache/restore@v5 uses: actions/cache/restore@v5
with: with:
path: ${{ github.workspace }}/Qt
key: thin-qt-macos-${{ matrix.soc }}-${{ steps.resolve_qt_version.outputs.version }} key: thin-qt-macos-${{ matrix.soc }}-${{ steps.resolve_qt_version.outputs.version }}
path: ${{ github.workspace }}/Qt
# Using jurplel/install-qt-action to install Qt without using brew # Using jurplel/install-qt-action to install Qt without using brew
# qt build using vcpkg either just fails or takes too long to build # Qt build using vcpkg either just fails or takes too long to build
- name: Install fat Qt ${{ steps.resolve_qt_version.outputs.version }} (${{ matrix.soc }} macOS) - name: "[macOS] Install fat Qt ${{ steps.resolve_qt_version.outputs.version }}"
if: matrix.os == 'macOS' && steps.restore_qt.outputs.cache-hit != 'true' if: matrix.os == 'macOS' && steps.restore_qt.outputs.cache-hit != 'true'
uses: jurplel/install-qt-action@v4 uses: jurplel/install-qt-action@v4
with: with:
version: ${{ steps.resolve_qt_version.outputs.version }}
arch: ${{ matrix.qt_arch }} arch: ${{ matrix.qt_arch }}
modules: ${{matrix.qt_modules}}
cache: false cache: false
dir: ${{ github.workspace }} dir: ${{ github.workspace }}
modules: ${{ matrix.qt_modules }}
version: ${{ steps.resolve_qt_version.outputs.version }}
- name: Thin Qt libraries (${{ matrix.soc }} macOS) - name: "[macOS] Create thin Qt libraries"
if: matrix.os == 'macOS' && steps.restore_qt.outputs.cache-hit != 'true' if: matrix.os == 'macOS' && steps.restore_qt.outputs.cache-hit != 'true'
run: .ci/thin_macos_qtlib.sh run: .ci/thin_macos_qtlib.sh
- name: Cache thin Qt libraries (${{ matrix.soc }} macOS) - name: "[macOS] Cache thin Qt libraries"
if: matrix.os == 'macOS' && steps.restore_qt.outputs.cache-hit != 'true' if: matrix.os == 'macOS' && steps.restore_qt.outputs.cache-hit != 'true'
uses: actions/cache/save@v5 uses: actions/cache/save@v5
with: with:
path: ${{ github.workspace }}/Qt
key: thin-qt-macos-${{ matrix.soc }}-${{ steps.resolve_qt_version.outputs.version }} key: thin-qt-macos-${{ matrix.soc }}-${{ steps.resolve_qt_version.outputs.version }}
path: ${{ github.workspace }}/Qt
- name: Install Qt ${{matrix.qt_version}} (Windows) - name: "[Windows] Install Qt ${{ matrix.qt_version }}"
if: matrix.os == 'Windows' if: matrix.os == 'Windows'
uses: jurplel/install-qt-action@v4 uses: jurplel/install-qt-action@v4
with: with:
# qt 6.11.0 only works with aqtinstall directly from git until aqtinstall 3.4 is released # Qt 6.11.0 only works with aqtinstall directly from git until aqtinstall 3.4 is released
aqtsource: git+https://github.com/miurahr/aqtinstall.git aqtsource: git+https://github.com/miurahr/aqtinstall.git
version: ${{ steps.resolve_qt_version.outputs.version }}
arch: ${{ matrix.qt_arch }} arch: ${{ matrix.qt_arch }}
modules: ${{matrix.qt_modules}}
cache: true cache: true
modules: ${{ matrix.qt_modules }}
version: ${{ steps.resolve_qt_version.outputs.version }}
- name: Install NSIS - name: "[Windows] Install NSIS"
if: matrix.os == 'Windows' if: matrix.os == 'Windows'
shell: bash shell: bash
run: choco install nsis run: choco install nsis
- name: Setup vcpkg cache - name: "Setup vcpkg cache"
id: vcpkg-cache id: vcpkg-cache
uses: TAServers/vcpkg-cache@v3 uses: TAServers/vcpkg-cache@v3
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
# uses environment variables, see compile.sh for more details # Uses environment variables, see compile.sh for more details
- name: Build Cockatrice - name: "Build Cockatrice"
id: build id: build
shell: bash shell: bash
env: env:
BUILDTYPE: '${{ matrix.type }}' BUILDTYPE: '${{ matrix.type }}'
MAKE_PACKAGE: '${{matrix.make_package}}' CCACHE_EVICTION_AGE: ${{ matrix.ccache_eviction_age }}
PACKAGE_SUFFIX: '${{matrix.package_suffix}}'
CMAKE_GENERATOR: ${{ matrix.cmake_generator }} CMAKE_GENERATOR: ${{ matrix.cmake_generator }}
CMAKE_GENERATOR_PLATFORM: ${{ matrix.cmake_generator_platform }} CMAKE_GENERATOR_PLATFORM: ${{ matrix.cmake_generator_platform }}
USE_CCACHE: ${{matrix.use_ccache}}
VCPKG_DISABLE_METRICS: 1
VCPKG_BINARY_SOURCES: 'clear;files,${{ steps.vcpkg-cache.outputs.path }},readwrite'
# macOS-specific environment variables, will be ignored on Windows
MACOS_CERTIFICATE: ${{ secrets.PROD_MACOS_CERTIFICATE }}
MACOS_CERTIFICATE_PWD: ${{ secrets.PROD_MACOS_CERTIFICATE_PWD }}
MACOS_CERTIFICATE_NAME: ${{ secrets.PROD_MACOS_CERTIFICATE_NAME }}
MACOS_CI_KEYCHAIN_PWD: ${{ secrets.PROD_MACOS_CI_KEYCHAIN_PWD }}
DEVELOPER_DIR: '/Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer' DEVELOPER_DIR: '/Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer'
MACOS_CERTIFICATE: ${{ secrets.PROD_MACOS_CERTIFICATE }}
MACOS_CERTIFICATE_NAME: ${{ secrets.PROD_MACOS_CERTIFICATE_NAME }}
MACOS_CERTIFICATE_PWD: ${{ secrets.PROD_MACOS_CERTIFICATE_PWD }}
MACOS_CI_KEYCHAIN_PWD: ${{ secrets.PROD_MACOS_CI_KEYCHAIN_PWD }}
MAKE_PACKAGE: '${{ matrix.make_package }}'
PACKAGE_SUFFIX: '${{ matrix.package_suffix }}'
TARGET_MACOS_VERSION: ${{ matrix.override_target }} TARGET_MACOS_VERSION: ${{ matrix.override_target }}
CCACHE_EVICTION_AGE: ${{ matrix.ccache_eviction_age }} USE_CCACHE: ${{ matrix.use_ccache }}
VCPKG_BINARY_SOURCES: 'clear;files,${{ steps.vcpkg-cache.outputs.path }},readwrite'
VCPKG_DISABLE_METRICS: 1
run: .ci/compile.sh --server --test --vcpkg run: .ci/compile.sh --server --test --vcpkg
# Delete used cache to emulate a ccache update. See https://github.com/actions/cache/issues/342 # Delete used cache to emulate a ccache update. See https://github.com/actions/cache/issues/342
- name: Delete remote compiler cache (ccache) - name: "[macOS] Delete remote compiler cache (ccache)"
if: github.ref == 'refs/heads/master' && matrix.use_ccache == 1 && steps.ccache_restore.outputs.cache-hit if: matrix.os == 'macOS' && matrix.use_ccache == 1 && github.ref == 'refs/heads/master' && steps.ccache_restore.outputs.cache-hit
continue-on-error: true continue-on-error: true
env: env:
GH_TOKEN: ${{ github.token }} GH_TOKEN: ${{ github.token }}
@ -465,14 +471,14 @@ jobs:
echo "Cache deleted successfully" echo "Cache deleted successfully"
fi fi
- name: Save updated compiler cache (ccache) - name: "[macOS] Save updated compiler cache (ccache)"
if: github.ref == 'refs/heads/master' && matrix.use_ccache == 1 if: matrix.os == 'macOS' && matrix.use_ccache == 1 && github.ref == 'refs/heads/master'
uses: actions/cache/save@v5 uses: actions/cache/save@v5
with: with:
path: ${{env.CCACHE_DIR}}
key: ${{ steps.ccache_restore.outputs.cache-primary-key }} key: ${{ steps.ccache_restore.outputs.cache-primary-key }}
path: ${{ env.CCACHE_DIR }}
- name: Sign app bundle - name: "[macOS] Sign app bundle"
if: matrix.os == 'macOS' && matrix.make_package && needs.configure.outputs.tag != null if: matrix.os == 'macOS' && matrix.make_package && needs.configure.outputs.tag != null
id: sign_macos id: sign_macos
env: env:
@ -485,12 +491,12 @@ jobs:
/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 fi
- name: Notarize app bundle - name: "[macOS] Notarize app bundle"
if: steps.sign_macos.outcome == 'success' if: matrix.os == 'macOS' && steps.sign_macos.outcome == 'success'
env: env:
MACOS_NOTARIZATION_APPLE_ID: ${{ secrets.PROD_MACOS_NOTARIZATION_APPLE_ID }} MACOS_NOTARIZATION_APPLE_ID: ${{ secrets.PROD_MACOS_NOTARIZATION_APPLE_ID }}
MACOS_NOTARIZATION_TEAM_ID: ${{ secrets.PROD_MACOS_NOTARIZATION_TEAM_ID }}
MACOS_NOTARIZATION_PWD: ${{ secrets.PROD_MACOS_NOTARIZATION_PWD }} MACOS_NOTARIZATION_PWD: ${{ secrets.PROD_MACOS_NOTARIZATION_PWD }}
MACOS_NOTARIZATION_TEAM_ID: ${{ secrets.PROD_MACOS_NOTARIZATION_TEAM_ID }}
run: | run: |
if [[ -n "$MACOS_NOTARIZATION_APPLE_ID" ]] if [[ -n "$MACOS_NOTARIZATION_APPLE_ID" ]]
then then
@ -517,46 +523,46 @@ jobs:
xcrun stapler staple "${{ steps.build.outputs.path }}" xcrun stapler staple "${{ steps.build.outputs.path }}"
fi fi
- name: Upload artifact - name: "Upload artifact"
if: matrix.make_package if: matrix.make_package
id: upload_artifact id: upload_artifact
uses: actions/upload-artifact@v7 uses: actions/upload-artifact@v7
with: with:
path: ${{steps.build.outputs.path}}
archive: false archive: false
if-no-files-found: error if-no-files-found: error
path: ${{ steps.build.outputs.path }}
- name: Upload PDBs (Program Databases) - name: "[Windows] Upload PDBs (Program Databases)"
if: matrix.os == 'Windows' && github.ref_type != 'tag' if: matrix.os == 'Windows' && github.ref_type != 'tag'
uses: actions/upload-artifact@v7 uses: actions/upload-artifact@v7
with: with:
if-no-files-found: error
name: ${{ steps.build.outputs.name }}-PDBs name: ${{ steps.build.outputs.name }}-PDBs
path: | path: |
build/cockatrice/Release/*.pdb build/cockatrice/Release/*.pdb
build/oracle/Release/*.pdb build/oracle/Release/*.pdb
build/servatrice/Release/*.pdb build/servatrice/Release/*.pdb
if-no-files-found: error
- name: Upload to release - name: "Upload to release"
if: needs.configure.outputs.tag != null && matrix.make_package == '1' if: needs.configure.outputs.tag != null && matrix.make_package == '1'
id: upload_release id: upload_release
shell: bash shell: bash
env: env:
GH_TOKEN: ${{github.token}}
tag_name: ${{needs.configure.outputs.tag}}
asset_name: ${{ steps.build.outputs.fullname }} asset_name: ${{ steps.build.outputs.fullname }}
asset_path: ${{ steps.build.outputs.path }} asset_path: ${{ steps.build.outputs.path }}
GH_TOKEN: ${{ github.token }}
tag_name: ${{ needs.configure.outputs.tag }}
run: gh release upload "$tag_name" "$asset_path#$asset_name" run: gh release upload "$tag_name" "$asset_path#$asset_name"
- name: Attest binary provenance - name: "Attest binary provenance"
if: steps.upload_release.outcome == 'success' if: steps.upload_release.outcome == 'success'
id: attestation id: attestation
uses: actions/attest@v4 uses: actions/attest@v4
with: with:
subject-path: ${{steps.build.outputs.path}}
show-summary: false show-summary: false
subject-path: ${{ steps.build.outputs.path }}
- name: Verify binary attestation - name: "Verify binary attestation"
if: steps.attestation.outcome == 'success' if: steps.attestation.outcome == 'success'
shell: bash shell: bash
env: env:

View file

@ -1,7 +1,7 @@
name: Code Style (C++) name: Code Style (C++)
on: on:
# push trigger not needed for linting, we do not allow direct pushes to master # Push trigger not needed for linting, we do not allow direct pushes to master
pull_request: pull_request:
paths: paths:
- '*/**' # matches all files not in root - '*/**' # matches all files not in root
@ -21,17 +21,20 @@ jobs:
runs-on: ubuntu-slim runs-on: ubuntu-slim
steps: steps:
- name: Checkout - name: "Checkout"
uses: actions/checkout@v6 uses: actions/checkout@v6
with: with:
fetch-depth: 20 # should be enough to find merge base fetch-depth: 20 # should be enough to find merge base
- name: Install dependencies - name: "Install dependencies"
shell: bash shell: bash
run: | run: |
sudo apt-get update sudo apt-get update
sudo apt-get install -y --no-install-recommends clang-format cmake-format shellcheck sudo apt-get install -y --no-install-recommends \
clang-format \
cmake-format \
shellcheck
- name: Check code formatting - name: "Check code formatting"
shell: bash shell: bash
run: ./.ci/lint_cpp.sh run: ./.ci/lint_cpp.sh

View file

@ -1,9 +1,10 @@
name: Build Docker Image name: Build Docker Image
permissions:
contents: read
packages: write
on: on:
release:
types:
- released # publishing of stable releases
push: push:
branches: branches:
- master - master
@ -13,6 +14,9 @@ on:
paths: paths:
- '.github/workflows/docker-release.yml' - '.github/workflows/docker-release.yml'
- 'Dockerfile' - 'Dockerfile'
release:
types:
- released # publishing of stable releases
# Cancel earlier, unfinished runs of this workflow on the same branch (unless on release) # Cancel earlier, unfinished runs of this workflow on the same branch (unless on release)
concurrency: concurrency:
@ -25,53 +29,49 @@ jobs:
if: ${{ github.repository_owner == 'Cockatrice' }} if: ${{ github.repository_owner == 'Cockatrice' }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps: steps:
- name: Checkout - name: "Checkout"
uses: actions/checkout@v6 uses: actions/checkout@v6
- name: Docker metadata - name: "Docker metadata"
id: metadata id: metadata
uses: docker/metadata-action@v6 uses: docker/metadata-action@v6
env: env:
DOCKER_METADATA_ANNOTATIONS_LEVELS: index # needed for GHCR DOCKER_METADATA_ANNOTATIONS_LEVELS: index # needed for GHCR
with: with:
annotations: |
org.opencontainers.image.title=Servatrice
org.opencontainers.image.url=https://cockatrice.github.io/
org.opencontainers.image.description=Server for Cockatrice, a cross-platform virtual tabletop for multiplayer card games
images: | images: |
ghcr.io/cockatrice/servatrice ghcr.io/cockatrice/servatrice
labels: | labels: |
org.opencontainers.image.title=Servatrice org.opencontainers.image.title=Servatrice
org.opencontainers.image.url=https://cockatrice.github.io/ org.opencontainers.image.url=https://cockatrice.github.io/
org.opencontainers.image.description=Server for Cockatrice, a cross-platform virtual tabletop for multiplayer card games org.opencontainers.image.description=Server for Cockatrice, a cross-platform virtual tabletop for multiplayer card games
annotations: |
org.opencontainers.image.title=Servatrice
org.opencontainers.image.url=https://cockatrice.github.io/
org.opencontainers.image.description=Server for Cockatrice, a cross-platform virtual tabletop for multiplayer card games
- name: Set up QEMU - name: "Set up QEMU"
uses: docker/setup-qemu-action@v4 uses: docker/setup-qemu-action@v4
- name: Set up Docker buildx - name: "Set up Docker buildx"
uses: docker/setup-buildx-action@v4 uses: docker/setup-buildx-action@v4
- name: Login to GitHub Container Registry - name: "Login to GitHub Container Registry"
if: contains(github.event.release.tag_name, 'Release') && github.event.release.target_commitish == 'master' if: contains(github.event.release.tag_name, 'Release') && github.event.release.target_commitish == 'master'
uses: docker/login-action@v4 uses: docker/login-action@v4
with: with:
password: ${{ github.token }}
registry: ghcr.io registry: ghcr.io
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ github.token }}
- name: Build and push Docker image - name: "Build and push Docker image"
uses: docker/build-push-action@v7 uses: docker/build-push-action@v7
with: with:
context: .
platforms: linux/amd64,linux/arm64
push: ${{ github.ref_type == 'tag' }}
tags: ${{ steps.metadata.outputs.tags }}
labels: ${{ steps.metadata.outputs.labels }}
annotations: ${{ steps.metadata.outputs.annotations }} annotations: ${{ steps.metadata.outputs.annotations }}
cache-from: type=gha,scope=servatrice cache-from: type=gha,scope=servatrice
cache-to: type=gha,mode=max,scope=servatrice cache-to: type=gha,mode=max,scope=servatrice
context: .
labels: ${{ steps.metadata.outputs.labels }}
platforms: linux/amd64,linux/arm64
push: ${{ github.ref_type == 'tag' }}
tags: ${{ steps.metadata.outputs.tags }}

View file

@ -1,18 +1,18 @@
name: Generate Docs name: Generate Docs
on: on:
release:
types:
- published # publishing of stable releases and pre-releases
pull_request: pull_request:
paths: paths:
- 'doc/doxygen/**' - 'doc/doxygen/**'
- '.github/workflows/documentation-build.yml' - '.github/workflows/documentation-build.yml'
- 'Doxyfile' - 'Doxyfile'
release:
types:
- published # publishing of stable releases and pre-releases
workflow_dispatch: workflow_dispatch:
env: env:
COCKATRICE_REF: ${{ github.ref_name }} # Tag name if the commit is tagged, otherwise branch name COCKATRICE_REF: ${{ github.ref_name }} # tag name if the commit is tagged, otherwise branch name
jobs: jobs:
docs: docs:
@ -20,22 +20,22 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: "Checkout code"
uses: actions/checkout@v6 uses: actions/checkout@v6
with: with:
submodules: recursive submodules: recursive
- name: Install Graphviz - name: "Install Graphviz"
run: | run: |
sudo apt-get install -y graphviz sudo apt-get install -y graphviz
dot -V dot -V
- name: Install Doxygen - name: "Install Doxygen"
uses: ssciwr/doxygen-install@v2 uses: ssciwr/doxygen-install@v2
with: with:
version: "1.16.1" version: "1.16.1"
- name: Update Doxygen Configuration - name: "Update Doxygen Configuration"
run: | run: |
git diff Doxyfile git diff Doxyfile
doxygen -u Doxyfile doxygen -u Doxyfile
@ -48,16 +48,16 @@ jobs:
exit 1 exit 1
fi fi
- name: Generate Documentation - name: "Generate Documentation"
if: always() if: always()
run: doxygen Doxyfile run: doxygen Doxyfile
- name: Deploy to cockatrice.github.io - name: "Deploy to cockatrice.github.io"
if: github.event_name == 'release' || github.event_name == 'workflow_dispatch' if: github.event_name == 'release' || github.event_name == 'workflow_dispatch'
uses: peaceiris/actions-gh-pages@v4 uses: peaceiris/actions-gh-pages@v4
with: with:
deploy_key: ${{ secrets.DOCS_DEPLOY_KEY }} deploy_key: ${{ secrets.DOCS_DEPLOY_KEY }}
destination_dir: docs # docs will be available at https://cockatrice.github.io/docs/
external_repository: Cockatrice/cockatrice.github.io external_repository: Cockatrice/cockatrice.github.io
publish_branch: master publish_branch: master
publish_dir: ./docs/html publish_dir: ./docs/html
destination_dir: docs # Docs will live under https://cockatrice.github.io/docs/

View file

@ -1,14 +1,14 @@
name: Update Translations name: Update Translations
on: on:
workflow_dispatch:
schedule:
# runs in the middle of each month starting a quarter (UTC) = two weeks after new strings are built
- cron: '0 0 15 1,4,7,10 *'
pull_request: pull_request:
paths: paths:
- '.tx/**' - '.tx/**'
- '.github/workflows/translations-pull.yml' - '.github/workflows/translations-pull.yml'
schedule:
# Runs in the middle of each month starting a quarter (UTC) = two weeks after new strings are built
- cron: '0 0 15 1,4,7,10 *'
workflow_dispatch:
jobs: jobs:
translations: translations:
@ -19,18 +19,18 @@ jobs:
runs-on: ubuntu-slim runs-on: ubuntu-slim
steps: steps:
- name: Checkout repo - name: "Checkout repo"
uses: actions/checkout@v6 uses: actions/checkout@v6
- name: Pull translated strings from Transifex - name: "Pull translated strings from Transifex"
uses: transifex/cli-action@v2 uses: transifex/cli-action@v2
with: with:
# used config file: https://github.com/Cockatrice/Cockatrice/blob/master/.tx/config # Used config file: https://github.com/Cockatrice/Cockatrice/blob/master/.tx/config
# https://github.com/transifex/cli#pulling-files-from-transifex # Docs: https://github.com/transifex/cli#pulling-files-from-transifex
token: ${{ secrets.TX_TOKEN }}
args: pull --force --all args: pull --force --all
token: ${{ secrets.TX_TOKEN }}
- name: Create pull request - name: "Create pull request"
if: github.event_name != 'pull_request' if: github.event_name != 'pull_request'
id: create_pr id: create_pr
uses: peter-evans/create-pull-request@v8 uses: peter-evans/create-pull-request@v8
@ -38,12 +38,7 @@ jobs:
add-paths: | add-paths: |
cockatrice/translations/*.ts cockatrice/translations/*.ts
oracle/translations/*.ts oracle/translations/*.ts
commit-message: Update translation files author: github-actions <github-actions@github.com> # owner of the commit
# author is the owner of the commit
author: github-actions <github-actions@github.com>
branch: ci-update_translations
delete-branch: true
title: 'Update translations'
body: | body: |
Pulled all translated strings from [Transifex][1]. Pulled all translated strings from [Transifex][1].
@ -53,12 +48,16 @@ jobs:
[1]: https://explore.transifex.com/cockatrice/cockatrice/ [1]: https://explore.transifex.com/cockatrice/cockatrice/
[2]: https://github.com/Cockatrice/Cockatrice/actions/workflows/translations-pull.yml?query=branch%3Amaster [2]: https://github.com/Cockatrice/Cockatrice/actions/workflows/translations-pull.yml?query=branch%3Amaster
branch: ci-update_translations
commit-message: Update translation files
delete-branch: true
draft: false
labels: | labels: |
CI CI
Translation Translation
draft: false title: 'Update translations'
- name: PR Status - name: "PR Status"
if: github.event_name != 'pull_request' if: github.event_name != 'pull_request'
shell: bash shell: bash
env: env:

View file

@ -1,14 +1,14 @@
name: Update Translation Source name: Update Translation Source
on: on:
workflow_dispatch:
schedule:
# runs at the start of each quarter (UTC)
- cron: '0 0 1 1,4,7,10 *'
pull_request: pull_request:
paths: paths:
- '.ci/update_translation_source_strings.sh' - '.ci/update_translation_source_strings.sh'
- '.github/workflows/translations-push.yml' - '.github/workflows/translations-push.yml'
schedule:
# Runs at the start of each quarter (UTC)
- cron: '0 0 1 1,4,7,10 *'
workflow_dispatch:
jobs: jobs:
translations: translations:
@ -19,16 +19,16 @@ jobs:
runs-on: ubuntu-slim runs-on: ubuntu-slim
steps: steps:
- name: Checkout repo - name: "Checkout repo"
uses: actions/checkout@v6 uses: actions/checkout@v6
- name: Install lupdate - name: "Install lupdate"
shell: bash shell: bash
run: | run: |
sudo apt-get update sudo apt-get update
sudo apt-get install -y --no-install-recommends qttools5-dev-tools sudo apt-get install -y --no-install-recommends qttools5-dev-tools
- name: Update Cockatrice translation source - name: "Update Cockatrice translation source"
id: cockatrice id: cockatrice
shell: bash shell: bash
run: | run: |
@ -36,15 +36,15 @@ jobs:
export DIRS="cockatrice/src $(find . -maxdepth 1 -type d -name 'libcockatrice_*')" export DIRS="cockatrice/src $(find . -maxdepth 1 -type d -name 'libcockatrice_*')"
FILE="$FILE" DIRS="$DIRS" .ci/update_translation_source_strings.sh FILE="$FILE" DIRS="$DIRS" .ci/update_translation_source_strings.sh
- name: Update Oracle translation source - name: "Update Oracle translation source"
id: oracle id: oracle
shell: bash shell: bash
env: env:
FILE: 'oracle/oracle_en@source.ts'
DIRS: 'oracle/src' DIRS: 'oracle/src'
FILE: 'oracle/oracle_en@source.ts'
run: .ci/update_translation_source_strings.sh run: .ci/update_translation_source_strings.sh
- name: Render template - name: "Render template"
id: template id: template
uses: chuhlomin/render-template/binary@v1 uses: chuhlomin/render-template/binary@v1
with: with:
@ -54,7 +54,7 @@ jobs:
oracle_output: ${{ steps.oracle.outputs.output }} oracle_output: ${{ steps.oracle.outputs.output }}
commit: ${{ github.sha }} commit: ${{ github.sha }}
- name: Create pull request - name: "Create pull request"
if: github.event_name != 'pull_request' if: github.event_name != 'pull_request'
id: create_pr id: create_pr
uses: peter-evans/create-pull-request@v8 uses: peter-evans/create-pull-request@v8
@ -62,19 +62,18 @@ jobs:
add-paths: | add-paths: |
cockatrice/cockatrice_en@source.ts cockatrice/cockatrice_en@source.ts
oracle/oracle_en@source.ts oracle/oracle_en@source.ts
commit-message: Update translation source strings author: github-actions <github-actions@github.com> # owner of the commit
# author is the owner of the commit
author: github-actions <github-actions@github.com>
branch: ci-update_translation_source
delete-branch: true
title: 'Update source strings'
body: ${{ steps.template.outputs.result }} body: ${{ steps.template.outputs.result }}
branch: ci-update_translation_source
commit-message: Update translation source strings
delete-branch: true
draft: false
labels: | labels: |
CI CI
Translation Translation
draft: false title: 'Update source strings'
- name: PR Status - name: "PR Status"
if: github.event_name != 'pull_request' if: github.event_name != 'pull_request'
shell: bash shell: bash
env: env:

View file

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

View file

@ -56,56 +56,58 @@ set(cockatrice_SOURCES
src/filters/filter_tree_model.cpp src/filters/filter_tree_model.cpp
src/filters/syntax_help.cpp src/filters/syntax_help.cpp
src/game/abstract_game.cpp src/game/abstract_game.cpp
src/game/board/abstract_card_drag_item.cpp src/game/arrow_registry.cpp
src/game/board/abstract_card_item.cpp src/game_graphics/board/abstract_card_drag_item.cpp
src/game/board/abstract_counter.cpp src/game_graphics/board/abstract_card_item.cpp
src/game_graphics/board/abstract_counter.cpp
src/game/board/arrow_data.cpp src/game/board/arrow_data.cpp
src/game/board/arrow_item.cpp src/game_graphics/board/arrow_item.cpp
src/game/board/arrow_target.cpp src/game_graphics/board/arrow_target.cpp
src/game/board/card_drag_item.cpp src/game_graphics/board/card_drag_item.cpp
src/game/board/card_item.cpp src/game_graphics/board/card_item.cpp
src/game/board/card_list.cpp src/game/board/card_list.cpp
src/game/board/card_state.cpp src/game/board/card_state.cpp
src/game/board/counter_general.cpp src/game_graphics/board/counter_general.cpp
src/game/board/counter_state.cpp src/game/board/counter_state.cpp
src/game/board/translate_counter_name.cpp src/game_graphics/board/translate_counter_name.cpp
src/game/deckview/deck_view.cpp src/game_graphics/deckview/deck_view.cpp
src/game/deckview/deck_view_container.cpp src/game_graphics/deckview/deck_view_container.cpp
src/game/deckview/tabbed_deck_view_container.cpp src/game_graphics/deckview/tabbed_deck_view_container.cpp
src/game/dialogs/dlg_create_token.cpp src/game_graphics/dialogs/dlg_create_token.cpp
src/game/dialogs/dlg_move_top_cards_until.cpp src/game_graphics/dialogs/dlg_move_top_cards_until.cpp
src/game/dialogs/dlg_roll_dice.cpp src/game_graphics/dialogs/dlg_roll_dice.cpp
src/game/game.cpp src/game/game.cpp
src/game/game_event_handler.cpp src/game/game_event_handler.cpp
src/game/game_meta_info.cpp src/game/game_meta_info.cpp
src/game/game_scene.cpp src/game_graphics/game_scene.cpp
src/game/game_state.cpp src/game/game_state.cpp
src/game/game_view.cpp src/game_graphics/game_view.cpp
src/game/hand_counter.cpp src/game_graphics/hand_counter.cpp
src/game/log/message_log_widget.cpp src/game_graphics/log/message_log_widget.cpp
src/game/phase.cpp src/game/phase.cpp
src/game/phases_toolbar.cpp src/game_graphics/phases_toolbar.cpp
src/game/player/menu/card_menu.cpp src/game_graphics/player/menu/card_menu.cpp
src/game/player/menu/custom_zone_menu.cpp src/game_graphics/player/menu/custom_zone_menu.cpp
src/game/player/menu/grave_menu.cpp src/game_graphics/player/menu/grave_menu.cpp
src/game/player/menu/hand_menu.cpp src/game_graphics/player/menu/hand_menu.cpp
src/game/player/menu/library_menu.cpp src/game_graphics/player/menu/library_menu.cpp
src/game/player/menu/move_menu.cpp src/game_graphics/player/menu/move_menu.cpp
src/game/player/menu/player_menu.cpp src/game_graphics/player/menu/player_menu.cpp
src/game/player/menu/pt_menu.cpp src/game_graphics/player/menu/pt_menu.cpp
src/game/player/menu/rfg_menu.cpp src/game_graphics/player/menu/rfg_menu.cpp
src/game/player/menu/say_menu.cpp src/game_graphics/player/menu/say_menu.cpp
src/game/player/menu/sideboard_menu.cpp src/game_graphics/player/menu/sideboard_menu.cpp
src/game/player/menu/utility_menu.cpp src/game_graphics/player/menu/utility_menu.cpp
src/game/player/player_actions.cpp src/game/player/player_actions.cpp
src/game/player/player_area.cpp src/game_graphics/player/player_area.cpp
src/game_graphics/player/player_dialogs.cpp
src/game/player/player_event_handler.cpp src/game/player/player_event_handler.cpp
src/game/player/player_graphics_item.cpp src/game_graphics/player/player_graphics_item.cpp
src/game/player/player_info.cpp src/game/player/player_info.cpp
src/game/player/player_list_widget.cpp src/game_graphics/player/player_list_widget.cpp
src/game/player/player_logic.cpp src/game/player/player_logic.cpp
src/game/player/player_manager.cpp src/game/player/player_manager.cpp
src/game/player/player_target.cpp src/game_graphics/player/player_target.cpp
src/game/replay.cpp src/game/replay.cpp
src/game/zones/card_zone_logic.cpp src/game/zones/card_zone_logic.cpp
src/game/zones/hand_zone_logic.cpp src/game/zones/hand_zone_logic.cpp
@ -181,6 +183,7 @@ set(cockatrice_SOURCES
src/interface/widgets/deck_analytics/analyzer_modules/mana_distribution/mana_distribution_single_display_widget.cpp src/interface/widgets/deck_analytics/analyzer_modules/mana_distribution/mana_distribution_single_display_widget.cpp
src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_total_widget.cpp src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_total_widget.cpp
src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_category_widget.cpp src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_category_widget.cpp
src/interface/widgets/deck_editor/card_database_view.cpp
src/interface/widgets/deck_editor/deck_list_history_manager_widget.cpp src/interface/widgets/deck_editor/deck_list_history_manager_widget.cpp
src/interface/widgets/deck_editor/deck_editor_card_database_dock_widget.cpp src/interface/widgets/deck_editor/deck_editor_card_database_dock_widget.cpp
src/interface/widgets/deck_editor/deck_editor_card_info_dock_widget.cpp src/interface/widgets/deck_editor/deck_editor_card_info_dock_widget.cpp

View file

@ -6,12 +6,12 @@
#include <QNetworkRequest> #include <QNetworkRequest>
#include <QRegularExpression> #include <QRegularExpression>
#include <QUrlQuery> #include <QUrlQuery>
#include <libcockatrice/card/database/card_database_manager.h>
#include <libcockatrice/deck_list/deck_list.h> #include <libcockatrice/deck_list/deck_list.h>
#include <libcockatrice/deck_list/tree/deck_list_card_node.h> #include <libcockatrice/deck_list/tree/deck_list_card_node.h>
#include <version_string.h> #include <version_string.h>
DeckStatsInterface::DeckStatsInterface(CardDatabase &_cardDatabase, QObject *parent) DeckStatsInterface::DeckStatsInterface(QObject *parent) : QObject(parent)
: QObject(parent), cardDatabase(_cardDatabase)
{ {
manager = new QNetworkAccessManager(this); manager = new QNetworkAccessManager(this);
connect(manager, &QNetworkAccessManager::finished, this, &DeckStatsInterface::queryFinished); connect(manager, &QNetworkAccessManager::finished, this, &DeckStatsInterface::queryFinished);
@ -70,8 +70,8 @@ void DeckStatsInterface::analyzeDeck(const DeckList &deck)
void DeckStatsInterface::copyDeckWithoutTokens(const DeckList &source, DeckList &destination) void DeckStatsInterface::copyDeckWithoutTokens(const DeckList &source, DeckList &destination)
{ {
auto copyIfNotAToken = [this, &destination](const auto node, const auto card) { auto copyIfNotAToken = [&destination](const auto node, const auto card) {
CardInfoPtr dbCard = cardDatabase.query()->getCardInfo(card->getName()); CardInfoPtr dbCard = CardDatabaseManager::query()->getCardInfo(card->getName());
if (dbCard && !dbCard->getIsToken()) { if (dbCard && !dbCard->getIsToken()) {
DecklistCardNode *addedCard = destination.addCard(card->getName(), node->getName(), -1); DecklistCardNode *addedCard = destination.addCard(card->getName(), node->getName(), -1);
addedCard->setNumber(card->getNumber()); addedCard->setNumber(card->getNumber());

View file

@ -7,7 +7,6 @@
#ifndef DECKSTATS_INTERFACE_H #ifndef DECKSTATS_INTERFACE_H
#define DECKSTATS_INTERFACE_H #define DECKSTATS_INTERFACE_H
#include <libcockatrice/card/database/card_database.h>
#include <libcockatrice/deck_list/deck_list.h> #include <libcockatrice/deck_list/deck_list.h>
class QByteArray; class QByteArray;
@ -21,8 +20,6 @@ class DeckStatsInterface : public QObject
private: private:
QNetworkAccessManager *manager; QNetworkAccessManager *manager;
CardDatabase &cardDatabase;
/** /**
* Deckstats doesn't recognize token cards, and instead tries to find the * Deckstats doesn't recognize token cards, and instead tries to find the
* closest non-token card instead. So we construct a new deck which has no * closest non-token card instead. So we construct a new deck which has no
@ -35,7 +32,7 @@ private slots:
void getAnalyzeRequestData(const DeckList &deck, QByteArray &data); void getAnalyzeRequestData(const DeckList &deck, QByteArray &data);
public: public:
explicit DeckStatsInterface(CardDatabase &_cardDatabase, QObject *parent = nullptr); explicit DeckStatsInterface(QObject *parent = nullptr);
void analyzeDeck(const DeckList &deck); void analyzeDeck(const DeckList &deck);
}; };

View file

@ -6,12 +6,12 @@
#include <QNetworkRequest> #include <QNetworkRequest>
#include <QRegularExpression> #include <QRegularExpression>
#include <QUrlQuery> #include <QUrlQuery>
#include <libcockatrice/card/database/card_database_manager.h>
#include <libcockatrice/deck_list/deck_list.h> #include <libcockatrice/deck_list/deck_list.h>
#include <libcockatrice/deck_list/tree/deck_list_card_node.h> #include <libcockatrice/deck_list/tree/deck_list_card_node.h>
#include <version_string.h> #include <version_string.h>
TappedOutInterface::TappedOutInterface(CardDatabase &_cardDatabase, QObject *parent) TappedOutInterface::TappedOutInterface(QObject *parent) : QObject(parent)
: QObject(parent), cardDatabase(_cardDatabase)
{ {
manager = new QNetworkAccessManager(this); manager = new QNetworkAccessManager(this);
connect(manager, &QNetworkAccessManager::finished, this, &TappedOutInterface::queryFinished); connect(manager, &QNetworkAccessManager::finished, this, &TappedOutInterface::queryFinished);
@ -97,8 +97,8 @@ void TappedOutInterface::analyzeDeck(const DeckList &deck)
void TappedOutInterface::copyDeckSplitMainAndSide(const DeckList &source, DeckList &mainboard, DeckList &sideboard) void TappedOutInterface::copyDeckSplitMainAndSide(const DeckList &source, DeckList &mainboard, DeckList &sideboard)
{ {
auto copyMainOrSide = [this, &mainboard, &sideboard](const auto node, const auto card) { auto copyMainOrSide = [&mainboard, &sideboard](const auto node, const auto card) {
CardInfoPtr dbCard = cardDatabase.query()->getCardInfo(card->getName()); CardInfoPtr dbCard = CardDatabaseManager::query()->getCardInfo(card->getName());
if (!dbCard || dbCard->getIsToken()) { if (!dbCard || dbCard->getIsToken()) {
return; return;
} }

View file

@ -7,8 +7,8 @@
#ifndef TAPPEDOUT_INTERFACE_H #ifndef TAPPEDOUT_INTERFACE_H
#define TAPPEDOUT_INTERFACE_H #define TAPPEDOUT_INTERFACE_H
#include <libcockatrice/card/database/card_database.h> #include <QLoggingCategory>
#include <libcockatrice/deck_list/deck_list.h> #include <QObject>
inline Q_LOGGING_CATEGORY(TappedOutInterfaceLog, "tapped_out_interface"); inline Q_LOGGING_CATEGORY(TappedOutInterfaceLog, "tapped_out_interface");
@ -29,14 +29,13 @@ class TappedOutInterface : public QObject
private: private:
QNetworkAccessManager *manager; QNetworkAccessManager *manager;
CardDatabase &cardDatabase;
void copyDeckSplitMainAndSide(const DeckList &source, DeckList &mainboard, DeckList &sideboard); void copyDeckSplitMainAndSide(const DeckList &source, DeckList &mainboard, DeckList &sideboard);
private slots: private slots:
void queryFinished(QNetworkReply *reply); void queryFinished(QNetworkReply *reply);
void getAnalyzeRequestData(const DeckList &deck, QByteArray &data); void getAnalyzeRequestData(const DeckList &deck, QByteArray &data);
public: public:
explicit TappedOutInterface(CardDatabase &_cardDatabase, QObject *parent = nullptr); explicit TappedOutInterface(QObject *parent = nullptr);
void analyzeDeck(const DeckList &deck); void analyzeDeck(const DeckList &deck);
}; };

View file

@ -309,6 +309,7 @@ SettingsCache::SettingsCache()
cardViewExpandedRowsMax = settings->value("interface/cardViewExpandedRowsMax", 20).toInt(); cardViewExpandedRowsMax = settings->value("interface/cardViewExpandedRowsMax", 20).toInt();
closeEmptyCardView = settings->value("interface/closeEmptyCardView", true).toBool(); closeEmptyCardView = settings->value("interface/closeEmptyCardView", true).toBool();
focusCardViewSearchBar = settings->value("interface/focusCardViewSearchBar", true).toBool(); focusCardViewSearchBar = settings->value("interface/focusCardViewSearchBar", true).toBool();
keepGameChatFocus = settings->value("interface/keepGameChatFocus", false).toBool();
showDragSelectionCount = settings->value("interface/showlassoselectioncount", true).toBool(); showDragSelectionCount = settings->value("interface/showlassoselectioncount", true).toBool();
showTotalSelectionCount = settings->value("interface/showpersistentselectioncount", true).toBool(); showTotalSelectionCount = settings->value("interface/showpersistentselectioncount", true).toBool();
@ -388,6 +389,7 @@ SettingsCache::SettingsCache()
ignoreUnregisteredUsers = settings->value("chat/ignore_unregistered", false).toBool(); ignoreUnregisteredUsers = settings->value("chat/ignore_unregistered", false).toBool();
ignoreUnregisteredUserMessages = settings->value("chat/ignore_unregistered_messages", false).toBool(); ignoreUnregisteredUserMessages = settings->value("chat/ignore_unregistered_messages", false).toBool();
ignoreNonBuddyUserMessages = settings->value("chat/ignore_nonbuddy_messages", false).toBool();
scaleCards = settings->value("cards/scaleCards", true).toBool(); scaleCards = settings->value("cards/scaleCards", true).toBool();
verticalCardOverlapPercent = settings->value("cards/verticalCardOverlapPercent", 33).toInt(); verticalCardOverlapPercent = settings->value("cards/verticalCardOverlapPercent", 33).toInt();
@ -456,6 +458,13 @@ void SettingsCache::setFocusCardViewSearchBar(QT_STATE_CHANGED_T value)
settings->setValue("interface/focusCardViewSearchBar", focusCardViewSearchBar); settings->setValue("interface/focusCardViewSearchBar", focusCardViewSearchBar);
} }
void SettingsCache::setKeepGameChatFocus(QT_STATE_CHANGED_T value)
{
keepGameChatFocus = value;
settings->setValue("interface/keepGameChatFocus", keepGameChatFocus);
emit keepGameChatFocusChanged(keepGameChatFocus);
}
void SettingsCache::setKnownMissingFeatures(const QString &_knownMissingFeatures) void SettingsCache::setKnownMissingFeatures(const QString &_knownMissingFeatures)
{ {
knownMissingFeatures = _knownMissingFeatures; knownMissingFeatures = _knownMissingFeatures;
@ -1117,6 +1126,12 @@ void SettingsCache::setIgnoreUnregisteredUserMessages(QT_STATE_CHANGED_T _ignore
settings->setValue("chat/ignore_unregistered_messages", ignoreUnregisteredUserMessages); settings->setValue("chat/ignore_unregistered_messages", ignoreUnregisteredUserMessages);
} }
void SettingsCache::setIgnoreNonBuddyUserMessages(QT_STATE_CHANGED_T _ignoreNonBuddyUserMessages)
{
ignoreNonBuddyUserMessages = static_cast<bool>(_ignoreNonBuddyUserMessages);
settings->setValue("chat/ignore_nonbuddy_messages", ignoreNonBuddyUserMessages);
}
void SettingsCache::setPixmapCacheSize(const int _pixmapCacheSize) void SettingsCache::setPixmapCacheSize(const int _pixmapCacheSize)
{ {
pixmapCacheSize = _pixmapCacheSize; pixmapCacheSize = _pixmapCacheSize;

View file

@ -183,6 +183,7 @@ signals:
void soundThemeChanged(); void soundThemeChanged();
void ignoreUnregisteredUsersChanged(); void ignoreUnregisteredUsersChanged();
void ignoreUnregisteredUserMessagesChanged(); void ignoreUnregisteredUserMessagesChanged();
void ignoreNonBuddyUserMessagesChanged();
void pixmapCacheSizeChanged(int newSizeInMBs); void pixmapCacheSizeChanged(int newSizeInMBs);
void networkCacheSizeChanged(int newSizeInMBs); void networkCacheSizeChanged(int newSizeInMBs);
void redirectCacheTtlChanged(int newTtl); void redirectCacheTtlChanged(int newTtl);
@ -194,6 +195,7 @@ signals:
void downloadSpoilerStatusChanged(); void downloadSpoilerStatusChanged();
void useTearOffMenusChanged(bool state); void useTearOffMenusChanged(bool state);
void roundCardCornersChanged(bool roundCardCorners); void roundCardCornersChanged(bool roundCardCorners);
void keepGameChatFocusChanged(bool value);
private: private:
QSettings *settings; QSettings *settings;
@ -294,6 +296,7 @@ private:
QString soundThemeName; QString soundThemeName;
bool ignoreUnregisteredUsers; bool ignoreUnregisteredUsers;
bool ignoreUnregisteredUserMessages; bool ignoreUnregisteredUserMessages;
bool ignoreNonBuddyUserMessages;
QString picUrl; QString picUrl;
QString picUrlFallback; QString picUrlFallback;
QString clientID; QString clientID;
@ -304,6 +307,7 @@ private:
int cardViewExpandedRowsMax; int cardViewExpandedRowsMax;
bool closeEmptyCardView; bool closeEmptyCardView;
bool focusCardViewSearchBar; bool focusCardViewSearchBar;
bool keepGameChatFocus;
int pixmapCacheSize; int pixmapCacheSize;
int networkCacheSize; int networkCacheSize;
int redirectCacheTtl; int redirectCacheTtl;
@ -788,6 +792,10 @@ public:
{ {
return ignoreUnregisteredUserMessages; return ignoreUnregisteredUserMessages;
} }
[[nodiscard]] bool getIgnoreNonBuddyUserMessages() const
{
return ignoreNonBuddyUserMessages;
}
[[nodiscard]] int getPixmapCacheSize() const [[nodiscard]] int getPixmapCacheSize() const
{ {
return pixmapCacheSize; return pixmapCacheSize;
@ -929,6 +937,7 @@ public:
void setCardViewExpandedRowsMax(int value); void setCardViewExpandedRowsMax(int value);
void setCloseEmptyCardView(QT_STATE_CHANGED_T value); void setCloseEmptyCardView(QT_STATE_CHANGED_T value);
void setFocusCardViewSearchBar(QT_STATE_CHANGED_T value); void setFocusCardViewSearchBar(QT_STATE_CHANGED_T value);
void setKeepGameChatFocus(QT_STATE_CHANGED_T value);
QString getClientID() override QString getClientID() override
{ {
return clientID; return clientID;
@ -961,6 +970,10 @@ public:
{ {
return focusCardViewSearchBar; return focusCardViewSearchBar;
} }
[[nodiscard]] bool getKeepGameChatFocus() const
{
return keepGameChatFocus;
}
[[nodiscard]] ShortcutsSettings &shortcuts() const [[nodiscard]] ShortcutsSettings &shortcuts() const
{ {
return *shortcutsSettings; return *shortcutsSettings;
@ -1111,6 +1124,7 @@ public slots:
void setSoundThemeName(const QString &_soundThemeName); void setSoundThemeName(const QString &_soundThemeName);
void setIgnoreUnregisteredUsers(QT_STATE_CHANGED_T _ignoreUnregisteredUsers); void setIgnoreUnregisteredUsers(QT_STATE_CHANGED_T _ignoreUnregisteredUsers);
void setIgnoreUnregisteredUserMessages(QT_STATE_CHANGED_T _ignoreUnregisteredUserMessages); void setIgnoreUnregisteredUserMessages(QT_STATE_CHANGED_T _ignoreUnregisteredUserMessages);
void setIgnoreNonBuddyUserMessages(QT_STATE_CHANGED_T _ignoreNonBuddyUserMessages);
void setPixmapCacheSize(const int _pixmapCacheSize); void setPixmapCacheSize(const int _pixmapCacheSize);
void setCardImageCacheMethod(CardPictureLoaderCacheMethod::CacheMethod _cardImageCachingMethod); void setCardImageCacheMethod(CardPictureLoaderCacheMethod::CacheMethod _cardImageCachingMethod);
void setNetworkCacheSizeInMB(const int _networkCacheSize); void setNetworkCacheSizeInMB(const int _networkCacheSize);

View file

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

View file

@ -3,7 +3,7 @@
#include "../interface/widgets/tabs/tab_game.h" #include "../interface/widgets/tabs/tab_game.h"
#include "player/player_logic.h" #include "player/player_logic.h"
AbstractGame::AbstractGame(TabGame *_tab) : QObject(_tab), tab(_tab) AbstractGame::AbstractGame(QObject *_parent) : QObject(_parent)
{ {
gameMetaInfo = new GameMetaInfo(this); gameMetaInfo = new GameMetaInfo(this);
gameEventHandler = new GameEventHandler(this); gameEventHandler = new GameEventHandler(this);

View file

@ -16,26 +16,19 @@
#include <libcockatrice/protocol/pb/game_replay.pb.h> #include <libcockatrice/protocol/pb/game_replay.pb.h>
class CardItem; class CardItem;
class TabGame;
class AbstractGame : public QObject class AbstractGame : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit AbstractGame(TabGame *tab); explicit AbstractGame(QObject *parent);
TabGame *tab;
GameMetaInfo *gameMetaInfo; GameMetaInfo *gameMetaInfo;
GameState *gameState; GameState *gameState;
GameEventHandler *gameEventHandler; GameEventHandler *gameEventHandler;
PlayerManager *playerManager; PlayerManager *playerManager;
CardItem *activeCard; CardItem *activeCard;
TabGame *getTab() const
{
return tab;
}
GameMetaInfo *getGameMetaInfo() GameMetaInfo *getGameMetaInfo()
{ {
return gameMetaInfo; return gameMetaInfo;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -60,7 +60,8 @@ public:
void handleActivePhaseChanged(int phase); void handleActivePhaseChanged(int phase);
void handleGameLeft(); void handleGameLeft();
void handleChatMessageSent(const QString &chatMessage); void handleChatMessageSent(const QString &chatMessage);
void handleArrowDeletion(int arrowId); void handleArrowDeletion(int creatorId, int arrowId);
void handleArrowDeletionFinished(const Response &response, int creatorId, int arrowId);
void eventSpectatorSay(const Event_GameSay &event, int eventPlayerId, const GameEventContext &context); void eventSpectatorSay(const Event_GameSay &event, int eventPlayerId, const GameEventContext &context);
void eventSpectatorLeave(const Event_Leave &event, int eventPlayerId, const GameEventContext &context); void eventSpectatorLeave(const Event_Leave &event, int eventPlayerId, const GameEventContext &context);
@ -112,6 +113,7 @@ signals:
void containerProcessingStarted(GameEventContext context); void containerProcessingStarted(GameEventContext context);
void setContextJudgeName(QString judgeName); void setContextJudgeName(QString judgeName);
void containerProcessingDone(); void containerProcessingDone();
void arrowDeleted(int creatorId, int arrowId);
void logSpectatorSay(ServerInfo_User userInfo, QString message); void logSpectatorSay(ServerInfo_User userInfo, QString message);
void logSpectatorLeave(QString name, QString reason); void logSpectatorLeave(QString name, QString reason);
void logGameStart(); void logGameStart();

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

@ -83,6 +83,7 @@ signals:
void logAlwaysRevealTopCard(PlayerLogic *player, CardZoneLogic *zone, bool reveal); void logAlwaysRevealTopCard(PlayerLogic *player, CardZoneLogic *zone, bool reveal);
void logAlwaysLookAtTopCard(PlayerLogic *player, CardZoneLogic *zone, bool reveal); void logAlwaysLookAtTopCard(PlayerLogic *player, CardZoneLogic *zone, bool reveal);
void cardZoneChanged(CardItem *card, bool sameZone); void cardZoneChanged(CardItem *card, bool sameZone);
void requestCardMenuUpdate(const CardItem *card);
public: public:
PlayerEventHandler(PlayerLogic *player); PlayerEventHandler(PlayerLogic *player);

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,7 +1,7 @@
#include "view_zone_logic.h" #include "view_zone_logic.h"
#include "../../client/settings/cache_settings.h" #include "../../client/settings/cache_settings.h"
#include "../board/card_item.h" #include "../../game_graphics/board/card_item.h"
/** /**
* @param _player the player that the cards are revealed to. * @param _player the player that the cards are revealed to.

View file

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

View file

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

View file

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

View file

@ -2,11 +2,11 @@
#include "arrow_item.h" #include "arrow_item.h"
#include "../../client/settings/cache_settings.h" #include "../../client/settings/cache_settings.h"
#include "../../game_graphics/zones/card_zone.h" #include "../../game/player/player_actions.h"
#include "../player/player_actions.h" #include "../../game/player/player_logic.h"
#include "../player/player_logic.h"
#include "../player/player_target.h" #include "../player/player_target.h"
#include "../z_values.h" #include "../z_values.h"
#include "../zones/card_zone.h"
#include "card_item.h" #include "card_item.h"
#include <QDebug> #include <QDebug>
@ -21,12 +21,8 @@
#include <libcockatrice/utility/color.h> #include <libcockatrice/utility/color.h>
#include <libcockatrice/utility/zone_names.h> #include <libcockatrice/utility/zone_names.h>
ArrowItem::ArrowItem(PlayerLogic *_player, ArrowItem::ArrowItem(QSharedPointer<const ArrowData> _data, ArrowTarget *_startItem, ArrowTarget *_targetItem)
int _id, : data(std::move(_data)), startItem(_startItem), targetItem(_targetItem)
ArrowTarget *_startItem,
ArrowTarget *_targetItem,
const QColor &_color)
: player(_player), id(_id), startItem(_startItem), targetItem(_targetItem), color(_color)
{ {
setZValue(ZValues::ARROWS); setZValue(ZValues::ARROWS);
@ -52,7 +48,7 @@ ArrowItem::ArrowItem(PlayerLogic *_player,
void ArrowItem::onTargetDestroyed() void ArrowItem::onTargetDestroyed()
{ {
emit requestDeletion(id); emit requestDeletion(data->creatorId, data->id);
} }
void ArrowItem::delArrow() void ArrowItem::delArrow()
@ -130,7 +126,7 @@ void ArrowItem::updatePath(const QPointF &endPoint)
void ArrowItem::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/) void ArrowItem::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/)
{ {
QColor paintColor(color); QColor paintColor(data->color);
if (fullColor) { if (fullColor) {
paintColor.setAlpha(200); paintColor.setAlpha(200);
} else { } else {
@ -142,7 +138,7 @@ void ArrowItem::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*opti
void ArrowItem::mousePressEvent(QGraphicsSceneMouseEvent *event) void ArrowItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
{ {
if (!player->getPlayerInfo()->getLocal()) { if (!data->isLocalCreator) {
event->ignore(); event->ignore();
return; return;
} }
@ -156,14 +152,20 @@ void ArrowItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
event->accept(); event->accept();
if (event->button() == Qt::RightButton) { if (event->button() == Qt::RightButton) {
emit requestDeletion(id); emit requestDeletion(data->creatorId, data->id);
} }
} }
// ArrowDragItem // ArrowDragItem
ArrowDragItem::ArrowDragItem(PlayerLogic *_owner, ArrowTarget *_startItem, const QColor &_color, int _deleteInPhase) ArrowDragItem::ArrowDragItem(PlayerLogic *_owner, ArrowTarget *_startItem, const QColor &_color, int _deleteInPhase)
: ArrowItem(_owner, -1, _startItem, nullptr, _color), deleteInPhase(_deleteInPhase) : ArrowItem(QSharedPointer<ArrowData>::create(ArrowData{.creatorId = _owner->getPlayerInfo()->getId(),
.isLocalCreator = true,
.id = -1,
.color = _color}),
_startItem,
nullptr),
player(_owner), deleteInPhase(_deleteInPhase)
{ {
} }
@ -238,7 +240,7 @@ void ArrowDragItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
CardZoneLogic *startZone = startCard->getZone(); CardZoneLogic *startZone = startCard->getZone();
Command_CreateArrow cmd; Command_CreateArrow cmd;
cmd.mutable_arrow_color()->CopyFrom(convertQColorToColor(color)); cmd.mutable_arrow_color()->CopyFrom(convertQColorToColor(data->color));
cmd.set_start_player_id(startZone->getPlayer()->getPlayerInfo()->getId()); cmd.set_start_player_id(startZone->getPlayer()->getPlayerInfo()->getId());
cmd.set_start_zone(startZone->getName().toStdString()); cmd.set_start_zone(startZone->getName().toStdString());
cmd.set_start_card_id(startCard->getId()); cmd.set_start_card_id(startCard->getId());
@ -284,7 +286,14 @@ void ArrowDragItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
// ArrowAttachItem // ArrowAttachItem
ArrowAttachItem::ArrowAttachItem(ArrowTarget *_startItem) ArrowAttachItem::ArrowAttachItem(ArrowTarget *_startItem)
: ArrowItem(_startItem->getOwner(), -1, _startItem, nullptr, Qt::green) : ArrowItem(
QSharedPointer<ArrowData>::create(ArrowData{.creatorId = _startItem->getOwner()->getPlayerInfo()->getId(),
.isLocalCreator = true,
.id = -1,
.color = Qt::green}),
_startItem,
nullptr),
player(_startItem->getOwner())
{ {
} }

View file

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

View file

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

View file

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

View file

@ -1,9 +1,9 @@
#include "card_drag_item.h" #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 "../game_scene.h"
#include "../zones/card_zone.h"
#include "../zones/table_zone.h"
#include "../zones/view_zone.h"
#include "card_item.h" #include "card_item.h"
#include <QCursor> #include <QCursor>

View file

@ -1,14 +1,14 @@
#include "card_item.h" #include "card_item.h"
#include "../../client/settings/cache_settings.h" #include "../../client/settings/cache_settings.h"
#include "../../game_graphics/zones/table_zone.h" #include "../../game/phase.h"
#include "../../game_graphics/zones/view_zone.h" #include "../../game/player/player_actions.h"
#include "../../game/player/player_logic.h"
#include "../../game/zones/view_zone_logic.h"
#include "../../interface/widgets/tabs/tab_game.h" #include "../../interface/widgets/tabs/tab_game.h"
#include "../game_scene.h" #include "../game_scene.h"
#include "../phase.h" #include "../zones/table_zone.h"
#include "../player/player_actions.h" #include "../zones/view_zone.h"
#include "../player/player_logic.h"
#include "../zones/view_zone_logic.h"
#include "arrow_item.h" #include "arrow_item.h"
#include "card_drag_item.h" #include "card_drag_item.h"
@ -40,7 +40,7 @@ void CardItem::prepareDelete()
{ {
if (owner != nullptr) { if (owner != nullptr) {
if (owner->getGame()->getActiveCard() == this) { if (owner->getGame()->getActiveCard() == this) {
owner->getPlayerMenu()->updateCardMenu(nullptr); emit owner->requestCardMenuUpdate(nullptr);
owner->getGame()->setActiveCard(nullptr); owner->getGame()->setActiveCard(nullptr);
} }
owner = nullptr; owner = nullptr;
@ -399,8 +399,11 @@ void CardItem::playCard(bool faceDown)
emit tz->toggleTapped(); emit tz->toggleTapped();
} else { } else {
if (SettingsCache::instance().getClickPlaysAllSelected()) { if (SettingsCache::instance().getClickPlaysAllSelected()) {
faceDown ? state->getZone()->getPlayer()->getPlayerActions()->actPlayFacedown() if (faceDown) {
: state->getZone()->getPlayer()->getPlayerActions()->actPlay(); emit playSelectedFaceDown(this);
} else {
emit playSelected(this);
}
} else { } else {
state->getZone()->getPlayer()->getPlayerActions()->playCard(this, faceDown); state->getZone()->getPlayer()->getPlayerActions()->playCard(this, faceDown);
} }
@ -460,7 +463,7 @@ void CardItem::handleClickedToPlay(bool shiftHeld)
{ {
if (isUnwritableRevealZone(state->getZone())) { if (isUnwritableRevealZone(state->getZone())) {
if (SettingsCache::instance().getClickPlaysAllSelected()) { if (SettingsCache::instance().getClickPlaysAllSelected()) {
state->getZone()->getPlayer()->getPlayerActions()->actHide(); emit hideSelected(this);
} else { } else {
state->getZone()->removeCard(this); state->getZone()->removeCard(this);
} }
@ -471,21 +474,15 @@ void CardItem::handleClickedToPlay(bool shiftHeld)
void CardItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) void CardItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{ {
if (event->button() == Qt::RightButton) { if (event->button() == Qt::RightButton && owner != nullptr) {
emit rightClicked(this, event->screenPos());
if (owner != nullptr) {
owner->getGame()->setActiveCard(this);
if (QMenu *cardMenu = owner->getPlayerMenu()->updateCardMenu(this)) {
cardMenu->popup(event->screenPos());
return; return;
} }
} if ((event->modifiers() != Qt::AltModifier) && (event->button() == Qt::LeftButton) &&
} else if ((event->modifiers() != Qt::AltModifier) && (event->button() == Qt::LeftButton) &&
(!SettingsCache::instance().getDoubleClickToPlay())) { (!SettingsCache::instance().getDoubleClickToPlay())) {
handleClickedToPlay(event->modifiers().testFlag(Qt::ShiftModifier)); handleClickedToPlay(event->modifiers().testFlag(Qt::ShiftModifier));
} }
if (owner != nullptr) {
if (owner != nullptr) { // cards without owner will be deleted
setCursor(Qt::OpenHandCursor); setCursor(Qt::OpenHandCursor);
} }
AbstractCardItem::mouseReleaseEvent(event); AbstractCardItem::mouseReleaseEvent(event);
@ -531,14 +528,14 @@ bool CardItem::animationEvent()
QVariant CardItem::itemChange(GraphicsItemChange change, const QVariant &value) QVariant CardItem::itemChange(GraphicsItemChange change, const QVariant &value)
{ {
if ((change == ItemSelectedHasChanged) && owner != nullptr) { if ((change == ItemSelectedHasChanged) && owner != nullptr) {
if (value == true) { bool selected = value.toBool();
owner->getGame()->setActiveCard(this);
owner->getPlayerMenu()->updateCardMenu(this);
} else if (owner->getGameScene()->selectedItems().isEmpty()) {
owner->getGame()->setActiveCard(nullptr); if (selected) {
owner->getPlayerMenu()->updateCardMenu(nullptr); owner->getGame()->setActiveCard(this);
} }
emit selectionChanged(this, selected);
} }
return AbstractCardItem::itemChange(change, value); return AbstractCardItem::itemChange(change, value);
} }

View file

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

View file

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

View file

@ -360,6 +360,16 @@ void DeckViewScene::rebuildTree()
return; return;
} }
QStringList requiredZones = {DECK_ZONE_MAIN, DECK_ZONE_SIDE};
for (const QString &zoneName : requiredZones) {
if (!cardContainers.contains(zoneName)) {
auto *container = new DeckViewCardContainer(zoneName);
cardContainers.insert(zoneName, container);
addItem(container);
}
}
for (auto *currentZone : deck->getZoneNodes()) { for (auto *currentZone : deck->getZoneNodes()) {
DeckViewCardContainer *container = cardContainers.value(currentZone->getName(), 0); DeckViewCardContainer *container = cardContainers.value(currentZone->getName(), 0);
if (!container) { if (!container) {

View file

@ -209,6 +209,7 @@ void DeckViewContainer::refreshShortcuts()
loadLocalButton->setShortcut(shortcuts.getSingleShortcut("DeckViewContainer/loadLocalButton")); loadLocalButton->setShortcut(shortcuts.getSingleShortcut("DeckViewContainer/loadLocalButton"));
loadRemoteButton->setShortcut(shortcuts.getSingleShortcut("DeckViewContainer/loadRemoteButton")); loadRemoteButton->setShortcut(shortcuts.getSingleShortcut("DeckViewContainer/loadRemoteButton"));
loadFromClipboardButton->setShortcut(shortcuts.getSingleShortcut("DeckViewContainer/loadFromClipboardButton")); loadFromClipboardButton->setShortcut(shortcuts.getSingleShortcut("DeckViewContainer/loadFromClipboardButton"));
loadFromWebsiteButton->setShortcut(shortcuts.getSingleShortcut("DeckViewContainer/loadFromWebsiteButton"));
unloadDeckButton->setShortcut(shortcuts.getSingleShortcut("DeckViewContainer/unloadDeckButton")); unloadDeckButton->setShortcut(shortcuts.getSingleShortcut("DeckViewContainer/unloadDeckButton"));
readyStartButton->setShortcut(shortcuts.getSingleShortcut("DeckViewContainer/readyStartButton")); readyStartButton->setShortcut(shortcuts.getSingleShortcut("DeckViewContainer/readyStartButton"));
sideboardLockButton->setShortcut(shortcuts.getSingleShortcut("DeckViewContainer/sideboardLockButton")); sideboardLockButton->setShortcut(shortcuts.getSingleShortcut("DeckViewContainer/sideboardLockButton"));

View file

@ -1,13 +1,17 @@
#include "game_scene.h" #include "game_scene.h"
#include "../client/settings/cache_settings.h" #include "../client/settings/cache_settings.h"
#include "../game_graphics/zones/select_zone.h" #include "../game/abstract_game.h"
#include "../game_graphics/zones/view_zone.h" #include "../game/player/player_actions.h"
#include "../game_graphics/zones/view_zone_widget.h" #include "../game/player/player_logic.h"
#include "../game_graphics/player/player_graphics_item.h"
#include "board/card_item.h" #include "board/card_item.h"
#include "phases_toolbar.h" #include "phases_toolbar.h"
#include "player/menu/player_menu.h"
#include "player/player_graphics_item.h" #include "player/player_graphics_item.h"
#include "player/player_logic.h" #include "zones/select_zone.h"
#include "zones/view_zone.h"
#include "zones/view_zone_widget.h"
#include <QBasicTimer> #include <QBasicTimer>
#include <QDebug> #include <QDebug>
@ -72,6 +76,80 @@ QList<CardItem *> GameScene::selectedCards() const
return selectedCards; return selectedCards;
} }
void GameScene::onCardSelectionChanged(AbstractCardItem *abstractCard, bool selected)
{
CardItem *card = qobject_cast<CardItem *>(abstractCard);
if (!card || !card->getOwner()) {
return;
}
auto *owner = card->getOwner();
if (selected) {
owner->requestCardMenuUpdate(card);
return;
}
if (selectedItems().isEmpty()) {
owner->getGame()->setActiveCard(nullptr);
owner->requestCardMenuUpdate(nullptr);
}
}
void GameScene::onCardRightClicked(AbstractCardItem *abstractCard, QPoint screenPos)
{
auto *card = qobject_cast<CardItem *>(abstractCard);
if (!card) {
return;
}
if (!card->getOwner()) {
return;
}
auto *view = playerViews.value(card->getOwner()->getPlayerInfo()->getId());
if (!view) {
return;
}
card->getOwner()->getGame()->setActiveCard(card);
if (auto *menu = view->getPlayerMenu()->updateCardMenu(card)) {
menu->popup(screenPos);
}
}
void GameScene::playSelected(AbstractCardItem *card)
{
if (!card) {
return;
}
if (!card->getOwner()) {
return;
}
card->getOwner()->getPlayerActions()->actPlay(selectedCards());
}
void GameScene::playSelectedFaceDown(AbstractCardItem *card)
{
if (!card) {
return;
}
if (!card->getOwner()) {
return;
}
card->getOwner()->getPlayerActions()->actPlayFacedown(selectedCards());
}
void GameScene::hideSelected(AbstractCardItem *card)
{
if (!card) {
return;
}
if (!card->getOwner()) {
return;
}
card->getOwner()->getPlayerActions()->actHide(selectedCards());
}
/** /**
* @brief Adds a player to the scene and stores their graphics item. * @brief Adds a player to the scene and stores their graphics item.
* @param player Player to add. * @param player Player to add.
@ -82,9 +160,11 @@ void GameScene::addPlayer(PlayerLogic *player)
{ {
qCInfo(GameScenePlayerAdditionRemovalLog) << "GameScene::addPlayer name=" << player->getPlayerInfo()->getName(); qCInfo(GameScenePlayerAdditionRemovalLog) << "GameScene::addPlayer name=" << player->getPlayerInfo()->getName();
playerViews.insert(player->getPlayerInfo()->getId(), player->getGraphicsItem()); auto *view = new PlayerGraphicsItem(player);
addItem(player->getGraphicsItem());
connect(player->getGraphicsItem(), &PlayerGraphicsItem::sizeChanged, this, &GameScene::rearrange); playerViews.insert(player->getPlayerInfo()->getId(), view);
addItem(view);
connect(view, &PlayerGraphicsItem::sizeChanged, this, &GameScene::rearrange);
connect(player, &PlayerLogic::concededChanged, this, [this](int id, bool conceded) { connect(player, &PlayerLogic::concededChanged, this, [this](int id, bool conceded) {
if (conceded) { if (conceded) {
@ -93,11 +173,13 @@ void GameScene::addPlayer(PlayerLogic *player)
rearrange(); rearrange();
}); });
connect(player, &PlayerLogic::arrowDeleted, this, &GameScene::onArrowDeleted); connect(player, &PlayerLogic::requestZoneViewToggle, this, &GameScene::toggleZoneView);
connect(player, &PlayerLogic::arrowCreateRequested, this, &GameScene::onArrowCreateRequested); connect(player, &PlayerLogic::requestRevealedZoneView, this, &GameScene::addRevealedZoneView);
connect(player, &PlayerLogic::arrowDeleteRequested, this, &GameScene::onArrowDeleteRequested); connect(player, &PlayerLogic::arrowDeleted, this, &GameScene::deleteArrow);
connect(player, &PlayerLogic::arrowsCleared, this, connect(player, &PlayerLogic::arrowCreateRequested, this, &GameScene::addArrow);
[this, id = player->getPlayerInfo()->getId()]() { clearArrowsForPlayer(id); }); connect(player, &PlayerLogic::arrowDeleteRequested, this, &GameScene::requestArrowDeletion);
connect(player, &PlayerLogic::arrowsClearedLocally, this,
[this, id = player->getPlayerInfo()->getId()]() { clearArrowsForPlayerLocally(id); });
connect(player->getPlayerEventHandler(), &PlayerEventHandler::cardZoneChanged, this, &GameScene::onCardZoneChanged); connect(player->getPlayerEventHandler(), &PlayerEventHandler::cardZoneChanged, this, &GameScene::onCardZoneChanged);
@ -123,6 +205,7 @@ void GameScene::removePlayer(PlayerLogic *player)
} }
auto *view = playerViews.take(player->getPlayerInfo()->getId()); auto *view = playerViews.take(player->getPlayerInfo()->getId());
removeItem(view); removeItem(view);
view->deleteLater();
rearrange(); rearrange();
} }
@ -204,7 +287,7 @@ QList<PlayerLogic *> GameScene::collectActivePlayers(int &firstPlayerIndex) cons
bool firstPlayerFound = false; bool firstPlayerFound = false;
for (auto *pgItem : playerViews.values()) { for (auto *pgItem : playerViews.values()) {
PlayerLogic *p = pgItem->getPlayer(); PlayerLogic *p = pgItem->getLogic();
if (p && !p->getConceded()) { if (p && !p->getConceded()) {
activePlayers.append(p); activePlayers.append(p);
if (!firstPlayerFound && p->getPlayerInfo()->getLocal()) { if (!firstPlayerFound && p->getPlayerInfo()->getLocal()) {
@ -275,12 +358,12 @@ QSizeF GameScene::computeSceneSizeAndPlayerLayout(const QList<PlayerLogic *> &pl
for (int j = 0; j < rowsInColumn; ++j) { for (int j = 0; j < rowsInColumn; ++j) {
PlayerLogic *player = playersIter.next(); PlayerLogic *player = playersIter.next();
if (col == 0) { if (col == 0) {
playersByColumn[col].prepend(player->getGraphicsItem()); playersByColumn[col].prepend(playerViews.value(player->getPlayerInfo()->getId()));
} else { } else {
playersByColumn[col].append(player->getGraphicsItem()); playersByColumn[col].append(playerViews.value(player->getPlayerInfo()->getId()));
} }
auto *pgItem = player->getGraphicsItem(); auto *pgItem = playerViews.value(player->getPlayerInfo()->getId());
thisColumnHeight += pgItem->boundingRect().height() + playerAreaSpacing; thisColumnHeight += pgItem->boundingRect().height() + playerAreaSpacing;
columnWidth[col] = std::max(columnWidth[col], (int)pgItem->boundingRect().width()); columnWidth[col] = std::max(columnWidth[col], (int)pgItem->boundingRect().width());
} }
@ -367,62 +450,62 @@ void GameScene::resizeColumnsAndPlayers(const QList<qreal> &minWidthByColumn, qr
} }
} }
void GameScene::onArrowCreateRequested(const ArrowData &data) void GameScene::addArrow(QSharedPointer<ArrowData> data)
{ {
auto *startView = playerViews.value(data.startPlayerId); auto *startView = playerViews.value(data->startPlayerId);
auto *targetView = playerViews.value(data.targetPlayerId); auto *targetView = playerViews.value(data->targetPlayerId);
if (!startView || !targetView) { if (!startView || !targetView) {
return; return;
} }
PlayerLogic *startLogic = startView->getPlayer(); PlayerLogic *startLogic = startView->getLogic();
auto *startZone = startLogic->getZones().value(data.startZone); auto *startZone = startLogic->getZones().value(data->startZone);
if (!startZone) { if (!startZone) {
return; return;
} }
CardItem *startCard = startZone->getCard(data.startCardId); CardItem *startCard = startZone->getCard(data->startCardId);
if (!startCard) { if (!startCard) {
return; return;
} }
ArrowTarget *targetItem = nullptr; ArrowTarget *targetItem = nullptr;
if (data.isPlayerTargeted()) { if (data->isPlayerTargeted()) {
targetItem = targetView->getPlayerTarget(); targetItem = targetView->getPlayerTarget();
} else { } else {
auto *zone = targetView->getPlayer()->getZones().value(data.targetZone); auto *zone = targetView->getLogic()->getZones().value(data->targetZone);
if (zone) { if (zone) {
targetItem = zone->getCard(data.targetCardId); targetItem = zone->getCard(data->targetCardId);
} }
} }
if (!targetItem) { if (!targetItem) {
return; return;
} }
auto *arrow = new ArrowItem(startView->getPlayer(), data.id, startCard, targetItem, data.color); auto *arrow = new ArrowItem(data, startCard, targetItem);
addItem(arrow); addItem(arrow);
arrowRegistry.insert(data.id, arrow); arrowRegistry.insert(data, arrow);
connect(arrow, &ArrowItem::requestDeletion, this, &GameScene::onArrowDeleteRequested); connect(arrow, &ArrowItem::requestDeletion, this, &GameScene::requestArrowDeletion);
} }
void GameScene::onArrowDeleted(int arrowId) void GameScene::deleteArrow(int playerId, int arrowId)
{ {
if (arrowRegistry.contains(arrowId)) { if (auto *arrow = arrowRegistry.take(playerId, arrowId)) {
arrowRegistry.take(arrowId)->delArrow(); arrow->delArrow();
} }
} }
void GameScene::onArrowDeleteRequested(int arrowId) void GameScene::requestArrowDeletion(int playerId, int arrowId)
{ {
if (arrowRegistry.contains(arrowId)) { if (arrowRegistry.contains(playerId, arrowId)) {
emit requestArrowDeletion(arrowId); emit arrowDeletionRequested(playerId, arrowId);
} }
} }
void GameScene::onCardZoneChanged(CardItem *card, bool sameZone) void GameScene::onCardZoneChanged(CardItem *card, bool sameZone)
{ {
QList<ArrowItem *> toDelete; QList<ArrowItem *> toDelete;
for (auto *arrow : arrowRegistry.values()) { for (auto *arrow : arrowRegistry.all()) {
if (arrow->getStartItem() == card || arrow->getTargetItem() == card) { if (arrow->getStartItem() == card || arrow->getTargetItem() == card) {
if (sameZone) { if (sameZone) {
arrow->updatePath(); arrow->updatePath();
@ -432,17 +515,22 @@ void GameScene::onCardZoneChanged(CardItem *card, bool sameZone)
} }
} }
for (auto *arrow : toDelete) { for (auto *arrow : toDelete) {
emit requestArrowDeletion(arrow->getId()); deleteArrow(arrow->getCreatorId(), arrow->getId());
} }
} }
void GameScene::clearArrowsForPlayer(int playerId) void GameScene::clearArrowsForPlayer(int playerId)
{ {
for (auto *arrow : arrowRegistry.values()) { for (int arrowId : arrowRegistry.idsForPlayer(playerId)) {
if (arrow->getPlayer()->getPlayerInfo()->getId() == playerId) { emit requestArrowDeletion(playerId, arrowId);
emit requestArrowDeletion(arrow->getId());
} }
} }
void GameScene::clearArrowsForPlayerLocally(int playerId)
{
for (int arrowId : arrowRegistry.idsForPlayer(playerId)) {
arrowRegistry.take(playerId, arrowId)->delArrow();
}
} }
// ---------- Hover Handling ---------- // ---------- Hover Handling ----------

View file

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

View file

@ -34,7 +34,6 @@ GameView::GameView(GameScene *scene, QWidget *parent) : QGraphicsView(scene, par
{ {
setBackgroundBrush(QBrush(QColor(0, 0, 0))); setBackgroundBrush(QBrush(QColor(0, 0, 0)));
setRenderHints(QPainter::TextAntialiasing | QPainter::Antialiasing); setRenderHints(QPainter::TextAntialiasing | QPainter::Antialiasing);
setFocusPolicy(Qt::ClickFocus);
setViewportUpdateMode(BoundingRectViewportUpdate); setViewportUpdateMode(BoundingRectViewportUpdate);
connect(scene, &GameScene::sceneRectChanged, this, &GameView::updateSceneRect); connect(scene, &GameScene::sceneRectChanged, this, &GameView::updateSceneRect);
@ -44,6 +43,9 @@ GameView::GameView(GameScene *scene, QWidget *parent) : QGraphicsView(scene, par
connect(scene, &GameScene::sigStopRubberBand, this, &GameView::stopRubberBand); connect(scene, &GameScene::sigStopRubberBand, this, &GameView::stopRubberBand);
connect(scene, &QGraphicsScene::selectionChanged, this, [this]() { updateTotalSelectionCount(); }); connect(scene, &QGraphicsScene::selectionChanged, this, [this]() { updateTotalSelectionCount(); });
setFocusDisabled(SettingsCache::instance().getKeepGameChatFocus());
connect(&SettingsCache::instance(), &SettingsCache::keepGameChatFocusChanged, this, &GameView::setFocusDisabled);
aCloseMostRecentZoneView = new QAction(this); aCloseMostRecentZoneView = new QAction(this);
connect(aCloseMostRecentZoneView, &QAction::triggered, scene, &GameScene::closeMostRecentZoneView); connect(aCloseMostRecentZoneView, &QAction::triggered, scene, &GameScene::closeMostRecentZoneView);
@ -186,3 +188,12 @@ void GameView::updateTotalSelectionCount(const QSize &viewSize)
totalCountLabel->hide(); totalCountLabel->hide();
} }
} }
/**
* Disabling focus on the game view will allow chat to maintain the autofocusing behavior of pre 2.10.3,
* at the cost of disabling the zone view search bar.
*/
void GameView::setFocusDisabled(bool disabled)
{
setFocusPolicy(disabled ? Qt::NoFocus : Qt::ClickFocus);
}

View file

@ -31,6 +31,7 @@ private slots:
void stopRubberBand(); void stopRubberBand();
void refreshShortcuts(); void refreshShortcuts();
void updateTotalSelectionCount(const QSize &viewSize = QSize()); void updateTotalSelectionCount(const QSize &viewSize = QSize());
void setFocusDisabled(bool disabled);
public slots: public slots:
void updateSceneRect(const QRectF &rect); void updateSceneRect(const QRectF &rect);

View file

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

View file

@ -7,8 +7,8 @@
#ifndef HANDCOUNTER_H #ifndef HANDCOUNTER_H
#define HANDCOUNTER_H #define HANDCOUNTER_H
#include "../game_graphics/board/abstract_graphics_item.h" #include "board/abstract_graphics_item.h"
#include "../game_graphics/board/graphics_item_type.h" #include "board/graphics_item_type.h"
#include <QString> #include <QString>

View file

@ -1,13 +1,13 @@
#include "message_log_widget.h" #include "message_log_widget.h"
#include "../../client/settings/card_counter_settings.h"
#include "../../client/sound_engine.h" #include "../../client/sound_engine.h"
#include "../../game/phase.h"
#include "../../game/player/player_logic.h"
#include "../../interface/widgets/tabs/tab_game.h" #include "../../interface/widgets/tabs/tab_game.h"
#include "../board/card_item.h" #include "../board/card_item.h"
#include "../board/translate_counter_name.h" #include "../board/translate_counter_name.h"
#include "../phase.h"
#include "../player/player_logic.h"
#include <../../client/settings/card_counter_settings.h>
#include <libcockatrice/protocol/pb/context_move_card.pb.h> #include <libcockatrice/protocol/pb/context_move_card.pb.h>
#include <libcockatrice/protocol/pb/context_mulligan.pb.h> #include <libcockatrice/protocol/pb/context_mulligan.pb.h>
#include <libcockatrice/utility/zone_names.h> #include <libcockatrice/utility/zone_names.h>
@ -650,14 +650,16 @@ void MessageLogWidget::logSetCardCounter(PlayerLogic *player, QString cardName,
QString finalStr; QString finalStr;
int delta = abs(oldValue - value); int delta = abs(oldValue - value);
if (value > oldValue) { if (value > oldValue) {
finalStr = tr("%1 places %2 \"%3\" counter(s) on %4 (now %5).", "", delta); finalStr = tr("%1 places %2 %3%4 counter(s) on %5 (now %6).", "", delta);
} else { } else {
finalStr = tr("%1 removes %2 \"%3\" counter(s) from %4 (now %5).", "", delta); finalStr = tr("%1 removes %2 %3%4 counter(s) from %5 (now %6).", "", delta);
} }
auto &cardCounterSettings = SettingsCache::instance().cardCounters(); auto &cardCounterSettings = SettingsCache::instance().cardCounters();
QString hex = cardCounterSettings.color(counterId).name();
appendHtmlServerMessage(finalStr.arg(sanitizeHtml(player->getPlayerInfo()->getName())) appendHtmlServerMessage(finalStr.arg(sanitizeHtml(player->getPlayerInfo()->getName()))
.arg("<font class=\"blue\">" + QString::number(delta) + "</font>") .arg("<font class=\"blue\">" + QString::number(delta) + "</font>")
.arg("<font color=\"" + hex + "\">●</font>")
.arg(cardCounterSettings.displayName(counterId)) .arg(cardCounterSettings.displayName(counterId))
.arg(cardLink(std::move(cardName))) .arg(cardLink(std::move(cardName)))
.arg(value)); .arg(value));

View file

@ -7,8 +7,8 @@
#ifndef MESSAGELOGWIDGET_H #ifndef MESSAGELOGWIDGET_H
#define MESSAGELOGWIDGET_H #define MESSAGELOGWIDGET_H
#include "../../game/zones/card_zone_logic.h"
#include "../../interface/widgets/server/chat_view/chat_view.h" #include "../../interface/widgets/server/chat_view/chat_view.h"
#include "../zones/card_zone_logic.h"
class AbstractGame; class AbstractGame;
class CardItem; class CardItem;

View file

@ -8,7 +8,7 @@
#ifndef PHASESTOOLBAR_H #ifndef PHASESTOOLBAR_H
#define PHASESTOOLBAR_H #define PHASESTOOLBAR_H
#include "../game_graphics/board/abstract_graphics_item.h" #include "board/abstract_graphics_item.h"
#include <QFrame> #include <QFrame>
#include <QGraphicsObject> #include <QGraphicsObject>

View file

@ -3,92 +3,121 @@
#include "../../../client/settings/card_counter_settings.h" #include "../../../client/settings/card_counter_settings.h"
#include "../../../interface/widgets/tabs/tab_game.h" #include "../../../interface/widgets/tabs/tab_game.h"
#include "../../board/card_item.h" #include "../../board/card_item.h"
#include "../../zones/view_zone_logic.h" #include "../../game/player/player_actions.h"
#include "../../game/player/player_logic.h"
#include "../../game/zones/view_zone_logic.h"
#include "../card_menu_action_type.h" #include "../card_menu_action_type.h"
#include "../player_actions.h" #include "../player_graphics_item.h"
#include "../player_logic.h"
#include "move_menu.h" #include "move_menu.h"
#include "pt_menu.h" #include "pt_menu.h"
#include <QPainter>
#include <libcockatrice/card/database/card_database_manager.h> #include <libcockatrice/card/database/card_database_manager.h>
#include <libcockatrice/card/relation/card_relation.h> #include <libcockatrice/card/relation/card_relation.h>
#include <libcockatrice/utility/zone_names.h> #include <libcockatrice/utility/zone_names.h>
CardMenu::CardMenu(PlayerLogic *_player, const CardItem *_card, bool _shortcutsActive) /**
* @brief Creates a circular icon filled with the specified color.
*/
static QIcon createCircleIcon(const QColor &color)
{
QPixmap pixmap(32, 32);
pixmap.fill(Qt::transparent);
QPainter painter(&pixmap);
painter.setRenderHint(QPainter::Antialiasing);
painter.setPen(Qt::NoPen);
painter.setBrush(color);
painter.drawEllipse(pixmap.rect());
return QIcon(pixmap);
}
template <typename Slot>
static QAction *makeAction(QObject *parent, Slot &&slot, bool checkable = false, bool checked = false)
{
auto *a = new QAction(parent);
a->setCheckable(checkable);
if (checkable) {
a->setChecked(checked);
}
QObject::connect(a, &QAction::triggered, parent, std::forward<Slot>(slot));
return a;
}
CardMenu::CardMenu(PlayerGraphicsItem *_player, const CardItem *_card, bool _shortcutsActive)
: player(_player), card(_card), shortcutsActive(_shortcutsActive) : player(_player), card(_card), shortcutsActive(_shortcutsActive)
{ {
auto playerActions = player->getPlayerActions(); const QList<PlayerLogic *> &players = player->getLogic()->getGame()->getPlayerManager()->getPlayers().values();
const QList<PlayerLogic *> &players = player->getGame()->getPlayerManager()->getPlayers().values();
for (auto playerToAdd : players) { for (auto playerToAdd : players) {
if (playerToAdd == player) { if (playerToAdd == player->getLogic()) {
continue; continue;
} }
playersInfo.append(qMakePair(playerToAdd->getPlayerInfo()->getName(), playerToAdd->getPlayerInfo()->getId())); playersInfo.append(qMakePair(playerToAdd->getPlayerInfo()->getName(), playerToAdd->getPlayerInfo()->getId()));
} }
connect(player->getGame()->getPlayerManager(), &PlayerManager::playerRemoved, this, &CardMenu::removePlayer); connect(player->getLogic()->getGame()->getPlayerManager(), &PlayerManager::playerRemoved, this,
&CardMenu::removePlayer);
aTap = new QAction(this); auto *actions = player->getLogic()->getPlayerActions();
aTap->setData(cmTap); auto *gameScene = player->getGameScene();
connect(aTap, &QAction::triggered, playerActions, &PlayerActions::cardMenuAction);
aDoesntUntap = new QAction(this); // Single selection resolver used by all lambdas — called at trigger time
aDoesntUntap->setData(cmDoesntUntap); auto sel = [gameScene]() { return gameScene->selectedCards(); };
aDoesntUntap->setCheckable(true);
aDoesntUntap->setChecked(card != nullptr && card->getDoesntUntap()); // Unified dispatcher for card menu actions
connect(aDoesntUntap, &QAction::triggered, playerActions, &PlayerActions::cardMenuAction); auto invoke = [actions, sel](CardMenuActionType type) {
return [actions, sel, type]() { actions->cardMenuAction(sel(), type); };
};
// Actions using invoke (type dispatch, need selection)
aTap = makeAction(this, invoke(cmTap));
aDoesntUntap = makeAction(this, invoke(cmDoesntUntap), /*checkable=*/true, card && card->getDoesntUntap());
aFlip = makeAction(this, invoke(cmFlip));
aPeek = makeAction(this, invoke(cmPeek));
aClone = makeAction(this, invoke(cmClone));
// Actions using selection directly
aUnattach = makeAction(this, [actions, sel]() { actions->actUnattach(sel()); });
aSetAnnotation = makeAction(this, [actions, sel]() { actions->actRequestSetAnnotationDialog(sel()); });
aPlay = makeAction(this, [actions, sel]() { actions->actPlay(sel()); });
aPlayFacedown = makeAction(this, [actions, sel]() { actions->actPlayFacedown(sel()); });
aHide = makeAction(this, [actions, sel]() { actions->actHide(sel()); });
aReduceLifeByPower = makeAction(this, [actions, sel]() { actions->actReduceLifeByPower(sel()); });
// Actions that use activeCard, not selection — direct connection
aAttach = new QAction(this); aAttach = new QAction(this);
connect(aAttach, &QAction::triggered, playerActions, &PlayerActions::actAttach);
aUnattach = new QAction(this);
connect(aUnattach, &QAction::triggered, playerActions, &PlayerActions::actUnattach);
aDrawArrow = new QAction(this); aDrawArrow = new QAction(this);
connect(aDrawArrow, &QAction::triggered, playerActions, &PlayerActions::actDrawArrow);
aSetAnnotation = new QAction(this);
connect(aSetAnnotation, &QAction::triggered, playerActions, &PlayerActions::actSetAnnotation);
aFlip = new QAction(this);
aFlip->setData(cmFlip);
connect(aFlip, &QAction::triggered, player->getPlayerActions(), &PlayerActions::cardMenuAction);
aPeek = new QAction(this);
aPeek->setData(cmPeek);
connect(aPeek, &QAction::triggered, player->getPlayerActions(), &PlayerActions::cardMenuAction);
aClone = new QAction(this);
aClone->setData(cmClone);
connect(aClone, &QAction::triggered, player->getPlayerActions(), &PlayerActions::cardMenuAction);
aSelectAll = new QAction(this); aSelectAll = new QAction(this);
connect(aSelectAll, &QAction::triggered, playerActions, &PlayerActions::actSelectAll);
aSelectRow = new QAction(this); aSelectRow = new QAction(this);
connect(aSelectRow, &QAction::triggered, playerActions, &PlayerActions::actSelectRow);
aSelectColumn = new QAction(this); aSelectColumn = new QAction(this);
connect(aSelectColumn, &QAction::triggered, playerActions, &PlayerActions::actSelectColumn);
aReduceLifeByPower = new QAction(this); connect(aAttach, &QAction::triggered, actions, &PlayerActions::actAttach);
connect(aReduceLifeByPower, &QAction::triggered, playerActions, &PlayerActions::actReduceLifeByPower); connect(aDrawArrow, &QAction::triggered, actions, &PlayerActions::actDrawArrow);
connect(aSelectAll, &QAction::triggered, actions, &PlayerActions::actSelectAll);
aPlay = new QAction(this); connect(aSelectRow, &QAction::triggered, actions, &PlayerActions::actSelectRow);
connect(aPlay, &QAction::triggered, playerActions, &PlayerActions::actPlay); connect(aSelectColumn, &QAction::triggered, actions, &PlayerActions::actSelectColumn);
aHide = new QAction(this);
connect(aHide, &QAction::triggered, playerActions, &PlayerActions::actHide);
aPlayFacedown = new QAction(this);
connect(aPlayFacedown, &QAction::triggered, playerActions, &PlayerActions::actPlayFacedown);
aRevealToAll = new QAction(this); aRevealToAll = new QAction(this);
mCardCounters = new QMenu; mCardCounters = new QMenu;
// Card counters
for (int i = 0; i < 6; ++i) { for (int i = 0; i < 6; ++i) {
auto *tempAddCounter = new QAction(this); QColor color = SettingsCache::instance().cardCounters().color(i);
auto *tempRemoveCounter = new QAction(this); QIcon circleIcon = createCircleIcon(color);
auto *tempSetCounter = new QAction(this);
aAddCounter.append(tempAddCounter); auto *addAction = makeAction(this, [actions, sel, i]() { actions->actAddCardCounter(sel(), i); });
aRemoveCounter.append(tempRemoveCounter); addAction->setIcon(circleIcon);
aSetCounter.append(tempSetCounter); aAddCounter.append(addAction);
connect(tempAddCounter, &QAction::triggered, playerActions,
[playerActions, i] { playerActions->actAddCardCounter(i); }); auto *removeAction = makeAction(this, [actions, sel, i]() { actions->actRemoveCardCounter(sel(), i); });
connect(tempRemoveCounter, &QAction::triggered, playerActions, removeAction->setIcon(circleIcon);
[playerActions, i] { playerActions->actRemoveCardCounter(i); }); aRemoveCounter.append(removeAction);
connect(tempSetCounter, &QAction::triggered, playerActions,
[playerActions, i] { playerActions->actSetCardCounter(i); }); auto *setAction = makeAction(this, [actions, sel, i]() { actions->actRequestSetCardCounterDialog(sel(), i); });
setAction->setIcon(circleIcon);
aSetCounter.append(setAction);
} }
setShortcutsActive(); setShortcutsActive();
@ -100,7 +129,7 @@ CardMenu::CardMenu(PlayerLogic *_player, const CardItem *_card, bool _shortcutsA
} }
bool revealedCard = false; bool revealedCard = false;
bool writeableCard = player->getPlayerInfo()->getLocalOrJudge(); bool writeableCard = player->getLogic()->getPlayerInfo()->getLocalOrJudge();
if (auto *view = qobject_cast<ZoneViewZoneLogic *>(card->getZone())) { if (auto *view = qobject_cast<ZoneViewZoneLogic *>(card->getZone())) {
if (view->getRevealZone()) { if (view->getRevealZone()) {
if (view->getWriteableRevealZone()) { if (view->getWriteableRevealZone()) {
@ -284,7 +313,9 @@ void CardMenu::createHandOrCustomZoneMenu(bool canModifyCard)
initContextualPlayersMenu(revealMenu, aRevealToAll); initContextualPlayersMenu(revealMenu, aRevealToAll);
connect(revealMenu, &QMenu::triggered, player->getPlayerActions(), &PlayerActions::actReveal); connect(revealMenu, &QMenu::triggered, this, [this](QAction *action) {
player->getLogic()->getPlayerActions()->actReveal(player->getGameScene()->selectedCards(), action);
});
addSeparator(); addSeparator();
addAction(aClone); addAction(aClone);
@ -369,8 +400,7 @@ void CardMenu::addRelatedCardView()
QAction *viewCard = viewRelatedCards->addAction(relatedCardName); QAction *viewCard = viewRelatedCards->addAction(relatedCardName);
Q_UNUSED(viewCard); Q_UNUSED(viewCard);
connect(viewCard, &QAction::triggered, player->getGame(), connect(viewCard, &QAction::triggered, this, [this, cardRef] { emit cardInfoRequested(cardRef); });
[this, cardRef] { player->getGame()->getTab()->viewCardInfo(cardRef); });
} }
} }
@ -432,7 +462,8 @@ void CardMenu::addRelatedCardActions()
auto *createRelated = new QAction(text, this); auto *createRelated = new QAction(text, this);
createRelated->setData(QVariant(index++)); createRelated->setData(QVariant(index++));
connect(createRelated, &QAction::triggered, player->getPlayerActions(), &PlayerActions::actCreateRelatedCard); connect(createRelated, &QAction::triggered, player->getLogic()->getPlayerActions(),
&PlayerActions::actCreateRelatedCard);
addAction(createRelated); addAction(createRelated);
} }
@ -441,7 +472,7 @@ void CardMenu::addRelatedCardActions()
createRelatedCards->setShortcuts( createRelatedCards->setShortcuts(
SettingsCache::instance().shortcuts().getShortcut("Player/aCreateRelatedTokens")); SettingsCache::instance().shortcuts().getShortcut("Player/aCreateRelatedTokens"));
} }
connect(createRelatedCards, &QAction::triggered, player->getPlayerActions(), connect(createRelatedCards, &QAction::triggered, player->getLogic()->getPlayerActions(),
&PlayerActions::actCreateAllRelatedCards); &PlayerActions::actCreateAllRelatedCards);
addAction(createRelatedCards); addAction(createRelatedCards);
} }

View file

@ -8,15 +8,20 @@
#define COCKATRICE_CARD_MENU_H #define COCKATRICE_CARD_MENU_H
#include <QMenu> #include <QMenu>
#include <libcockatrice/utility/card_ref.h>
class CardItem; class CardItem;
class PlayerGraphicsItem;
class PlayerLogic; class PlayerLogic;
class CardMenu : public QMenu class CardMenu : public QMenu
{ {
Q_OBJECT Q_OBJECT
signals:
void cardInfoRequested(const CardRef &cardRef);
public: public:
explicit CardMenu(PlayerLogic *player, const CardItem *card, bool shortcutsActive); explicit CardMenu(PlayerGraphicsItem *player, const CardItem *card, bool shortcutsActive);
void removePlayer(PlayerLogic *playerToRemove); void removePlayer(PlayerLogic *playerToRemove);
void createTableMenu(bool canModifyCard); void createTableMenu(bool canModifyCard);
void createStackMenu(bool canModifyCard); void createStackMenu(bool canModifyCard);
@ -41,7 +46,7 @@ public:
QList<QAction *> aAddCounter, aSetCounter, aRemoveCounter; QList<QAction *> aAddCounter, aSetCounter, aRemoveCounter;
private: private:
PlayerLogic *player; PlayerGraphicsItem *player;
const CardItem *card; const CardItem *card;
QList<QPair<QString, int>> playersInfo; QList<QPair<QString, int>> playersInfo;
bool shortcutsActive; bool shortcutsActive;

View file

@ -1,13 +1,14 @@
#include "custom_zone_menu.h" #include "custom_zone_menu.h"
#include "../player_logic.h" #include "../../game/player/player_logic.h"
#include "../player_graphics_item.h"
CustomZoneMenu::CustomZoneMenu(PlayerLogic *_player) : player(_player) CustomZoneMenu::CustomZoneMenu(PlayerGraphicsItem *_player) : player(_player)
{ {
menuAction()->setVisible(false); menuAction()->setVisible(false);
connect(player, &PlayerLogic::clearCustomZonesMenu, this, &CustomZoneMenu::clearCustomZonesMenu); connect(player->getLogic(), &PlayerLogic::clearCustomZonesMenu, this, &CustomZoneMenu::clearCustomZonesMenu);
connect(player, &PlayerLogic::addViewCustomZoneActionToCustomZoneMenu, this, connect(player->getLogic(), &PlayerLogic::addViewCustomZoneActionToCustomZoneMenu, this,
&CustomZoneMenu::addViewCustomZoneActionToCustomZoneMenu); &CustomZoneMenu::addViewCustomZoneActionToCustomZoneMenu);
retranslateUi(); retranslateUi();
@ -17,7 +18,7 @@ void CustomZoneMenu::retranslateUi()
{ {
setTitle(tr("C&ustom Zones")); setTitle(tr("C&ustom Zones"));
if (player->getPlayerInfo()->getLocalOrJudge()) { if (player->getLogic()->getPlayerInfo()->getLocalOrJudge()) {
for (auto aViewZone : actions()) { for (auto aViewZone : actions()) {
aViewZone->setText(tr("View custom zone '%1'").arg(aViewZone->data().toString())); aViewZone->setText(tr("View custom zone '%1'").arg(aViewZone->data().toString()));
@ -37,5 +38,5 @@ void CustomZoneMenu::addViewCustomZoneActionToCustomZoneMenu(QString zoneName)
QAction *aViewZone = addAction(tr("View custom zone '%1'").arg(zoneName)); QAction *aViewZone = addAction(tr("View custom zone '%1'").arg(zoneName));
aViewZone->setData(zoneName); aViewZone->setData(zoneName);
connect(aViewZone, &QAction::triggered, this, connect(aViewZone, &QAction::triggered, this,
[zoneName, this]() { player->getGameScene()->toggleZoneView(player, zoneName, -1); }); [zoneName, this]() { player->getGameScene()->toggleZoneView(player->getLogic(), zoneName, -1); });
} }

View file

@ -11,12 +11,12 @@
#include <QMenu> #include <QMenu>
class PlayerLogic; class PlayerGraphicsItem;
class CustomZoneMenu : public QMenu, public AbstractPlayerComponent class CustomZoneMenu : public QMenu, public AbstractPlayerComponent
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit CustomZoneMenu(PlayerLogic *player); explicit CustomZoneMenu(PlayerGraphicsItem *player);
void retranslateUi() override; void retranslateUi() override;
void setShortcutsActive() override void setShortcutsActive() override
{ {
@ -26,7 +26,7 @@ public:
} }
private: private:
PlayerLogic *player; PlayerGraphicsItem *player;
private slots: private slots:
void clearCustomZonesMenu(); void clearCustomZonesMenu();
void addViewCustomZoneActionToCustomZoneMenu(QString zoneName); void addViewCustomZoneActionToCustomZoneMenu(QString zoneName);

View file

@ -1,21 +1,22 @@
#include "grave_menu.h" #include "grave_menu.h"
#include "../../abstract_game.h" #include "../../game/abstract_game.h"
#include "../player_actions.h" #include "../../game/player/player_actions.h"
#include "../player_logic.h" #include "../../game/player/player_logic.h"
#include "../player_graphics_item.h"
#include <QAction> #include <QAction>
#include <QMenu> #include <QMenu>
#include <libcockatrice/utility/zone_names.h> #include <libcockatrice/utility/zone_names.h>
GraveyardMenu::GraveyardMenu(PlayerLogic *_player, QWidget *parent) : TearOffMenu(parent), player(_player) GraveyardMenu::GraveyardMenu(PlayerGraphicsItem *_player, QWidget *parent) : TearOffMenu(parent), player(_player)
{ {
createMoveActions(); createMoveActions();
createViewActions(); createViewActions();
addAction(aViewGraveyard); addAction(aViewGraveyard);
if (player->getPlayerInfo()->local || player->getPlayerInfo()->judge) { if (player->getLogic()->getPlayerInfo()->local || player->getLogic()->getPlayerInfo()->judge) {
mRevealRandomGraveyardCard = addMenu(QString()); mRevealRandomGraveyardCard = addMenu(QString());
connect(mRevealRandomGraveyardCard, &QMenu::aboutToShow, this, connect(mRevealRandomGraveyardCard, &QMenu::aboutToShow, this,
&GraveyardMenu::populateRevealRandomMenuWithActivePlayers); &GraveyardMenu::populateRevealRandomMenuWithActivePlayers);
@ -36,9 +37,9 @@ GraveyardMenu::GraveyardMenu(PlayerLogic *_player, QWidget *parent) : TearOffMen
void GraveyardMenu::createMoveActions() void GraveyardMenu::createMoveActions()
{ {
auto grave = player->getGraveZone(); auto grave = player->getLogic()->getGraveZone();
if (player->getPlayerInfo()->local || player->getPlayerInfo()->judge) { if (player->getLogic()->getPlayerInfo()->local || player->getLogic()->getPlayerInfo()->judge) {
aMoveGraveToTopLibrary = new QAction(this); aMoveGraveToTopLibrary = new QAction(this);
aMoveGraveToTopLibrary->setData(QList<QVariant>() << ZoneNames::DECK << 0); aMoveGraveToTopLibrary->setData(QList<QVariant>() << ZoneNames::DECK << 0);
@ -60,7 +61,7 @@ void GraveyardMenu::createMoveActions()
void GraveyardMenu::createViewActions() void GraveyardMenu::createViewActions()
{ {
PlayerActions *playerActions = player->getPlayerActions(); PlayerActions *playerActions = player->getLogic()->getPlayerActions();
aViewGraveyard = new QAction(this); aViewGraveyard = new QAction(this);
connect(aViewGraveyard, &QAction::triggered, playerActions, &PlayerActions::actViewGraveyard); connect(aViewGraveyard, &QAction::triggered, playerActions, &PlayerActions::actViewGraveyard);
@ -76,9 +77,9 @@ void GraveyardMenu::populateRevealRandomMenuWithActivePlayers()
mRevealRandomGraveyardCard->addSeparator(); mRevealRandomGraveyardCard->addSeparator();
const auto &players = player->getGame()->getPlayerManager()->getPlayers().values(); const auto &players = player->getLogic()->getGame()->getPlayerManager()->getPlayers().values();
for (auto *other : players) { for (auto *other : players) {
if (other == player) { if (other == player->getLogic()) {
continue; continue;
} }
QAction *a = mRevealRandomGraveyardCard->addAction(other->getPlayerInfo()->getName()); QAction *a = mRevealRandomGraveyardCard->addAction(other->getPlayerInfo()->getName());
@ -90,7 +91,7 @@ void GraveyardMenu::populateRevealRandomMenuWithActivePlayers()
void GraveyardMenu::onRevealRandomTriggered() void GraveyardMenu::onRevealRandomTriggered()
{ {
if (auto *a = qobject_cast<QAction *>(sender())) { if (auto *a = qobject_cast<QAction *>(sender())) {
player->getPlayerActions()->actRevealRandomGraveyardCard(a->data().toInt()); player->getLogic()->getPlayerActions()->actRevealRandomGraveyardCard(a->data().toInt());
} }
} }
@ -100,7 +101,7 @@ void GraveyardMenu::retranslateUi()
aViewGraveyard->setText(tr("&View graveyard")); aViewGraveyard->setText(tr("&View graveyard"));
if (player->getPlayerInfo()->getLocalOrJudge()) { if (player->getLogic()->getPlayerInfo()->getLocalOrJudge()) {
moveGraveMenu->setTitle(tr("&Move graveyard to...")); moveGraveMenu->setTitle(tr("&Move graveyard to..."));
aMoveGraveToTopLibrary->setText(tr("&Top of library")); aMoveGraveToTopLibrary->setText(tr("&Top of library"));
aMoveGraveToBottomLibrary->setText(tr("&Bottom of library")); aMoveGraveToBottomLibrary->setText(tr("&Bottom of library"));

View file

@ -13,7 +13,7 @@
#include <QAction> #include <QAction>
#include <QMenu> #include <QMenu>
class PlayerLogic; class PlayerGraphicsItem;
class GraveyardMenu : public TearOffMenu, public AbstractPlayerComponent class GraveyardMenu : public TearOffMenu, public AbstractPlayerComponent
{ {
Q_OBJECT Q_OBJECT
@ -21,7 +21,7 @@ signals:
void newPlayerActionCreated(QAction *action); void newPlayerActionCreated(QAction *action);
public: public:
explicit GraveyardMenu(PlayerLogic *player, QWidget *parent = nullptr); explicit GraveyardMenu(PlayerGraphicsItem *player, QWidget *parent = nullptr);
void createMoveActions(); void createMoveActions();
void createViewActions(); void createViewActions();
void populateRevealRandomMenuWithActivePlayers(); void populateRevealRandomMenuWithActivePlayers();
@ -40,7 +40,7 @@ public:
QAction *aMoveGraveToRfg = nullptr; QAction *aMoveGraveToRfg = nullptr;
private: private:
PlayerLogic *player; PlayerGraphicsItem *player;
}; };
#endif // COCKATRICE_GRAVE_MENU_H #endif // COCKATRICE_GRAVE_MENU_H

View file

@ -3,18 +3,22 @@
#include "../../../client/settings/cache_settings.h" #include "../../../client/settings/cache_settings.h"
#include "../../../client/settings/shortcuts_settings.h" #include "../../../client/settings/shortcuts_settings.h"
#include "../../../game_graphics/zones/hand_zone.h" #include "../../../game_graphics/zones/hand_zone.h"
#include "../../abstract_game.h" #include "../../game/abstract_game.h"
#include "../player_actions.h" #include "../../game/player/player_actions.h"
#include "../player_logic.h" #include "../../game/player/player_logic.h"
#include "../player_graphics_item.h"
#include <QAction> #include <QAction>
#include <QMenu> #include <QMenu>
#include <libcockatrice/utility/zone_names.h> #include <libcockatrice/utility/zone_names.h>
HandMenu::HandMenu(PlayerLogic *_player, PlayerActions *actions, QWidget *parent) : TearOffMenu(parent), player(_player) HandMenu::HandMenu(PlayerGraphicsItem *_player, QWidget *parent) : TearOffMenu(parent), player(_player)
{ {
if (player->getPlayerInfo()->local || player->getPlayerInfo()->judge) { auto *actions = player->getLogic()->getPlayerActions();
if (player->getLogic()->getPlayerInfo()->local || player->getLogic()->getPlayerInfo()->judge) {
aViewHand = new QAction(this); aViewHand = new QAction(this);
connect(aViewHand, &QAction::triggered, actions, &PlayerActions::actViewHand); connect(aViewHand, &QAction::triggered, actions, &PlayerActions::actViewHand);
addAction(aViewHand); addAction(aViewHand);
@ -58,7 +62,7 @@ HandMenu::HandMenu(PlayerLogic *_player, PlayerActions *actions, QWidget *parent
addSeparator(); addSeparator();
aMulligan = new QAction(this); aMulligan = new QAction(this);
connect(aMulligan, &QAction::triggered, actions, &PlayerActions::actMulligan); connect(aMulligan, &QAction::triggered, actions, &PlayerActions::actRequestMulliganDialog);
addAction(aMulligan); addAction(aMulligan);
// Mulligan same size // Mulligan same size
@ -75,7 +79,7 @@ HandMenu::HandMenu(PlayerLogic *_player, PlayerActions *actions, QWidget *parent
mMoveHandMenu = addTearOffMenu(QString()); mMoveHandMenu = addTearOffMenu(QString());
if (player->getPlayerInfo()->local || player->getPlayerInfo()->judge) { if (player->getLogic()->getPlayerInfo()->local || player->getLogic()->getPlayerInfo()->judge) {
aMoveHandToTopLibrary = new QAction(this); aMoveHandToTopLibrary = new QAction(this);
aMoveHandToTopLibrary->setData(QList<QVariant>() << ZoneNames::DECK << 0); aMoveHandToTopLibrary->setData(QList<QVariant>() << ZoneNames::DECK << 0);
aMoveHandToBottomLibrary = new QAction(this); aMoveHandToBottomLibrary = new QAction(this);
@ -85,7 +89,7 @@ HandMenu::HandMenu(PlayerLogic *_player, PlayerActions *actions, QWidget *parent
aMoveHandToRfg = new QAction(this); aMoveHandToRfg = new QAction(this);
aMoveHandToRfg->setData(QList<QVariant>() << ZoneNames::EXILE << 0); aMoveHandToRfg->setData(QList<QVariant>() << ZoneNames::EXILE << 0);
auto hand = player->getHandZone(); auto hand = player->getLogic()->getHandZone();
connect(aMoveHandToTopLibrary, &QAction::triggered, hand, &HandZoneLogic::moveAllToZone); connect(aMoveHandToTopLibrary, &QAction::triggered, hand, &HandZoneLogic::moveAllToZone);
connect(aMoveHandToBottomLibrary, &QAction::triggered, hand, &HandZoneLogic::moveAllToZone); connect(aMoveHandToBottomLibrary, &QAction::triggered, hand, &HandZoneLogic::moveAllToZone);
@ -107,7 +111,7 @@ void HandMenu::retranslateUi()
{ {
setTitle(tr("&Hand")); setTitle(tr("&Hand"));
if (player->getPlayerInfo()->getLocalOrJudge()) { if (player->getLogic()->getPlayerInfo()->getLocalOrJudge()) {
aViewHand->setText(tr("&View hand")); aViewHand->setText(tr("&View hand"));
mSortHand->setTitle(tr("Sort hand by...")); mSortHand->setTitle(tr("Sort hand by..."));
@ -166,9 +170,9 @@ void HandMenu::populateRevealHandMenuWithActivePlayers()
mRevealHand->addSeparator(); mRevealHand->addSeparator();
const auto &players = player->getGame()->getPlayerManager()->getPlayers().values(); const auto &players = player->getLogic()->getGame()->getPlayerManager()->getPlayers().values();
for (auto *other : players) { for (auto *other : players) {
if (other == player) { if (other == player->getLogic()) {
continue; continue;
} }
QAction *a = mRevealHand->addAction(other->getPlayerInfo()->getName()); QAction *a = mRevealHand->addAction(other->getPlayerInfo()->getName());
@ -185,9 +189,9 @@ void HandMenu::populateRevealRandomHandCardMenuWithActivePlayers()
mRevealRandomHandCard->addSeparator(); mRevealRandomHandCard->addSeparator();
const auto &players = player->getGame()->getPlayerManager()->getPlayers().values(); const auto &players = player->getLogic()->getGame()->getPlayerManager()->getPlayers().values();
for (auto *other : players) { for (auto *other : players) {
if (other == player) { if (other == player->getLogic()) {
continue; continue;
} }
QAction *a = mRevealRandomHandCard->addAction(other->getPlayerInfo()->getName()); QAction *a = mRevealRandomHandCard->addAction(other->getPlayerInfo()->getName());
@ -204,7 +208,7 @@ void HandMenu::onRevealHandTriggered()
} }
const int targetId = action->data().toInt(); const int targetId = action->data().toInt();
player->getPlayerActions()->actRevealHand(targetId); player->getLogic()->getPlayerActions()->actRevealHand(targetId);
} }
void HandMenu::onRevealRandomHandCardTriggered() void HandMenu::onRevealRandomHandCardTriggered()
@ -215,5 +219,5 @@ void HandMenu::onRevealRandomHandCardTriggered()
} }
const int targetId = action->data().toInt(); const int targetId = action->data().toInt();
player->getPlayerActions()->actRevealRandomHandCard(targetId); player->getLogic()->getPlayerActions()->actRevealRandomHandCard(targetId);
} }

View file

@ -13,7 +13,7 @@
#include <QAction> #include <QAction>
#include <QMenu> #include <QMenu>
class PlayerLogic; class PlayerGraphicsItem;
class PlayerActions; class PlayerActions;
class HandMenu : public TearOffMenu, public AbstractPlayerComponent class HandMenu : public TearOffMenu, public AbstractPlayerComponent
@ -21,7 +21,7 @@ class HandMenu : public TearOffMenu, public AbstractPlayerComponent
Q_OBJECT Q_OBJECT
public: public:
HandMenu(PlayerLogic *player, PlayerActions *actions, QWidget *parent = nullptr); HandMenu(PlayerGraphicsItem *player, QWidget *parent = nullptr);
QMenu *revealHandMenu() const QMenu *revealHandMenu() const
{ {
@ -43,7 +43,7 @@ private slots:
void onRevealRandomHandCardTriggered(); void onRevealRandomHandCardTriggered();
private: private:
PlayerLogic *player; PlayerGraphicsItem *player;
QAction *aViewHand = nullptr; QAction *aViewHand = nullptr;
QAction *aMulligan = nullptr; QAction *aMulligan = nullptr;

View file

@ -3,14 +3,16 @@
#include "../../../client/settings/cache_settings.h" #include "../../../client/settings/cache_settings.h"
#include "../../../client/settings/shortcuts_settings.h" #include "../../../client/settings/shortcuts_settings.h"
#include "../../../interface/widgets/tabs/tab_game.h" #include "../../../interface/widgets/tabs/tab_game.h"
#include "../../abstract_game.h" #include "../../game/abstract_game.h"
#include "../player_actions.h" #include "../../game/player/player_actions.h"
#include "../player_logic.h" #include "../../game/player/player_logic.h"
#include "../player_graphics_item.h"
#include <QAction> #include <QAction>
#include <QGraphicsView>
#include <QMenu> #include <QMenu>
LibraryMenu::LibraryMenu(PlayerLogic *_player, QWidget *parent) : TearOffMenu(parent), player(_player) LibraryMenu::LibraryMenu(PlayerGraphicsItem *_player, QWidget *parent) : TearOffMenu(parent), player(_player)
{ {
createDrawActions(); createDrawActions();
createShuffleActions(); createShuffleActions();
@ -75,8 +77,8 @@ LibraryMenu::LibraryMenu(PlayerLogic *_player, QWidget *parent) : TearOffMenu(pa
bottomLibraryMenu->addSeparator(); bottomLibraryMenu->addSeparator();
bottomLibraryMenu->addAction(aShuffleBottomCards); bottomLibraryMenu->addAction(aShuffleBottomCards);
connect(player, &PlayerLogic::resetTopCardMenuActions, this, &LibraryMenu::resetTopCardMenuActions); connect(player->getLogic(), &PlayerLogic::resetTopCardMenuActions, this, &LibraryMenu::resetTopCardMenuActions);
connect(player, &PlayerLogic::deckChanged, this, &LibraryMenu::enableOpenInDeckEditorAction); connect(player->getLogic(), &PlayerLogic::deckChanged, this, &LibraryMenu::enableOpenInDeckEditorAction);
retranslateUi(); retranslateUi();
} }
@ -94,41 +96,41 @@ void LibraryMenu::resetTopCardMenuActions()
void LibraryMenu::createDrawActions() void LibraryMenu::createDrawActions()
{ {
PlayerActions *playerActions = player->getPlayerActions(); PlayerActions *playerActions = player->getLogic()->getPlayerActions();
if (player->getPlayerInfo()->local || player->getPlayerInfo()->judge) { if (player->getLogic()->getPlayerInfo()->local || player->getLogic()->getPlayerInfo()->judge) {
aDrawCard = new QAction(this); aDrawCard = new QAction(this);
connect(aDrawCard, &QAction::triggered, playerActions, &PlayerActions::actDrawCard); connect(aDrawCard, &QAction::triggered, playerActions, &PlayerActions::actDrawCard);
aDrawCards = new QAction(this); aDrawCards = new QAction(this);
connect(aDrawCards, &QAction::triggered, playerActions, &PlayerActions::actDrawCards); connect(aDrawCards, &QAction::triggered, playerActions, &PlayerActions::actRequestDrawCardsDialog);
aUndoDraw = new QAction(this); aUndoDraw = new QAction(this);
connect(aUndoDraw, &QAction::triggered, playerActions, &PlayerActions::actUndoDraw); connect(aUndoDraw, &QAction::triggered, playerActions, &PlayerActions::actUndoDraw);
aDrawBottomCard = new QAction(this); aDrawBottomCard = new QAction(this);
connect(aDrawBottomCard, &QAction::triggered, playerActions, &PlayerActions::actDrawBottomCard); connect(aDrawBottomCard, &QAction::triggered, playerActions, &PlayerActions::actDrawBottomCard);
aDrawBottomCards = new QAction(this); aDrawBottomCards = new QAction(this);
connect(aDrawBottomCards, &QAction::triggered, playerActions, &PlayerActions::actDrawBottomCards); connect(aDrawBottomCards, &QAction::triggered, playerActions, &PlayerActions::actRequestDrawBottomCardsDialog);
} }
} }
void LibraryMenu::createShuffleActions() void LibraryMenu::createShuffleActions()
{ {
PlayerActions *playerActions = player->getPlayerActions(); PlayerActions *playerActions = player->getLogic()->getPlayerActions();
if (player->getPlayerInfo()->local || player->getPlayerInfo()->judge) { if (player->getLogic()->getPlayerInfo()->local || player->getLogic()->getPlayerInfo()->judge) {
aShuffle = new QAction(this); aShuffle = new QAction(this);
connect(aShuffle, &QAction::triggered, playerActions, &PlayerActions::actShuffle); connect(aShuffle, &QAction::triggered, playerActions, &PlayerActions::actShuffle);
aShuffleTopCards = new QAction(this); aShuffleTopCards = new QAction(this);
connect(aShuffleTopCards, &QAction::triggered, playerActions, &PlayerActions::actShuffleTop); connect(aShuffleTopCards, &QAction::triggered, playerActions, &PlayerActions::actRequestShuffleTopDialog);
aShuffleBottomCards = new QAction(this); aShuffleBottomCards = new QAction(this);
connect(aShuffleBottomCards, &QAction::triggered, playerActions, &PlayerActions::actShuffleBottom); connect(aShuffleBottomCards, &QAction::triggered, playerActions, &PlayerActions::actRequestShuffleBottomDialog);
} }
} }
void LibraryMenu::createMoveActions() void LibraryMenu::createMoveActions()
{ {
PlayerActions *playerActions = player->getPlayerActions(); PlayerActions *playerActions = player->getLogic()->getPlayerActions();
if (player->getPlayerInfo()->local || player->getPlayerInfo()->judge) { if (player->getLogic()->getPlayerInfo()->local || player->getLogic()->getPlayerInfo()->judge) {
aMoveTopToPlay = new QAction(this); aMoveTopToPlay = new QAction(this);
connect(aMoveTopToPlay, &QAction::triggered, playerActions, &PlayerActions::actMoveTopCardToPlay); connect(aMoveTopToPlay, &QAction::triggered, playerActions, &PlayerActions::actMoveTopCardToPlay);
aMoveTopToPlayFaceDown = new QAction(this); aMoveTopToPlayFaceDown = new QAction(this);
@ -149,7 +151,8 @@ void LibraryMenu::createMoveActions()
connect(aMoveTopCardsToExileFaceDown, &QAction::triggered, playerActions, connect(aMoveTopCardsToExileFaceDown, &QAction::triggered, playerActions,
&PlayerActions::actMoveTopCardsToExileFaceDown); &PlayerActions::actMoveTopCardsToExileFaceDown);
aMoveTopCardsUntil = new QAction(this); aMoveTopCardsUntil = new QAction(this);
connect(aMoveTopCardsUntil, &QAction::triggered, playerActions, &PlayerActions::actMoveTopCardsUntil); connect(aMoveTopCardsUntil, &QAction::triggered, playerActions,
&PlayerActions::actRequestMoveTopCardsUntilDialog);
aMoveTopCardToBottom = new QAction(this); aMoveTopCardToBottom = new QAction(this);
connect(aMoveTopCardToBottom, &QAction::triggered, playerActions, &PlayerActions::actMoveTopCardToBottom); connect(aMoveTopCardToBottom, &QAction::triggered, playerActions, &PlayerActions::actMoveTopCardToBottom);
@ -181,16 +184,16 @@ void LibraryMenu::createMoveActions()
void LibraryMenu::createViewActions() void LibraryMenu::createViewActions()
{ {
PlayerActions *playerActions = player->getPlayerActions(); PlayerActions *playerActions = player->getLogic()->getPlayerActions();
if (player->getPlayerInfo()->local || player->getPlayerInfo()->judge) { if (player->getLogic()->getPlayerInfo()->local || player->getLogic()->getPlayerInfo()->judge) {
aViewLibrary = new QAction(this); aViewLibrary = new QAction(this);
connect(aViewLibrary, &QAction::triggered, playerActions, &PlayerActions::actViewLibrary); connect(aViewLibrary, &QAction::triggered, playerActions, &PlayerActions::actViewLibrary);
aViewTopCards = new QAction(this); aViewTopCards = new QAction(this);
connect(aViewTopCards, &QAction::triggered, playerActions, &PlayerActions::actViewTopCards); connect(aViewTopCards, &QAction::triggered, playerActions, &PlayerActions::actRequestViewTopCardsDialog);
aViewBottomCards = new QAction(this); aViewBottomCards = new QAction(this);
connect(aViewBottomCards, &QAction::triggered, playerActions, &PlayerActions::actViewBottomCards); connect(aViewBottomCards, &QAction::triggered, playerActions, &PlayerActions::actRequestViewBottomCardsDialog);
aAlwaysRevealTopCard = new QAction(this); aAlwaysRevealTopCard = new QAction(this);
aAlwaysRevealTopCard->setCheckable(true); aAlwaysRevealTopCard->setCheckable(true);
connect(aAlwaysRevealTopCard, &QAction::triggered, playerActions, &PlayerActions::actAlwaysRevealTopCard); connect(aAlwaysRevealTopCard, &QAction::triggered, playerActions, &PlayerActions::actAlwaysRevealTopCard);
@ -207,7 +210,7 @@ void LibraryMenu::retranslateUi()
{ {
setTitle(tr("&Library")); setTitle(tr("&Library"));
if (player->getPlayerInfo()->getLocalOrJudge()) { if (player->getLogic()->getPlayerInfo()->getLocalOrJudge()) {
aViewLibrary->setText(tr("&View library")); aViewLibrary->setText(tr("&View library"));
aViewTopCards->setText(tr("View &top cards of library...")); aViewTopCards->setText(tr("View &top cards of library..."));
aViewBottomCards->setText(tr("View bottom cards of library...")); aViewBottomCards->setText(tr("View bottom cards of library..."));
@ -263,9 +266,9 @@ void LibraryMenu::populateRevealLibraryMenuWithActivePlayers()
mRevealLibrary->addSeparator(); mRevealLibrary->addSeparator();
const auto &players = player->getGame()->getPlayerManager()->getPlayers().values(); const auto &players = player->getLogic()->getGame()->getPlayerManager()->getPlayers().values();
for (auto *other : players) { for (auto *other : players) {
if (other == player) { if (other == player->getLogic()) {
continue; continue;
} }
QAction *a = mRevealLibrary->addAction(other->getPlayerInfo()->getName()); QAction *a = mRevealLibrary->addAction(other->getPlayerInfo()->getName());
@ -278,9 +281,9 @@ void LibraryMenu::populateLendLibraryMenuWithActivePlayers()
{ {
mLendLibrary->clear(); mLendLibrary->clear();
const auto &players = player->getGame()->getPlayerManager()->getPlayers().values(); const auto &players = player->getLogic()->getGame()->getPlayerManager()->getPlayers().values();
for (auto *other : players) { for (auto *other : players) {
if (other == player) { if (other == player->getLogic()) {
continue; continue;
} }
QAction *a = mLendLibrary->addAction(other->getPlayerInfo()->getName()); QAction *a = mLendLibrary->addAction(other->getPlayerInfo()->getName());
@ -299,9 +302,9 @@ void LibraryMenu::populateRevealTopCardMenuWithActivePlayers()
mRevealTopCard->addSeparator(); mRevealTopCard->addSeparator();
const auto &players = player->getGame()->getPlayerManager()->getPlayers().values(); const auto &players = player->getLogic()->getGame()->getPlayerManager()->getPlayers().values();
for (auto *other : players) { for (auto *other : players) {
if (other == player) { if (other == player->getLogic()) {
continue; continue;
} }
QAction *a = mRevealTopCard->addAction(other->getPlayerInfo()->getName()); QAction *a = mRevealTopCard->addAction(other->getPlayerInfo()->getName());
@ -313,27 +316,33 @@ void LibraryMenu::populateRevealTopCardMenuWithActivePlayers()
void LibraryMenu::onRevealLibraryTriggered() void LibraryMenu::onRevealLibraryTriggered()
{ {
if (auto *a = qobject_cast<QAction *>(sender())) { if (auto *a = qobject_cast<QAction *>(sender())) {
player->getPlayerActions()->actRevealLibrary(a->data().toInt()); player->getLogic()->getPlayerActions()->actRevealLibrary(a->data().toInt());
} }
} }
void LibraryMenu::onLendLibraryTriggered() void LibraryMenu::onLendLibraryTriggered()
{ {
if (auto *a = qobject_cast<QAction *>(sender())) { if (auto *a = qobject_cast<QAction *>(sender())) {
player->getPlayerActions()->actLendLibrary(a->data().toInt()); player->getLogic()->getPlayerActions()->actLendLibrary(a->data().toInt());
} }
} }
void LibraryMenu::onRevealTopCardTriggered() void LibraryMenu::onRevealTopCardTriggered()
{ {
QWidget *parent = nullptr;
if (auto *view = player->scene() ? player->scene()->views().value(0) : nullptr) {
parent = view->window();
}
if (auto *a = qobject_cast<QAction *>(sender())) { if (auto *a = qobject_cast<QAction *>(sender())) {
int deckSize = player->getDeckZone()->getCards().size();
bool ok; int deckSize = player->getLogic()->getDeckZone()->getCards().size();
int number = QInputDialog::getInt(player->getGame()->getTab(), tr("Reveal top cards of library"), bool ok = true;
int number = QInputDialog::getInt(parent, tr("Reveal top cards of library"),
tr("Number of cards: (max. %1)").arg(deckSize), defaultNumberTopCards, 1, tr("Number of cards: (max. %1)").arg(deckSize), defaultNumberTopCards, 1,
deckSize, 1, &ok); deckSize, 1, &ok);
if (ok) { if (ok) {
player->getPlayerActions()->actRevealTopCards(a->data().toInt(), number); player->getLogic()->getPlayerActions()->actRevealTopCards(a->data().toInt(), number);
defaultNumberTopCards = number; defaultNumberTopCards = number;
} }
} }

View file

@ -13,6 +13,7 @@
#include <QAction> #include <QAction>
#include <QMenu> #include <QMenu>
class PlayerGraphicsItem;
class PlayerLogic; class PlayerLogic;
class PlayerActions; class PlayerActions;
@ -24,7 +25,7 @@ public slots:
void resetTopCardMenuActions(); void resetTopCardMenuActions();
public: public:
LibraryMenu(PlayerLogic *player, QWidget *parent = nullptr); LibraryMenu(PlayerGraphicsItem *player, QWidget *parent = nullptr);
void createDrawActions(); void createDrawActions();
void createShuffleActions(); void createShuffleActions();
void createMoveActions(); void createMoveActions();
@ -111,7 +112,7 @@ public:
int defaultNumberTopCards = 1; int defaultNumberTopCards = 1;
private: private:
PlayerLogic *player; PlayerGraphicsItem *player;
}; };
#endif // COCKATRICE_LIBRARY_MENU_H #endif // COCKATRICE_LIBRARY_MENU_H

View file

@ -1,10 +1,11 @@
#include "move_menu.h" #include "move_menu.h"
#include "../../game/player/player_actions.h"
#include "../../game/player/player_logic.h"
#include "../card_menu_action_type.h" #include "../card_menu_action_type.h"
#include "../player_actions.h" #include "../player_graphics_item.h"
#include "../player_logic.h"
MoveMenu::MoveMenu(PlayerLogic *player) : QMenu(tr("Move to")) MoveMenu::MoveMenu(PlayerGraphicsItem *player) : QMenu(tr("Move to"))
{ {
aMoveToTopLibrary = new QAction(this); aMoveToTopLibrary = new QAction(this);
aMoveToTopLibrary->setData(cmMoveToTopLibrary); aMoveToTopLibrary->setData(cmMoveToTopLibrary);
@ -20,14 +21,22 @@ MoveMenu::MoveMenu(PlayerLogic *player) : QMenu(tr("Move to"))
aMoveToExile = new QAction(this); aMoveToExile = new QAction(this);
aMoveToExile->setData(cmMoveToExile); aMoveToExile->setData(cmMoveToExile);
connect(aMoveToTopLibrary, &QAction::triggered, player->getPlayerActions(), &PlayerActions::cardMenuAction); auto *actions = player->getLogic()->getPlayerActions();
connect(aMoveToBottomLibrary, &QAction::triggered, player->getPlayerActions(), &PlayerActions::cardMenuAction);
connect(aMoveToXfromTopOfLibrary, &QAction::triggered, player->getPlayerActions(), auto invoke = [player](CardMenuActionType type) {
&PlayerActions::actMoveCardXCardsFromTop); return [type, player]() {
connect(aMoveToTable, &QAction::triggered, player->getPlayerActions(), &PlayerActions::cardMenuAction); player->getLogic()->getPlayerActions()->cardMenuAction(player->getGameScene()->selectedCards(), type);
connect(aMoveToHand, &QAction::triggered, player->getPlayerActions(), &PlayerActions::cardMenuAction); };
connect(aMoveToGraveyard, &QAction::triggered, player->getPlayerActions(), &PlayerActions::cardMenuAction); };
connect(aMoveToExile, &QAction::triggered, player->getPlayerActions(), &PlayerActions::cardMenuAction);
connect(aMoveToTopLibrary, &QAction::triggered, actions, invoke(cmMoveToTopLibrary));
connect(aMoveToBottomLibrary, &QAction::triggered, actions, invoke(cmMoveToBottomLibrary));
connect(aMoveToXfromTopOfLibrary, &QAction::triggered, actions,
&PlayerActions::actRequestMoveCardXCardsFromTopDialog);
connect(aMoveToTable, &QAction::triggered, actions, invoke(cmMoveToTable));
connect(aMoveToHand, &QAction::triggered, actions, invoke(cmMoveToHand));
connect(aMoveToGraveyard, &QAction::triggered, actions, invoke(cmMoveToGraveyard));
connect(aMoveToExile, &QAction::triggered, actions, invoke(cmMoveToExile));
addAction(aMoveToTopLibrary); addAction(aMoveToTopLibrary);
addAction(aMoveToXfromTopOfLibrary); addAction(aMoveToXfromTopOfLibrary);

View file

@ -8,13 +8,13 @@
#define COCKATRICE_MOVE_MENU_H #define COCKATRICE_MOVE_MENU_H
#include <QMenu> #include <QMenu>
class PlayerLogic; class PlayerGraphicsItem;
class MoveMenu : public QMenu class MoveMenu : public QMenu
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit MoveMenu(PlayerLogic *player); explicit MoveMenu(PlayerGraphicsItem *player);
void setShortcutsActive(); void setShortcutsActive();
void retranslateUi(); void retranslateUi();

View file

@ -5,17 +5,21 @@
#include "../../../game_graphics/zones/table_zone.h" #include "../../../game_graphics/zones/table_zone.h"
#include "../../../interface/widgets/tabs/tab_game.h" #include "../../../interface/widgets/tabs/tab_game.h"
#include "../../board/card_item.h" #include "../../board/card_item.h"
#include "../player_graphics_item.h"
#include "card_menu.h" #include "card_menu.h"
#include "hand_menu.h" #include "hand_menu.h"
#include <libcockatrice/protocol/pb/command_reveal_cards.pb.h> #include <libcockatrice/protocol/pb/command_reveal_cards.pb.h>
PlayerMenu::PlayerMenu(PlayerLogic *_player) : QObject(_player), player(_player) PlayerMenu::PlayerMenu(PlayerGraphicsItem *_player) : QObject(_player), player(_player)
{ {
connect(player->getLogic(), &PlayerLogic::requestCardMenuUpdate, this, &PlayerMenu::updateCardMenu);
connect(this, &PlayerMenu::cardInfoRequested, player, &PlayerGraphicsItem::cardInfoRequested);
playerMenu = new TearOffMenu(); playerMenu = new TearOffMenu();
if (player->getPlayerInfo()->getLocalOrJudge()) { if (player->getLogic()->getPlayerInfo()->getLocalOrJudge()) {
handMenu = addManagedMenu<HandMenu>(player, player->getPlayerActions(), playerMenu); handMenu = addManagedMenu<HandMenu>(player, playerMenu);
libraryMenu = addManagedMenu<LibraryMenu>(player, playerMenu); libraryMenu = addManagedMenu<LibraryMenu>(player, playerMenu);
} else { } else {
handMenu = nullptr; handMenu = nullptr;
@ -25,7 +29,7 @@ PlayerMenu::PlayerMenu(PlayerLogic *_player) : QObject(_player), player(_player)
graveMenu = addManagedMenu<GraveyardMenu>(player, playerMenu); graveMenu = addManagedMenu<GraveyardMenu>(player, playerMenu);
rfgMenu = addManagedMenu<RfgMenu>(player, playerMenu); rfgMenu = addManagedMenu<RfgMenu>(player, playerMenu);
if (player->getPlayerInfo()->getLocalOrJudge()) { if (player->getLogic()->getPlayerInfo()->getLocalOrJudge()) {
sideboardMenu = addManagedMenu<SideboardMenu>(player, playerMenu); sideboardMenu = addManagedMenu<SideboardMenu>(player, playerMenu);
customZonesMenu = addManagedMenu<CustomZoneMenu>(player); customZonesMenu = addManagedMenu<CustomZoneMenu>(player);
playerMenu->addSeparator(); playerMenu->addSeparator();
@ -40,7 +44,7 @@ PlayerMenu::PlayerMenu(PlayerLogic *_player) : QObject(_player), player(_player)
utilityMenu = nullptr; utilityMenu = nullptr;
} }
if (player->getPlayerInfo()->getLocal()) { if (player->getLogic()->getPlayerInfo()->getLocal()) {
sayMenu = addManagedMenu<SayMenu>(player); sayMenu = addManagedMenu<SayMenu>(player);
} else { } else {
sayMenu = nullptr; sayMenu = nullptr;
@ -55,13 +59,13 @@ PlayerMenu::PlayerMenu(PlayerLogic *_player) : QObject(_player), player(_player)
void PlayerMenu::setMenusForGraphicItems() void PlayerMenu::setMenusForGraphicItems()
{ {
player->getGraphicsItem()->getTableZoneGraphicsItem()->setMenu(playerMenu); player->getTableZoneGraphicsItem()->setMenu(playerMenu);
player->getGraphicsItem()->getGraveyardZoneGraphicsItem()->setMenu(graveMenu, graveMenu->aViewGraveyard); player->getGraveyardZoneGraphicsItem()->setMenu(graveMenu, graveMenu->aViewGraveyard);
player->getGraphicsItem()->getRfgZoneGraphicsItem()->setMenu(rfgMenu, rfgMenu->aViewRfg); player->getRfgZoneGraphicsItem()->setMenu(rfgMenu, rfgMenu->aViewRfg);
if (player->getPlayerInfo()->getLocalOrJudge()) { if (player->getLogic()->getPlayerInfo()->getLocalOrJudge()) {
player->getGraphicsItem()->getHandZoneGraphicsItem()->setMenu(handMenu); player->getHandZoneGraphicsItem()->setMenu(handMenu);
player->getGraphicsItem()->getDeckZoneGraphicsItem()->setMenu(libraryMenu, libraryMenu->aDrawCard); player->getDeckZoneGraphicsItem()->setMenu(libraryMenu, libraryMenu->aDrawCard);
player->getGraphicsItem()->getSideboardZoneGraphicsItem()->setMenu(sideboardMenu); player->getSideboardZoneGraphicsItem()->setMenu(sideboardMenu);
} }
} }
@ -74,12 +78,14 @@ QMenu *PlayerMenu::updateCardMenu(const CardItem *card)
// If is spectator (as spectators don't need card menus), return // If is spectator (as spectators don't need card menus), return
// only update the menu if the card is actually selected // only update the menu if the card is actually selected
if ((player->getGame()->getPlayerManager()->isSpectator() && !player->getGame()->getPlayerManager()->isJudge()) || if ((player->getLogic()->getGame()->getPlayerManager()->isSpectator() &&
player->getGame()->getActiveCard() != card) { !player->getLogic()->getGame()->getPlayerManager()->isJudge()) ||
player->getLogic()->getGame()->getActiveCard() != card) {
return nullptr; return nullptr;
} }
QMenu *menu = new CardMenu(player, card, shortcutsActive); CardMenu *menu = new CardMenu(player, card, shortcutsActive);
connect(menu, &CardMenu::cardInfoRequested, this, &PlayerMenu::cardInfoRequested);
emit cardMenuUpdated(menu); emit cardMenuUpdated(menu);
return menu; return menu;
@ -87,7 +93,7 @@ QMenu *PlayerMenu::updateCardMenu(const CardItem *card)
void PlayerMenu::retranslateUi() void PlayerMenu::retranslateUi()
{ {
playerMenu->setTitle(tr("Player \"%1\"").arg(player->getPlayerInfo()->getName())); playerMenu->setTitle(tr("Player \"%1\"").arg(player->getLogic()->getPlayerInfo()->getName()));
for (auto *component : managedComponents) { for (auto *component : managedComponents) {
component->retranslateUi(); component->retranslateUi();
@ -104,7 +110,8 @@ void PlayerMenu::refreshShortcuts()
{ {
if (shortcutsActive) { if (shortcutsActive) {
// Judges get access to every player's menus but only want shortcuts to be set for their own. // Judges get access to every player's menus but only want shortcuts to be set for their own.
if (player->getPlayerInfo()->getLocalOrJudge() && !player->getPlayerInfo()->getLocal()) { if (player->getLogic()->getPlayerInfo()->getLocalOrJudge() &&
!player->getLogic()->getPlayerInfo()->getLocal()) {
setShortcutsInactive(); setShortcutsInactive();
} else { } else {
setShortcutsActive(); setShortcutsActive();

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