Merge branch 'master' into tooomm-qt5

This commit is contained in:
tooomm 2026-07-03 23:02:35 +02:00
commit ae9ce701f4
290 changed files with 10683 additions and 4451 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,14 +1,14 @@
name: Update Translations name: Update Translations
on: on:
workflow_dispatch:
schedule:
# runs in the middle of each month starting a quarter (UTC) = two weeks after new strings are built
- cron: '0 0 15 1,4,7,10 *'
pull_request: pull_request:
paths: paths:
- '.tx/**' - '.tx/**'
- '.github/workflows/translations-pull.yml' - '.github/workflows/translations-pull.yml'
schedule:
# Runs in the middle of each month starting a quarter (UTC) = two weeks after new strings are built
- cron: '0 0 15 1,4,7,10 *'
workflow_dispatch:
jobs: jobs:
translations: translations:
@ -19,18 +19,20 @@ jobs:
runs-on: ubuntu-slim runs-on: ubuntu-slim
steps: steps:
- name: Checkout repo - name: "Checkout repo"
uses: actions/checkout@v6 uses: actions/checkout@v7
- name: Pull translated strings from Transifex - name: "Pull translated strings from Transifex"
# Do not run this step for PR's from forks, they don't have access to the secret
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false
uses: transifex/cli-action@v2 uses: transifex/cli-action@v2
with: with:
# used config file: https://github.com/Cockatrice/Cockatrice/blob/master/.tx/config # Used config file: https://github.com/Cockatrice/Cockatrice/blob/master/.tx/config
# https://github.com/transifex/cli#pulling-files-from-transifex # Docs: https://github.com/transifex/cli#pulling-files-from-transifex
token: ${{ secrets.TX_TOKEN }}
args: pull --force --all args: pull --force --all
token: ${{ secrets.TX_TOKEN }}
- name: Create pull request - name: "Create pull request"
if: github.event_name != 'pull_request' if: github.event_name != 'pull_request'
id: create_pr id: create_pr
uses: peter-evans/create-pull-request@v8 uses: peter-evans/create-pull-request@v8
@ -38,12 +40,7 @@ jobs:
add-paths: | add-paths: |
cockatrice/translations/*.ts cockatrice/translations/*.ts
oracle/translations/*.ts oracle/translations/*.ts
commit-message: Update translation files author: github-actions <github-actions@github.com> # owner of the commit
# author is the owner of the commit
author: github-actions <github-actions@github.com>
branch: ci-update_translations
delete-branch: true
title: 'Update translations'
body: | body: |
Pulled all translated strings from [Transifex][1]. Pulled all translated strings from [Transifex][1].
@ -53,20 +50,22 @@ jobs:
[1]: https://explore.transifex.com/cockatrice/cockatrice/ [1]: https://explore.transifex.com/cockatrice/cockatrice/
[2]: https://github.com/Cockatrice/Cockatrice/actions/workflows/translations-pull.yml?query=branch%3Amaster [2]: https://github.com/Cockatrice/Cockatrice/actions/workflows/translations-pull.yml?query=branch%3Amaster
branch: ci-update_translations
commit-message: Update translation files
delete-branch: true
draft: false
labels: | labels: |
CI CI
Translation Translation
draft: false title: 'Update translations'
- name: PR Status - name: "PR Status"
if: github.event_name != 'pull_request' if: github.event_name != 'pull_request'
shell: bash shell: bash
env: env:
STATUS: ${{ steps.create_pr.outputs.pull-request-operation }} PR_NUMBER: ${{ steps.create_pr.outputs.pull-request-number }}
PR_URL: ${{ steps.create_pr.outputs.pull-request-url }}
STATUS: ${{ case(steps.create_pr.outputs.pull-request-operation == 'none', 'unchanged', steps.create_pr.outputs.pull-request-operation) }}
run: | run: |
if [[ "$STATUS" == "none" ]]; then echo "PR #$PR_NUMBER $STATUS!" >> "$GITHUB_STEP_SUMMARY"
echo "PR #${{ steps.create_pr.outputs.pull-request-number }} unchanged!" >> $GITHUB_STEP_SUMMARY echo "URL: $PR_URL" >> "$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 name: Update Translation Source
on: on:
workflow_dispatch:
schedule:
# runs at the start of each quarter (UTC)
- cron: '0 0 1 1,4,7,10 *'
pull_request: pull_request:
paths: paths:
- '.ci/update_translation_source_strings.sh' - '.ci/update_translation_source_strings.sh'
- '.github/workflows/translations-push.yml' - '.github/workflows/translations-push.yml'
schedule:
# Runs at the start of each quarter (UTC)
- cron: '0 0 1 1,4,7,10 *'
workflow_dispatch:
jobs: jobs:
translations: translations:
@ -19,34 +19,33 @@ jobs:
runs-on: ubuntu-slim runs-on: ubuntu-slim
steps: steps:
- name: Checkout repo - name: "Checkout repo"
uses: actions/checkout@v6 uses: actions/checkout@v7
- name: Install lupdate - name: "Install lupdate"
shell: bash shell: bash
run: | run: |
sudo apt-get update sudo apt-get update
sudo apt-get install -y --no-install-recommends qt6-l10n-tools sudo apt-get install -y --no-install-recommends qt6-l10n-tools
- name: Update Cockatrice translation source - name: "Update Cockatrice translation source"
id: cockatrice id: cockatrice
shell: bash shell: bash
env: env:
FILE: 'cockatrice/cockatrice_en@source.ts' FILE: cockatrice/cockatrice_en@source.ts
run: | run: >
DIRS="cockatrice/src $(find . -maxdepth 1 -type d -name 'libcockatrice_*')" DIRS="cockatrice/src $(find . -maxdepth 1 -type d -name 'libcockatrice_*')"
export DIRS
.ci/update_translation_source_strings.sh .ci/update_translation_source_strings.sh
- name: Update Oracle translation source - name: "Update Oracle translation source"
id: oracle id: oracle
shell: bash shell: bash
env: env:
FILE: 'oracle/oracle_en@source.ts'
DIRS: 'oracle/src' DIRS: 'oracle/src'
FILE: 'oracle/oracle_en@source.ts'
run: .ci/update_translation_source_strings.sh run: .ci/update_translation_source_strings.sh
- name: Render template - name: "Render template"
id: template id: template
uses: chuhlomin/render-template/binary@v1 uses: chuhlomin/render-template/binary@v1
with: with:
@ -56,7 +55,7 @@ jobs:
oracle_output: ${{ steps.oracle.outputs.output }} oracle_output: ${{ steps.oracle.outputs.output }}
commit: ${{ github.sha }} commit: ${{ github.sha }}
- name: Create pull request - name: "Create pull request"
if: github.event_name != 'pull_request' if: github.event_name != 'pull_request'
id: create_pr id: create_pr
uses: peter-evans/create-pull-request@v8 uses: peter-evans/create-pull-request@v8
@ -64,27 +63,24 @@ jobs:
add-paths: | add-paths: |
cockatrice/cockatrice_en@source.ts cockatrice/cockatrice_en@source.ts
oracle/oracle_en@source.ts oracle/oracle_en@source.ts
commit-message: Update translation source strings author: github-actions <github-actions@github.com> # owner of the commit
# author is the owner of the commit
author: github-actions <github-actions@github.com>
branch: ci-update_translation_source
delete-branch: true
title: 'Update source strings'
body: ${{ steps.template.outputs.result }} body: ${{ steps.template.outputs.result }}
branch: ci-update_translation_source
commit-message: Update translation source strings
delete-branch: true
draft: false
labels: | labels: |
CI CI
Translation Translation
draft: false title: 'Update source strings'
- name: PR Status - name: "PR Status"
if: github.event_name != 'pull_request' if: github.event_name != 'pull_request'
shell: bash shell: bash
env: env:
STATUS: ${{ steps.create_pr.outputs.pull-request-operation }} PR_NUMBER: ${{ steps.create_pr.outputs.pull-request-number }}
PR_URL: ${{ steps.create_pr.outputs.pull-request-url }}
STATUS: ${{ case(steps.create_pr.outputs.pull-request-operation == 'none', 'unchanged', steps.create_pr.outputs.pull-request-operation) }}
run: | run: |
if [[ "$STATUS" == "none" ]]; then echo "PR #$PR_NUMBER $STATUS!" >> "$GITHUB_STEP_SUMMARY"
echo "PR #${{ steps.create_pr.outputs.pull-request-number }} unchanged!" >> $GITHUB_STEP_SUMMARY echo "URL: $PR_URL" >> "$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

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

View file

@ -56,56 +56,59 @@ set(cockatrice_SOURCES
src/filters/filter_tree_model.cpp src/filters/filter_tree_model.cpp
src/filters/syntax_help.cpp src/filters/syntax_help.cpp
src/game/abstract_game.cpp src/game/abstract_game.cpp
src/game/board/abstract_card_drag_item.cpp src/game/arrow_registry.cpp
src/game/board/abstract_card_item.cpp src/game_graphics/board/abstract_card_drag_item.cpp
src/game/board/abstract_counter.cpp src/game_graphics/board/abstract_card_item.cpp
src/game_graphics/board/abstract_counter.cpp
src/game/board/arrow_data.cpp src/game/board/arrow_data.cpp
src/game/board/arrow_item.cpp src/game_graphics/board/arrow_item.cpp
src/game/board/arrow_target.cpp src/game_graphics/board/arrow_target.cpp
src/game/board/card_drag_item.cpp src/game_graphics/board/card_drag_item.cpp
src/game/board/card_item.cpp src/game_graphics/board/card_item.cpp
src/game/board/card_list.cpp src/game/board/card_list.cpp
src/game/board/card_state.cpp src/game/board/card_state.cpp
src/game/board/counter_general.cpp src/game_graphics/board/counter_general.cpp
src/game/board/counter_state.cpp src/game/board/counter_state.cpp
src/game/board/translate_counter_name.cpp src/game_graphics/board/translate_counter_name.cpp
src/game/deckview/deck_view.cpp src/game_graphics/deckview/deck_view.cpp
src/game/deckview/deck_view_container.cpp src/game_graphics/deckview/deck_view_container.cpp
src/game/deckview/tabbed_deck_view_container.cpp src/game_graphics/deckview/tabbed_deck_view_container.cpp
src/game/dialogs/dlg_create_token.cpp src/game_graphics/dialogs/dlg_create_token.cpp
src/game/dialogs/dlg_move_top_cards_until.cpp src/game_graphics/dialogs/dlg_move_top_cards_until.cpp
src/game/dialogs/dlg_roll_dice.cpp src/game_graphics/dialogs/dlg_roll_dice.cpp
src/game/game.cpp src/game/game.cpp
src/game/game_event_handler.cpp src/game/game_event_handler.cpp
src/game/game_meta_info.cpp src/game/game_meta_info.cpp
src/game/game_scene.cpp src/game_graphics/game_scene.cpp
src/game/game_state.cpp src/game/game_state.cpp
src/game/game_view.cpp src/game_graphics/game_view.cpp
src/game/hand_counter.cpp src/game_graphics/hand_counter.cpp
src/game/log/message_log_widget.cpp src/game/selection_subtype_tally.cpp
src/game_graphics/log/message_log_widget.cpp
src/game/phase.cpp src/game/phase.cpp
src/game/phases_toolbar.cpp src/game_graphics/phases_toolbar.cpp
src/game/player/menu/card_menu.cpp src/game_graphics/player/menu/card_menu.cpp
src/game/player/menu/custom_zone_menu.cpp src/game_graphics/player/menu/custom_zone_menu.cpp
src/game/player/menu/grave_menu.cpp src/game_graphics/player/menu/grave_menu.cpp
src/game/player/menu/hand_menu.cpp src/game_graphics/player/menu/hand_menu.cpp
src/game/player/menu/library_menu.cpp src/game_graphics/player/menu/library_menu.cpp
src/game/player/menu/move_menu.cpp src/game_graphics/player/menu/move_menu.cpp
src/game/player/menu/player_menu.cpp src/game_graphics/player/menu/player_menu.cpp
src/game/player/menu/pt_menu.cpp src/game_graphics/player/menu/pt_menu.cpp
src/game/player/menu/rfg_menu.cpp src/game_graphics/player/menu/rfg_menu.cpp
src/game/player/menu/say_menu.cpp src/game_graphics/player/menu/say_menu.cpp
src/game/player/menu/sideboard_menu.cpp src/game_graphics/player/menu/sideboard_menu.cpp
src/game/player/menu/utility_menu.cpp src/game_graphics/player/menu/utility_menu.cpp
src/game/player/player_actions.cpp src/game/player/player_actions.cpp
src/game/player/player_area.cpp src/game_graphics/player/player_area.cpp
src/game_graphics/player/player_dialogs.cpp
src/game/player/player_event_handler.cpp src/game/player/player_event_handler.cpp
src/game/player/player_graphics_item.cpp src/game_graphics/player/player_graphics_item.cpp
src/game/player/player_info.cpp src/game/player/player_info.cpp
src/game/player/player_list_widget.cpp src/game_graphics/player/player_list_widget.cpp
src/game/player/player_logic.cpp src/game/player/player_logic.cpp
src/game/player/player_manager.cpp src/game/player/player_manager.cpp
src/game/player/player_target.cpp src/game_graphics/player/player_target.cpp
src/game/replay.cpp src/game/replay.cpp
src/game/zones/card_zone_logic.cpp src/game/zones/card_zone_logic.cpp
src/game/zones/hand_zone_logic.cpp src/game/zones/hand_zone_logic.cpp
@ -181,6 +184,7 @@ set(cockatrice_SOURCES
src/interface/widgets/deck_analytics/analyzer_modules/mana_distribution/mana_distribution_single_display_widget.cpp src/interface/widgets/deck_analytics/analyzer_modules/mana_distribution/mana_distribution_single_display_widget.cpp
src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_total_widget.cpp src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_total_widget.cpp
src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_category_widget.cpp src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_category_widget.cpp
src/interface/widgets/deck_editor/card_database_view.cpp
src/interface/widgets/deck_editor/deck_list_history_manager_widget.cpp src/interface/widgets/deck_editor/deck_list_history_manager_widget.cpp
src/interface/widgets/deck_editor/deck_editor_card_database_dock_widget.cpp src/interface/widgets/deck_editor/deck_editor_card_database_dock_widget.cpp
src/interface/widgets/deck_editor/deck_editor_card_info_dock_widget.cpp src/interface/widgets/deck_editor/deck_editor_card_info_dock_widget.cpp
@ -233,10 +237,14 @@ set(cockatrice_SOURCES
src/interface/widgets/server/handle_public_servers.cpp src/interface/widgets/server/handle_public_servers.cpp
src/interface/widgets/server/remote/remote_decklist_tree_widget.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/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_context_menu.cpp
src/interface/widgets/server/user/user_info_box.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_info_connection.cpp
src/interface/widgets/server/user/user_list_manager.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/server/user/user_list_widget.cpp
src/interface/widgets/settings_page/appearance_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/deck_editor_settings_page.cpp
@ -324,6 +332,7 @@ set(cockatrice_SOURCES
src/interface/widgets/tabs/tab.cpp src/interface/widgets/tabs/tab.cpp
src/interface/widgets/tabs/tab_account.cpp src/interface/widgets/tabs/tab_account.cpp
src/interface/widgets/tabs/tab_admin.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_editor.cpp
src/interface/widgets/tabs/tab_deck_storage.cpp src/interface/widgets/tabs/tab_deck_storage.cpp
src/interface/widgets/tabs/tab_game.cpp src/interface/widgets/tabs/tab_game.cpp
@ -346,6 +355,8 @@ set(cockatrice_SOURCES
src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_budget_navigation_widget.h 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.cpp
src/interface/widgets/utility/compact_push_button.h src/interface/widgets/utility/compact_push_button.h
src/interface/widgets/server/user/user_info_popup.cpp
src/interface/widgets/server/user/user_info_popup.h
) )
add_subdirectory(sounds) add_subdirectory(sounds)

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

View file

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

View file

@ -309,9 +309,11 @@ SettingsCache::SettingsCache()
cardViewExpandedRowsMax = settings->value("interface/cardViewExpandedRowsMax", 20).toInt(); cardViewExpandedRowsMax = settings->value("interface/cardViewExpandedRowsMax", 20).toInt();
closeEmptyCardView = settings->value("interface/closeEmptyCardView", true).toBool(); closeEmptyCardView = settings->value("interface/closeEmptyCardView", true).toBool();
focusCardViewSearchBar = settings->value("interface/focusCardViewSearchBar", true).toBool(); focusCardViewSearchBar = settings->value("interface/focusCardViewSearchBar", true).toBool();
keepGameChatFocus = settings->value("interface/keepGameChatFocus", false).toBool();
showDragSelectionCount = settings->value("interface/showlassoselectioncount", true).toBool(); showDragSelectionCount = settings->value("interface/showlassoselectioncount", true).toBool();
showTotalSelectionCount = settings->value("interface/showpersistentselectioncount", true).toBool(); showTotalSelectionCount = settings->value("interface/showpersistentselectioncount", true).toBool();
showSubtypeSelectionTally = settings->value("interface/showsubtypeselectiontally", true).toBool();
showShortcuts = settings->value("menu/showshortcuts", true).toBool(); showShortcuts = settings->value("menu/showshortcuts", true).toBool();
showGameSelectorFilterToolbar = settings->value("menu/showgameselectorfiltertoolbar", true).toBool(); showGameSelectorFilterToolbar = settings->value("menu/showgameselectorfiltertoolbar", true).toBool();
@ -370,6 +372,7 @@ SettingsCache::SettingsCache()
openDeckInNewTab = settings->value("editor/openDeckInNewTab", false).toBool(); openDeckInNewTab = settings->value("editor/openDeckInNewTab", false).toBool();
rewindBufferingMs = settings->value("replay/rewindBufferingMs", 200).toInt(); rewindBufferingMs = settings->value("replay/rewindBufferingMs", 200).toInt();
styleUserList = settings->value("appearance/styleUserList", true).toBool();
chatMention = settings->value("chat/mention", true).toBool(); chatMention = settings->value("chat/mention", true).toBool();
chatMentionCompleter = settings->value("chat/mentioncompleter", true).toBool(); chatMentionCompleter = settings->value("chat/mentioncompleter", true).toBool();
chatMentionForeground = settings->value("chat/mentionforeground", true).toBool(); chatMentionForeground = settings->value("chat/mentionforeground", true).toBool();
@ -388,6 +391,7 @@ SettingsCache::SettingsCache()
ignoreUnregisteredUsers = settings->value("chat/ignore_unregistered", false).toBool(); ignoreUnregisteredUsers = settings->value("chat/ignore_unregistered", false).toBool();
ignoreUnregisteredUserMessages = settings->value("chat/ignore_unregistered_messages", false).toBool(); ignoreUnregisteredUserMessages = settings->value("chat/ignore_unregistered_messages", false).toBool();
ignoreNonBuddyUserMessages = settings->value("chat/ignore_nonbuddy_messages", false).toBool();
scaleCards = settings->value("cards/scaleCards", true).toBool(); scaleCards = settings->value("cards/scaleCards", true).toBool();
verticalCardOverlapPercent = settings->value("cards/verticalCardOverlapPercent", 33).toInt(); verticalCardOverlapPercent = settings->value("cards/verticalCardOverlapPercent", 33).toInt();
@ -456,6 +460,13 @@ void SettingsCache::setFocusCardViewSearchBar(QT_STATE_CHANGED_T value)
settings->setValue("interface/focusCardViewSearchBar", focusCardViewSearchBar); settings->setValue("interface/focusCardViewSearchBar", focusCardViewSearchBar);
} }
void SettingsCache::setKeepGameChatFocus(QT_STATE_CHANGED_T value)
{
keepGameChatFocus = value;
settings->setValue("interface/keepGameChatFocus", keepGameChatFocus);
emit keepGameChatFocusChanged(keepGameChatFocus);
}
void SettingsCache::setKnownMissingFeatures(const QString &_knownMissingFeatures) void SettingsCache::setKnownMissingFeatures(const QString &_knownMissingFeatures)
{ {
knownMissingFeatures = _knownMissingFeatures; knownMissingFeatures = _knownMissingFeatures;
@ -1036,6 +1047,13 @@ void SettingsCache::setRewindBufferingMs(int _rewindBufferingMs)
settings->setValue("replay/rewindBufferingMs", rewindBufferingMs); settings->setValue("replay/rewindBufferingMs", rewindBufferingMs);
} }
void SettingsCache::setStyleUserList(QT_STATE_CHANGED_T _styleUserList)
{
styleUserList = static_cast<bool>(_styleUserList);
settings->setValue("appearance/styleUserList", styleUserList);
emit styleUserListChanged();
}
void SettingsCache::setChatMention(QT_STATE_CHANGED_T _chatMention) void SettingsCache::setChatMention(QT_STATE_CHANGED_T _chatMention)
{ {
chatMention = static_cast<bool>(_chatMention); chatMention = static_cast<bool>(_chatMention);
@ -1117,6 +1135,12 @@ void SettingsCache::setIgnoreUnregisteredUserMessages(QT_STATE_CHANGED_T _ignore
settings->setValue("chat/ignore_unregistered_messages", ignoreUnregisteredUserMessages); settings->setValue("chat/ignore_unregistered_messages", ignoreUnregisteredUserMessages);
} }
void SettingsCache::setIgnoreNonBuddyUserMessages(QT_STATE_CHANGED_T _ignoreNonBuddyUserMessages)
{
ignoreNonBuddyUserMessages = static_cast<bool>(_ignoreNonBuddyUserMessages);
settings->setValue("chat/ignore_nonbuddy_messages", ignoreNonBuddyUserMessages);
}
void SettingsCache::setPixmapCacheSize(const int _pixmapCacheSize) void SettingsCache::setPixmapCacheSize(const int _pixmapCacheSize)
{ {
pixmapCacheSize = _pixmapCacheSize; pixmapCacheSize = _pixmapCacheSize;
@ -1372,6 +1396,12 @@ void SettingsCache::setShowTotalSelectionCount(QT_STATE_CHANGED_T _showTotalSele
settings->setValue("interface/showpersistentselectioncount", showTotalSelectionCount); settings->setValue("interface/showpersistentselectioncount", showTotalSelectionCount);
} }
void SettingsCache::setShowSubtypeSelectionTally(QT_STATE_CHANGED_T _showSubtypeSelectionTally)
{
showSubtypeSelectionTally = static_cast<bool>(_showSubtypeSelectionTally);
settings->setValue("interface/showsubtypeselectiontally", showSubtypeSelectionTally);
}
void SettingsCache::loadPaths() void SettingsCache::loadPaths()
{ {
QString dataPath = getDataPath(); QString dataPath = getDataPath();

View file

@ -183,17 +183,20 @@ signals:
void soundThemeChanged(); void soundThemeChanged();
void ignoreUnregisteredUsersChanged(); void ignoreUnregisteredUsersChanged();
void ignoreUnregisteredUserMessagesChanged(); void ignoreUnregisteredUserMessagesChanged();
void ignoreNonBuddyUserMessagesChanged();
void pixmapCacheSizeChanged(int newSizeInMBs); void pixmapCacheSizeChanged(int newSizeInMBs);
void networkCacheSizeChanged(int newSizeInMBs); void networkCacheSizeChanged(int newSizeInMBs);
void redirectCacheTtlChanged(int newTtl); void redirectCacheTtlChanged(int newTtl);
void cardPictureLoaderCacheMethodChanged(int cardPictureLoaderCacheMethod); void cardPictureLoaderCacheMethodChanged(int cardPictureLoaderCacheMethod);
void localCardImageStorageNamingSchemeChanged(int localCardImageStorageNamingScheme); void localCardImageStorageNamingSchemeChanged(int localCardImageStorageNamingScheme);
void masterVolumeChanged(int value); void masterVolumeChanged(int value);
void styleUserListChanged();
void chatMentionCompleterChanged(); void chatMentionCompleterChanged();
void downloadSpoilerTimeIndexChanged(); void downloadSpoilerTimeIndexChanged();
void downloadSpoilerStatusChanged(); void downloadSpoilerStatusChanged();
void useTearOffMenusChanged(bool state); void useTearOffMenusChanged(bool state);
void roundCardCornersChanged(bool roundCardCorners); void roundCardCornersChanged(bool roundCardCorners);
void keepGameChatFocusChanged(bool value);
private: private:
QSettings *settings; QSettings *settings;
@ -282,6 +285,7 @@ private:
bool autoRotateSidewaysLayoutCards; bool autoRotateSidewaysLayoutCards;
bool openDeckInNewTab; bool openDeckInNewTab;
int rewindBufferingMs; int rewindBufferingMs;
bool styleUserList;
bool chatMention; bool chatMention;
bool chatMentionCompleter; bool chatMentionCompleter;
QString chatMentionColor; QString chatMentionColor;
@ -294,6 +298,7 @@ private:
QString soundThemeName; QString soundThemeName;
bool ignoreUnregisteredUsers; bool ignoreUnregisteredUsers;
bool ignoreUnregisteredUserMessages; bool ignoreUnregisteredUserMessages;
bool ignoreNonBuddyUserMessages;
QString picUrl; QString picUrl;
QString picUrlFallback; QString picUrlFallback;
QString clientID; QString clientID;
@ -304,6 +309,7 @@ private:
int cardViewExpandedRowsMax; int cardViewExpandedRowsMax;
bool closeEmptyCardView; bool closeEmptyCardView;
bool focusCardViewSearchBar; bool focusCardViewSearchBar;
bool keepGameChatFocus;
int pixmapCacheSize; int pixmapCacheSize;
int networkCacheSize; int networkCacheSize;
int redirectCacheTtl; int redirectCacheTtl;
@ -349,6 +355,7 @@ private:
bool showStatusBar; bool showStatusBar;
bool showDragSelectionCount; bool showDragSelectionCount;
bool showTotalSelectionCount; bool showTotalSelectionCount;
bool showSubtypeSelectionTally;
public: public:
SettingsCache(); SettingsCache();
@ -472,6 +479,10 @@ public:
{ {
return showTotalSelectionCount; return showTotalSelectionCount;
} }
[[nodiscard]] bool getShowSubtypeSelectionTally() const
{
return showSubtypeSelectionTally;
}
[[nodiscard]] bool getNotificationsEnabled() const [[nodiscard]] bool getNotificationsEnabled() const
{ {
return notificationsEnabled; return notificationsEnabled;
@ -734,6 +745,10 @@ public:
{ {
return rewindBufferingMs; return rewindBufferingMs;
} }
[[nodiscard]] bool getStyleUserList() const
{
return styleUserList;
}
[[nodiscard]] bool getChatMention() const [[nodiscard]] bool getChatMention() const
{ {
return chatMention; return chatMention;
@ -788,6 +803,10 @@ public:
{ {
return ignoreUnregisteredUserMessages; return ignoreUnregisteredUserMessages;
} }
[[nodiscard]] bool getIgnoreNonBuddyUserMessages() const
{
return ignoreNonBuddyUserMessages;
}
[[nodiscard]] int getPixmapCacheSize() const [[nodiscard]] int getPixmapCacheSize() const
{ {
return pixmapCacheSize; return pixmapCacheSize;
@ -929,6 +948,7 @@ public:
void setCardViewExpandedRowsMax(int value); void setCardViewExpandedRowsMax(int value);
void setCloseEmptyCardView(QT_STATE_CHANGED_T value); void setCloseEmptyCardView(QT_STATE_CHANGED_T value);
void setFocusCardViewSearchBar(QT_STATE_CHANGED_T value); void setFocusCardViewSearchBar(QT_STATE_CHANGED_T value);
void setKeepGameChatFocus(QT_STATE_CHANGED_T value);
QString getClientID() override QString getClientID() override
{ {
return clientID; return clientID;
@ -961,6 +981,10 @@ public:
{ {
return focusCardViewSearchBar; return focusCardViewSearchBar;
} }
[[nodiscard]] bool getKeepGameChatFocus() const
{
return keepGameChatFocus;
}
[[nodiscard]] ShortcutsSettings &shortcuts() const [[nodiscard]] ShortcutsSettings &shortcuts() const
{ {
return *shortcutsSettings; return *shortcutsSettings;
@ -1100,6 +1124,7 @@ public slots:
void setAutoRotateSidewaysLayoutCards(QT_STATE_CHANGED_T _autoRotateSidewaysLayoutCards); void setAutoRotateSidewaysLayoutCards(QT_STATE_CHANGED_T _autoRotateSidewaysLayoutCards);
void setOpenDeckInNewTab(QT_STATE_CHANGED_T _openDeckInNewTab); void setOpenDeckInNewTab(QT_STATE_CHANGED_T _openDeckInNewTab);
void setRewindBufferingMs(int _rewindBufferingMs); void setRewindBufferingMs(int _rewindBufferingMs);
void setStyleUserList(QT_STATE_CHANGED_T _styleUserList);
void setChatMention(QT_STATE_CHANGED_T _chatMention); void setChatMention(QT_STATE_CHANGED_T _chatMention);
void setChatMentionCompleter(QT_STATE_CHANGED_T _chatMentionCompleter); void setChatMentionCompleter(QT_STATE_CHANGED_T _chatMentionCompleter);
void setChatMentionForeground(QT_STATE_CHANGED_T _chatMentionForeground); void setChatMentionForeground(QT_STATE_CHANGED_T _chatMentionForeground);
@ -1111,6 +1136,7 @@ public slots:
void setSoundThemeName(const QString &_soundThemeName); void setSoundThemeName(const QString &_soundThemeName);
void setIgnoreUnregisteredUsers(QT_STATE_CHANGED_T _ignoreUnregisteredUsers); void setIgnoreUnregisteredUsers(QT_STATE_CHANGED_T _ignoreUnregisteredUsers);
void setIgnoreUnregisteredUserMessages(QT_STATE_CHANGED_T _ignoreUnregisteredUserMessages); void setIgnoreUnregisteredUserMessages(QT_STATE_CHANGED_T _ignoreUnregisteredUserMessages);
void setIgnoreNonBuddyUserMessages(QT_STATE_CHANGED_T _ignoreNonBuddyUserMessages);
void setPixmapCacheSize(const int _pixmapCacheSize); void setPixmapCacheSize(const int _pixmapCacheSize);
void setCardImageCacheMethod(CardPictureLoaderCacheMethod::CacheMethod _cardImageCachingMethod); void setCardImageCacheMethod(CardPictureLoaderCacheMethod::CacheMethod _cardImageCachingMethod);
void setNetworkCacheSizeInMB(const int _networkCacheSize); void setNetworkCacheSizeInMB(const int _networkCacheSize);
@ -1155,5 +1181,6 @@ public slots:
void setRoundCardCorners(bool _roundCardCorners); void setRoundCardCorners(bool _roundCardCorners);
void setShowDragSelectionCount(QT_STATE_CHANGED_T _showDragSelectionCount); void setShowDragSelectionCount(QT_STATE_CHANGED_T _showDragSelectionCount);
void setShowTotalSelectionCount(QT_STATE_CHANGED_T _showTotalSelectionCount); void setShowTotalSelectionCount(QT_STATE_CHANGED_T _showTotalSelectionCount);
void setShowSubtypeSelectionTally(QT_STATE_CHANGED_T _showSubtypeSelectionTally);
}; };
#endif #endif

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,64 @@
#include "selection_subtype_tally.h"
#include "../game_graphics/board/card_item.h"
#include <QMap>
#include <algorithm>
namespace
{
/** @brief Extracts subtypes from a single card face's type line. */
QStringList extractSubtypesFromFace(const QString &faceType)
{
// Card type format: "Creature — Goblin Warrior" or "Legendary Enchantment — Saga"
QStringList parts = faceType.split(QStringLiteral(""));
if (parts.size() > 1) {
return parts[1].split(QStringLiteral(" "), Qt::SkipEmptyParts);
}
return {};
}
} // anonymous namespace
namespace SelectionSubtypeTally
{
QList<SubtypeEntry> countSubtypes(const QList<CardItem *> &cards)
{
QMap<QString, int> subtypeCounts;
for (CardItem *card : cards) {
if (card->getFaceDown() || card->getCard().isEmpty()) {
continue;
}
QString cardType = card->getCardInfo().getCardType();
// Handle double-faced cards: "Creature — Human // Creature — Werewolf"
QStringList cardFaces = cardType.split(QStringLiteral(" // "));
for (const QString &face : cardFaces) {
QStringList subtypes = extractSubtypesFromFace(face);
for (const QString &subtype : subtypes) {
subtypeCounts[subtype]++;
}
}
}
QList<SubtypeEntry> entries;
for (auto it = subtypeCounts.constBegin(); it != subtypeCounts.constEnd(); ++it) {
entries.append({it.key(), it.value()});
}
// Sort by count ascending, then alphabetically (lowest counts at bottom of display)
std::sort(entries.begin(), entries.end(), [](const SubtypeEntry &a, const SubtypeEntry &b) {
if (a.count != b.count) {
return a.count < b.count;
}
return a.name < b.name;
});
return entries;
}
} // namespace SelectionSubtypeTally

View file

@ -0,0 +1,36 @@
#ifndef SELECTION_SUBTYPE_TALLY_H
#define SELECTION_SUBTYPE_TALLY_H
#include <QList>
#include <QString>
class CardItem;
/** @brief A single subtype (e.g., "Goblin", "Warrior") with its occurrence count. */
struct SubtypeEntry
{
QString name; ///< The subtype name
int count; ///< Number of selected cards with this subtype
bool operator==(const SubtypeEntry &other) const
{
return name == other.name && count == other.count;
}
};
/**
* @brief Extracts and tallies subtypes from selected cards.
*/
namespace SelectionSubtypeTally
{
/**
* @brief Parses card type lines and counts each subtype occurrence.
*
* Skips face-down cards and cards without type info.
* @param cards The list of selected card items to analyze.
* @return Entries sorted by count ascending, then alphabetically.
*/
QList<SubtypeEntry> countSubtypes(const QList<CardItem *> &cards);
} // namespace SelectionSubtypeTally
#endif

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -7,12 +7,11 @@
#ifndef CARDITEM_H #ifndef CARDITEM_H
#define CARDITEM_H #define CARDITEM_H
#include "../zones/card_zone_logic.h" #include "../../game/board/card_state.h"
#include "../../game/zones/card_zone_logic.h"
#include "abstract_card_item.h" #include "abstract_card_item.h"
#include "card_state.h"
#include <libcockatrice/network/server/remote/game/server_card.h> #include <libcockatrice/network/server/remote/game/server_card.h>
#include <libcockatrice/utility/trice_limits.h>
class CardDatabase; class CardDatabase;
class CardDragItem; class CardDragItem;

View file

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

View file

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

View file

@ -19,7 +19,7 @@
#include <libcockatrice/protocol/pb/command_set_sideboard_plan.pb.h> #include <libcockatrice/protocol/pb/command_set_sideboard_plan.pb.h>
#include <libcockatrice/protocol/pb/response_deck_download.pb.h> #include <libcockatrice/protocol/pb/response_deck_download.pb.h>
#include <libcockatrice/protocol/pending_command.h> #include <libcockatrice/protocol/pending_command.h>
#include <libcockatrice/utility/trice_limits.h> #include <libcockatrice/utility/string_limits.h>
ToggleButton::ToggleButton(QWidget *parent) : QPushButton(parent), state(false) ToggleButton::ToggleButton(QWidget *parent) : QPushButton(parent), state(false)
{ {
@ -209,6 +209,7 @@ void DeckViewContainer::refreshShortcuts()
loadLocalButton->setShortcut(shortcuts.getSingleShortcut("DeckViewContainer/loadLocalButton")); loadLocalButton->setShortcut(shortcuts.getSingleShortcut("DeckViewContainer/loadLocalButton"));
loadRemoteButton->setShortcut(shortcuts.getSingleShortcut("DeckViewContainer/loadRemoteButton")); loadRemoteButton->setShortcut(shortcuts.getSingleShortcut("DeckViewContainer/loadRemoteButton"));
loadFromClipboardButton->setShortcut(shortcuts.getSingleShortcut("DeckViewContainer/loadFromClipboardButton")); loadFromClipboardButton->setShortcut(shortcuts.getSingleShortcut("DeckViewContainer/loadFromClipboardButton"));
loadFromWebsiteButton->setShortcut(shortcuts.getSingleShortcut("DeckViewContainer/loadFromWebsiteButton"));
unloadDeckButton->setShortcut(shortcuts.getSingleShortcut("DeckViewContainer/unloadDeckButton")); unloadDeckButton->setShortcut(shortcuts.getSingleShortcut("DeckViewContainer/unloadDeckButton"));
readyStartButton->setShortcut(shortcuts.getSingleShortcut("DeckViewContainer/readyStartButton")); readyStartButton->setShortcut(shortcuts.getSingleShortcut("DeckViewContainer/readyStartButton"));
sideboardLockButton->setShortcut(shortcuts.getSingleShortcut("DeckViewContainer/sideboardLockButton")); sideboardLockButton->setShortcut(shortcuts.getSingleShortcut("DeckViewContainer/sideboardLockButton"));

View file

@ -20,7 +20,7 @@
#include <libcockatrice/deck_list/deck_list.h> #include <libcockatrice/deck_list/deck_list.h>
#include <libcockatrice/models/database/card_database_model.h> #include <libcockatrice/models/database/card_database_model.h>
#include <libcockatrice/models/database/token/token_display_model.h> #include <libcockatrice/models/database/token/token_display_model.h>
#include <libcockatrice/utility/trice_limits.h> #include <libcockatrice/utility/string_limits.h>
DlgCreateToken::DlgCreateToken(const QStringList &_predefinedTokens, QWidget *parent) DlgCreateToken::DlgCreateToken(const QStringList &_predefinedTokens, QWidget *parent)
: QDialog(parent), predefinedTokens(_predefinedTokens) : QDialog(parent), predefinedTokens(_predefinedTokens)

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,13 +1,13 @@
#include "message_log_widget.h" #include "message_log_widget.h"
#include "../../client/settings/card_counter_settings.h"
#include "../../client/sound_engine.h" #include "../../client/sound_engine.h"
#include "../../game/phase.h"
#include "../../game/player/player_logic.h"
#include "../../interface/widgets/tabs/tab_game.h" #include "../../interface/widgets/tabs/tab_game.h"
#include "../board/card_item.h" #include "../board/card_item.h"
#include "../board/translate_counter_name.h" #include "../board/translate_counter_name.h"
#include "../phase.h"
#include "../player/player_logic.h"
#include <../../client/settings/card_counter_settings.h>
#include <libcockatrice/protocol/pb/context_move_card.pb.h> #include <libcockatrice/protocol/pb/context_move_card.pb.h>
#include <libcockatrice/protocol/pb/context_mulligan.pb.h> #include <libcockatrice/protocol/pb/context_mulligan.pb.h>
#include <libcockatrice/utility/zone_names.h> #include <libcockatrice/utility/zone_names.h>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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