Compare commits

..

6 commits

Author SHA1 Message Date
Lukas Brübach
779b4d2c95 braced again
Took 3 minutes
2026-05-24 01:59:27 +02:00
Lukas Brübach
0155d2b2bc Use aggregate and QList
Took 8 minutes

Took 6 seconds
2026-05-24 01:38:03 +02:00
Lukas Brübach
96b331746b Fix compile errors.
Took 54 minutes

Took 14 seconds
2026-05-24 01:27:52 +02:00
Lukas Brübach
7d6d625ed4 Delete on finish
Took 16 minutes

Took 21 seconds
2026-05-23 22:14:30 +02:00
Lukas Brübach
e9e3515628 Follow parent window properly.
Took 19 seconds
2026-05-23 18:22:53 +02:00
Brübach, Lukas
7168802bbf [App] First-run tutorial
Took 5 minutes

Took 3 minutes
2026-05-23 09:17:37 +02:00
641 changed files with 49630 additions and 83492 deletions

View file

@ -159,7 +159,6 @@ if [[ $PACKAGE_TYPE ]]; then
fi
if [[ $USE_VCPKG ]]; then
flags+=("-DUSE_VCPKG=1")
flags+=("-DVCPKG_INSTALL_OPTIONS=--x-abi-tools-use-exact-versions")
fi
# Add cmake --build flags

View file

@ -13,9 +13,17 @@ fi
# Check formatting using format.sh
echo "Checking your code using format.sh..."
./format.sh --color-diff --cmake --shell --print-version --branch origin/master
diff="$(./format.sh --diff --cmake --shell --print-version --branch origin/master)"
err=$?
sep="
----------
"
used_version="${diff%%"$sep"*}"
diff="${diff#*"$sep"}"
changes_to_make="${diff%%"$sep"*}"
files_to_edit="${diff#*"$sep"}"
case $err in
1)
cat <<EOM
@ -28,10 +36,19 @@ case $err in
*** Then commit and push those changes to this branch. ***
*** Check our CONTRIBUTING.md file for more details. ***
*** ***
*** Thank you ❤️ ***
*** Thank you ❤️ ***
*** ***
***********************************************************
Used version:
$used_version
Affected files:
$files_to_edit
The following changes should be made:
$changes_to_make
Exiting...
EOM
exit 2
@ -48,6 +65,9 @@ EOM
*** ***
***********************************************************
Used version:
$used_version
Exiting...
EOM
exit 0

5
.codacy.yml Normal file
View file

@ -0,0 +1,5 @@
---
exclude_paths:
- '**/translations/*.ts'
# codacy config documentation: https://support.codacy.com/hc/en-us/articles/115002130625-Codacy-Configuration-File

View file

@ -334,7 +334,7 @@ the tr() call, also you can add an extra string as a hint for translators:
QString message = tr("Everyone draws %n cards", "english hint for translators", amount);
```
See [Qt's wiki on translations](
https://doc.qt.io/qt-6/i18n-source-translation.html#handling-plurals)
https://doc.qt.io/qt-5/i18n-source-translation.html#handling-plurals)
If you're about to propose a change that adds or modifies any translatable
string in the code, you don't need to take care of adding the new strings to

View file

@ -1,10 +1,10 @@
name: Build Desktop
permissions:
actions: write # needed to delete entries in GHA cache (update ccache)
attestations: write # needed to persist the attestation.
contents: write
id-token: write # needed for signing certificate in attestation
id-token: write
attestations: write
actions: write # needed for ccache action to be able to delete gha caches
on:
push:
@ -19,7 +19,7 @@ on:
- '.github/workflows/desktop-build.yml'
- 'CMakeLists.txt'
- 'vcpkg.json'
- 'vcpkg' # needed to match submodule bumps (gitlink)
- 'vcpkg'
tags:
- '*'
pull_request:
@ -32,7 +32,7 @@ on:
- '.github/workflows/desktop-build.yml'
- 'CMakeLists.txt'
- 'vcpkg.json'
- 'vcpkg' # needed to match submodule bumps (gitlink)
- 'vcpkg'
# Cancel earlier, unfinished runs of this workflow on the same branch (unless on release)
concurrency:
@ -44,217 +44,216 @@ jobs:
name: Configure
runs-on: ubuntu-slim
outputs:
tag: ${{ steps.configure.outputs.tag }}
sha: ${{ steps.configure.outputs.sha }}
tag: ${{steps.configure.outputs.tag}}
sha: ${{steps.configure.outputs.sha}}
steps:
- name: "Configure"
env:
RESOLVED_SHA: ${{ case(github.event_name == 'pull_request', github.event.pull_request.head.sha, github.sha) }}
steps:
- name: Configure
id: configure
shell: bash
run: |
if [[ "$GITHUB_REF_TYPE" == 'tag' ]]; then # release
echo "tag=$GITHUB_REF_NAME" >> "$GITHUB_OUTPUT"
tag_regex='^refs/tags/'
if [[ $GITHUB_EVENT_NAME == pull-request ]]; then # pull request
sha="${{github.event.pull_request.head.sha}}"
elif [[ $GITHUB_REF =~ $tag_regex ]]; then # release
sha="$GITHUB_SHA"
tag="${GITHUB_REF/refs\/tags\//}"
echo "tag=$tag" >>"$GITHUB_OUTPUT"
else # push to branch
sha="$GITHUB_SHA"
fi
echo "sha=$RESOLVED_SHA" >> "$GITHUB_OUTPUT"
echo "sha=$sha" >>"$GITHUB_OUTPUT"
- name: "Checkout"
- name: Checkout
if: steps.configure.outputs.tag != null
uses: actions/checkout@v7
uses: actions/checkout@v6
with:
fetch-depth: 0 # fetch all history for all branches and tags
fetch-depth: 0
- name: "Prepare release parameters"
- name: Prepare release parameters
id: prepare
if: steps.configure.outputs.tag != null
shell: bash
env:
TAG: ${{ steps.configure.outputs.tag }}
TAG: ${{steps.configure.outputs.tag}}
run: .ci/prep_release.sh
- name: "Create release"
- name: Create release
if: steps.configure.outputs.tag != null
id: create_release
shell: bash
env:
GH_TOKEN: ${{ github.token }}
tag_name: ${{ steps.configure.outputs.tag }}
target: ${{ steps.configure.outputs.sha }}
release_name: ${{ steps.prepare.outputs.title }}
body_path: ${{ steps.prepare.outputs.body_path }}
prerelease: ${{ steps.prepare.outputs.is_beta }}
GH_TOKEN: ${{github.token}}
tag_name: ${{steps.configure.outputs.tag}}
target: ${{steps.configure.outputs.sha}}
release_name: ${{steps.prepare.outputs.title}}
body_path: ${{steps.prepare.outputs.body_path}}
prerelease: ${{steps.prepare.outputs.is_beta}}
run: |
args=()
[[ $prerelease == yes ]] && args+=(--prerelease)
gh release create "$tag_name" --verify-tag --draft "${args[@]}" \
--target "$target" \
--title "$release_name" \
--notes-file "$body_path"
if [[ $prerelease == yes ]]; then
args="--prerelease"
fi
gh release create "$tag_name" --draft --verify-tag $args \
--target "$target" --title "$release_name" \
--notes-file "$body_path"
build-linux:
strategy:
fail-fast: false
matrix:
# The files in ".ci/$distro$version" correspond to the values given here
# These names correspond to the files in ".ci/$distro$version"
include:
- distro: Arch
allow-failure: yes
package: skip # We are packaged in Arch already
allow-failure: yes
- distro: Servatrice_Debian
version: 12
package: DEB
server_only: yes
test: skip
server_only: yes
- distro: Debian
version: 12
package: DEB
test: skip # Running tests on all distros is superfluous
- distro: Debian
version: 13
package: DEB
- distro: Fedora
version: 43
package: RPM
test: skip # Running tests on all distros is superfluous
- distro: Fedora
version: 44
package: RPM
- distro: Ubuntu
version: 24.04
package: DEB
test: skip # Running tests on all distros is superfluous
- distro: Ubuntu
version: 26.04
package: DEB
name: ${{ matrix.distro }} ${{ matrix.version }}
name: ${{matrix.distro}} ${{matrix.version}}
needs: configure
runs-on: ubuntu-latest
continue-on-error: ${{ matrix.allow-failure == 'yes' }}
continue-on-error: ${{matrix.allow-failure == 'yes'}}
timeout-minutes: 70
env:
CACHE: ${{ github.workspace }}/.cache/${{ matrix.distro }}${{ matrix.version }} # directory for caching docker image and ccache
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
CCACHE_SIZE: 550M # space of all repo is 10Gi: https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#usage-limits-and-eviction-policy
CMAKE_GENERATOR: 'Ninja'
NAME: ${{ matrix.distro }}${{ matrix.version }}
steps:
- name: "Checkout"
uses: actions/checkout@v7
- name: Checkout
uses: actions/checkout@v6
- name: "Restore compiler cache (ccache)"
- name: Restore compiler cache (ccache)
id: ccache_restore
uses: actions/cache/restore@v6
uses: actions/cache/restore@v5
env:
BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
with:
key: ccache-${{ matrix.distro }}${{ matrix.version }}-${{ env.BRANCH_NAME }}
path: ${{ env.CACHE }}
restore-keys: ccache-${{ matrix.distro }}${{ matrix.version }}-
path: ${{env.CACHE}}
key: ccache-${{matrix.distro}}${{matrix.version}}-${{env.BRANCH_NAME}}
restore-keys: ccache-${{matrix.distro}}${{matrix.version}}-
- name: "Build ${{ matrix.distro }} ${{ matrix.version }} Docker image"
- name: Build ${{matrix.distro}} ${{matrix.version}} Docker image
shell: bash
run: source .ci/docker.sh --build
- name: "Build debug and test"
- name: Build debug and test
if: matrix.test != 'skip'
shell: bash
run: |
source .ci/docker.sh
RUN --server --debug --test --ccache "$CCACHE_SIZE" \
--cmake-generator "$CMAKE_GENERATOR"
--cmake-generator "$CMAKE_GENERATOR"
- name: "Build release package"
- name: Build release package
id: build
if: matrix.package != 'skip'
shell: bash
env:
SUFFIX: '-${{ matrix.distro }}${{ matrix.version }}'
package: '${{ matrix.package }}'
server_only: '${{ matrix.server_only }}'
SUFFIX: '-${{matrix.distro}}${{matrix.version}}'
package: '${{matrix.package}}'
server_only: '${{matrix.server_only}}'
run: |
source .ci/docker.sh
args=()
[[ $server_only == yes ]] && args+=(--no-client)
[[ $GITHUB_REF == "refs/heads/master" ]] && args+=(--evict-ccache "$CCACHE_EVICTION_AGE")
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
- name: "Delete remote compiler cache (ccache)"
- name: Delete remote compiler cache (ccache)
if: github.ref == 'refs/heads/master' && steps.ccache_restore.outputs.cache-hit
continue-on-error: true
env:
CACHE_PRIMARY_KEY: ${{ steps.ccache_restore.outputs.cache-primary-key }}
GH_TOKEN: ${{ github.token }}
run: |
if gh cache delete --repo "$GITHUB_REPOSITORY" "$CACHE_PRIMARY_KEY"; then
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)"
- name: Save updated compiler cache (ccache)
if: github.ref == 'refs/heads/master'
uses: actions/cache/save@v6
uses: actions/cache/save@v5
with:
path: ${{env.CACHE}}
key: ${{ steps.ccache_restore.outputs.cache-primary-key }}
path: ${{ env.CACHE }}
- name: "Upload artifact"
- name: Upload artifact
id: upload_artifact
if: matrix.package != 'skip'
uses: actions/upload-artifact@v7
with:
path: ${{steps.build.outputs.path}}
archive: false
if-no-files-found: error
path: ${{ steps.build.outputs.path }}
- name: "Upload to release"
- name: Upload to release
id: upload_release
if: matrix.package != 'skip' && needs.configure.outputs.tag != null
shell: bash
env:
asset_name: ${{ steps.build.outputs.fullname }}
asset_path: ${{ steps.build.outputs.path }}
GH_TOKEN: ${{ github.token }}
tag_name: ${{ needs.configure.outputs.tag }}
GH_TOKEN: ${{github.token}}
tag_name: ${{needs.configure.outputs.tag}}
asset_name: ${{steps.build.outputs.fullname}}
asset_path: ${{steps.build.outputs.path}}
run: gh release upload "$tag_name" "$asset_path#$asset_name"
- name: "Attest binary provenance"
- name: Attest binary provenance
id: attestation
if: steps.upload_release.outcome == 'success'
uses: actions/attest@v4
with:
subject-path: ${{steps.build.outputs.path}}
show-summary: false
subject-path: ${{ steps.build.outputs.path }}
- name: "Verify binary attestation"
- name: Verify binary attestation
if: steps.attestation.outcome == 'success'
shell: bash
env:
BUILD_PATH: ${{ steps.build.outputs.path }}
GH_TOKEN: ${{ github.token }}
run: gh attestation verify "$BUILD_PATH" --repo Cockatrice/Cockatrice
GH_TOKEN: ${{github.token}}
run: gh attestation verify "${{steps.build.outputs.path}}" --repo Cockatrice/Cockatrice
build-vcpkg:
strategy:
@ -264,234 +263,234 @@ jobs:
- os: macOS
target: 13
runner: macos-15-intel
ccache_eviction_age: 7d
cmake_generator: Ninja
make_package: 1
soc: Intel
xcode: "16.4"
type: Release
override_target: 13
make_package: 1
package_suffix: "-macOS13_Intel"
qt_version: 6.11.0
qt_arch: clang_64
qt_modules: qtimageformats qtmultimedia qtwebsockets
soc: Intel
type: Release
cmake_generator: Ninja
use_ccache: 1
xcode: "16.4"
ccache_eviction_age: 7d
- os: macOS
target: 14
runner: macos-14
ccache_eviction_age: 7d
cmake_generator: Ninja
soc: Apple
xcode: "15.4"
type: Release
make_package: 1
package_suffix: "-macOS14"
qt_version: 6.11.0
qt_arch: clang_64
qt_modules: qtimageformats qtmultimedia qtwebsockets
soc: Apple
type: Release
cmake_generator: Ninja
use_ccache: 1
xcode: "15.4"
ccache_eviction_age: 7d
- os: macOS
target: 15
runner: macos-15
ccache_eviction_age: 7d
cmake_generator: Ninja
soc: Apple
xcode: "16.4"
type: Release
make_package: 1
package_suffix: "-macOS15"
qt_version: 6.11.0
qt_arch: clang_64
qt_modules: qtimageformats qtmultimedia qtwebsockets
soc: Apple
type: Release
cmake_generator: Ninja
use_ccache: 1
xcode: "16.4"
ccache_eviction_age: 7d
- os: macOS
target: 15
runner: macos-15
ccache_eviction_age: 7d
cmake_generator: Ninja
qt_version: 6.11.0
qt_modules: qtimageformats qtmultimedia qtwebsockets
soc: Apple
type: Debug
use_ccache: 1
xcode: "16.4"
type: Debug
qt_version: 6.11.0
qt_arch: clang_64
qt_modules: qtimageformats qtmultimedia qtwebsockets
cmake_generator: Ninja
use_ccache: 1
ccache_eviction_age: 7d
- os: Windows
target: 10
runner: windows-2025
cmake_generator: "Visual Studio 18 2026"
cmake_generator_platform: x64
type: Release
make_package: 1
package_suffix: "-Win10"
qt_version: 6.11.0
qt_arch: win64_msvc2022_64
qt_modules: qtimageformats qtmultimedia qtwebsockets
type: Release
cmake_generator: "Visual Studio 17 2022"
cmake_generator_platform: x64
name: ${{ matrix.os }} ${{ matrix.target }}${{ matrix.soc == 'Intel' && ' Intel' || '' }}${{ matrix.type == 'Debug' && ' Debug' || '' }}
name: ${{matrix.os}} ${{matrix.target}}${{ matrix.soc == 'Intel' && ' Intel' || '' }}${{ matrix.type == 'Debug' && ' Debug' || '' }}
needs: configure
runs-on: ${{ matrix.runner }}
runs-on: ${{matrix.runner}}
timeout-minutes: 100
env:
CCACHE_DIR: ${{ github.workspace }}/.cache/
CCACHE_SIZE: 550M # space of all repo is 10Gi: https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#usage-limits-and-eviction-policy
CCACHE_DIR: ${{github.workspace}}/.cache/
# Cache size over the entire repo is 10Gi:
# https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#usage-limits-and-eviction-policy
CCACHE_SIZE: 550M
steps:
- name: "Checkout"
uses: actions/checkout@v7
- name: Checkout
uses: actions/checkout@v6
with:
submodules: recursive
- name: "[Windows] Add msbuild to PATH"
- name: Add msbuild to PATH
if: matrix.os == 'Windows'
id: add-msbuild
uses: microsoft/setup-msbuild@v3
with:
msbuild-architecture: x64
- name: "[macOS] Setup ccache"
if: matrix.os == 'macOS' && matrix.use_ccache == 1
- name: Setup ccache
if: matrix.use_ccache == 1 && matrix.os == 'macOS'
run: brew install ccache
- name: "[macOS] Restore compiler cache (ccache)"
if: matrix.os == 'macOS' && matrix.use_ccache == 1
- name: Restore compiler cache (ccache)
if: matrix.use_ccache == 1
id: ccache_restore
uses: actions/cache/restore@v6
uses: actions/cache/restore@v5
env:
BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
with:
key: ccache-${{ matrix.runner }}-${{ matrix.soc }}-${{ matrix.type }}-${{ env.BRANCH_NAME }}
path: ${{ env.CCACHE_DIR }}
restore-keys: ccache-${{ matrix.runner }}-${{ matrix.soc }}-${{ matrix.type }}-
path: ${{env.CCACHE_DIR}}
key: ccache-${{matrix.runner}}-${{matrix.soc}}-${{matrix.type}}-${{env.BRANCH_NAME}}
restore-keys: ccache-${{matrix.runner}}-${{matrix.soc}}-${{matrix.type}}-
- name: "Install aqtinstall"
- name: Install aqtinstall
run: pipx install aqtinstall
# Resolve given wildcard versions (e.g. Qt 6.6.*) to latest version via aqtinstall to avoid stale caches on new releases
- name: "Resolve latest Qt patch version"
env:
QT_VERSION: ${{ matrix.qt_version }}
- name: Resolve latest Qt patch version
id: resolve_qt_version
shell: bash
run: .ci/resolve_latest_aqt_qt_version.sh "$QT_VERSION"
run: .ci/resolve_latest_aqt_qt_version.sh "${{matrix.qt_version}}"
- name: "[macOS] Restore thin Qt ${{ steps.resolve_qt_version.outputs.version }} libraries"
- name: Restore thin Qt ${{ steps.resolve_qt_version.outputs.version }} libraries (${{ matrix.soc }} macOS)
if: matrix.os == 'macOS'
id: restore_qt
uses: actions/cache/restore@v6
uses: actions/cache/restore@v5
with:
key: thin-qt-macos-${{ matrix.soc }}-${{ steps.resolve_qt_version.outputs.version }}
path: ${{ github.workspace }}/Qt
key: thin-qt-macos-${{ matrix.soc }}-${{ steps.resolve_qt_version.outputs.version }}
# Using jurplel/install-qt-action to install Qt without using brew
# Qt build using vcpkg either just fails or takes too long to build
- name: "[macOS] Install fat Qt ${{ steps.resolve_qt_version.outputs.version }}"
# qt build using vcpkg either just fails or takes too long to build
- name: Install fat Qt ${{ steps.resolve_qt_version.outputs.version }} (${{ matrix.soc }} macOS)
if: matrix.os == 'macOS' && steps.restore_qt.outputs.cache-hit != 'true'
uses: jurplel/install-qt-action@v4
with:
cache: false
dir: ${{ github.workspace }}
modules: ${{ matrix.qt_modules }}
version: ${{ steps.resolve_qt_version.outputs.version }}
arch: ${{matrix.qt_arch}}
modules: ${{matrix.qt_modules}}
cache: false
dir: ${{github.workspace}}
- name: "[macOS] Create thin Qt libraries"
- name: Thin Qt libraries (${{ matrix.soc }} macOS)
if: matrix.os == 'macOS' && steps.restore_qt.outputs.cache-hit != 'true'
run: .ci/thin_macos_qtlib.sh
- name: "[macOS] Cache thin Qt libraries"
- name: Cache thin Qt libraries (${{ matrix.soc }} macOS)
if: matrix.os == 'macOS' && steps.restore_qt.outputs.cache-hit != 'true'
uses: actions/cache/save@v6
uses: actions/cache/save@v5
with:
key: thin-qt-macos-${{ matrix.soc }}-${{ steps.resolve_qt_version.outputs.version }}
path: ${{ github.workspace }}/Qt
key: thin-qt-macos-${{ matrix.soc }}-${{ steps.resolve_qt_version.outputs.version }}
- name: "[Windows] Install Qt ${{ matrix.qt_version }}"
- name: Install Qt ${{matrix.qt_version}} (Windows)
if: matrix.os == 'Windows'
uses: jurplel/install-qt-action@v4
with:
# Qt 6.11.0 only works with aqtinstall directly from git until aqtinstall 3.4 is released
# qt 6.11.0 only works with aqtinstall directly from git until aqtinstall 3.4 is released
aqtsource: git+https://github.com/miurahr/aqtinstall.git
cache: true
modules: ${{ matrix.qt_modules }}
version: ${{ steps.resolve_qt_version.outputs.version }}
arch: ${{matrix.qt_arch}}
modules: ${{matrix.qt_modules}}
cache: true
- name: "[Windows] Install NSIS"
- name: Install NSIS
if: matrix.os == 'Windows'
shell: bash
run: choco install nsis
- name: "Setup vcpkg cache"
- name: Setup vcpkg cache
id: vcpkg-cache
uses: TAServers/vcpkg-cache@v3
with:
token: ${{ secrets.GITHUB_TOKEN }}
# Uses environment variables, see compile.sh for more details
- name: "Build Cockatrice"
# uses environment variables, see compile.sh for more details
- name: Build Cockatrice
id: build
shell: bash
env:
BUILDTYPE: '${{ matrix.type }}'
CCACHE_EVICTION_AGE: ${{ matrix.ccache_eviction_age }}
CMAKE_GENERATOR: ${{ matrix.cmake_generator }}
CMAKE_GENERATOR_PLATFORM: ${{ matrix.cmake_generator_platform }}
DEVELOPER_DIR: '/Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer'
MACOS_CERTIFICATE: ${{ secrets.PROD_MACOS_CERTIFICATE }}
MACOS_CERTIFICATE_NAME: ${{ secrets.PROD_MACOS_CERTIFICATE_NAME }}
MACOS_CERTIFICATE_PWD: ${{ secrets.PROD_MACOS_CERTIFICATE_PWD }}
MACOS_CI_KEYCHAIN_PWD: ${{ secrets.PROD_MACOS_CI_KEYCHAIN_PWD }}
MAKE_PACKAGE: '${{ matrix.make_package }}'
PACKAGE_SUFFIX: '${{ matrix.package_suffix }}'
TARGET_MACOS_VERSION: ${{ matrix.override_target }}
USE_CCACHE: ${{ matrix.use_ccache }}
VCPKG_BINARY_SOURCES: 'clear;files,${{ steps.vcpkg-cache.outputs.path }},readwrite'
BUILDTYPE: '${{matrix.type}}'
MAKE_PACKAGE: '${{matrix.make_package}}'
PACKAGE_SUFFIX: '${{matrix.package_suffix}}'
CMAKE_GENERATOR: ${{matrix.cmake_generator}}
CMAKE_GENERATOR_PLATFORM: ${{matrix.cmake_generator_platform}}
USE_CCACHE: ${{matrix.use_ccache}}
VCPKG_DISABLE_METRICS: 1
VCPKG_BINARY_SOURCES: 'clear;files,${{ steps.vcpkg-cache.outputs.path }},readwrite'
# macOS-specific environment variables, will be ignored on Windows
MACOS_CERTIFICATE: ${{ secrets.PROD_MACOS_CERTIFICATE }}
MACOS_CERTIFICATE_PWD: ${{ secrets.PROD_MACOS_CERTIFICATE_PWD }}
MACOS_CERTIFICATE_NAME: ${{ secrets.PROD_MACOS_CERTIFICATE_NAME }}
MACOS_CI_KEYCHAIN_PWD: ${{ secrets.PROD_MACOS_CI_KEYCHAIN_PWD }}
DEVELOPER_DIR: '/Applications/Xcode_${{matrix.xcode}}.app/Contents/Developer'
TARGET_MACOS_VERSION: ${{ matrix.override_target }}
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
- name: "[macOS] Delete remote compiler cache (ccache)"
if: matrix.os == 'macOS' && matrix.use_ccache == 1 && github.ref == 'refs/heads/master' && steps.ccache_restore.outputs.cache-hit
- 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:
CACHE_PRIMARY_KEY: ${{ steps.ccache_restore.outputs.cache-primary-key }}
GH_TOKEN: ${{ github.token }}
run: |
if gh cache delete --repo "$GITHUB_REPOSITORY" "$CACHE_PRIMARY_KEY"; then
if gh cache delete --repo ${{ github.repository }} ${{ steps.ccache_restore.outputs.cache-primary-key }}; then
echo "Cache deleted successfully"
fi
- name: "[macOS] Save updated compiler cache (ccache)"
if: matrix.os == 'macOS' && matrix.use_ccache == 1 && github.ref == 'refs/heads/master'
uses: actions/cache/save@v6
- name: Save updated compiler cache (ccache)
if: github.ref == 'refs/heads/master' && matrix.use_ccache == 1
uses: actions/cache/save@v5
with:
path: ${{env.CCACHE_DIR}}
key: ${{ steps.ccache_restore.outputs.cache-primary-key }}
path: ${{ env.CCACHE_DIR }}
- name: "[macOS] Sign app bundle"
- name: Sign app bundle
if: matrix.os == 'macOS' && matrix.make_package && needs.configure.outputs.tag != null
id: sign_macos
env:
BUILD_PATH: ${{ steps.build.outputs.path }}
MACOS_CERTIFICATE_NAME: ${{ secrets.PROD_MACOS_CERTIFICATE_NAME }}
MACOS_CI_KEYCHAIN_PWD: ${{ secrets.PROD_MACOS_CI_KEYCHAIN_PWD }}
run: |
if [[ -n "$MACOS_CERTIFICATE_NAME" ]]
then
security unlock-keychain -p "$MACOS_CI_KEYCHAIN_PWD" build.keychain
/usr/bin/codesign --sign="$MACOS_CERTIFICATE_NAME" --entitlements=".ci/macos.entitlements" --options=runtime --force --deep --timestamp --verbose "$BUILD_PATH"
/usr/bin/codesign --sign="$MACOS_CERTIFICATE_NAME" --entitlements=".ci/macos.entitlements" --options=runtime --force --deep --timestamp --verbose "${{steps.build.outputs.path}}"
fi
- name: "[macOS] Notarize app bundle"
if: matrix.os == 'macOS' && steps.sign_macos.outcome == 'success'
- name: Notarize app bundle
if: steps.sign_macos.outcome == 'success'
env:
BUILD_PATH: ${{ steps.build.outputs.path }}
MACOS_NOTARIZATION_APPLE_ID: ${{ secrets.PROD_MACOS_NOTARIZATION_APPLE_ID }}
MACOS_NOTARIZATION_PWD: ${{ secrets.PROD_MACOS_NOTARIZATION_PWD }}
MACOS_NOTARIZATION_TEAM_ID: ${{ secrets.PROD_MACOS_NOTARIZATION_TEAM_ID }}
MACOS_NOTARIZATION_PWD: ${{ secrets.PROD_MACOS_NOTARIZATION_PWD }}
run: |
if [[ -n "$MACOS_NOTARIZATION_APPLE_ID" ]]
then
@ -503,7 +502,7 @@ jobs:
# Therefore, we create a zip file containing our app bundle, so that we can send it to the
# notarization service
echo "Creating temp notarization archive"
ditto -c -k --keepParent "$BUILD_PATH" "notarization.zip"
ditto -c -k --keepParent "${{steps.build.outputs.path}}" "notarization.zip"
# Here we send the notarization request to the Apple's Notarization service, waiting for the result.
# This typically takes a few seconds inside a CI environment, but it might take more depending on the App
@ -515,52 +514,51 @@ jobs:
# Finally, we need to "attach the staple" to our executable, which will allow our app to be
# validated by macOS even when an internet connection is not available.
echo "Attach staple"
xcrun stapler staple "$BUILD_PATH"
xcrun stapler staple "${{steps.build.outputs.path}}"
fi
- name: "Upload artifact"
- name: Upload artifact
if: matrix.make_package
id: upload_artifact
uses: actions/upload-artifact@v7
with:
path: ${{steps.build.outputs.path}}
archive: false
if-no-files-found: error
path: ${{ steps.build.outputs.path }}
- name: "[Windows] Upload PDBs (Program Databases)"
- name: Upload PDBs (Program Databases)
if: matrix.os == 'Windows' && github.ref_type != 'tag'
uses: actions/upload-artifact@v7
with:
if-no-files-found: error
name: ${{ steps.build.outputs.name }}-PDBs
name: ${{steps.build.outputs.name}}-PDBs
path: |
build/cockatrice/Release/*.pdb
build/oracle/Release/*.pdb
build/servatrice/Release/*.pdb
if-no-files-found: error
- name: "Upload to release"
- name: Upload to release
if: needs.configure.outputs.tag != null && matrix.make_package == '1'
id: upload_release
shell: bash
env:
asset_name: ${{ steps.build.outputs.fullname }}
asset_path: ${{ steps.build.outputs.path }}
GH_TOKEN: ${{ github.token }}
tag_name: ${{ needs.configure.outputs.tag }}
GH_TOKEN: ${{github.token}}
tag_name: ${{needs.configure.outputs.tag}}
asset_name: ${{steps.build.outputs.fullname}}
asset_path: ${{steps.build.outputs.path}}
run: gh release upload "$tag_name" "$asset_path#$asset_name"
- name: "Attest binary provenance"
- name: Attest binary provenance
if: steps.upload_release.outcome == 'success'
id: attestation
uses: actions/attest@v4
with:
subject-path: ${{steps.build.outputs.path}}
show-summary: false
subject-path: ${{ steps.build.outputs.path }}
- name: "Verify binary attestation"
- name: Verify binary attestation
if: steps.attestation.outcome == 'success'
shell: bash
env:
BUILD_PATH: ${{ steps.build.outputs.path }}
GH_TOKEN: ${{ github.token }}
run: gh attestation verify "$BUILD_PATH" --repo Cockatrice/Cockatrice
GH_TOKEN: ${{github.token}}
run: gh attestation verify "${{steps.build.outputs.path}}" --repo Cockatrice/Cockatrice

View file

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

View file

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

View file

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

View file

@ -1,14 +1,14 @@
name: Update Translations
on:
workflow_dispatch:
schedule:
# runs in the middle of each month starting a quarter (UTC) = two weeks after new strings are built
- cron: '0 0 15 1,4,7,10 *'
pull_request:
paths:
- '.tx/**'
- '.github/workflows/translations-pull.yml'
schedule:
# Runs in the middle of each month starting a quarter (UTC) = two weeks after new strings are built
- cron: '0 0 15 1,4,7,10 *'
workflow_dispatch:
jobs:
translations:
@ -19,20 +19,18 @@ jobs:
runs-on: ubuntu-slim
steps:
- name: "Checkout repo"
uses: actions/checkout@v7
- name: Checkout repo
uses: actions/checkout@v6
- name: "Pull translated strings from Transifex"
# Do not run this step for PR's from forks, they don't have access to the secret
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false
- name: Pull translated strings from Transifex
uses: transifex/cli-action@v2
with:
# Used config file: https://github.com/Cockatrice/Cockatrice/blob/master/.tx/config
# Docs: https://github.com/transifex/cli#pulling-files-from-transifex
args: pull --force --all
# used config file: https://github.com/Cockatrice/Cockatrice/blob/master/.tx/config
# https://github.com/transifex/cli#pulling-files-from-transifex
token: ${{ secrets.TX_TOKEN }}
args: pull --force --all
- name: "Create pull request"
- name: Create pull request
if: github.event_name != 'pull_request'
id: create_pr
uses: peter-evans/create-pull-request@v8
@ -40,32 +38,35 @@ jobs:
add-paths: |
cockatrice/translations/*.ts
oracle/translations/*.ts
author: github-actions <github-actions@github.com> # owner of the commit
commit-message: Update translation files
# author is the owner of the commit
author: github-actions <github-actions@github.com>
branch: ci-update_translations
delete-branch: true
title: 'Update translations'
body: |
Pulled all translated strings from [Transifex][1].
---
*This PR is automatically generated and updated by the workflow at `.github/workflows/translations-pull.yml`. Review [action runs][2].*<br>
*After merging, all new languages and translations are available in the next build.*
[1]: https://explore.transifex.com/cockatrice/cockatrice/
[2]: https://github.com/Cockatrice/Cockatrice/actions/workflows/translations-pull.yml?query=branch%3Amaster
branch: ci-update_translations
commit-message: Update translation files
delete-branch: true
draft: false
labels: |
CI
Translation
title: 'Update translations'
draft: false
- name: "PR Status"
- name: PR Status
if: github.event_name != 'pull_request'
shell: bash
env:
PR_NUMBER: ${{ steps.create_pr.outputs.pull-request-number }}
PR_URL: ${{ steps.create_pr.outputs.pull-request-url }}
STATUS: ${{ case(steps.create_pr.outputs.pull-request-operation == 'none', 'unchanged', steps.create_pr.outputs.pull-request-operation) }}
STATUS: ${{ steps.create_pr.outputs.pull-request-operation }}
run: |
echo "PR #$PR_NUMBER $STATUS!" >> "$GITHUB_STEP_SUMMARY"
echo "URL: $PR_URL" >> "$GITHUB_STEP_SUMMARY"
if [[ "$STATUS" == "none" ]]; then
echo "PR #${{ steps.create_pr.outputs.pull-request-number }} unchanged!" >> $GITHUB_STEP_SUMMARY
else
echo "PR #${{ steps.create_pr.outputs.pull-request-number }} $STATUS!" >> $GITHUB_STEP_SUMMARY
fi
echo "URL: ${{ steps.create_pr.outputs.pull-request-url }}" >> $GITHUB_STEP_SUMMARY

View file

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

1
.gitignore vendored
View file

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

View file

@ -64,11 +64,10 @@ if(WIN32 OR USE_VCPKG)
else()
set(QTDIR
""
CACHE PATH "Path to Qt (e.g. C:/Qt/6.4.2/msvc2019_64)"
CACHE PATH "Path to Qt (e.g. C:/Qt/5.7/msvc2015_64)"
)
message(
WARNING
"QTDIR variable is missing. Please set this variable to specify path to Qt (e.g. C:/Qt/6.4.2/msvc2019_64)"
WARNING "QTDIR variable is missing. Please set this variable to specify path to Qt (e.g. C:/Qt/5.7/msvc2015_64)"
)
endif()
endif()
@ -175,7 +174,7 @@ elseif(CMAKE_COMPILER_IS_GNUCXX)
-Wno-error=delete-non-virtual-dtor
-Wno-error=sign-compare
-Wno-error=missing-declarations
-Wno-error=sfinae-incomplete # GCC 16+: Qt MOC + protobuf forward decls trigger this
-Wno-error=sfinae-incomplete # GCC 16+: Qt MOC + protobuf forward decls trigger this
)
foreach(FLAG ${ADDITIONAL_DEBUG_FLAGS})
@ -281,7 +280,11 @@ if(UNIX)
if(CPACK_GENERATOR STREQUAL "RPM")
set(CPACK_RPM_PACKAGE_LICENSE "GPLv2")
set(CPACK_RPM_MAIN_COMPONENT "cockatrice")
set(CPACK_RPM_PACKAGE_REQUIRES "protobuf, qt6-qttools, qt6-qtsvg, qt6-qtmultimedia, qt6-qtimageformats")
if(Qt6_FOUND)
set(CPACK_RPM_PACKAGE_REQUIRES "protobuf, qt6-qttools, qt6-qtsvg, qt6-qtmultimedia, qt6-qtimageformats")
elseif(Qt5_FOUND)
set(CPACK_RPM_PACKAGE_REQUIRES "protobuf, qt5-qttools, qt5-qtsvg, qt5-qtmultimedia")
endif()
set(CPACK_RPM_PACKAGE_GROUP "Amusements/Games")
set(CPACK_RPM_PACKAGE_URL "http://github.com/Cockatrice/Cockatrice")
# stop directories from making package conflicts
@ -299,8 +302,12 @@ if(UNIX)
set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON)
set(CPACK_DEBIAN_PACKAGE_SECTION "games")
set(CPACK_DEBIAN_PACKAGE_HOMEPAGE "http://github.com/Cockatrice/Cockatrice")
set(CPACK_DEBIAN_PACKAGE_DEPENDS "libqt6multimedia6, libqt6svg6, qt6-qpa-plugins, qt6-image-formats-plugins")
set(CPACK_DEBIAN_PACKAGE_RECOMMENDS "libqt6sql6-mysql") # for connecting servatrice to a mysql db
if(Qt6_FOUND)
set(CPACK_DEBIAN_PACKAGE_DEPENDS "libqt6multimedia6, libqt6svg6, qt6-qpa-plugins, qt6-image-formats-plugins")
set(CPACK_DEBIAN_PACKAGE_RECOMMENDS "libqt6sql6-mysql") # for connecting servatrice to a mysql db
elseif(Qt5_FOUND)
set(CPACK_DEBIAN_PACKAGE_DEPENDS "libqt5multimedia5-plugins, libqt5svg5")
endif()
endif()
endif()
elseif(WIN32)

View file

@ -349,7 +349,7 @@ OPTIMIZE_OUTPUT_SLICE = NO
#
# Note see also the list of default file extension mappings.
EXTENSION_MAPPING = proto=C++
EXTENSION_MAPPING =
# If the MARKDOWN_SUPPORT tag is enabled then Doxygen pre-processes all comments
# according to the Markdown format, which allows for more readable
@ -1086,8 +1086,7 @@ FILE_PATTERNS = *.cc \
*.h++ \
*.markdown \
*.md \
*.dox \
*.proto
*.dox
# The RECURSIVE tag can be used to specify whether or not subdirectories should
# be searched for input files as well.
@ -1104,7 +1103,6 @@ RECURSIVE = YES
EXCLUDE = build/ \
cmake/ \
cmake-build-debug/ \
doc/doxygen/theme/docs/ \
doc/doxygen/theme/include/ \
vcpkg/
@ -1197,7 +1195,7 @@ INPUT_FILTER =
# need to set EXTENSION_MAPPING for the extension otherwise the files are not
# properly processed by Doxygen.
FILTER_PATTERNS = "*.proto=python doc/doxygen/filters/proto2cpp.py"
FILTER_PATTERNS =
# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
# INPUT_FILTER) will also be used to filter the input files that are used for

View file

@ -158,6 +158,7 @@ The following flags (with their non-default values) can be passed to `cmake`:
| `-DWARNING_AS_ERROR=0` | Don't treat compilation warnings as errors in debug mode |
| `-DUPDATE_TRANSLATIONS=1` | Configure `make` to update the translation .ts files for new strings in the source code<br> **Note:** `make clean` will remove the .ts files |
| `-DTEST=1` | Enable regression tests<br> **Note:** `make test` to run tests, *googletest* will be downloaded if not available |
| `-DFORCE_USE_QT5=1` | Skip looking for Qt6 before trying to find Qt5 |
# Run

View file

@ -1,7 +1,8 @@
# Find a compatible Qt version
# Inputs: WITH_SERVER, WITH_CLIENT, WITH_ORACLE
# Inputs: WITH_SERVER, WITH_CLIENT, WITH_ORACLE, FORCE_USE_QT5
# Optional Input: QT6_DIR -- Hint as to where Qt6 lives on the system
# Output: COCKATRICE_QT_VERSION_NAME -- Example values: Qt6
# Optional Input: QT5_DIR -- Hint as to where Qt5 lives on the system
# Output: COCKATRICE_QT_VERSION_NAME -- Example values: Qt5, Qt6
# Output: SERVATRICE_QT_MODULES
# Output: COCKATRICE_QT_MODULES
# Output: ORACLE_QT_MODULES
@ -38,37 +39,69 @@ set(REQUIRED_QT_COMPONENTS ${REQUIRED_QT_COMPONENTS} ${_SERVATRICE_NEEDED} ${_CO
)
list(REMOVE_DUPLICATES REQUIRED_QT_COMPONENTS)
# Linguist is now a component in Qt6 instead of an external package
find_package(
Qt6 6.4.2
COMPONENTS ${REQUIRED_QT_COMPONENTS} Linguist
QUIET HINTS ${Qt6_DIR}
)
if(NOT Qt6_FOUND)
message(FATAL_ERROR "No suitable version of Qt was found")
if(NOT FORCE_USE_QT5)
# Linguist is now a component in Qt6 instead of an external package
find_package(
Qt6 6.4.2
COMPONENTS ${REQUIRED_QT_COMPONENTS} Linguist
QUIET HINTS ${Qt6_DIR}
)
endif()
set(COCKATRICE_QT_VERSION_NAME Qt6)
if(Qt6_FOUND)
set(COCKATRICE_QT_VERSION_NAME Qt6)
list(FIND Qt6LinguistTools_TARGETS Qt6::lrelease QT6_LRELEASE_INDEX)
if(QT6_LRELEASE_INDEX EQUAL -1)
message(WARNING "Qt6 lrelease not found.")
list(FIND Qt6LinguistTools_TARGETS Qt6::lrelease QT6_LRELEASE_INDEX)
if(QT6_LRELEASE_INDEX EQUAL -1)
message(WARNING "Qt6 lrelease not found.")
endif()
list(FIND Qt6LinguistTools_TARGETS Qt6::lupdate QT6_LUPDATE_INDEX)
if(QT6_LUPDATE_INDEX EQUAL -1)
message(WARNING "Qt6 lupdate not found.")
endif()
else()
find_package(
Qt5 5.15.2
COMPONENTS ${REQUIRED_QT_COMPONENTS}
QUIET HINTS ${Qt5_DIR}
)
if(Qt5_FOUND)
set(COCKATRICE_QT_VERSION_NAME Qt5)
else()
message(FATAL_ERROR "No suitable version of Qt was found")
endif()
# Qt5 Linguist is in a separate package
find_package(Qt5LinguistTools QUIET)
if(Qt5LinguistTools_FOUND)
if(NOT Qt5_LRELEASE_EXECUTABLE)
message(WARNING "Qt5 lrelease not found.")
endif()
if(NOT Qt5_LUPDATE_EXECUTABLE)
message(WARNING "Qt5 lupdate not found.")
endif()
else()
message(WARNING "Linguist Tools not found, cannot handle translations")
endif()
endif()
list(FIND Qt6LinguistTools_TARGETS Qt6::lupdate QT6_LUPDATE_INDEX)
if(QT6_LUPDATE_INDEX EQUAL -1)
message(WARNING "Qt6 lupdate not found.")
if(Qt5_POSITION_INDEPENDENT_CODE OR Qt6_FOUND)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
endif()
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
# Establish Qt Plugins directory & Library directories
get_target_property(QT_LIBRARY_DIR ${COCKATRICE_QT_VERSION_NAME}::Core LOCATION)
get_filename_component(QT_LIBRARY_DIR ${QT_LIBRARY_DIR} DIRECTORY)
get_filename_component(QT_PLUGINS_DIR "${Qt6Core_DIR}/../../../${QT6_INSTALL_PLUGINS}" ABSOLUTE)
get_filename_component(QT_LIBRARY_DIR "${QT_LIBRARY_DIR}/../../.." ABSOLUTE)
if(UNIX AND APPLE)
# Mac needs a bit more help finding all necessary components
list(APPEND QT_LIBRARY_DIR "/usr/local/lib")
if(Qt6_FOUND)
get_filename_component(QT_PLUGINS_DIR "${Qt6Core_DIR}/../../../${QT6_INSTALL_PLUGINS}" ABSOLUTE)
get_filename_component(QT_LIBRARY_DIR "${QT_LIBRARY_DIR}/../../.." ABSOLUTE)
if(UNIX AND APPLE)
# Mac needs a bit more help finding all necessary components
list(APPEND QT_LIBRARY_DIR "/usr/local/lib")
endif()
elseif(Qt5_FOUND)
get_filename_component(QT_PLUGINS_DIR "${Qt5Core_DIR}/../../../plugins" ABSOLUTE)
get_filename_component(QT_LIBRARY_DIR "${QT_LIBRARY_DIR}/.." ABSOLUTE)
endif()
message(DEBUG "QT_PLUGINS_DIR = ${QT_PLUGINS_DIR}")
message(DEBUG "QT_LIBRARY_DIR = ${QT_LIBRARY_DIR}")

View file

@ -1,118 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<!-- ========================= -->
<!-- CORE BUNDLE METADATA -->
<!-- ========================= -->
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleExecutable</key>
<string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string>
<key>CFBundleGetInfoString</key>
<string>${MACOSX_BUNDLE_INFO_STRING}</string>
<key>CFBundleIconFile</key>
<string>${MACOSX_BUNDLE_ICON_FILE}</string>
<key>CFBundleIdentifier</key>
<string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleLongVersionString</key>
<string>${MACOSX_BUNDLE_LONG_VERSION_STRING}</string>
<key>CFBundleName</key>
<string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string>
<key>CSResourcesFileMapped</key>
<true/>
<key>LSRequiresCarbon</key>
<true/>
<key>NSHumanReadableCopyright</key>
<string>${MACOSX_BUNDLE_COPYRIGHT}</string>
<key>NSHighResolutionCapable</key>
<true/>
<!-- ========================= -->
<!-- FILE TYPE (.cod) SUPPORT -->
<!-- ========================= -->
<key>UTExportedTypeDeclarations</key>
<array>
<dict>
<key>UTTypeIdentifier</key>
<string>org.cockatrice.deck</string>
<key>UTTypeDescription</key>
<string>Cockatrice Deck</string>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
</array>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>cod</string>
</array>
</dict>
</dict>
</array>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeName</key>
<string>Cockatrice Deck</string>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>LSHandlerRank</key>
<string>Default</string>
<key>LSItemContentTypes</key>
<array>
<string>org.cockatrice.deck</string>
</array>
</dict>
</array>
<!-- ========================= -->
<!-- URL SCHEME (cockatrice://) -->
<!-- ========================= -->
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>Cockatrice URL Scheme</string>
<key>CFBundleURLSchemes</key>
<array>
<string>cockatrice</string>
</array>
</dict>
</array>
</dict>
</plist>

View file

@ -117,22 +117,21 @@ ${If} $InstDir == ""
; we need to set a default based on the install mode
StrCpy $InstDir $0
${EndIf}
Call SetModeDestinationFromInstdir
; --- Detect portable install when using /R (must come BEFORE 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}
; Now that $PortableMode reflects reality, commit InstDir into the correct slot
Call SetModeDestinationFromInstdir
${If} $ReinstallMode = 1
${AndIf} $PortableMode = 0
Call AutoUninstallIfNeeded
${EndIf}

View file

@ -56,62 +56,56 @@ set(cockatrice_SOURCES
src/filters/filter_tree_model.cpp
src/filters/syntax_help.cpp
src/game/abstract_game.cpp
src/game/arrow_registry.cpp
src/game_graphics/board/abstract_card_drag_item.cpp
src/game_graphics/board/abstract_card_item.cpp
src/game_graphics/board/abstract_counter.cpp
src/game/board/abstract_card_drag_item.cpp
src/game/board/abstract_card_item.cpp
src/game/board/abstract_counter.cpp
src/game/board/arrow_data.cpp
src/game_graphics/board/arrow_item.cpp
src/game_graphics/board/arrow_target.cpp
src/game_graphics/board/card_drag_item.cpp
src/game_graphics/board/card_item.cpp
src/game/board/arrow_item.cpp
src/game/board/arrow_target.cpp
src/game/board/card_drag_item.cpp
src/game/board/card_item.cpp
src/game/board/card_list.cpp
src/game/board/card_state.cpp
src/game_graphics/board/counter_general.cpp
src/game/board/counter_general.cpp
src/game/board/counter_state.cpp
src/game_graphics/board/translate_counter_name.cpp
src/game_graphics/deckview/deck_view.cpp
src/game_graphics/deckview/deck_view_container.cpp
src/game_graphics/deckview/tabbed_deck_view_container.cpp
src/game_graphics/dialogs/dlg_create_token.cpp
src/game_graphics/dialogs/dlg_move_top_cards_until.cpp
src/game_graphics/dialogs/dlg_roll_dice.cpp
src/game/board/translate_counter_name.cpp
src/game/deckview/deck_view.cpp
src/game/deckview/deck_view_container.cpp
src/game/deckview/tabbed_deck_view_container.cpp
src/game/dialogs/dlg_create_token.cpp
src/game/dialogs/dlg_move_top_cards_until.cpp
src/game/dialogs/dlg_roll_dice.cpp
src/game/game.cpp
src/game/game_event_handler.cpp
src/game/game_meta_info.cpp
src/game_graphics/game_scene.cpp
src/game/game_scene.cpp
src/game/game_state.cpp
src/game_graphics/game_view.cpp
src/game_graphics/hand_counter.cpp
src/game_graphics/log/message_log_widget.cpp
src/game/game_view.cpp
src/game/hand_counter.cpp
src/game/log/message_log_widget.cpp
src/game/phase.cpp
src/game_graphics/phases_toolbar.cpp
src/game_graphics/player/menu/card_menu.cpp
src/game_graphics/player/menu/custom_zone_menu.cpp
src/game_graphics/player/menu/grave_menu.cpp
src/game_graphics/player/menu/hand_menu.cpp
src/game_graphics/player/menu/library_menu.cpp
src/game_graphics/player/menu/move_menu.cpp
src/game_graphics/player/menu/player_menu.cpp
src/game_graphics/player/menu/pt_menu.cpp
src/game_graphics/player/menu/rfg_menu.cpp
src/game_graphics/player/menu/say_menu.cpp
src/game_graphics/player/menu/sideboard_menu.cpp
src/game_graphics/player/menu/tally_menu.cpp
src/game_graphics/player/menu/utility_menu.cpp
src/game_graphics/tally/stats_tally.cpp
src/game_graphics/tally/subtype_tally.cpp
src/game_graphics/tally/tally.cpp
src/game/phases_toolbar.cpp
src/game/player/menu/card_menu.cpp
src/game/player/menu/custom_zone_menu.cpp
src/game/player/menu/grave_menu.cpp
src/game/player/menu/hand_menu.cpp
src/game/player/menu/library_menu.cpp
src/game/player/menu/move_menu.cpp
src/game/player/menu/player_menu.cpp
src/game/player/menu/pt_menu.cpp
src/game/player/menu/rfg_menu.cpp
src/game/player/menu/say_menu.cpp
src/game/player/menu/sideboard_menu.cpp
src/game/player/menu/utility_menu.cpp
src/game/player/player_actions.cpp
src/game_graphics/player/player_area.cpp
src/game_graphics/player/player_dialogs.cpp
src/game/player/player_area.cpp
src/game/player/player_event_handler.cpp
src/game_graphics/player/player_graphics_item.cpp
src/game/player/player_graphics_item.cpp
src/game/player/player_info.cpp
src/game_graphics/player/player_list_widget.cpp
src/game/player/player_list_widget.cpp
src/game/player/player_logic.cpp
src/game/player/player_manager.cpp
src/game_graphics/player/player_target.cpp
src/game/player/player_target.cpp
src/game/replay.cpp
src/game/zones/card_zone_logic.cpp
src/game/zones/hand_zone_logic.cpp
@ -135,12 +129,6 @@ set(cockatrice_SOURCES
src/interface/card_picture_loader/card_picture_loader_worker.cpp
src/interface/card_picture_loader/card_picture_loader_worker_work.cpp
src/interface/card_picture_loader/card_picture_to_load.cpp
src/interface/intents/intent.cpp
src/interface/intents/intent.h
src/interface/intents/intent_open_local_deck.cpp
src/interface/intents/intent_open_local_deck.h
src/interface/intents/intent_wait_for_database_load.cpp
src/interface/intents/intent_wait_for_database_load.h
src/interface/layouts/flow_layout.cpp
src/interface/layouts/overlap_layout.cpp
src/interface/widgets/utility/line_edit_completer.cpp
@ -193,7 +181,6 @@ set(cockatrice_SOURCES
src/interface/widgets/deck_analytics/analyzer_modules/mana_distribution/mana_distribution_single_display_widget.cpp
src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_total_widget.cpp
src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_category_widget.cpp
src/interface/widgets/deck_editor/card_database_view.cpp
src/interface/widgets/deck_editor/deck_list_history_manager_widget.cpp
src/interface/widgets/deck_editor/deck_editor_card_database_dock_widget.cpp
src/interface/widgets/deck_editor/deck_editor_card_info_dock_widget.cpp
@ -240,9 +227,7 @@ set(cockatrice_SOURCES
src/interface/widgets/quick_settings/settings_button_widget.cpp
src/interface/widgets/quick_settings/settings_popup_widget.cpp
src/interface/widgets/replay/replay_manager.cpp
src/interface/widgets/replay/replay_quick_settings_widget.cpp
src/interface/widgets/replay/replay_timeline_widget.cpp
src/interface/widgets/replay/replay_widget.cpp
src/interface/widgets/server/chat_view/chat_view.cpp
src/interface/widgets/server/game_filter_configs.cpp
src/interface/widgets/server/game_selector.cpp
@ -251,22 +236,15 @@ set(cockatrice_SOURCES
src/interface/widgets/server/handle_public_servers.cpp
src/interface/widgets/server/remote/remote_decklist_tree_widget.cpp
src/interface/widgets/server/remote/remote_replay_list_tree_widget.cpp
src/interface/widgets/server/user/user_avatar_provider.cpp
src/interface/widgets/server/user/user_card_art_provider.cpp
src/interface/widgets/server/user/user_card_settings_dialog.cpp
src/interface/widgets/server/user/user_context_menu.cpp
src/interface/widgets/server/user/user_info_box.cpp
src/interface/widgets/server/user/user_info_connection.cpp
src/interface/widgets/server/user/user_list_manager.cpp
src/interface/widgets/server/user/user_list_painter.cpp
src/interface/widgets/server/user/user_list_widget.cpp
src/interface/widgets/settings_page/abstract_settings_page.cpp
src/interface/widgets/settings_page/appearance_settings_page.cpp
src/interface/widgets/settings_page/deck_editor_settings_page.cpp
src/interface/widgets/settings_page/general_settings_page.cpp
src/interface/widgets/settings_page/messages_settings_page.cpp
src/interface/widgets/settings_page/settings_search_delegate.cpp
src/interface/widgets/settings_page/settings_search_model.cpp
src/interface/widgets/settings_page/shortcut_settings_page.cpp
src/interface/widgets/settings_page/sound_settings_page.cpp
src/interface/widgets/settings_page/storage_settings_page.cpp
@ -305,7 +283,6 @@ set(cockatrice_SOURCES
src/interface/widgets/visual_deck_storage/visual_deck_storage_widget.cpp
src/interface/window_main.cpp
src/main.cpp
src/single_instance_manager.cpp
src/interface/widgets/tabs/abstract_tab_deck_editor.cpp
src/interface/widgets/tabs/api/archidekt/tab_archidekt.cpp
src/interface/widgets/tabs/api/archidekt/api_response/archidekt_deck_listing_api_response.cpp
@ -321,13 +298,6 @@ set(cockatrice_SOURCES
src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_entry_display_widget.cpp
src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_listings_display_widget.cpp
src/interface/widgets/tabs/api/archidekt/display/archidekt_deck_preview_image_display_widget.cpp
src/interface/widgets/tabs/api/commander_spellbook/api_response/card_in_deck_request.cpp
src/interface/widgets/tabs/api/commander_spellbook/api_response/commander_spellbook_deck_request.cpp
src/interface/widgets/tabs/api/commander_spellbook/api_response/commander_spellbook_card_result.cpp
src/interface/widgets/tabs/api/commander_spellbook/api_response/commander_spellbook_variant_result.cpp
src/interface/widgets/tabs/api/commander_spellbook/api_response/commander_spellbook_estimate_bracket_result.cpp
src/interface/widgets/tabs/api/commander_spellbook/commander_spellbook_bracket_explainer.cpp
src/interface/widgets/tabs/api/commander_spellbook/commander_spellbook_api_accessor.cpp
src/interface/widgets/tabs/api/edhrec/api_response/archidekt_links/edhrec_api_response_archidekt_links.cpp
src/interface/widgets/tabs/api/edhrec/api_response/average_deck/edhrec_average_deck_api_response.cpp
src/interface/widgets/tabs/api/edhrec/api_response/average_deck/edhrec_deck_api_response.cpp
@ -357,7 +327,6 @@ set(cockatrice_SOURCES
src/interface/widgets/tabs/tab.cpp
src/interface/widgets/tabs/tab_account.cpp
src/interface/widgets/tabs/tab_admin.cpp
src/interface/widgets/tabs/tab_card_art_rules.cpp
src/interface/widgets/tabs/tab_deck_editor.cpp
src/interface/widgets/tabs/tab_deck_storage.cpp
src/interface/widgets/tabs/tab_game.cpp
@ -374,31 +343,12 @@ set(cockatrice_SOURCES
src/interface/widgets/tabs/visual_deck_storage/tab_deck_storage_visual.cpp
src/interface/key_signals.cpp
src/interface/logger.cpp
src/interface/widgets/tabs/api/commander_spellbook/commander_bracket_service.cpp
src/interface/widgets/tabs/api/commander_spellbook/commander_bracket_widget.cpp
src/interface/widgets/tabs/api/commander_spellbook/handle_commander_brackets.cpp
src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_bracket_navigation_widget.cpp
src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_bracket_navigation_widget.h
src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_budget_navigation_widget.cpp
src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_budget_navigation_widget.h
src/interface/widgets/utility/compact_push_button.cpp
src/interface/widgets/utility/compact_push_button.h
src/single_instance_manager.h
src/client/url_scheme_event_filter.h
src/interface/intents/intent_connect_to_server.cpp
src/interface/intents/intent_connect_to_server.h
src/interface/intents/intent_disconnect_from_server.cpp
src/interface/intents/intent_disconnect_from_server.h
src/interface/intents/intent_join_server_game.cpp
src/interface/intents/intent_join_server_game.h
src/interface/intents/intent_join_server_room.cpp
src/interface/intents/intent_join_server_room.h
src/interface/intents/intent_login.cpp
src/interface/intents/intent_login.h
src/interface/intents/url_parser.cpp
src/interface/intents/url_parser.h
src/interface/widgets/server/user/user_info_popup.cpp
src/interface/widgets/server/user/user_info_popup.h
)
add_subdirectory(sounds)
@ -440,7 +390,11 @@ if(APPLE)
set(cockatrice_SOURCES ${cockatrice_SOURCES} ${CMAKE_CURRENT_SOURCE_DIR}/resources/appicon.icns)
endif(APPLE)
qt6_add_resources(cockatrice_RESOURCES_RCC ${cockatrice_RESOURCES})
if(Qt6_FOUND)
qt6_add_resources(cockatrice_RESOURCES_RCC ${cockatrice_RESOURCES})
elseif(Qt5_FOUND)
qt5_add_resources(cockatrice_RESOURCES_RCC ${cockatrice_RESOURCES})
endif()
# Declare path variables
set(ICONDIR
@ -452,37 +406,71 @@ set(DESKTOPDIR
CACHE STRING "desktop file destination"
)
set(MIMEDIR
share/mime/packages
CACHE STRING "mime file destination"
)
set(COCKATRICE_MAC_QM_INSTALL_DIR "cockatrice.app/Contents/Resources/translations")
set(COCKATRICE_UNIX_QM_INSTALL_DIR "share/cockatrice/translations")
set(COCKATRICE_WIN32_QM_INSTALL_DIR "translations")
qt6_add_executable(
cockatrice
WIN32
MACOSX_BUNDLE
${cockatrice_SOURCES}
${cockatrice_RESOURCES_RCC}
${cockatrice_MOC_SRCS}
MANUAL_FINALIZATION
)
if(Qt6_FOUND)
qt6_add_executable(
cockatrice
WIN32
MACOSX_BUNDLE
${cockatrice_SOURCES}
${cockatrice_RESOURCES_RCC}
${cockatrice_MOC_SRCS}
MANUAL_FINALIZATION
)
elseif(Qt5_FOUND)
# Qt5 Translations need to be linked at executable creation time
if(Qt5LinguistTools_FOUND)
if(UPDATE_TRANSLATIONS)
qt5_create_translation(cockatrice_QM ${translate_SRCS} ${cockatrice_TS})
else()
qt5_add_translation(cockatrice_QM ${cockatrice_TS})
endif()
endif()
add_executable(
cockatrice WIN32 MACOSX_BUNDLE ${cockatrice_MOC_SRCS} ${cockatrice_QM} ${cockatrice_RESOURCES_RCC}
${cockatrice_SOURCES}
)
if(UNIX)
if(APPLE)
install(FILES ${cockatrice_QM} DESTINATION ${COCKATRICE_MAC_QM_INSTALL_DIR})
else()
install(FILES ${cockatrice_QM} DESTINATION ${COCKATRICE_UNIX_QM_INSTALL_DIR})
endif()
elseif(WIN32)
install(FILES ${cockatrice_QM} DESTINATION ${COCKATRICE_WIN32_QM_INSTALL_DIR})
endif()
endif()
target_link_libraries(
cockatrice
PUBLIC libcockatrice_card
libcockatrice_deck_list
libcockatrice_filters
libcockatrice_utility
libcockatrice_network
libcockatrice_models
libcockatrice_rng
libcockatrice_settings
${COCKATRICE_QT_MODULES}
)
if(Qt5_FOUND)
target_link_libraries(
cockatrice
libcockatrice_card
libcockatrice_deck_list
libcockatrice_filters
libcockatrice_utility
libcockatrice_network
libcockatrice_models
libcockatrice_rng
libcockatrice_settings
${COCKATRICE_QT_MODULES}
)
else()
target_link_libraries(
cockatrice
PUBLIC libcockatrice_card
libcockatrice_deck_list
libcockatrice_filters
libcockatrice_utility
libcockatrice_network
libcockatrice_models
libcockatrice_rng
libcockatrice_settings
${COCKATRICE_QT_MODULES}
)
endif()
if(UNIX)
if(APPLE)
@ -502,23 +490,6 @@ if(UNIX)
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/resources/cockatrice.png DESTINATION ${ICONDIR}/hicolor/48x48/apps)
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/resources/cockatrice.svg DESTINATION ${ICONDIR}/hicolor/scalable/apps)
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/cockatrice.desktop DESTINATION ${DESKTOPDIR})
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/cockatrice-cod.xml DESTINATION ${MIMEDIR})
# Refresh the freedesktop databases so the file associations and scheme
# handler register without requiring the user to run them manually. The
# tools may be missing on minimal systems; that is fine, packaging systems
# usually refresh these databases through their own triggers.
find_program(UPDATE_MIME_DATABASE update-mime-database)
if(UPDATE_MIME_DATABASE)
install(CODE "execute_process(COMMAND \"${UPDATE_MIME_DATABASE}\" \"${CMAKE_INSTALL_PREFIX}/share/mime\")")
endif()
find_program(UPDATE_DESKTOP_DATABASE update-desktop-database)
if(UPDATE_DESKTOP_DATABASE)
install(
CODE "execute_process(COMMAND \"${UPDATE_DESKTOP_DATABASE}\" \"${CMAKE_INSTALL_PREFIX}/share/applications\")"
)
endif()
endif()
elseif(WIN32)
install(TARGETS cockatrice RUNTIME DESTINATION ./)
@ -529,7 +500,7 @@ if(APPLE)
set(plugin_dest_dir cockatrice.app/Contents/Plugins)
set(qtconf_dest_dir cockatrice.app/Contents/Resources)
# Qt plugins: audio, iconengines, imageformats, multimedia, platforms, printsupport, styles, tls
# Qt plugins: audio (Qt5), iconengines, imageformats, multimedia (Qt6), platforms, printsupport (Qt5), styles, tls (Qt6)
install(
DIRECTORY "${QT_PLUGINS_DIR}/"
DESTINATION ${plugin_dest_dir}
@ -596,7 +567,7 @@ if(WIN32)
PATTERN "*.ini"
)
# Qt plugins: audio, iconengines, imageformats, multimedia, platforms, printsupport, styles, tls
# Qt plugins: audio (Qt5), iconengines, imageformats, multimedia (Qt6) platforms, printsupport (Qt5), styles, tls (Qt6)
install(
DIRECTORY "${QT_PLUGINS_DIR}/"
DESTINATION ${plugin_dest_dir}
@ -652,7 +623,7 @@ Data = Resources\")
endif()
endif()
if(Qt6LinguistTools_FOUND)
if(Qt6_FOUND AND Qt6LinguistTools_FOUND)
#Qt6 Translations happen after the executable is built up
if(UPDATE_TRANSLATIONS)
qt6_add_translations(
@ -679,4 +650,6 @@ if(Qt6LinguistTools_FOUND)
endif()
endif()
qt6_finalize_target(cockatrice)
if(Qt6_FOUND)
qt6_finalize_target(cockatrice)
endif()

View file

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info">
<mime-type type="application/x-cockatrice">
<comment>Cockatrice Deck File</comment>
<glob pattern="*.cod"/>
</mime-type>
</mime-info>

View file

@ -3,8 +3,6 @@
Version=1.0
Type=Application
Name=Cockatrice
Exec=cockatrice %U
Exec=cockatrice
Icon=cockatrice
Categories=Game;CardGame;
MimeType=application/x-cockatrice;
X-Scheme-Handler/cockatrice=true

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -6,7 +6,6 @@
#ifndef TABBED_DECK_VIEW_CONTAINER_H
#define TABBED_DECK_VIEW_CONTAINER_H
#include "../../interface/widgets/general/tutorial/tutorial_controller.h"
#include "deck_view_container.h"
#include <QTabWidget>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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