Compare commits

...

47 commits

Author SHA1 Message Date
ebbit1q
b1fe4c85d3
fix sending decks to tappedout (#6832)
Some checks failed
Build Desktop / Configure (push) Has been cancelled
Build Docker Image / amd64 & arm64 (push) Has been cancelled
Build Desktop / Debian 11 (push) Has been cancelled
Build Desktop / Debian 13 (push) Has been cancelled
Build Desktop / Debian 12 (push) Has been cancelled
Build Desktop / Fedora 43 (push) Has been cancelled
Build Desktop / Fedora 42 (push) Has been cancelled
Build Desktop / Servatrice_Debian 11 (push) Has been cancelled
Build Desktop / Ubuntu 24.04 (push) Has been cancelled
Build Desktop / Ubuntu 26.04 (push) Has been cancelled
Build Desktop / Ubuntu 22.04 (push) Has been cancelled
Build Desktop / Arch (push) Has been cancelled
Build Desktop / macOS 14 (push) Has been cancelled
Build Desktop / macOS 15 (push) Has been cancelled
Build Desktop / macOS 13 Intel (push) Has been cancelled
Build Desktop / macOS 15 Debug (push) Has been cancelled
Build Desktop / Windows 10 (push) Has been cancelled
2026-04-23 02:28:12 +02:00
Vorliz
a20f3c0fb4
Fix #6659: Correct logging for bottom-of-library card moves (#6764)
Some checks failed
Build Desktop / Configure (push) Has been cancelled
Build Docker Image / amd64 & arm64 (push) Has been cancelled
Build Desktop / Debian 11 (push) Has been cancelled
Build Desktop / Debian 13 (push) Has been cancelled
Build Desktop / Debian 12 (push) Has been cancelled
Build Desktop / Fedora 43 (push) Has been cancelled
Build Desktop / Fedora 42 (push) Has been cancelled
Build Desktop / Servatrice_Debian 11 (push) Has been cancelled
Build Desktop / Ubuntu 24.04 (push) Has been cancelled
Build Desktop / Ubuntu 26.04 (push) Has been cancelled
Build Desktop / Ubuntu 22.04 (push) Has been cancelled
Build Desktop / Arch (push) Has been cancelled
Build Desktop / macOS 14 (push) Has been cancelled
Build Desktop / macOS 15 (push) Has been cancelled
Build Desktop / macOS 13 Intel (push) Has been cancelled
Build Desktop / macOS 15 Debug (push) Has been cancelled
Build Desktop / Windows 10 (push) Has been cancelled
* Fix #6659:  Correct logging for bottom-of-library card moves

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

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

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

* chore: run format on test

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

* refactor: change isFromBottom check to static function

* update comments

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

---------

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

Took 3 seconds

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

Took 13 minutes


Took 41 seconds

Took 15 seconds

* Change button style.

Took 24 minutes

Took 4 seconds

---------

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

Took 2 minutes

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

Took 15 minutes

* default to false, actually

Took 2 minutes

---------

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

Took 23 minutes

* Small fix.

Took 3 minutes

---------

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

Took 26 minutes

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-04-15 14:14:30 +02:00
ebbit1q
f97864b72b
set the minimum qt6 version to 6.4 (#6811)
Some checks failed
Build Desktop / Configure (push) Has been cancelled
Build Docker Image / amd64 & arm64 (push) Has been cancelled
Build Desktop / Debian 11 (push) Has been cancelled
Build Desktop / Debian 13 (push) Has been cancelled
Build Desktop / Debian 12 (push) Has been cancelled
Build Desktop / Fedora 43 (push) Has been cancelled
Build Desktop / Fedora 42 (push) Has been cancelled
Build Desktop / Servatrice_Debian 11 (push) Has been cancelled
Build Desktop / Ubuntu 24.04 (push) Has been cancelled
Build Desktop / Ubuntu 26.04 (push) Has been cancelled
Build Desktop / Ubuntu 22.04 (push) Has been cancelled
Build Desktop / Arch (push) Has been cancelled
Build Desktop / macOS 14 (push) Has been cancelled
Build Desktop / macOS 15 (push) Has been cancelled
Build Desktop / macOS 13 Intel (push) Has been cancelled
Build Desktop / macOS 15 Debug (push) Has been cancelled
Build Desktop / Windows 10 (push) Has been cancelled
* set the minimum qt6 version to 6.4

this will be separate from the qt5 removal after release

* add include for optional
2026-04-14 00:59:46 +02:00
ebbit1q
338c56678a
add workaround for windows11 theme (#6810)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 11 (push) Blocked by required conditions
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Fedora 42 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 11 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 22.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
2026-04-13 14:49:31 +02:00
Jeremy Letto
8cc65b8967
Initial implementation completion and refactor (#6806)
Some checks failed
Build Desktop / Configure (push) Has been cancelled
Build Docker Image / amd64 & arm64 (push) Has been cancelled
Build Web / React (Node 16) (push) Has been cancelled
Build Web / React (Node lts/*) (push) Has been cancelled
Build Desktop / Debian 11 (push) Has been cancelled
Build Desktop / Debian 13 (push) Has been cancelled
Build Desktop / Debian 12 (push) Has been cancelled
Build Desktop / Fedora 43 (push) Has been cancelled
Build Desktop / Fedora 42 (push) Has been cancelled
Build Desktop / Servatrice_Debian 11 (push) Has been cancelled
Build Desktop / Ubuntu 24.04 (push) Has been cancelled
Build Desktop / Ubuntu 26.04 (push) Has been cancelled
Build Desktop / Ubuntu 22.04 (push) Has been cancelled
Build Desktop / Arch (push) Has been cancelled
Build Desktop / macOS 14 (push) Has been cancelled
Build Desktop / macOS 15 (push) Has been cancelled
Build Desktop / macOS 13 Intel (push) Has been cancelled
Build Desktop / macOS 15 Debug (push) Has been cancelled
Build Desktop / Windows 10 (push) Has been cancelled
2026-04-11 23:51:10 -04:00
ebbit1q
2e10b2f5d5
make general settings scrollable (#6800) 2026-04-12 02:38:04 +02:00
ebbit1q
92fe406c22
remove break statement when closing views (#6801)
it's possible that multiple views need to be closed
this is an oversight of #4570
2026-04-12 02:37:43 +02:00
tooomm
d677e2bb70
Print success msg if cache deletion did succeed (#6802) 2026-04-12 02:37:30 +02:00
ebbit1q
e977f123ce
add ccache eviction for files older than 7 days (#6795)
* add ccache eviction for files older than 7 days

also clean up some of the scripts to be more internally consistent

* move name build into the docker container again

* remove one extra empty line [skip ci]

* allow canceling concurrent builds on master for both desktop and docker

* add ccache eviction age to macos as well
2026-04-11 19:18:12 +02:00
tooomm
f56b672307
CI: Allow failing of ccache deletion step (#6799)
* Don't fail cache delete step

* Use `continue-on-error`

* remove leftover
2026-04-11 18:20:35 +02:00
tooomm
29c1d7f3e4
add timeout to job matrixes (#6797) 2026-04-11 18:19:11 +02:00
ebbit1q
36aba81b1b
adds eternal to prioritisation list (#6793)
Some checks failed
Build Desktop / Configure (push) Has been cancelled
Build Docker Image / amd64 & arm64 (push) Has been cancelled
Build Desktop / Debian 11 (push) Has been cancelled
Build Desktop / Debian 13 (push) Has been cancelled
Build Desktop / Debian 12 (push) Has been cancelled
Build Desktop / Fedora 43 (push) Has been cancelled
Build Desktop / Fedora 42 (push) Has been cancelled
Build Desktop / Servatrice_Debian 11 (push) Has been cancelled
Build Desktop / Ubuntu 24.04 (push) Has been cancelled
Build Desktop / Ubuntu 26.04 (push) Has been cancelled
Build Desktop / Ubuntu 22.04 (push) Has been cancelled
Build Desktop / Arch (push) Has been cancelled
Build Desktop / macOS 14 (push) Has been cancelled
Build Desktop / macOS 15 (push) Has been cancelled
Build Desktop / macOS 13 Intel (push) Has been cancelled
Build Desktop / macOS 15 Debug (push) Has been cancelled
Build Desktop / Windows 10 (push) Has been cancelled
2026-04-10 18:18:08 +02:00
ebbit1q
f9fb03b26b
update all runners to use qt6.11 (#6794)
* update all runners to use qt6.11

* use aqt version directly from repo
2026-04-10 18:17:43 +02:00
ebbit1q
d2732ac742
fix prepare cards (#6792)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 11 (push) Blocked by required conditions
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Fedora 42 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 11 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 22.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
2026-04-10 13:42:05 +02:00
transifex-integration[bot]
9aa5702e14
Translate cockatrice_en@source.ts in en_US (#6783)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 11 (push) Blocked by required conditions
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Fedora 42 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 11 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 22.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
100% translated source file: 'cockatrice_en@source.ts'
on 'en_US'.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2026-04-09 21:27:14 +02:00
ebbit1q
2d412bfe52
fix cardloading on qt5 (#6790) 2026-04-09 21:16:38 +02:00
RickyRister
ac06fb9d1c
[Game] Fix crash by properly parenting QObjects (#6788)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 11 (push) Blocked by required conditions
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Fedora 42 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 11 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 22.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
2026-04-09 15:02:00 +02:00
BruebachL
d7b31f2f9d
Set OracleWizard style to "Modern" instead of "Aero" (#6778)
Some checks failed
Build Desktop / Configure (push) Has been cancelled
Build Docker Image / amd64 & arm64 (push) Has been cancelled
Build Desktop / Debian 11 (push) Has been cancelled
Build Desktop / Debian 13 (push) Has been cancelled
Build Desktop / Debian 12 (push) Has been cancelled
Build Desktop / Fedora 43 (push) Has been cancelled
Build Desktop / Fedora 42 (push) Has been cancelled
Build Desktop / Servatrice_Debian 11 (push) Has been cancelled
Build Desktop / Ubuntu 24.04 (push) Has been cancelled
Build Desktop / Ubuntu 26.04 (push) Has been cancelled
Build Desktop / Ubuntu 22.04 (push) Has been cancelled
Build Desktop / Arch (push) Has been cancelled
Build Desktop / macOS 14 (push) Has been cancelled
Build Desktop / macOS 15 (push) Has been cancelled
Build Desktop / macOS 13 Intel (push) Has been cancelled
Build Desktop / macOS 15 Debug (push) Has been cancelled
Build Desktop / Windows 10 (push) Has been cancelled
* Use fusions own palette.

Took 6 minutes

* Start from default palette always.

Took 4 minutes

* Add modern style.

Took 24 seconds

* Scope this fix to Windows.

Took 4 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-04-07 15:58:42 +02:00
BruebachL
635fae101d
Use palette colors for resizable panel stylesheets. (#6782)
Took 7 minutes

Took 5 seconds

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-04-07 15:57:03 +02:00
BruebachL
335022c4aa
Include themes (#6781)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 11 (push) Blocked by required conditions
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Fedora 42 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 11 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 22.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
* Properly reset default theme.

Took 9 minutes

* Descend in preference on windows.

Took 9 minutes

* Also reset style for custom themes.

Took 3 minutes

* Try things

Took 9 minutes

* Add modern windows style.

Took 8 minutes

* Update theme_manager.cpp

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-04-07 15:02:00 +02:00
Bruno Alexandre Rosa
dbed6890da
feat: support expressions when setting card counters (#6753)
* feat: enable expressions in card counters

* fix includes

* fix multiple selection

* cleanup useless conversions

* const ref where possible

* do not use const, be consistent with local patterns in the file
2026-04-06 22:36:45 -07:00
ebbit1q
3ec9ae9772
fix compiling on arch (#6768)
Some checks failed
Build Desktop / Configure (push) Has been cancelled
Build Docker Image / amd64 & arm64 (push) Has been cancelled
Build Desktop / Debian 11 (push) Has been cancelled
Build Desktop / Debian 13 (push) Has been cancelled
Build Desktop / Debian 12 (push) Has been cancelled
Build Desktop / Fedora 43 (push) Has been cancelled
Build Desktop / Fedora 42 (push) Has been cancelled
Build Desktop / Servatrice_Debian 11 (push) Has been cancelled
Build Desktop / Ubuntu 24.04 (push) Has been cancelled
Build Desktop / Ubuntu 26.04 (push) Has been cancelled
Build Desktop / Ubuntu 22.04 (push) Has been cancelled
Build Desktop / Arch (push) Has been cancelled
Build Desktop / macOS 14 (push) Has been cancelled
Build Desktop / macOS 15 (push) Has been cancelled
Build Desktop / macOS 13 Intel (push) Has been cancelled
Build Desktop / macOS 15 Debug (push) Has been cancelled
Build Desktop / Windows 10 (push) Has been cancelled
* fix compiling on arch

* redo all the logging in affected files
2026-04-05 21:52:46 +02:00
RickyRister
a46ab5cd68
[Game] Allow uppercase X in expressions (#6767)
Some checks failed
Build Desktop / Configure (push) Has been cancelled
Build Docker Image / amd64 & arm64 (push) Has been cancelled
Build Desktop / Debian 11 (push) Has been cancelled
Build Desktop / Debian 13 (push) Has been cancelled
Build Desktop / Debian 12 (push) Has been cancelled
Build Desktop / Fedora 43 (push) Has been cancelled
Build Desktop / Fedora 42 (push) Has been cancelled
Build Desktop / Servatrice_Debian 11 (push) Has been cancelled
Build Desktop / Ubuntu 24.04 (push) Has been cancelled
Build Desktop / Ubuntu 26.04 (push) Has been cancelled
Build Desktop / Ubuntu 22.04 (push) Has been cancelled
Build Desktop / Arch (push) Has been cancelled
Build Desktop / macOS 14 (push) Has been cancelled
Build Desktop / macOS 15 (push) Has been cancelled
Build Desktop / macOS 13 Intel (push) Has been cancelled
Build Desktop / macOS 15 Debug (push) Has been cancelled
Build Desktop / Windows 10 (push) Has been cancelled
2026-04-04 11:40:38 +02:00
SlightlyCircuitous
690a00aa6c
Add Ubuntu 26.04 "Resolute Racoon" build (#6766)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 11 (push) Blocked by required conditions
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Fedora 42 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 11 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 22.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
* Create 26.04 Dockerfile

* Update desktop-build.yml

* Update release_template.md

* Add ca-certificates package to build
2026-04-03 20:40:42 +02:00
tooomm
ba786a0289
Update dependabot.yml (#6756)
Some checks failed
Build Docker Image / amd64 & arm64 (push) Has been cancelled
2026-04-01 14:54:27 +02:00
github-actions[bot]
9dfac77ba2
Update translation source strings (#6762)
Some checks failed
Build Docker Image / amd64 & arm64 (push) Waiting to run
Build Desktop / Configure (push) Has been cancelled
Build Desktop / Debian 11 (push) Has been cancelled
Build Desktop / Debian 13 (push) Has been cancelled
Build Desktop / Debian 12 (push) Has been cancelled
Build Desktop / Fedora 43 (push) Has been cancelled
Build Desktop / Fedora 42 (push) Has been cancelled
Build Desktop / Servatrice_Debian 11 (push) Has been cancelled
Build Desktop / Ubuntu 22.04 (push) Has been cancelled
Build Desktop / Ubuntu 24.04 (push) Has been cancelled
Build Desktop / Arch (push) Has been cancelled
Build Desktop / macOS 14 (push) Has been cancelled
Build Desktop / macOS 15 (push) Has been cancelled
Build Desktop / macOS 13 Intel (push) Has been cancelled
Build Desktop / macOS 15 Debug (push) Has been cancelled
Build Desktop / Windows 10 (push) Has been cancelled
2026-04-01 05:49:17 +02:00
dependabot[bot]
f3370a4f52
Bump doc/doxygen/theme from 1f36200 to d52eafe (#6751)
Some checks failed
Build Desktop / Configure (push) Has been cancelled
Build Docker Image / amd64 & arm64 (push) Has been cancelled
Build Desktop / Debian 11 (push) Has been cancelled
Build Desktop / Debian 13 (push) Has been cancelled
Build Desktop / Debian 12 (push) Has been cancelled
Build Desktop / Fedora 43 (push) Has been cancelled
Build Desktop / Fedora 42 (push) Has been cancelled
Build Desktop / Servatrice_Debian 11 (push) Has been cancelled
Build Desktop / Ubuntu 22.04 (push) Has been cancelled
Build Desktop / Ubuntu 24.04 (push) Has been cancelled
Build Desktop / Arch (push) Has been cancelled
Build Desktop / macOS 14 (push) Has been cancelled
Build Desktop / macOS 15 (push) Has been cancelled
Build Desktop / macOS 13 Intel (push) Has been cancelled
Build Desktop / macOS 15 Debug (push) Has been cancelled
Build Desktop / Windows 10 (push) Has been cancelled
Bumps [doc/doxygen/theme](https://github.com/jothepro/doxygen-awesome-css) from `1f36200` to `d52eafe`.
- [Release notes](https://github.com/jothepro/doxygen-awesome-css/releases)
- [Commits](1f3620084f...d52eafe3e9)

---
updated-dependencies:
- dependency-name: doc/doxygen/theme
  dependency-version: d52eafe3e9303399fda15661f3d7bb8fe3d7eabc
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-28 07:05:24 +01:00
Bruno Alexandre Rosa
7caa88bc58
fix: solve deprecated literal operator error by updating peglib (#6745)
* fix: solve deprecated literal operator error

* update peglib

dcabc63cf4
2026-03-27 18:37:30 +01:00
RickyRister
34a5b8b9ce
[SettingsManager] Make setting getters const (#6748)
* [SettingsManager] Make setting getters const

* remove hashGameType from header
2026-03-27 18:13:25 +01:00
tooomm
d8e3807ec5
Dependabot: Enable git submodules tracking (#6727)
* Enable gitsubmodules

* update comment
2026-03-27 18:11:56 +01:00
ebbit1q
42bd8164a0
remove hardcoded white in vde banner widget (#6684)
* remove hardcoded white in vde banner widget

* set text color to white on higher opacities
2026-03-27 18:10:29 +01:00
ebbit1q
abf6e72ad1
add a nulcheck in the card item animation timer (#6740) 2026-03-27 18:08:51 +01:00
RickyRister
74cce5ccb2
[SettingsManager] Properly handle multithreaded access (#6747) 2026-03-27 17:12:49 +01:00
219 changed files with 35805 additions and 28445 deletions

View file

@ -9,20 +9,18 @@ RUN apt-get update && \
file \
g++ \
git \
libgl-dev \
liblzma-dev \
libmariadb-dev-compat \
libprotobuf-dev \
libqt6multimedia6 \
libqt6sql6-mysql \
libqt6svg6-dev \
libqt6websockets6-dev \
libqt5multimedia5-plugins \
libqt5sql5-mysql \
libqt5svg5-dev \
libqt5websockets5-dev \
ninja-build \
protobuf-compiler \
qt6-image-formats-plugins \
qt6-l10n-tools \
qt6-multimedia-dev \
qt6-tools-dev \
qt6-tools-dev-tools \
qt5-image-formats-plugins \
qtmultimedia5-dev \
qttools5-dev \
qttools5-dev-tools \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*

View file

@ -0,0 +1,29 @@
FROM ubuntu:26.04
RUN apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
build-essential \
ca-certificates \
ccache \
clang-format \
cmake \
file \
g++ \
git \
libgl-dev \
liblzma-dev \
libmariadb-dev-compat \
libprotobuf-dev \
libqt6multimedia6 \
libqt6sql6-mysql \
ninja-build \
protobuf-compiler \
qt6-image-formats-plugins \
qt6-l10n-tools \
qt6-multimedia-dev \
qt6-svg-dev \
qt6-tools-dev \
qt6-tools-dev-tools \
qt6-websockets-dev \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*

View file

@ -10,9 +10,11 @@
# --test runs tests
# --debug or --release sets the build type ie CMAKE_BUILD_TYPE
# --ccache [<size>] uses ccache and shows stats, optionally provide size
# --evict-ccache <age> runs ccache eviction based on given age after build
# --dir <dir> sets the name of the build dir, default is "build"
# --cmake-generator <generator> sets CMAKE_GENERATOR as used by cmake
# --target-macos-version <version> sets the min os version - only used for macOS builds
# uses env: BUILDTYPE MAKE_INSTALL MAKE_PACKAGE PACKAGE_TYPE PACKAGE_SUFFIX MAKE_SERVER MAKE_NO_CLIENT MAKE_TEST USE_CCACHE CCACHE_SIZE BUILD_DIR CMAKE_GENERATOR TARGET_MACOS_VERSION
# uses env: BUILDTYPE MAKE_INSTALL MAKE_PACKAGE PACKAGE_TYPE PACKAGE_SUFFIX MAKE_SERVER MAKE_NO_CLIENT MAKE_TEST USE_CCACHE CCACHE_SIZE CCACHE_EVICTION_AGE BUILD_DIR CMAKE_GENERATOR TARGET_MACOS_VERSION
# (correspond to args: --debug/--release --install --package <package type> --suffix <suffix> --server --test --ccache <ccache_size> --dir <dir>)
# exitcode: 1 for failure, 3 for invalid arguments
@ -71,6 +73,15 @@ while [[ $# != 0 ]]; do
shift
fi
;;
'--evict-ccache')
shift
if [[ $# == 0 ]]; then
echo "::error file=$0::--evict-ccache expects an argument"
exit 3
fi
CCACHE_EVICTION_AGE=$1
shift
;;
'--vcpkg')
USE_VCPKG=1
shift
@ -84,6 +95,15 @@ while [[ $# != 0 ]]; do
BUILD_DIR="$1"
shift
;;
'--cmake-generator')
shift
if [[ $# == 0 ]]; then
echo "::error file=$0::--cmake-generator expects an argument"
exit 3
fi
export CMAKE_GENERATOR=$1
shift
;;
'--target-macos-version')
shift
if [[ $# == 0 ]]; then
@ -251,9 +271,16 @@ cmake --build . "${buildflags[@]}"
echo "::endgroup::"
if [[ $USE_CCACHE ]]; then
if [[ $CCACHE_EVICTION_AGE ]]; then
echo "::group::evict ccache files older than $CCACHE_EVICTION_AGE"
ccache --evict-older-than "$CCACHE_EVICTION_AGE"
echo "::endgroup::"
fi
echo "::group::Show ccache stats again"
ccachestatsverbose
echo "::endgroup::"
elif [[ $CCACHE_EVICTION_AGE ]]; then
echo "::error file=$0::ccache eviction is enabled while ccache is disabled!"
fi
if [[ $RUNNER_OS == macOS ]]; then

View file

@ -3,17 +3,28 @@
# This script is to be used by the ci environment from the project root directory, do not use it from somewhere else.
# Creates or loads docker images to use in compilation, creates RUN function to start compilation on the docker image.
# <arg> sets the name of the docker image, these correspond to directories in .ci
#
# usage: source <script> <name> [--get] [--build] [--save] [--interactive] [--set-cache <location>]
# <name> sets the name of the docker image, these correspond to directories in .ci
# --get loads the image from a previously saved image cache, will build if no image is found
# --build builds the image from the Dockerfile in .ci/$NAME
# --save stores the image, if an image was loaded it will not be stored
# --interactive immediately starts the image interactively for debugging
# --set-cache <location> sets the location to cache the image or for ccache
#
# requires: docker
# uses env: NAME CACHE BUILD GET SAVE INTERACTIVE
# (correspond to args: <name> --set-cache <cache> --build --get --save --interactive)
# sets env: RUN CCACHE_DIR IMAGE_NAME RUN_ARGS RUN_OPTS BUILD_SCRIPT
# exitcode: 1 for failure, 2 for missing dockerfile, 3 for invalid arguments
#
# exported RUN function will run the BUILD_SCRIPT inside of the docker container.
# note that the docker container will not inherit any environment variables!
#
# usage: RUN [arguments for build script]
# roughly equivalent to `docker run $IMAGE_NAME bash $BUILD_SCRIPT $@`
# uses env: CCACHE_DIR IMAGE_NAME RUN_ARGS RUN_OPTS BUILD_SCRIPT
# exitcode: 3 for invalid arguments, returns the returncode of docker run
export BUILD_SCRIPT=".ci/compile.sh"
project_name="cockatrice"
@ -41,12 +52,17 @@ while [[ $# != 0 ]]; do
shift
;;
'--set-cache')
CACHE=$2
shift
if [[ $# == 0 ]]; then
echo "--set-cache expects an argument" >&2
exit 3
fi
CACHE=$1
shift
if ! [[ -d $CACHE ]]; then
echo "could not find cache path: $CACHE" >&2
return 3
fi
shift 2
;;
*)
if [[ ${1:0:1} == - ]]; then
@ -149,10 +165,11 @@ function RUN ()
args+=(--mount "type=bind,source=$CCACHE_DIR,target=/.ccache")
args+=(--env "CCACHE_DIR=/.ccache")
fi
if [[ -n "$CMAKE_GENERATOR" ]]; then
args+=(--env "CMAKE_GENERATOR=$CMAKE_GENERATOR")
if [[ $GITHUB_OUTPUT ]]; then
args+=(--mount "type=bind,source=$GITHUB_OUTPUT,target=/gh_output")
args+=(--env "GITHUB_OUTPUT=/gh_output")
fi
# shellcheck disable=2086
# shellcheck disable=2086
docker run "${args[@]}" $RUN_ARGS "$IMAGE_NAME" bash "$BUILD_SCRIPT" $RUN_OPTS "$@"
return $?
else

View file

@ -17,6 +17,7 @@ Available pre-compiled binaries for installation:
<kbd>macOS 13+</kbd> <sub><i>Ventura</i></sub> <sub>Intel</sub>
<b>Linux</b>
<kbd>Ubuntu 26.04 LTS</kbd> <sub><i>Resolute Racoon</i></sub>
<kbd>Ubuntu 24.04 LTS</kbd> <sub><i>Noble Numbat</i></sub>
<kbd>Ubuntu 22.04 LTS</kbd> <sub><i>Jammy Jellyfish</i></sub>
<kbd>Debian 13</kbd> <sub><i>Trixie</i></sub>

View file

@ -2,19 +2,21 @@
version: 2
updates:
# # Enable version updates for git submodules
# Not yet possible to bump only on tags or releases, see:
# Enable version updates for git submodules
# If SemVer is used, updates will happen to new releases only (not HEAD)
# https://github.com/dependabot/dependabot-core/issues/1639
# https://github.com/dependabot/dependabot-core/issues/2192
# Alternative: Action that updates submodule and can be manually run on demand (workflow_dispatch)
# - package-ecosystem: "gitsubmodule"
# # Look for `.gitmodules` in the `root` directory
# directory: "/"
# # Check for updates once a month
# schedule:
# interval: "monthly"
# # Limit the amout of open PR's (default = 5, disabled = 0, security updates are not impacted)
# open-pull-requests-limit: 1
- package-ecosystem: "gitsubmodule"
# Look for `.gitmodules` in the `root` directory
directory: "/"
ignore:
# Ignore updates for vcpkg (Bump to latest tag not working (no SemVer used) & macOS Intel triplet broken with newer releases)
- dependency-name: "vcpkg"
# Check for updates once a month
schedule:
interval: "monthly"
# Limit the amout of open PR's (default = 5, disabled = 0, security updates are not impacted)
open-pull-requests-limit: 2
# # Enable version updates for Docker
# Not yet possible to bump from one LTS version to the next and skip others, see:

View file

@ -38,15 +38,15 @@ on:
- 'vcpkg.json'
- 'vcpkg'
# Cancel earlier, unfinished runs of this workflow on the same branch (unless on master)
# Cancel earlier, unfinished runs of this workflow on the same branch (unless on release)
concurrency:
group: "${{ github.workflow }} @ ${{ github.ref_name }}"
cancel-in-progress: ${{ github.ref_name != 'master' }}
cancel-in-progress: ${{ github.ref_type != 'tag' }}
jobs:
configure:
name: Configure
runs-on: ubuntu-latest
runs-on: ubuntu-slim
outputs:
tag: ${{steps.configure.outputs.tag}}
sha: ${{steps.configure.outputs.sha}}
@ -142,21 +142,28 @@ jobs:
- distro: Ubuntu
version: 22.04
package: DEB
test: skip # Running tests on all distros is superfluous
- distro: Ubuntu
version: 24.04
package: DEB
- distro: Ubuntu
version: 26.04
package: DEB
name: ${{matrix.distro}} ${{matrix.version}}
needs: configure
runs-on: ubuntu-latest
continue-on-error: ${{matrix.allow-failure == 'yes'}}
timeout-minutes: 70
env:
NAME: ${{matrix.distro}}${{matrix.version}}
CACHE: ${{github.workspace}}/.cache/${{matrix.distro}}${{matrix.version}} # directory for caching docker image and ccache
# Cache size over the entire repo is 10Gi:
# https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#usage-limits-and-eviction-policy
CCACHE_SIZE: 550M
CCACHE_EVICTION_AGE: 7d
CMAKE_GENERATOR: 'Ninja'
steps:
@ -180,34 +187,43 @@ jobs:
- name: Build debug and test
if: matrix.test != 'skip'
shell: bash
env:
CMAKE_GENERATOR: '${{env.CMAKE_GENERATOR}}'
run: |
source .ci/docker.sh
RUN --server --debug --test --ccache "$CCACHE_SIZE"
RUN --server --debug --test --ccache "$CCACHE_SIZE" \
--cmake-generator "$CMAKE_GENERATOR"
- name: Build release package
id: build
if: matrix.package != 'skip'
shell: bash
env:
BUILD_DIR: build
SUFFIX: '-${{matrix.distro}}${{matrix.version}}'
package: '${{matrix.package}}'
CMAKE_GENERATOR: '${{env.CMAKE_GENERATOR}}'
NO_CLIENT: ${{matrix.server_only == 'yes' && '--no-client' || '' }}
server_only: '${{matrix.server_only}}'
run: |
source .ci/docker.sh
RUN --server --release --package "$package" --dir "$BUILD_DIR" \
--ccache "$CCACHE_SIZE" $NO_CLIENT
.ci/name_build.sh
args=()
if [[ $server_only == yes ]]; then
args+=(--no-client)
fi
if [[ $GITHUB_REF == "refs/heads/master" ]]; then
args+=(--evict-ccache "$CCACHE_EVICTION_AGE")
fi
args+=(--ccache "$CCACHE_SIZE")
args+=(--cmake-generator "$CMAKE_GENERATOR")
args+=(--suffix "$SUFFIX")
RUN --server --release --package "$package" "${args[@]}"
# Delete used cache to emulate a ccache update. See https://github.com/actions/cache/issues/342.
# Delete used cache to emulate a ccache update. See https://github.com/actions/cache/issues/342
- name: Delete remote compiler cache (ccache)
if: github.ref == 'refs/heads/master' && steps.ccache_restore.outputs.cache-hit
continue-on-error: true
env:
GH_TOKEN: ${{ github.token }}
run: gh cache delete --repo ${{ github.repository }} ${{ steps.ccache_restore.outputs.cache-primary-key }}
run: |
if gh cache delete --repo ${{ github.repository }} ${{ steps.ccache_restore.outputs.cache-primary-key }}; then
echo "Cache deleted successfully"
fi
- name: Save updated compiler cache (ccache)
if: github.ref == 'refs/heads/master'
@ -265,11 +281,12 @@ jobs:
override_target: 13
make_package: 1
package_suffix: "-macOS13_Intel"
qt_version: 6.10.*
qt_version: 6.11.*
qt_arch: clang_64
qt_modules: qtimageformats qtmultimedia qtwebsockets
cmake_generator: Ninja
use_ccache: 1
ccache_eviction_age: 7d
- os: macOS
target: 14
@ -279,11 +296,12 @@ jobs:
type: Release
make_package: 1
package_suffix: "-macOS14"
qt_version: 6.10.*
qt_version: 6.11.*
qt_arch: clang_64
qt_modules: qtimageformats qtmultimedia qtwebsockets
cmake_generator: Ninja
use_ccache: 1
ccache_eviction_age: 7d
- os: macOS
target: 15
@ -293,11 +311,12 @@ jobs:
type: Release
make_package: 1
package_suffix: "-macOS15"
qt_version: 6.10.*
qt_version: 6.11.*
qt_arch: clang_64
qt_modules: qtimageformats qtmultimedia qtwebsockets
cmake_generator: Ninja
use_ccache: 1
ccache_eviction_age: 7d
- os: macOS
target: 15
@ -305,11 +324,12 @@ jobs:
soc: Apple
xcode: "16.4"
type: Debug
qt_version: 6.10.*
qt_version: 6.11.*
qt_arch: clang_64
qt_modules: qtimageformats qtmultimedia qtwebsockets
cmake_generator: Ninja
use_ccache: 1
ccache_eviction_age: 7d
- os: Windows
target: 10
@ -317,7 +337,7 @@ jobs:
type: Release
make_package: 1
package_suffix: "-Win10"
qt_version: 6.10.*
qt_version: 6.11.*
qt_arch: win64_msvc2022_64
qt_modules: qtimageformats qtmultimedia qtwebsockets
cmake_generator: "Visual Studio 17 2022"
@ -326,6 +346,7 @@ jobs:
name: ${{matrix.os}} ${{matrix.target}}${{ matrix.soc == 'Intel' && ' Intel' || '' }}${{ matrix.type == 'Debug' && ' Debug' || '' }}
needs: configure
runs-on: ${{matrix.runner}}
timeout-minutes: 100
env:
CCACHE_DIR: ${{github.workspace}}/.cache/
# Cache size over the entire repo is 10Gi:
@ -404,6 +425,8 @@ jobs:
if: matrix.os == 'Windows'
uses: jurplel/install-qt-action@v4
with:
# qt 6.11.0 only works with aqtinstall directly from git until aqtinstall 3.4 is released
aqtsource: git+https://github.com/miurahr/aqtinstall.git
version: ${{ steps.resolve_qt_version.outputs.version }}
arch: ${{matrix.qt_arch}}
modules: ${{matrix.qt_modules}}
@ -440,14 +463,19 @@ jobs:
MACOS_CI_KEYCHAIN_PWD: ${{ secrets.PROD_MACOS_CI_KEYCHAIN_PWD }}
DEVELOPER_DIR: '/Applications/Xcode_${{matrix.xcode}}.app/Contents/Developer'
TARGET_MACOS_VERSION: ${{ matrix.override_target }}
CCACHE_EVICTION_AGE: ${{ matrix.ccache_eviction_age }}
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)
if: github.ref == 'refs/heads/master' && matrix.use_ccache == 1 && steps.ccache_restore.outputs.cache-hit
continue-on-error: true
env:
GH_TOKEN: ${{ github.token }}
run: gh cache delete --repo ${{ github.repository }} ${{ steps.ccache_restore.outputs.cache-primary-key }}
run: |
if gh cache delete --repo ${{ github.repository }} ${{ steps.ccache_restore.outputs.cache-primary-key }}; then
echo "Cache deleted successfully"
fi
- name: Save updated compiler cache (ccache)
if: github.ref == 'refs/heads/master' && matrix.use_ccache == 1

View file

@ -20,13 +20,13 @@ on:
jobs:
format:
runs-on: ubuntu-22.04
runs-on: ubuntu-slim
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 20 # should be enough to find merge base
fetch-depth: 20 # should be enough to find merge base
- name: Install dependencies
shell: bash

View file

@ -13,6 +13,11 @@ on:
- '.github/workflows/docker-release.yml'
- 'Dockerfile'
# Cancel earlier, unfinished runs of this workflow on the same branch (unless on release)
concurrency:
group: "${{ github.workflow }} @ ${{ github.ref_name }}"
cancel-in-progress: ${{ github.ref_type != 'tag' }}
jobs:
docker:
name: amd64 & arm64

View file

@ -16,7 +16,7 @@ jobs:
if: github.event_name != 'schedule' || github.repository_owner == 'Cockatrice'
name: Pull languages
runs-on: ubuntu-latest
runs-on: ubuntu-slim
steps:
- name: Checkout repo

View file

@ -16,7 +16,7 @@ jobs:
if: github.event_name != 'schedule' || github.repository_owner == 'Cockatrice'
name: Push strings
runs-on: ubuntu-latest
runs-on: ubuntu-slim
steps:
- name: Checkout repo
@ -46,7 +46,7 @@ jobs:
- name: Render template
id: template
uses: chuhlomin/render-template@v1
uses: chuhlomin/render-template/binary@v1
with:
template: .ci/update_translation_source_strings_template.md
vars: |

View file

@ -10,7 +10,7 @@ on:
jobs:
ESLint:
runs-on: ubuntu-latest
runs-on: ubuntu-slim
defaults:
run:

View file

@ -74,11 +74,11 @@ endif()
# A project name is needed for CPack
# Version can be overriden by git tags, see cmake/getversion.cmake
project("Cockatrice" VERSION 2.11.0)
project("Cockatrice" VERSION 3.0.0)
# Set release name if not provided via env/cmake var
if(NOT DEFINED GIT_TAG_RELEASENAME)
set(GIT_TAG_RELEASENAME "Omenpath")
set(GIT_TAG_RELEASENAME "Graduation Day")
endif()
# Use c++20 for all targets

View file

@ -42,7 +42,7 @@ list(REMOVE_DUPLICATES REQUIRED_QT_COMPONENTS)
if(NOT FORCE_USE_QT5)
# Linguist is now a component in Qt6 instead of an external package
find_package(
Qt6 6.2.3
Qt6 6.4.2
COMPONENTS ${REQUIRED_QT_COMPONENTS} Linguist
QUIET HINTS ${Qt6_DIR}
)

View file

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

View file

@ -564,6 +564,9 @@ if(WIN32)
PATTERN "styles/qopensslbackend.dll"
PATTERN "styles/qschannelbackend.dll"
PATTERN "styles/qwindowsvistastyle.dll"
PATTERN "styles/qwindows11style.dll"
PATTERN "styles/qmodernwindowsstyle.dll"
PATTERN "styles/qmodernwindowsstyled.dll"
PATTERN "tls/qcertonlybackend.dll"
PATTERN "tls/qopensslbackend.dll"
PATTERN "tls/qschannelbackend.dll"

File diff suppressed because it is too large Load diff

View file

@ -89,6 +89,8 @@ void TappedOutInterface::analyzeDeck(const DeckList &deck)
QNetworkRequest request(QUrl("https://tappedout.net/mtg-decks/paste/"));
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
request.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING));
// we interpret the redirect and open it in the browser instead, do not follow redirects
request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::ManualRedirectPolicy);
manager->post(request, data);
}

View file

@ -11,6 +11,8 @@ CardCounterSettings::CardCounterSettings(const QString &settingsPath, QObject *p
void CardCounterSettings::setColor(int counterId, const QColor &color)
{
QSettings settings = getSettings();
QString key = QString("cards/counters/%1/color").arg(counterId);
if (settings.value(key).value<QColor>() == color)
@ -36,7 +38,7 @@ QColor CardCounterSettings::color(int counterId) const
defaultColor = QColor::fromHsv(h, s, v);
}
return settings.value(QString("cards/counters/%1/color").arg(counterId), defaultColor).value<QColor>();
return getSettings().value(QString("cards/counters/%1/color").arg(counterId), defaultColor).value<QColor>();
}
QString CardCounterSettings::displayName(int counterId) const

View file

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

View file

@ -429,13 +429,13 @@ void GameEventHandler::eventLeave(const Event_Leave &event, int eventPlayerId, c
if (!player)
return;
player->clear();
emit playerLeft(eventPlayerId);
emit logLeave(player, getLeaveReason(event.reason()));
game->getPlayerManager()->removePlayer(eventPlayerId);
player->clear();
player->deleteLater();
// Rearrange all remaining zones so that attachment relationship updates take place

View file

@ -54,7 +54,7 @@ MessageLogWidget::getFromStr(CardZoneLogic *zone, QString cardName, int position
fromStr = tr(" from the top of their library");
}
}
} else if (position >= zone->getCards().size() - 1) {
} else if (position == zone->getCards().size()) {
if (cardName.isEmpty()) {
if (ownerChange) {
cardName = tr("the bottom card of %1's library").arg(zone->getPlayer()->getPlayerInfo()->getName());

View file

@ -10,7 +10,7 @@
#include <libcockatrice/protocol/pb/command_reveal_cards.pb.h>
PlayerMenu::PlayerMenu(Player *_player) : player(_player)
PlayerMenu::PlayerMenu(Player *_player) : QObject(_player), player(_player)
{
playerMenu = new TearOffMenu();

View file

@ -37,7 +37,7 @@ private slots:
void refreshShortcuts();
public:
PlayerMenu(Player *player);
explicit PlayerMenu(Player *player);
/// Lifecycle methods: delegate to all managedComponents, plus counters separately via player->getCounters().
void retranslateUi();

View file

@ -3,6 +3,7 @@
#include "../../interface/widgets/tabs/tab_game.h"
#include "../../interface/widgets/utility/get_text_with_max.h"
#include "../board/card_item.h"
#include "../client/settings/card_counter_settings.h"
#include "../dialogs/dlg_move_top_cards_until.h"
#include "../dialogs/dlg_roll_dice.h"
#include "../zones/hand_zone.h"
@ -27,13 +28,15 @@
#include <libcockatrice/protocol/pb/command_shuffle.pb.h>
#include <libcockatrice/protocol/pb/command_undo_draw.pb.h>
#include <libcockatrice/protocol/pb/context_move_card.pb.h>
#include <libcockatrice/utility/expression.h>
#include <libcockatrice/utility/trice_limits.h>
#include <libcockatrice/utility/zone_names.h>
// milliseconds in between triggers of the move top cards until action
static constexpr int MOVE_TOP_CARD_UNTIL_INTERVAL = 100;
PlayerActions::PlayerActions(Player *_player) : player(_player), lastTokenTableRow(0), movingCardsUntil(false)
PlayerActions::PlayerActions(Player *_player)
: QObject(_player), player(_player), lastTokenTableRow(0), movingCardsUntil(false)
{
moveTopCardTimer = new QTimer(this);
moveTopCardTimer->setInterval(MOVE_TOP_CARD_UNTIL_INTERVAL);
@ -1572,23 +1575,34 @@ void PlayerActions::actCardCounterTrigger()
break;
}
case 11: { // set counter with dialog
bool ok;
player->setDialogSemaphore(true);
int oldValue = 0;
if (player->getGameScene()->selectedItems().size() == 1) {
auto *card = static_cast<CardItem *>(player->getGameScene()->selectedItems().first());
oldValue = card->getCounters().value(counterId, 0);
// If a single card is selected, we show the old value in the dialog. Otherwise, we show "x"
QList<QGraphicsItem *> sel = player->getGameScene()->selectedItems();
QString oldValueForDlg = "x";
if (sel.size() == 1) {
auto *card = dynamic_cast<CardItem *>(sel.first());
oldValueForDlg = QString::number(card->getCounters().value(counterId, 0));
}
int number = QInputDialog::getInt(player->getGame()->getTab(), tr("Set counters"), tr("Number:"), oldValue,
0, MAX_COUNTERS_ON_CARD, 1, &ok);
auto &cardCounterSettings = SettingsCache::instance().cardCounters();
QString counterName = cardCounterSettings.displayName(counterId);
AbstractCounterDialog dialog(counterName, oldValueForDlg, player->getGame()->getTab());
int ok = dialog.exec();
player->setDialogSemaphore(false);
if (player->clearCardsToDelete() || !ok) {
return;
}
for (const auto &item : player->getGameScene()->selectedItems()) {
auto *card = static_cast<CardItem *>(item);
for (const auto &item : sel) {
auto *card = dynamic_cast<CardItem *>(item);
int oldValue = card->getCounters().value(counterId, 0);
Expression exp(oldValue);
int number = static_cast<int>(exp.parse(dialog.textValue()));
auto *cmd = new Command_SetCardCounter;
cmd->set_zone(card->getZone()->getName().toStdString());
cmd->set_card_id(card->getId());

View file

@ -32,7 +32,7 @@
#include <libcockatrice/protocol/pb/event_shuffle.pb.h>
#include <libcockatrice/utility/zone_names.h>
PlayerEventHandler::PlayerEventHandler(Player *_player) : player(_player)
PlayerEventHandler::PlayerEventHandler(Player *_player) : QObject(_player), player(_player)
{
}
@ -60,7 +60,6 @@ void PlayerEventHandler::eventShuffle(const Event_Shuffle &event)
// we want to close empty views as well
if (length == 0 || length > absStart) { // note this assumes views always start at the top of the library
view->close();
break;
}
} else {
qWarning() << zone->getName() << "of" << player->getPlayerInfo()->getName() << "holds empty zoneview!";
@ -595,4 +594,4 @@ void PlayerEventHandler::processGameEvent(GameEvent::GameEventType type,
qWarning() << "unhandled game event" << type;
}
}
}
}

View file

@ -28,6 +28,7 @@ CardPictureLoader::CardPictureLoader() : QObject(nullptr)
connect(&SettingsCache::instance(), &SettingsCache::picDownloadChanged, this,
&CardPictureLoader::picDownloadChanged);
qRegisterMetaType<ExactCard>();
connect(worker, &CardPictureLoaderWorker::imageLoaded, this, &CardPictureLoader::imageLoaded);
statusBar = new CardPictureLoaderStatusBar(nullptr);

View file

@ -13,6 +13,7 @@
#include <QPrinter>
#include <QTextCursor>
#include <libcockatrice/deck_list/deck_list.h>
#include <optional>
inline Q_LOGGING_CATEGORY(DeckLoaderLog, "deck_loader");

View file

@ -12,6 +12,7 @@
#include <QMap>
#include <QPixmap>
#include <libcockatrice/network/server/remote/user_level.h>
#include <optional>
inline Q_LOGGING_CATEGORY(PixelMapGeneratorLog, "pixel_map_generator");

View file

@ -91,6 +91,10 @@ struct PaletteColorInfo
ThemeManager::ThemeManager(QObject *parent) : QObject(parent)
{
defaultStyleName = qApp->style()->objectName();
// FIXME workaround for windows11 style being broken
if (defaultStyleName == "windows11") {
defaultStyleName = "windowsvista";
}
ensureThemeDirectoryExists();
#if (QT_VERSION >= QT_VERSION_CHECK(6, 5, 0))
connect(QGuiApplication::styleHints(), &QStyleHints::colorSchemeChanged, this, &ThemeManager::themeChangedSlot);
@ -179,7 +183,7 @@ QBrush ThemeManager::loadExtraBrush(QString fileName, QBrush &fallbackBrush)
static inline QPalette createDarkGreenFusionPalette()
{
QPalette p;
QPalette p = QStyleFactory::create("Fusion")->standardPalette();
// ---------- Core backgrounds ----------
p.setColor(QPalette::Window, QColor(30, 30, 30)); // #ff1e1e1e
@ -248,7 +252,7 @@ static inline QPalette createDarkGreenFusionPalette()
static inline QPalette createLightGreenFusionPalette()
{
QPalette p;
QPalette p = QStyleFactory::create("Fusion")->standardPalette();
// ---------- Core backgrounds ----------
p.setColor(QPalette::Window, QColor(240, 240, 240)); // #fff0f0f0
@ -332,13 +336,15 @@ void ThemeManager::themeChangedSlot()
}
if (themeName == FUSION_THEME_NAME) {
qApp->setStyle(QStyleFactory::create("Fusion"));
QStyle *fusionStyle = QStyleFactory::create("Fusion");
qApp->setStyle(fusionStyle);
#if (QT_VERSION >= QT_VERSION_CHECK(6, 5, 0))
QPalette palette;
// Start from Fusion's own palette so dark mode is handled correctly,
// then apply any tweaks on top of it.
QPalette palette = fusionStyle->standardPalette();
if (QGuiApplication::styleHints()->colorScheme() == Qt::ColorScheme::Dark) {
palette.setColor(QPalette::AlternateBase, QColor(53, 53, 53));
}
qApp->setPalette(palette);
#endif
} else if (themeName == FUSION_THEME_NAME_LIGHT) {
@ -348,7 +354,7 @@ void ThemeManager::themeChangedSlot()
qApp->setStyle(QStyleFactory::create("Fusion"));
qApp->setPalette(createDarkGreenFusionPalette());
} else {
qApp->setStyle(defaultStyleName); // setting the style also sets the palette
qApp->setStyle(QStyleFactory::create(defaultStyleName)); // setting the style also sets the palette
}
if (dirPath.isEmpty()) {

View file

@ -343,11 +343,9 @@ void CardInfoPictureWidget::mousePressEvent(QMouseEvent *event)
QWidget::mousePressEvent(event);
if (event->button() == Qt::RightButton) {
createRightClickMenu()->popup(QCursor::pos());
} else {
emit cardClicked();
}
emit cardClicked();
emit cardClicked(event);
}
void CardInfoPictureWidget::hideEvent(QHideEvent *event)

View file

@ -43,7 +43,7 @@ signals:
void hoveredOnCard(const ExactCard &hoveredCard);
void cardScaleFactorChanged(int _scale);
void cardChanged(const ExactCard &card);
void cardClicked();
void cardClicked(QMouseEvent *event);
protected:
void resizeEvent(QResizeEvent *event) override;

View file

@ -62,7 +62,7 @@ void CardInfoTextWidget::setCard(const ExactCard &exactCard)
text += QString("<tr><td>%1</td><td width=\"5\"></td><td>%2</td></tr>")
.arg(tr("Name:"), card->getName().toHtmlEscaped());
if (exactCard.getPrinting() != PrintingInfo()) {
if (!exactCard.getPrinting().isEmpty()) {
QString setShort = exactCard.getPrinting().getSet()->getShortName().toHtmlEscaped();
QString cardNum = exactCard.getPrinting().getProperty("num").toHtmlEscaped();

View file

@ -29,10 +29,14 @@ DeckAnalyticsWidget::DeckAnalyticsWidget(QWidget *parent, DeckListStatisticsAnal
removeButton = new QPushButton(this);
saveButton = new QPushButton(this);
loadButton = new QPushButton(this);
includeSideboardCheckBox = new QCheckBox(this);
includeSideboardCheckBox->setChecked(false);
controlLayout->addWidget(addButton);
controlLayout->addWidget(removeButton);
controlLayout->addWidget(saveButton);
controlLayout->addWidget(loadButton);
controlLayout->addWidget(includeSideboardCheckBox);
layout->addWidget(controlContainer);
@ -40,6 +44,7 @@ DeckAnalyticsWidget::DeckAnalyticsWidget(QWidget *parent, DeckListStatisticsAnal
connect(removeButton, &QPushButton::clicked, this, &DeckAnalyticsWidget::onRemoveSelected);
connect(saveButton, &QPushButton::clicked, this, &DeckAnalyticsWidget::saveLayout);
connect(loadButton, &QPushButton::clicked, this, &DeckAnalyticsWidget::loadLayout);
connect(includeSideboardCheckBox, &QCheckBox::clicked, this, &DeckAnalyticsWidget::includeSideboardChanged);
// Scroll area and container
scrollArea = new QScrollArea(this);
@ -66,6 +71,13 @@ void DeckAnalyticsWidget::retranslateUi()
removeButton->setText(tr("Remove Panel"));
saveButton->setText(tr("Save Layout"));
loadButton->setText(tr("Load Layout"));
includeSideboardCheckBox->setText(tr("Include Sideboard"));
}
void DeckAnalyticsWidget::includeSideboardChanged(bool checked)
{
statsAnalyzer->getConfig().includeSideboard = checked;
updateDisplays();
}
void DeckAnalyticsWidget::updateDisplays()

View file

@ -11,6 +11,7 @@
#include "deck_list_statistics_analyzer.h"
#include "resizable_panel.h"
#include <QCheckBox>
#include <QJsonObject>
#include <QScrollArea>
#include <QVBoxLayout>
@ -29,6 +30,7 @@ public slots:
public:
explicit DeckAnalyticsWidget(QWidget *parent, DeckListStatisticsAnalyzer *analyzer);
void retranslateUi();
void includeSideboardChanged(bool checked);
private slots:
void onAddPanel();
@ -57,6 +59,8 @@ private:
QPushButton *saveButton;
QPushButton *loadButton;
QCheckBox *includeSideboardCheckBox;
QScrollArea *scrollArea;
QWidget *panelContainer;
QVBoxLayout *panelLayout;

View file

@ -19,7 +19,13 @@ void DeckListStatisticsAnalyzer::analyze()
{
clearData();
QList<const DecklistCardNode *> nodes = model->getCardNodes();
QList<const DecklistCardNode *> nodes;
if (config.includeSideboard) {
nodes = model->getCardNodes();
} else {
nodes = model->getCardNodesForZone(DECK_ZONE_MAIN);
}
for (auto node : nodes) {
CardInfoPtr info = CardDatabaseManager::query()->getCardInfo(node->getName());

View file

@ -17,6 +17,7 @@ struct DeckListStatisticsAnalyzerConfig
bool computeCategories = true;
bool computeCurveBreakdowns = true;
bool computeProbabilities = true;
bool includeSideboard = false;
};
class DeckListStatisticsAnalyzer : public QObject
@ -133,6 +134,11 @@ public:
return model;
}
DeckListStatisticsAnalyzerConfig &getConfig()
{
return config;
}
signals:
void statsUpdated();

View file

@ -20,7 +20,6 @@ ResizablePanel::ResizablePanel(const QString &_typeId, AbstractAnalyticsPanelWid
frame = new QFrame(this);
frame->setFrameShape(QFrame::Box);
frame->setLineWidth(2);
frame->setStyleSheet("border: none;");
auto *frameLayout = new QVBoxLayout(frame);
frameLayout->setContentsMargins(0, 0, 0, 0);
@ -30,15 +29,13 @@ ResizablePanel::ResizablePanel(const QString &_typeId, AbstractAnalyticsPanelWid
frameLayout->addWidget(analyticsPanel);
dropIndicator = new QFrame(frame);
dropIndicator->setStyleSheet("background-color: #3daee9;");
dropIndicator->setFixedHeight(3);
dropIndicator->hide(); // hidden by default
dropIndicator->raise(); // make sure it's above children
selectionOverlay = new QFrame(frame);
selectionOverlay->setStyleSheet("background-color: rgba(61,174,233,50);"); // semi-transparent blue
selectionOverlay->hide(); // hidden by default
selectionOverlay->raise(); // make sure it is above children
selectionOverlay->hide(); // hidden by default
selectionOverlay->raise(); // make sure it is above children
selectionOverlay->setAttribute(Qt::WA_TransparentForMouseEvents);
// Bottom bar with drag button and resize handle
@ -51,24 +48,41 @@ ResizablePanel::ResizablePanel(const QString &_typeId, AbstractAnalyticsPanelWid
dragButton = new QPushButton("", bottomBar);
dragButton->setFixedSize(40, 8);
dragButton->setCursor(Qt::OpenHandCursor);
dragButton->setStyleSheet("QPushButton { "
"background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #4a4a4a, stop:1 #3a3a3a); "
"border: none; color: #888; font-size: 10px; }"
"QPushButton:hover { background: #5a5a5a; }");
bottomLayout->addWidget(dragButton);
// Resize handle fills the rest
resizeHandle = new QWidget(bottomBar);
resizeHandle->setFixedHeight(8);
resizeHandle->setCursor(Qt::SizeVerCursor);
resizeHandle->setStyleSheet("background: qlineargradient(x1:0, y1:0, x2:0, y2:1, "
"stop:0 #3a3a3a, stop:1 #2a2a2a);");
bottomLayout->addWidget(resizeHandle, 1);
frameLayout->addWidget(bottomBar);
mainLayout->addWidget(frame);
const QPalette &pal = QApplication::palette();
QColor mid = pal.color(QPalette::Mid);
QColor dark = pal.color(QPalette::Dark);
QColor midLight = pal.color(QPalette::Midlight);
QColor shadow = pal.color(QPalette::Shadow);
QColor placeholderText = pal.color(QPalette::PlaceholderText);
frame->setStyleSheet("QFrame { border: none; }");
dropIndicator->setStyleSheet("QFrame { background-color: #3daee9; }");
selectionOverlay->setStyleSheet("QFrame { background-color: rgba(61,174,233,50); }");
dragButton->setStyleSheet(QString("QPushButton { "
"background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 %1, stop:1 %2); "
"border: none; color: %3; font-size: 10px; }"
"QPushButton:hover { background: %4; }")
.arg(mid.name(), dark.name(), placeholderText.name(), midLight.name()));
resizeHandle->setStyleSheet(QString("QWidget { background: qlineargradient(x1:0, y1:0, x2:0, y2:1, "
"stop:0 %1, stop:1 %2); }")
.arg(dark.name(), shadow.name()));
// Set size policy
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);

View file

@ -1785,7 +1785,7 @@ DlgSettings::DlgSettings(QWidget *parent) : QDialog(parent)
contentsWidget->setSpacing(5);
pagesWidget = new QStackedWidget;
pagesWidget->addWidget(new GeneralSettingsPage);
pagesWidget->addWidget(makeScrollable(new GeneralSettingsPage));
pagesWidget->addWidget(makeScrollable(new AppearanceSettingsPage));
pagesWidget->addWidget(makeScrollable(new UserInterfaceSettingsPage));
pagesWidget->addWidget(new DeckEditorSettingsPage);

View file

@ -219,7 +219,9 @@ void DlgUpdate::downloadSuccessful(const QUrl &filepath)
{
setLabel(tr("Installing..."));
// Try to open the installer. If it opens, quit Cockatrice
if (QDesktopServices::openUrl(filepath)) {
if (QProcess::startDetached(filepath.toLocalFile(),
QStringList()
<< "/R" << QString("/D=%1").arg(QCoreApplication::applicationDirPath()))) {
QMetaObject::invokeMethod(static_cast<MainWindow *>(parent()), "close", Qt::QueuedConnection);
qCInfo(DlgUpdateLog) << "Opened downloaded update file successfully - closing Cockatrice";
close();

View file

@ -7,8 +7,8 @@
#include <QPainter>
#include <QVBoxLayout>
BannerWidget::BannerWidget(QWidget *parent, const QString &text, Qt::Orientation orientation, int transparency)
: QWidget(parent), gradientOrientation(orientation), transparency(qBound(0, transparency, 100))
BannerWidget::BannerWidget(QWidget *parent, const QString &text, Qt::Orientation orientation, int transparency_)
: QWidget(parent), gradientOrientation(orientation), transparency(qBound(0, transparency_, 100))
{
auto layout = new QHBoxLayout(this);
@ -18,7 +18,12 @@ BannerWidget::BannerWidget(QWidget *parent, const QString &text, Qt::Orientation
// Create the banner label and set properties
bannerLabel = new QLabel(text, this);
bannerLabel->setAlignment(Qt::AlignCenter);
bannerLabel->setStyleSheet("font-size: 24px; font-weight: bold; color: white;");
QString textColor;
if (transparency > 50) {
textColor = " color: white;";
}
bannerLabel->setStyleSheet("font-size: 24px; font-weight: bold;" + textColor);
layout->addWidget(iconLabel);
layout->addWidget(bannerLabel);

View file

@ -91,6 +91,7 @@ GameSelector::GameSelector(AbstractClient *_client,
bool filtersSetToDefault = showFilters && gameListProxyModel->areFilterParametersSetToDefaults();
clearFilterButton->setEnabled(!filtersSetToDefault);
connect(clearFilterButton, &QPushButton::clicked, this, &GameSelector::actClearFilter);
connect(gameListProxyModel, &GamesProxyModel::filtersChanged, this, &GameSelector::checkClearFilterButtonState);
if (room) {
createButton = new QPushButton;
@ -188,15 +189,16 @@ void GameSelector::actSetFilter()
dlg.getShowOnlyIfSpectatorsCanChat(), dlg.getShowOnlyIfSpectatorsCanSeeHands());
gameListProxyModel->saveFilterParameters(gameTypeMap);
clearFilterButton->setEnabled(!gameListProxyModel->areFilterParametersSetToDefaults());
updateTitle();
}
void GameSelector::checkClearFilterButtonState()
{
clearFilterButton->setEnabled(!gameListProxyModel->areFilterParametersSetToDefaults());
}
void GameSelector::actClearFilter()
{
clearFilterButton->setEnabled(false);
gameListProxyModel->resetFilterParameters();
gameListProxyModel->saveFilterParameters(gameTypeMap);

View file

@ -40,6 +40,7 @@ private slots:
* Updates the proxy model with selected filter parameters and refreshes the displayed game list.
*/
void actSetFilter();
void checkClearFilterButtonState();
/**
* @brief Clears all filters applied to the game list.

View file

@ -19,32 +19,46 @@ GameSelectorQuickFilterToolBar::GameSelectorQuickFilterToolBar(QWidget *parent,
mainLayout->setSpacing(5);
searchBar = new QLineEdit(this);
searchBar->setText(model->getCreatorNameFilters().join(", "));
connect(searchBar, &QLineEdit::textChanged, this, [this](const QString &text) { model->setGameNameFilter(text); });
searchBar->setText(model->getGameNameFilter());
connect(searchBar, &QLineEdit::textChanged, this, [this](const QString &text) {
applyFilters([&](auto &, auto &, auto &, auto &, auto &, auto &, auto &, QString &gameNameFilter, auto &,
auto &, auto &, auto &, auto &, auto &, auto &, auto &, auto &) { gameNameFilter = text; });
});
hideGamesNotCreatedByBuddiesCheckBox = new QCheckBox(this);
hideGamesNotCreatedByBuddiesCheckBox->setChecked(model->getHideBuddiesOnlyGames());
hideGamesNotCreatedByBuddiesCheckBox->setChecked(model->getHideNotBuddyCreatedGames());
connect(hideGamesNotCreatedByBuddiesCheckBox, &QCheckBox::toggled, this, [this](bool checked) {
if (checked) {
QStringList buddyNames;
for (auto buddy : tabSupervisor->getUserListManager()->getBuddyList().values()) {
buddyNames << QString::fromStdString(buddy.name());
applyFilters([&](auto &, auto &, auto &, auto &, auto &, bool &hideNotBuddyCreatedGames, auto &, auto &,
QStringList &creatorNameFilters, auto &, auto &, auto &, auto &, auto &, auto &, auto &,
auto &) {
hideNotBuddyCreatedGames = checked;
if (checked) {
QStringList buddyNames;
for (auto buddy : tabSupervisor->getUserListManager()->getBuddyList().values()) {
buddyNames << QString::fromStdString(buddy.name());
}
creatorNameFilters = buddyNames;
} else {
creatorNameFilters.clear();
}
model->setCreatorNameFilters(buddyNames);
} else {
model->setCreatorNameFilters({});
}
});
});
hideFullGamesCheckBox = new QCheckBox(this);
hideFullGamesCheckBox->setChecked(model->getHideFullGames());
connect(hideFullGamesCheckBox, &QCheckBox::toggled, this,
[this](bool checked) { model->setHideFullGames(checked); });
connect(hideFullGamesCheckBox, &QCheckBox::toggled, this, [this](bool checked) {
applyFilters([&](auto &, auto &, bool &hideFullGames, auto &, auto &, auto &, auto &, auto &, auto &, auto &,
auto &, auto &, auto &, auto &, auto &, auto &, auto &) { hideFullGames = checked; });
});
hideStartedGamesCheckBox = new QCheckBox(this);
hideStartedGamesCheckBox->setChecked(model->getHideGamesThatStarted());
connect(hideStartedGamesCheckBox, &QCheckBox::toggled, this,
[this](bool checked) { model->setHideGamesThatStarted(checked); });
connect(hideStartedGamesCheckBox, &QCheckBox::toggled, this, [this](bool checked) {
applyFilters([&](auto &, auto &, auto &, bool &hideGamesThatStarted, auto &, auto &, auto &, auto &, auto &,
auto &, auto &, auto &, auto &, auto &, auto &, auto &,
auto &) { hideGamesThatStarted = checked; });
});
filterToFormatComboBox = new QComboBox(this);
@ -69,13 +83,15 @@ GameSelectorQuickFilterToolBar::GameSelectorQuickFilterToolBar(QWidget *parent,
// Update proxy model on selection change
connect(filterToFormatComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [this](int index) {
QVariant data = filterToFormatComboBox->itemData(index);
if (!data.isValid()) {
model->setGameTypeFilter({}); // empty = no filter
} else {
int typeId = data.toInt();
model->setGameTypeFilter({typeId});
}
applyFilters([&](auto &, auto &, auto &, auto &, auto &, auto &, auto &, auto &, auto &,
QSet<int> &gameTypeFilter, auto &, auto &, auto &, auto &, auto &, auto &, auto &) {
QVariant data = filterToFormatComboBox->itemData(index);
if (!data.isValid()) {
gameTypeFilter.clear();
} else {
gameTypeFilter = {data.toInt()};
}
});
});
hideGamesNotCreatedByBuddiesCheckBox->setMinimumSize(20, 20);
@ -96,9 +112,87 @@ GameSelectorQuickFilterToolBar::GameSelectorQuickFilterToolBar(QWidget *parent,
setLayout(mainLayout);
syncFromModel();
connect(model, &GamesProxyModel::filtersChanged, this, &GameSelectorQuickFilterToolBar::syncFromModel);
retranslateUi();
}
void GameSelectorQuickFilterToolBar::syncFromModel()
{
QSignalBlocker b1(searchBar);
QSignalBlocker b2(filterToFormatComboBox);
QSignalBlocker b3(hideGamesNotCreatedByBuddiesCheckBox);
QSignalBlocker b4(hideFullGamesCheckBox);
QSignalBlocker b5(hideStartedGamesCheckBox);
searchBar->setText(model->getGameNameFilter());
hideGamesNotCreatedByBuddiesCheckBox->setChecked(model->getHideNotBuddyCreatedGames());
hideFullGamesCheckBox->setChecked(model->getHideFullGames());
hideStartedGamesCheckBox->setChecked(model->getHideGamesThatStarted());
QSet<int> types = model->getGameTypeFilter();
if (types.size() == 1) {
int idx = filterToFormatComboBox->findData(*types.begin());
filterToFormatComboBox->setCurrentIndex(idx >= 0 ? idx : 0);
} else {
filterToFormatComboBox->setCurrentIndex(0);
}
}
void GameSelectorQuickFilterToolBar::applyFilters(std::function<void(bool &,
bool &,
bool &,
bool &,
bool &,
bool &,
bool &,
QString &,
QStringList &,
QSet<int> &,
int &,
int &,
QTime &,
bool &,
bool &,
bool &,
bool &)> mutator)
{
bool hideBuddiesOnlyGames = model->getHideBuddiesOnlyGames();
bool hideIgnoredUserGames = model->getHideIgnoredUserGames();
bool hideFullGames = model->getHideFullGames();
bool hideGamesThatStarted = model->getHideGamesThatStarted();
bool hidePasswordProtectedGames = model->getHidePasswordProtectedGames();
bool hideNotBuddyCreatedGames = model->getHideNotBuddyCreatedGames();
bool hideOpenDecklistGames = model->getHideOpenDecklistGames();
QString gameNameFilter = model->getGameNameFilter();
QStringList creatorNameFilters = model->getCreatorNameFilters();
QSet<int> gameTypeFilter = model->getGameTypeFilter();
int minPlayers = model->getMaxPlayersFilterMin();
int maxPlayers = model->getMaxPlayersFilterMax();
QTime maxGameAge = model->getMaxGameAge();
bool showOnlyIfSpectatorsCanWatch = model->getShowOnlyIfSpectatorsCanWatch();
bool showSpectatorPasswordProtected = model->getShowSpectatorPasswordProtected();
bool showOnlyIfSpectatorsCanChat = model->getShowOnlyIfSpectatorsCanChat();
bool showOnlyIfSpectatorsCanSeeHands = model->getShowOnlyIfSpectatorsCanSeeHands();
mutator(hideBuddiesOnlyGames, hideIgnoredUserGames, hideFullGames, hideGamesThatStarted, hidePasswordProtectedGames,
hideNotBuddyCreatedGames, hideOpenDecklistGames, gameNameFilter, creatorNameFilters, gameTypeFilter,
minPlayers, maxPlayers, maxGameAge, showOnlyIfSpectatorsCanWatch, showSpectatorPasswordProtected,
showOnlyIfSpectatorsCanChat, showOnlyIfSpectatorsCanSeeHands);
model->setGameFilters(hideBuddiesOnlyGames, hideIgnoredUserGames, hideFullGames, hideGamesThatStarted,
hidePasswordProtectedGames, hideNotBuddyCreatedGames, hideOpenDecklistGames, gameNameFilter,
creatorNameFilters, gameTypeFilter, minPlayers, maxPlayers, maxGameAge,
showOnlyIfSpectatorsCanWatch, showSpectatorPasswordProtected, showOnlyIfSpectatorsCanChat,
showOnlyIfSpectatorsCanSeeHands);
}
void GameSelectorQuickFilterToolBar::retranslateUi()
{
searchBar->setPlaceholderText(tr("Filter by game name..."));

View file

@ -18,6 +18,24 @@ public:
TabSupervisor *tabSupervisor,
GamesProxyModel *model,
const QMap<int, QString> &allGameTypes);
void syncFromModel();
void applyFilters(std::function<void(bool &,
bool &,
bool &,
bool &,
bool &,
bool &,
bool &,
QString &,
QStringList &,
QSet<int> &,
int &,
int &,
QTime &,
bool &,
bool &,
bool &,
bool &)> mutator);
void retranslateUi();
private:

View file

@ -326,6 +326,7 @@ void GamesProxyModel::setGameFilters(bool _hideBuddiesOnlyGames,
#else
invalidateFilter();
#endif
emit filtersChanged();
}
int GamesProxyModel::getNumFilteredGames() const

View file

@ -138,6 +138,9 @@ private:
bool showOnlyIfSpectatorsCanChat;
bool showOnlyIfSpectatorsCanSeeHands;
signals:
void filtersChanged();
public:
/**
* @brief Constructs a GamesProxyModel.

View file

@ -4,10 +4,29 @@ void ArchidektApiResponseCardEntry::fromJson(const QJsonObject &json)
{
id = json.value("id").toInt();
categories.clear();
auto categoriesJson = json.value("categories").toArray();
for (auto category : categoriesJson) {
categories.append(category.toString());
for (const auto &categoryValue : categoriesJson) {
Category cat;
if (categoryValue.isObject()) {
QJsonObject obj = categoryValue.toObject();
cat.id = obj.value("id").toInt();
cat.name = obj.value("name").toString();
cat.isPremier = obj.value("isPremier").toBool();
cat.includedInDeck = obj.value("includedInDeck").toBool();
cat.includedInPrice = obj.value("includedInPrice").toBool();
} else if (categoryValue.isString()) {
cat.name = categoryValue.toString();
// assume mainboard unless known otherwise
cat.includedInDeck = true;
}
categories.append(cat);
}
companion = json.value("companion").toBool();
@ -27,7 +46,13 @@ void ArchidektApiResponseCardEntry::fromJson(const QJsonObject &json)
void ArchidektApiResponseCardEntry::debugPrint() const
{
qDebug() << "Id:" << id;
qDebug() << "Categories:" << categories;
for (auto category : categories) {
qDebug() << "Category ID:" << category.id;
qDebug() << "Category Name:" << category.name;
qDebug() << "Category Premier:" << category.isPremier;
qDebug() << "Category Included in Deck:" << category.includedInDeck;
qDebug() << "Category Included in Price:" << category.includedInPrice;
}
qDebug() << "Companion:" << companion;
qDebug() << "FlippedDefault:" << flippedDefault;
qDebug() << "Label:" << label;

View file

@ -9,6 +9,15 @@
#include <QString>
#include <QVector>
struct Category
{
int id;
QString name;
bool isPremier;
bool includedInDeck;
bool includedInPrice;
};
class ArchidektApiResponseCardEntry
{
public:
@ -26,7 +35,7 @@ public:
return card;
};
QStringList getCategories() const
QList<Category> getCategories() const
{
return categories;
}
@ -38,7 +47,7 @@ public:
private:
int id;
QStringList categories;
QList<Category> categories;
bool companion;
bool flippedDefault;
QString label;

View file

@ -63,16 +63,60 @@ ArchidektApiResponseDeckDisplayWidget::ArchidektApiResponseDeckDisplayWidget(QWi
QString tempDeck;
QTextStream deckStream(&tempDeck);
for (auto card : response.getCards()) {
QString mainboardText;
QString sideboardText;
QTextStream mainStream(&mainboardText);
QTextStream sideStream(&sideboardText);
for (const auto &card : response.getCards()) {
QString fullName = card.getCard().getOracleCard().value("name").toString();
// We don't really care about the second card, the card database already has it as a relation
QString cleanName = fullName.split("//").first().trimmed();
tempDeck += QString("%1 %2 (%3) %4\n")
.arg(card.getQuantity())
.arg(cleanName)
.arg(card.getCard().getEdition().getEditionCode().toUpper())
.arg(card.getCard().getCollectorNumber());
QString line = QString("%1 %2 (%3) %4\n")
.arg(card.getQuantity())
.arg(cleanName)
.arg(card.getCard().getEdition().getEditionCode().toUpper())
.arg(card.getCard().getCollectorNumber());
bool isCommander = false;
bool isSideboardCategory = false;
bool includedInDeck = false;
for (const auto &cat : card.getCategories()) {
if (cat.name.compare("Commander", Qt::CaseInsensitive) == 0) {
isCommander = true;
}
if (cat.name.compare("Sideboard", Qt::CaseInsensitive) == 0 ||
cat.name.compare("Maybeboard", Qt::CaseInsensitive) == 0) {
isSideboardCategory = true;
}
if (cat.includedInDeck) {
includedInDeck = true;
}
}
QString target;
if (isCommander || isSideboardCategory) {
sideStream << line;
} else if (includedInDeck) {
mainStream << line;
} else {
sideStream << line;
}
}
// Combine with blank line separator
tempDeck = mainboardText;
if (!sideboardText.isEmpty()) {
tempDeck += "\n";
tempDeck += sideboardText;
}
model = new DeckListModel(this);

View file

@ -3,6 +3,7 @@
#include "../../../../../general/display/background_plate_widget.h"
#include "../../tab_edhrec_main.h"
#include <QMouseEvent>
#include <libcockatrice/card/database/card_database_manager.h>
EdhrecApiResponseCardDetailsDisplayWidget::EdhrecApiResponseCardDetailsDisplayWidget(
@ -48,7 +49,7 @@ EdhrecApiResponseCardDetailsDisplayWidget::EdhrecApiResponseCardDetailsDisplayWi
if (parentTab) {
cardPictureWidget->setScaleFactor(parentTab->getCardSizeSlider()->getSlider()->value());
connect(cardPictureWidget, &CardInfoPictureWidget::cardClicked, this,
&EdhrecApiResponseCardDetailsDisplayWidget::actRequestPageNavigation);
&EdhrecApiResponseCardDetailsDisplayWidget::mousePressEvent);
connect(parentTab->getCardSizeSlider()->getSlider(), &QSlider::valueChanged, cardPictureWidget,
&CardInfoPictureWidget::setScaleFactor);
connect(this, &EdhrecApiResponseCardDetailsDisplayWidget::requestUrl, parentTab,
@ -59,7 +60,9 @@ EdhrecApiResponseCardDetailsDisplayWidget::EdhrecApiResponseCardDetailsDisplayWi
void EdhrecApiResponseCardDetailsDisplayWidget::mousePressEvent(QMouseEvent *event)
{
QWidget::mousePressEvent(event);
actRequestPageNavigation();
if (event->button() == Qt::LeftButton) {
actRequestPageNavigation();
}
}
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)

View file

@ -259,6 +259,9 @@ TabGame::~TabGame()
if (replayManager) {
delete replayManager->replay;
}
for (auto &player : game->getPlayerManager()->getPlayers()) {
player->clear();
}
}
void TabGame::updatePlayerListDockTitle()

View file

@ -30,6 +30,8 @@
#include <libcockatrice/protocol/pb/response_replay_get_code.pb.h>
#include <libcockatrice/protocol/pending_command.h>
inline Q_LOGGING_CATEGORY(TabReplaysLog, "replays_tab");
TabReplays::TabReplays(TabSupervisor *_tabSupervisor, AbstractClient *_client, const ServerInfo_User *currentUserInfo)
: Tab(_tabSupervisor), client(_client)
{
@ -265,9 +267,11 @@ void TabReplays::actOpenLocalReplay()
f.close();
GameReplay *replay = new GameReplay;
replay->ParseFromArray(_data.data(), _data.size());
emit openReplay(replay);
if (replay->ParseFromArray(_data.data(), _data.size())) {
emit openReplay(replay);
} else {
qCWarning(TabReplaysLog) << "could not parse replay!";
}
}
}
@ -379,9 +383,12 @@ void TabReplays::openRemoteReplayFinished(const Response &r)
const Response_ReplayDownload &resp = r.GetExtension(Response_ReplayDownload::ext);
GameReplay *replay = new GameReplay;
replay->ParseFromString(resp.replay_data());
if (replay->ParseFromString(resp.replay_data())) {
emit openReplay(replay);
emit openReplay(replay);
} else {
qCWarning(TabReplaysLog) << "could not parse remote replay!";
}
}
void TabReplays::actDownload()

View file

@ -0,0 +1,21 @@
#ifndef COCKATRICE_VISUAL_DATABASE_DISPLAY_FILTER_BUTTON_H
#define COCKATRICE_VISUAL_DATABASE_DISPLAY_FILTER_BUTTON_H
#include <QString>
const QString visualDatabaseDisplayFilterButtonStyle = QString(R"(
QPushButton {
background-color: palette(button);
color: palette(button-text);
padding: 5px 10px;
border-radius: 4px;
border: 1px solid palette(dark);
}
QPushButton:checked {
background-color: palette(highlight);
color: palette(highlighted-text);
border: 1px solid palette(shadow);
}
)");
#endif // COCKATRICE_VISUAL_DATABASE_DISPLAY_FILTER_BUTTON_H

View file

@ -1,6 +1,7 @@
#include "visual_database_display_format_legality_filter_widget.h"
#include "../../../filters/filter_tree_model.h"
#include "visual_database_display_filter_button.h"
#include <QLabel>
#include <QPushButton>
@ -80,8 +81,7 @@ void VisualDatabaseDisplayFormatLegalityFilterWidget::createFormatButtons()
for (auto it = allFormatsWithCount.begin(); it != allFormatsWithCount.end(); ++it) {
auto *button = new QPushButton(it.key(), flowWidget);
button->setCheckable(true);
button->setStyleSheet("QPushButton { background-color: lightgray; border: 1px solid gray; padding: 5px; }"
"QPushButton:checked { background-color: green; color: white; }");
button->setStyleSheet(visualDatabaseDisplayFilterButtonStyle);
flowWidget->addWidget(button);
formatButtons[it.key()] = button;

View file

@ -1,6 +1,7 @@
#include "visual_database_display_main_type_filter_widget.h"
#include "../../../filters/filter_tree_model.h"
#include "visual_database_display_filter_button.h"
#include <QLabel>
#include <QPushButton>
@ -75,8 +76,8 @@ void VisualDatabaseDisplayMainTypeFilterWidget::createMainTypeButtons()
for (auto it = allMainCardTypesWithCount.begin(); it != allMainCardTypesWithCount.end(); ++it) {
auto *button = new QPushButton(it.key(), flowWidget);
button->setCheckable(true);
button->setStyleSheet("QPushButton { background-color: lightgray; border: 1px solid gray; padding: 5px; }"
"QPushButton:checked { background-color: green; color: white; }");
button->setStyleSheet(visualDatabaseDisplayFilterButtonStyle);
flowWidget->addWidget(button);
typeButtons[it.key()] = button;

View file

@ -3,6 +3,7 @@
#include "../../../interface/widgets/dialogs/dlg_load_deck_from_clipboard.h"
#include "../../../interface/widgets/tabs/abstract_tab_deck_editor.h"
#include "../deck_editor/deck_state_manager.h"
#include "visual_database_display_filter_button.h"
#include <QHBoxLayout>
@ -95,8 +96,8 @@ void VisualDatabaseDisplayNameFilterWidget::createNameFilter(const QString &name
// Create a button for the filter
auto *button = new QPushButton(name, flowWidget);
button->setStyleSheet("QPushButton { background-color: lightgray; border: 1px solid gray; padding: 5px; }"
"QPushButton:hover { background-color: red; color: white; }");
button->setStyleSheet(visualDatabaseDisplayFilterButtonStyle);
connect(button, &QPushButton::clicked, this, [this, name]() {
removeNameFilter(name);

View file

@ -2,6 +2,7 @@
#include "../../../client/settings/cache_settings.h"
#include "../../../filters/filter_tree_model.h"
#include "visual_database_display_filter_button.h"
#include <QLineEdit>
#include <QPushButton>
@ -101,8 +102,8 @@ void VisualDatabaseDisplaySetFilterWidget::createSetButtons()
auto *button = new QPushButton(longName + " (" + shortName + ")", flowWidget);
button->setCheckable(true);
button->setStyleSheet("QPushButton { background-color: lightgray; border: 1px solid gray; padding: 5px; }"
"QPushButton:checked { background-color: green; color: white; }");
button->setStyleSheet(visualDatabaseDisplayFilterButtonStyle);
flowWidget->addWidget(button);
setButtons[shortName] = button;

View file

@ -1,6 +1,7 @@
#include "visual_database_display_sub_type_filter_widget.h"
#include "../../../filters/filter_tree_model.h"
#include "visual_database_display_filter_button.h"
#include <QLabel>
#include <QLineEdit>
@ -80,8 +81,8 @@ void VisualDatabaseDisplaySubTypeFilterWidget::createSubTypeButtons()
for (auto it = allSubCardTypesWithCount.begin(); it != allSubCardTypesWithCount.end(); ++it) {
auto *button = new QPushButton(it.key(), flowWidget);
button->setCheckable(true);
button->setStyleSheet("QPushButton { background-color: lightgray; border: 1px solid gray; padding: 5px; }"
"QPushButton:checked { background-color: green; color: white; }");
button->setStyleSheet(visualDatabaseDisplayFilterButtonStyle);
flowWidget->addWidget(button);
typeButtons[it.key()] = button;

View file

@ -84,6 +84,7 @@
const QString MainWindow::appName = "Cockatrice";
const QStringList MainWindow::fileNameFilters = QStringList() << QObject::tr("Cockatrice card database (*.xml)")
<< QObject::tr("All files (*.*)");
inline Q_LOGGING_CATEGORY(MainWindowLog, "main_window");
/**
* Replaces the tab-specific menus that are shown in the menuBar.
@ -277,9 +278,11 @@ void MainWindow::actWatchReplay()
file.close();
replay = new GameReplay;
replay->ParseFromArray(buf.data(), buf.size());
tabSupervisor->openReplay(replay);
if (replay->ParseFromArray(buf.data(), buf.size())) {
tabSupervisor->openReplay(replay);
} else {
qCWarning(MainWindowLog) << "failed to parse replay!";
}
}
void MainWindow::localGameEnded()

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

@ -1 +1 @@
Subproject commit 1f3620084ff75734ed192101acf40e9dff01d848
Subproject commit d52eafe3e9303399fda15661f3d7bb8fe3d7eabc

View file

@ -133,7 +133,7 @@ ExactCard CardDatabaseQuerier::getRandomCard() const
ExactCard CardDatabaseQuerier::getCardFromSameSet(const QString &cardName, const PrintingInfo &otherPrinting) const
{
// The source card does not have a printing defined, which means we can't get a card from the same set.
if (otherPrinting == PrintingInfo()) {
if (otherPrinting.isEmpty()) {
return getCard({cardName});
}
@ -360,4 +360,4 @@ QMap<QString, int> CardDatabaseQuerier::getAllFormatsWithCount() const
}
return formatCounts;
}
}

View file

@ -114,5 +114,6 @@ public:
*/
void emitPixmapUpdated() const;
};
Q_DECLARE_METATYPE(ExactCard)
#endif // EXACT_CARD_H

View file

@ -54,6 +54,16 @@ public:
return this->set == other.set && this->properties == other.properties;
}
/**
* @brief check if the info is empty, as if default constructed.
*
* @return True if both set and properties are empty, otherwise false.
*/
bool isEmpty() const
{
return set == nullptr && properties.isEmpty();
}
private:
CardSetPtr set; ///< The set this variation belongs to.
QVariantHash properties; ///< Key-value store for variation-specific attributes.

View file

@ -12,9 +12,9 @@ public:
virtual void setEnabled(QString shortName, bool enabled) = 0;
virtual void setIsKnown(QString shortName, bool isknown) = 0;
virtual unsigned int getSortKey(QString shortName) = 0;
virtual bool isEnabled(QString shortName) = 0;
virtual bool isKnown(QString shortName) = 0;
virtual unsigned int getSortKey(QString shortName) const = 0;
virtual bool isEnabled(QString shortName) const = 0;
virtual bool isKnown(QString shortName) const = 0;
};
#endif // COCKATRICE_INTERFACE_CARD_SET_PRIORITY_CONTROLLER_H

View file

@ -16,15 +16,15 @@ public:
{
}
unsigned int getSortKey(QString /* shortName */) override
unsigned int getSortKey(QString /* shortName */) const override
{
return 0;
}
bool isEnabled(QString /* shortName */) override
bool isEnabled(QString /* shortName */) const override
{
return true;
}
bool isKnown(QString /* shortName */) override
bool isKnown(QString /* shortName */) const override
{
return true;
}

View file

@ -390,14 +390,18 @@ void RemoteClient::readData()
return;
ServerMessage newServerMessage;
newServerMessage.ParseFromArray(inputBuffer.data(), messageLength);
qCDebug(RemoteClientLog).noquote() << "IN" << getSafeDebugString(newServerMessage);
bool ok = newServerMessage.ParseFromArray(inputBuffer.data(), messageLength);
inputBuffer.remove(0, messageLength);
messageInProgress = false;
processProtocolItem(newServerMessage);
if (ok) {
qCDebug(RemoteClientLog).noquote() << "IN" << getSafeDebugString(newServerMessage);
processProtocolItem(newServerMessage);
} else {
qCDebug(RemoteClientLog) << "parsing error!";
}
if (getStatus() == StatusDisconnecting) // use thread-safe getter
doDisconnectFromServer();
@ -408,11 +412,13 @@ void RemoteClient::websocketMessageReceived(const QByteArray &message)
{
lastDataReceived = timeRunning;
ServerMessage newServerMessage;
newServerMessage.ParseFromArray(message.data(), message.length());
if (newServerMessage.ParseFromArray(message.data(), message.length())) {
qCDebug(RemoteClientLog).noquote() << "IN" << getSafeDebugString(newServerMessage);
qCDebug(RemoteClientLog).noquote() << "IN" << getSafeDebugString(newServerMessage);
processProtocolItem(newServerMessage);
processProtocolItem(newServerMessage);
} else {
qCDebug(RemoteClientLog) << "parsing error!";
}
}
void RemoteClient::sendCommandContainer(const CommandContainer &cont)
@ -426,19 +432,27 @@ void RemoteClient::sendCommandContainer(const CommandContainer &cont)
qCDebug(RemoteClientLog).noquote() << "OUT" << getSafeDebugString(cont);
QByteArray buf;
bool ok;
if (usingWebSocket) {
buf.resize(size);
cont.SerializeToArray(buf.data(), size);
websocket->sendBinaryMessage(buf);
ok = cont.SerializeToArray(buf.data(), size);
if (ok) {
websocket->sendBinaryMessage(buf);
}
} else {
buf.resize(size + 4);
cont.SerializeToArray(buf.data() + 4, size);
buf.data()[3] = (unsigned char)size;
buf.data()[2] = (unsigned char)(size >> 8);
buf.data()[1] = (unsigned char)(size >> 16);
buf.data()[0] = (unsigned char)(size >> 24);
ok = cont.SerializeToArray(buf.data() + 4, size);
if (ok) {
buf.data()[3] = (unsigned char)size;
buf.data()[2] = (unsigned char)(size >> 8);
buf.data()[1] = (unsigned char)(size >> 16);
buf.data()[0] = (unsigned char)(size >> 24);
socket->write(buf);
socket->write(buf);
}
}
if (!ok) {
qCDebug(RemoteClientLog) << "transmit error!";
}
}

View file

@ -49,6 +49,7 @@
#include <libcockatrice/rng/rng_abstract.h>
#include <libcockatrice/utility/trice_limits.h>
#include <libcockatrice/utility/zone_names.h>
#include <ranges>
Server_AbstractPlayer::Server_AbstractPlayer(Server_Game *_game,
int _playerId,
@ -228,6 +229,37 @@ shouldBeFaceDown(const MoveCardStruct &cardStruct, const Server_CardZone *startZ
return false;
}
/**
* @brief Determines whether a set of moved cards is from the bottom of the deck
*/
static bool shouldBeFromTheBottom(const Server_CardZone *startZone, const std::set<MoveCardStruct> &cardsToMove)
{
if (!startZone) {
return false;
}
if (startZone->getName() != ZoneNames::DECK) {
return false;
}
int movedCount = static_cast<int>(cardsToMove.size());
int tailStart = startZone->getCards().size() - movedCount;
if (tailStart <= 0) { // if the entire deck is moved it should not be considered from the bottom
return false;
}
// check if the move is a contiguous block at the end of the deck, fail fast when not
int expectedPosition = tailStart;
for (const auto &card : cardsToMove) {
if (card.position != expectedPosition) {
return false;
}
++expectedPosition;
}
return true;
}
Response::ResponseCode Server_AbstractPlayer::moveCard(GameEventStorage &ges,
Server_CardZone *startzone,
const QList<const CardToMove *> &_cards,
@ -244,8 +276,11 @@ Response::ResponseCode Server_AbstractPlayer::moveCard(GameEventStorage &ges,
return Response::RespContextError;
}
if (!targetzone->hasCoords() && (xCoord <= -1)) {
xCoord = targetzone->getCards().size();
if (!targetzone->hasCoords()) {
yCoord = 0;
if (xCoord <= -1) {
xCoord = targetzone->getCards().size();
}
}
std::set<MoveCardStruct> cardsToMove;
@ -285,164 +320,21 @@ Response::ResponseCode Server_AbstractPlayer::moveCard(GameEventStorage &ges,
bool revealTopStart = false;
bool revealTopTarget = false;
for (auto cardStruct : cardsToMove) {
Server_Card *card = cardStruct.card;
int originalPosition = cardStruct.position;
bool isFromBottom = shouldBeFromTheBottom(startzone, cardsToMove);
bool sourceBeingLookedAt;
int position = startzone->removeCard(card, sourceBeingLookedAt);
// Attachment relationships can be retained when moving a card onto the opponent's table
if (startzone->getName() != targetzone->getName()) {
// Delete all attachment relationships
if (card->getParentCard()) {
card->setParentCard(nullptr);
}
// Make a copy of the list because the original one gets modified during the loop
QList<Server_Card *> attachedCards = card->getAttachedCards();
for (auto &attachedCard : attachedCards) {
attachedCard->getZone()->getPlayer()->unattachCard(ges, attachedCard);
}
if (isFromBottom) {
std::ranges::reverse_view reversedCardsToMove{cardsToMove};
for (auto card : reversedCardsToMove) {
processMoveCard(ges, startzone, targetzone, card, xCoord, yCoord, xIndex, revealTopStart, revealTopTarget,
isReversed, undoingDraw);
}
if (startzone != targetzone) {
// Delete all arrows from and to the card
for (auto *player : game->getPlayers().values()) {
QList<int> arrowsToDelete;
for (Server_Arrow *arrow : player->getArrows()) {
if ((arrow->getStartCard() == card) || (arrow->getTargetItem() == card))
arrowsToDelete.append(arrow->getId());
}
for (int j : arrowsToDelete) {
player->deleteArrow(j);
}
}
}
if (shouldDestroyOnMove(card, startzone, targetzone)) {
Event_DestroyCard event;
event.set_zone_name(startzone->getName().toStdString());
event.set_card_id(static_cast<google::protobuf::uint32>(card->getId()));
ges.enqueueGameEvent(event, playerId);
if (Server_Card *stashedCard = card->takeStashedCard()) {
stashedCard->setId(newCardId());
ges.enqueueGameEvent(makeCreateTokenEvent(startzone, stashedCard, card->getX(), card->getY()),
playerId);
card->deleteLater();
card = stashedCard;
} else {
card->deleteLater();
card = nullptr;
}
}
if (card) {
++xIndex;
int newX = isReversed ? targetzone->getCards().size() - xCoord + xIndex : xCoord + xIndex;
bool faceDown = shouldBeFaceDown(cardStruct, startzone, targetzone);
if (targetzone->hasCoords()) {
newX = targetzone->getFreeGridColumn(newX, yCoord, card->getName(), faceDown);
} else {
yCoord = 0;
card->resetState(targetzone->getName() == ZoneNames::STACK);
}
targetzone->insertCard(card, newX, yCoord);
int targetLookedCards = targetzone->getCardsBeingLookedAt();
bool sourceKnownToPlayer = isReversed || (sourceBeingLookedAt && !card->getFaceDown());
if (targetzone->getType() == ServerInfo_Zone::HiddenZone && targetLookedCards >= newX) {
if (sourceKnownToPlayer) {
targetLookedCards += 1;
} else {
targetLookedCards = newX;
}
targetzone->setCardsBeingLookedAt(targetLookedCards);
}
bool targetHiddenToOthers = faceDown || (targetzone->getType() != ServerInfo_Zone::PublicZone);
bool sourceHiddenToOthers = card->getFaceDown() || (startzone->getType() != ServerInfo_Zone::PublicZone);
int oldCardId = card->getId();
if ((faceDown && (startzone != targetzone)) || (targetzone->getPlayer() != startzone->getPlayer())) {
card->setId(targetzone->getPlayer()->newCardId());
}
card->setFaceDown(faceDown);
Event_MoveCard eventOthers;
eventOthers.set_start_player_id(startzone->getPlayer()->getPlayerId());
eventOthers.set_start_zone(startzone->getName().toStdString());
eventOthers.set_target_player_id(targetzone->getPlayer()->getPlayerId());
if (startzone != targetzone) {
eventOthers.set_target_zone(targetzone->getName().toStdString());
}
eventOthers.set_y(yCoord);
eventOthers.set_face_down(faceDown);
Event_MoveCard eventPrivate(eventOthers);
if (sourceBeingLookedAt || targetzone->getType() != ServerInfo_Zone::HiddenZone ||
startzone->getType() != ServerInfo_Zone::HiddenZone) {
eventPrivate.set_card_id(oldCardId);
eventPrivate.set_new_card_id(card->getId());
} else {
eventPrivate.set_card_id(-1);
eventPrivate.set_new_card_id(-1);
}
if (sourceKnownToPlayer || !(faceDown || targetzone->getType() == ServerInfo_Zone::HiddenZone)) {
QString privateCardName = card->getName();
eventPrivate.set_card_name(privateCardName.toStdString());
eventPrivate.set_new_card_provider_id(card->getProviderId().toStdString());
}
if (startzone->getType() == ServerInfo_Zone::HiddenZone) {
eventPrivate.set_position(position);
} else {
eventPrivate.set_position(-1);
}
eventPrivate.set_x(newX);
if (
// cards from public zones have their id known, their previous position is already known, the event does
// not accomodate for previous locations in zones with coordinates (which are always public)
(startzone->getType() != ServerInfo_Zone::PublicZone) &&
// other players are not allowed to be able to track which card is which in private zones like the hand
(startzone->getType() != ServerInfo_Zone::PrivateZone)) {
eventOthers.set_position(position);
}
if (
// other players are not allowed to be able to track which card is which in private zones like the hand
(targetzone->getType() != ServerInfo_Zone::PrivateZone)) {
eventOthers.set_x(newX);
}
if ((startzone->getType() == ServerInfo_Zone::PublicZone) ||
(targetzone->getType() == ServerInfo_Zone::PublicZone)) {
eventOthers.set_card_id(oldCardId);
if (!(sourceHiddenToOthers && targetHiddenToOthers)) {
QString publicCardName = card->getName();
eventOthers.set_card_name(publicCardName.toStdString());
eventOthers.set_new_card_provider_id(card->getProviderId().toStdString());
}
eventOthers.set_new_card_id(card->getId());
}
ges.enqueueGameEvent(eventPrivate, playerId, GameEventStorageItem::SendToPrivate, playerId);
ges.enqueueGameEvent(eventOthers, playerId, GameEventStorageItem::SendToOthers);
if (originalPosition == 0) {
revealTopStart = true;
}
if (newX == 0) {
revealTopTarget = true;
}
// handle side effects for this card
onCardBeingMoved(ges, cardStruct, startzone, targetzone, undoingDraw);
} else {
for (auto card : cardsToMove) {
processMoveCard(ges, startzone, targetzone, card, xCoord, yCoord, xIndex, revealTopStart, revealTopTarget,
isReversed, undoingDraw);
}
}
if (revealTopStart) {
revealTopCardIfNeeded(startzone, ges);
}
@ -462,6 +354,174 @@ Response::ResponseCode Server_AbstractPlayer::moveCard(GameEventStorage &ges,
return Response::RespOk;
}
void Server_AbstractPlayer::processMoveCard(GameEventStorage &ges,
Server_CardZone *startzone,
Server_CardZone *targetzone,
MoveCardStruct cardStruct,
int xCoord,
int yCoord,
int &xIndex,
bool &revealTopStart,
bool &revealTopTarget,
bool isReversed,
bool undoingDraw)
{
Server_Card *card = cardStruct.card;
int originalPosition = cardStruct.position;
bool sourceBeingLookedAt;
int position = startzone->removeCard(card, sourceBeingLookedAt);
// Attachment relationships can be retained when moving a card onto the opponent's table
if (startzone->getName() != targetzone->getName()) {
// Delete all attachment relationships
if (card->getParentCard()) {
card->setParentCard(nullptr);
}
// Make a copy of the list because the original one gets modified during the loop
QList<Server_Card *> attachedCards = card->getAttachedCards();
for (auto &attachedCard : attachedCards) {
attachedCard->getZone()->getPlayer()->unattachCard(ges, attachedCard);
}
}
if (startzone != targetzone) {
// Delete all arrows from and to the card
for (auto *player : game->getPlayers().values()) {
QList<int> arrowsToDelete;
for (Server_Arrow *arrow : player->getArrows()) {
if ((arrow->getStartCard() == card) || (arrow->getTargetItem() == card))
arrowsToDelete.append(arrow->getId());
}
for (int j : arrowsToDelete) {
player->deleteArrow(j);
}
}
}
if (shouldDestroyOnMove(card, startzone, targetzone)) {
Event_DestroyCard event;
event.set_zone_name(startzone->getName().toStdString());
event.set_card_id(static_cast<google::protobuf::uint32>(card->getId()));
ges.enqueueGameEvent(event, playerId);
if (Server_Card *stashedCard = card->takeStashedCard()) {
stashedCard->setId(newCardId());
ges.enqueueGameEvent(makeCreateTokenEvent(startzone, stashedCard, card->getX(), card->getY()), playerId);
card->deleteLater();
card = stashedCard;
} else {
card->deleteLater();
card = nullptr;
}
}
if (card) {
++xIndex;
int newX = isReversed ? targetzone->getCards().size() - xCoord + xIndex : xCoord + xIndex;
bool faceDown = shouldBeFaceDown(cardStruct, startzone, targetzone);
if (targetzone->hasCoords()) {
newX = targetzone->getFreeGridColumn(newX, yCoord, card->getName(), faceDown);
} else {
card->resetState(targetzone->getName() == ZoneNames::STACK);
}
targetzone->insertCard(card, newX, yCoord);
int targetLookedCards = targetzone->getCardsBeingLookedAt();
bool sourceKnownToPlayer = isReversed || (sourceBeingLookedAt && !card->getFaceDown());
if (targetzone->getType() == ServerInfo_Zone::HiddenZone && targetLookedCards >= newX) {
if (sourceKnownToPlayer) {
targetLookedCards += 1;
} else {
targetLookedCards = newX;
}
targetzone->setCardsBeingLookedAt(targetLookedCards);
}
bool targetHiddenToOthers = faceDown || (targetzone->getType() != ServerInfo_Zone::PublicZone);
bool sourceHiddenToOthers = card->getFaceDown() || (startzone->getType() != ServerInfo_Zone::PublicZone);
int oldCardId = card->getId();
if ((faceDown && (startzone != targetzone)) || (targetzone->getPlayer() != startzone->getPlayer())) {
card->setId(targetzone->getPlayer()->newCardId());
}
card->setFaceDown(faceDown);
Event_MoveCard eventOthers;
eventOthers.set_start_player_id(startzone->getPlayer()->getPlayerId());
eventOthers.set_start_zone(startzone->getName().toStdString());
eventOthers.set_target_player_id(targetzone->getPlayer()->getPlayerId());
if (startzone != targetzone) {
eventOthers.set_target_zone(targetzone->getName().toStdString());
}
eventOthers.set_y(yCoord);
eventOthers.set_face_down(faceDown);
Event_MoveCard eventPrivate(eventOthers);
if (sourceBeingLookedAt || targetzone->getType() != ServerInfo_Zone::HiddenZone ||
startzone->getType() != ServerInfo_Zone::HiddenZone) {
eventPrivate.set_card_id(oldCardId);
eventPrivate.set_new_card_id(card->getId());
} else {
eventPrivate.set_card_id(-1);
eventPrivate.set_new_card_id(-1);
}
if (sourceKnownToPlayer || !(faceDown || targetzone->getType() == ServerInfo_Zone::HiddenZone)) {
QString privateCardName = card->getName();
eventPrivate.set_card_name(privateCardName.toStdString());
eventPrivate.set_new_card_provider_id(card->getProviderId().toStdString());
}
if (startzone->getType() == ServerInfo_Zone::HiddenZone) {
eventPrivate.set_position(position);
} else {
eventPrivate.set_position(-1);
}
eventPrivate.set_x(newX);
if (
// cards from public zones have their id known, their previous position is already known, the event does
// not accomodate for previous locations in zones with coordinates (which are always public)
(startzone->getType() != ServerInfo_Zone::PublicZone) &&
// other players are not allowed to be able to track which card is which in private zones like the hand
(startzone->getType() != ServerInfo_Zone::PrivateZone)) {
eventOthers.set_position(position);
}
if (
// other players are not allowed to be able to track which card is which in private zones like the hand
(targetzone->getType() != ServerInfo_Zone::PrivateZone)) {
eventOthers.set_x(newX);
}
if ((startzone->getType() == ServerInfo_Zone::PublicZone) ||
(targetzone->getType() == ServerInfo_Zone::PublicZone)) {
eventOthers.set_card_id(oldCardId);
if (!(sourceHiddenToOthers && targetHiddenToOthers)) {
QString publicCardName = card->getName();
eventOthers.set_card_name(publicCardName.toStdString());
eventOthers.set_new_card_provider_id(card->getProviderId().toStdString());
}
eventOthers.set_new_card_id(card->getId());
}
ges.enqueueGameEvent(eventPrivate, playerId, GameEventStorageItem::SendToPrivate, playerId);
ges.enqueueGameEvent(eventOthers, playerId, GameEventStorageItem::SendToOthers);
if (originalPosition == 0) {
revealTopStart = true;
}
if (newX == 0) {
revealTopTarget = true;
}
// handle side effects for this card
onCardBeingMoved(ges, cardStruct, startzone, targetzone, undoingDraw);
}
}
void Server_AbstractPlayer::onCardBeingMoved(GameEventStorage &ges,
const MoveCardStruct &cardStruct,
Server_CardZone *startzone,

View file

@ -93,6 +93,19 @@ public:
bool fixFreeSpaces = true,
bool undoingDraw = false,
bool isReversed = false);
void processMoveCard(GameEventStorage &ges,
Server_CardZone *startzone,
Server_CardZone *targetzone,
MoveCardStruct cardStruct,
int xCoord,
int yCoord,
int &xIndex,
bool &revealTopStart,
bool &revealTopTarget,
bool isReversed,
bool undoingDraw);
virtual void onCardBeingMoved(GameEventStorage &ges,
const MoveCardStruct &cardStruct,
Server_CardZone *startzone,

View file

@ -101,6 +101,10 @@ QString getSafeDebugString(const ::google::protobuf::Message &message)
#endif // GOOGLE_PROTOBUF_VERSION > 3004000
std::string debug_string;
printer.PrintToString(message, &debug_string);
return QString::number(size) + " bytes " + QString::fromStdString(debug_string);
bool ok = printer.PrintToString(message, &debug_string);
if (ok) {
return QString::number(size) + " bytes " + QString::fromStdString(debug_string);
} else {
return "[could not convert message to string]";
}
}

View file

@ -20,17 +20,17 @@ void CardDatabaseSettings::setIsKnown(QString shortName, bool isknown)
setValue(isknown, "isknown", "sets", std::move(shortName));
}
unsigned int CardDatabaseSettings::getSortKey(QString shortName)
unsigned int CardDatabaseSettings::getSortKey(QString shortName) const
{
return getValue("sortkey", "sets", std::move(shortName)).toUInt();
}
bool CardDatabaseSettings::isEnabled(QString shortName)
bool CardDatabaseSettings::isEnabled(QString shortName) const
{
return getValue("enabled", "sets", std::move(shortName)).toBool();
}
bool CardDatabaseSettings::isKnown(QString shortName)
bool CardDatabaseSettings::isKnown(QString shortName) const
{
return getValue("isknown", "sets", std::move(shortName)).toBool();
}

View file

@ -22,9 +22,9 @@ public:
void setEnabled(QString shortName, bool enabled) override;
void setIsKnown(QString shortName, bool isknown) override;
unsigned int getSortKey(QString shortName) override;
bool isEnabled(QString shortName) override;
bool isKnown(QString shortName) override;
unsigned int getSortKey(QString shortName) const override;
bool isEnabled(QString shortName) const override;
bool isKnown(QString shortName) const override;
private:
explicit CardDatabaseSettings(const QString &settingPath, QObject *parent = nullptr);

View file

@ -15,7 +15,7 @@ void CardOverrideSettings::deleteCardPreferenceOverride(const QString &cardName)
deleteValue(cardName);
}
QString CardOverrideSettings::getCardPreferenceOverride(const QString &cardName)
QString CardOverrideSettings::getCardPreferenceOverride(const QString &cardName) const
{
return getValue(cardName).toString();
}

View file

@ -22,7 +22,7 @@ public:
void deleteCardPreferenceOverride(const QString &cardName);
QString getCardPreferenceOverride(const QString &cardName);
QString getCardPreferenceOverride(const QString &cardName) const;
private:
explicit CardOverrideSettings(const QString &settingPath, QObject *parent = nullptr);

View file

@ -11,22 +11,22 @@ DebugSettings::DebugSettings(const QString &settingPath, QObject *parent)
}
}
bool DebugSettings::getShowCardId()
bool DebugSettings::getShowCardId() const
{
return getValue("showCardId").toBool();
}
bool DebugSettings::getLocalGameOnStartup()
bool DebugSettings::getLocalGameOnStartup() const
{
return getValue("onStartup", "localgame").toBool();
}
int DebugSettings::getLocalGamePlayerCount()
int DebugSettings::getLocalGamePlayerCount() const
{
return getValue("playerCount", "localgame").toInt();
}
QString DebugSettings::getDeckPathForPlayer(const QString &playerName)
QString DebugSettings::getDeckPathForPlayer(const QString &playerName) const
{
return getValue(playerName, "localgame", "deck").toString();
}

View file

@ -17,12 +17,12 @@ class DebugSettings : public SettingsManager
DebugSettings(const DebugSettings & /*other*/);
public:
bool getShowCardId();
bool getShowCardId() const;
bool getLocalGameOnStartup();
int getLocalGamePlayerCount();
bool getLocalGameOnStartup() const;
int getLocalGamePlayerCount() const;
QString getDeckPathForPlayer(const QString &playerName);
QString getDeckPathForPlayer(const QString &playerName) const;
};
#endif // DEBUG_SETTINGS_H

View file

@ -18,7 +18,7 @@ void DownloadSettings::setDownloadUrls(const QStringList &downloadURLs)
setValue(QVariant::fromValue(downloadURLs), "urls");
}
QStringList DownloadSettings::getAllURLs()
QStringList DownloadSettings::getAllURLs() const
{
return getValue("urls").toStringList();
}

View file

@ -19,7 +19,7 @@ class DownloadSettings : public SettingsManager
public:
explicit DownloadSettings(const QString &, QObject *);
QStringList getAllURLs();
QStringList getAllURLs() const;
void setDownloadUrls(const QStringList &downloadURLs);
void resetToDefaultURLs();
};

View file

@ -8,11 +8,11 @@ GameFiltersSettings::GameFiltersSettings(const QString &settingPath, QObject *pa
{
}
/*
/**
* The game type might contain special characters, so to use it in
* QSettings we just hash it.
*/
QString GameFiltersSettings::hashGameType(const QString &gameType) const
static QString hashGameType(const QString &gameType)
{
return QCryptographicHash::hash(gameType.toUtf8(), QCryptographicHash::Md5).toHex();
}
@ -22,7 +22,7 @@ void GameFiltersSettings::setHideBuddiesOnlyGames(bool hide)
setValue(hide, "hide_buddies_only_games");
}
bool GameFiltersSettings::isHideBuddiesOnlyGames()
bool GameFiltersSettings::isHideBuddiesOnlyGames() const
{
QVariant previous = getValue("hide_buddies_only_games");
return previous == QVariant() ? false : previous.toBool();
@ -33,7 +33,7 @@ void GameFiltersSettings::setHideFullGames(bool hide)
setValue(hide, "hide_full_games");
}
bool GameFiltersSettings::isHideFullGames()
bool GameFiltersSettings::isHideFullGames() const
{
QVariant previous = getValue("hide_full_games");
return previous == QVariant() ? false : previous.toBool();
@ -44,7 +44,7 @@ void GameFiltersSettings::setHideGamesThatStarted(bool hide)
setValue(hide, "hide_games_that_started");
}
bool GameFiltersSettings::isHideGamesThatStarted()
bool GameFiltersSettings::isHideGamesThatStarted() const
{
QVariant previous = getValue("hide_games_that_started");
return previous == QVariant() ? false : previous.toBool();
@ -55,7 +55,7 @@ void GameFiltersSettings::setHidePasswordProtectedGames(bool hide)
setValue(hide, "hide_password_protected_games");
}
bool GameFiltersSettings::isHidePasswordProtectedGames()
bool GameFiltersSettings::isHidePasswordProtectedGames() const
{
QVariant previous = getValue("hide_password_protected_games");
return previous == QVariant() ? false : previous.toBool();
@ -66,7 +66,7 @@ void GameFiltersSettings::setHideIgnoredUserGames(bool hide)
setValue(hide, "hide_ignored_user_games");
}
bool GameFiltersSettings::isHideIgnoredUserGames()
bool GameFiltersSettings::isHideIgnoredUserGames() const
{
QVariant previous = getValue("hide_ignored_user_games");
return previous == QVariant() ? true : previous.toBool();
@ -77,7 +77,7 @@ void GameFiltersSettings::setHideNotBuddyCreatedGames(bool hide)
setValue(hide, "hide_not_buddy_created_games");
}
bool GameFiltersSettings::isHideNotBuddyCreatedGames()
bool GameFiltersSettings::isHideNotBuddyCreatedGames() const
{
QVariant previous = getValue("hide_not_buddy_created_games");
return previous == QVariant() ? false : previous.toBool();
@ -88,7 +88,7 @@ void GameFiltersSettings::setHideOpenDecklistGames(bool hide)
setValue(hide, "hide_open_decklist_games");
}
bool GameFiltersSettings::isHideOpenDecklistGames()
bool GameFiltersSettings::isHideOpenDecklistGames() const
{
QVariant previous = getValue("hide_open_decklist_games");
return previous == QVariant() ? false : previous.toBool();
@ -99,7 +99,7 @@ void GameFiltersSettings::setGameNameFilter(QString gameName)
setValue(gameName, "game_name_filter");
}
QString GameFiltersSettings::getGameNameFilter()
QString GameFiltersSettings::getGameNameFilter() const
{
return getValue("game_name_filter").toString();
}
@ -109,7 +109,7 @@ void GameFiltersSettings::setCreatorNameFilters(QStringList creatorName)
setValue(creatorName, "creator_name_filter");
}
QStringList GameFiltersSettings::getCreatorNameFilters()
QStringList GameFiltersSettings::getCreatorNameFilters() const
{
return getValue("creator_name_filter").toStringList();
}
@ -119,7 +119,7 @@ void GameFiltersSettings::setMinPlayers(int min)
setValue(min, "min_players");
}
int GameFiltersSettings::getMinPlayers()
int GameFiltersSettings::getMinPlayers() const
{
QVariant previous = getValue("min_players");
return previous == QVariant() ? 1 : previous.toInt();
@ -130,7 +130,7 @@ void GameFiltersSettings::setMaxPlayers(int max)
setValue(max, "max_players");
}
int GameFiltersSettings::getMaxPlayers()
int GameFiltersSettings::getMaxPlayers() const
{
QVariant previous = getValue("max_players");
return previous == QVariant() ? 99 : previous.toInt();
@ -141,7 +141,7 @@ void GameFiltersSettings::setMaxGameAge(const QTime &maxGameAge)
setValue(maxGameAge, "max_game_age_time");
}
QTime GameFiltersSettings::getMaxGameAge()
QTime GameFiltersSettings::getMaxGameAge() const
{
QVariant previous = getValue("max_game_age_time");
return previous.toTime();
@ -157,7 +157,7 @@ void GameFiltersSettings::setGameHashedTypeEnabled(QString gametypeHASHED, bool
setValue(enabled, gametypeHASHED);
}
bool GameFiltersSettings::isGameTypeEnabled(QString gametype)
bool GameFiltersSettings::isGameTypeEnabled(QString gametype) const
{
QVariant previous = getValue("game_type/" + hashGameType(gametype));
return previous == QVariant() ? false : previous.toBool();
@ -168,7 +168,7 @@ void GameFiltersSettings::setShowOnlyIfSpectatorsCanWatch(bool show)
setValue(show, "show_only_if_spectators_can_watch");
}
bool GameFiltersSettings::isShowOnlyIfSpectatorsCanWatch()
bool GameFiltersSettings::isShowOnlyIfSpectatorsCanWatch() const
{
QVariant previous = getValue("show_only_if_spectators_can_watch");
return previous == QVariant() ? false : previous.toBool();
@ -179,7 +179,7 @@ void GameFiltersSettings::setShowSpectatorPasswordProtected(bool show)
setValue(show, "show_spectator_password_protected");
}
bool GameFiltersSettings::isShowSpectatorPasswordProtected()
bool GameFiltersSettings::isShowSpectatorPasswordProtected() const
{
QVariant previous = getValue("show_spectator_password_protected");
return previous == QVariant() ? false : previous.toBool();
@ -190,7 +190,7 @@ void GameFiltersSettings::setShowOnlyIfSpectatorsCanChat(bool show)
setValue(show, "show_only_if_spectators_can_chat");
}
bool GameFiltersSettings::isShowOnlyIfSpectatorsCanChat()
bool GameFiltersSettings::isShowOnlyIfSpectatorsCanChat() const
{
QVariant previous = getValue("show_only_if_spectators_can_chat");
return previous == QVariant() ? false : previous.toBool();
@ -201,7 +201,7 @@ void GameFiltersSettings::setShowOnlyIfSpectatorsCanSeeHands(bool show)
setValue(show, "show_only_if_spectators_can_see_hands");
}
bool GameFiltersSettings::isShowOnlyIfSpectatorsCanSeeHands()
bool GameFiltersSettings::isShowOnlyIfSpectatorsCanSeeHands() const
{
QVariant previous = getValue("show_only_if_spectators_can_see_hands");
return previous == QVariant() ? false : previous.toBool();

View file

@ -16,23 +16,23 @@ class GameFiltersSettings : public SettingsManager
friend class SettingsCache;
public:
bool isHideBuddiesOnlyGames();
bool isHideFullGames();
bool isHideGamesThatStarted();
bool isHidePasswordProtectedGames();
bool isHideIgnoredUserGames();
bool isHideNotBuddyCreatedGames();
bool isHideOpenDecklistGames();
QString getGameNameFilter();
QStringList getCreatorNameFilters();
int getMinPlayers();
int getMaxPlayers();
QTime getMaxGameAge();
bool isGameTypeEnabled(QString gametype);
bool isShowOnlyIfSpectatorsCanWatch();
bool isShowSpectatorPasswordProtected();
bool isShowOnlyIfSpectatorsCanChat();
bool isShowOnlyIfSpectatorsCanSeeHands();
bool isHideBuddiesOnlyGames() const;
bool isHideFullGames() const;
bool isHideGamesThatStarted() const;
bool isHidePasswordProtectedGames() const;
bool isHideIgnoredUserGames() const;
bool isHideNotBuddyCreatedGames() const;
bool isHideOpenDecklistGames() const;
QString getGameNameFilter() const;
QStringList getCreatorNameFilters() const;
int getMinPlayers() const;
int getMaxPlayers() const;
QTime getMaxGameAge() const;
bool isGameTypeEnabled(QString gametype) const;
bool isShowOnlyIfSpectatorsCanWatch() const;
bool isShowSpectatorPasswordProtected() const;
bool isShowOnlyIfSpectatorsCanChat() const;
bool isShowOnlyIfSpectatorsCanSeeHands() const;
void setHideBuddiesOnlyGames(bool hide);
void setHideIgnoredUserGames(bool hide);
@ -56,8 +56,6 @@ public:
private:
explicit GameFiltersSettings(const QString &settingPath, QObject *parent = nullptr);
GameFiltersSettings(const GameFiltersSettings & /*other*/);
[[nodiscard]] QString hashGameType(const QString &gameType) const;
};
#endif // GAMEFILTERSSETTINGS_H

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