Compare commits

..

No commits in common. "master" and "2026-02-08-Development-2.11.0-beta.49" have entirely different histories.

347 changed files with 29482 additions and 37677 deletions

View file

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

View file

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

View file

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

View file

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

View file

@ -2,6 +2,7 @@
# used by the ci to rename build artifacts
# renames the file to [original name][SUFFIX].[original extension]
# where SUFFIX is either available in the environment or as the first arg
# if MAKE_ZIP is set instead a zip is made
# expected to be run in the build directory unless BUILD_DIR is set
# adds output to GITHUB_OUTPUT
builddir="${BUILD_DIR:=.}"
@ -21,8 +22,8 @@ set -e
# find file
found="$(find "$builddir" -maxdepth 1 -type f -name "$findrx" -print -quit)"
path="${found%/*}" # remove all including first "/" from right side
file="${found##*/}" # remove all including last "/" from left side
path="${found%/*}" # remove all after last /
file="${found##*/}" # remove all before last /
if [[ ! $file ]]; then
echo "::error file=$0::could not find package"
exit 1
@ -34,16 +35,21 @@ if ! cd "$path"; then
fi
# set filename
name="${file%.*}" # remove all including first "." from right side
new_name="$name$SUFFIX"
extension="${file##*.}" # remove all including last "." from left side
filename="$new_name.$extension"
echo "renaming '$file' to '$filename'"
mv "$file" "$filename"
name="${file%.*}" # remove all after last .
new_name="$name$SUFFIX."
if [[ $MAKE_ZIP ]]; then
filename="${new_name}zip"
echo "creating zip '$filename' from '$file'"
zip "$filename" "$file"
else
extension="${file##*.}" # remove all before last .
filename="$new_name$extension"
echo "renaming '$file' to '$filename'"
mv "$file" "$filename"
fi
cd "$oldpwd"
relative_path="$path/$filename"
ls -l "$relative_path"
echo "path=$relative_path" >>"$GITHUB_OUTPUT"
echo "name=$new_name" >>"$GITHUB_OUTPUT"
echo "fullname=$filename" >>"$GITHUB_OUTPUT"
echo "name=$filename" >>"$GITHUB_OUTPUT"

View file

@ -10,6 +10,7 @@ Available pre-compiled binaries for installation:
<b>Windows</b>
<kbd>Windows 10+</kbd>
<kbd>Windows 7+</kbd>
<b>macOS</b>
<kbd>macOS 15+</kbd> <sub><i>Sequoia</i></sub> <sub>Apple M</sub>
@ -17,7 +18,6 @@ Available pre-compiled binaries for installation:
<kbd>macOS 13+</kbd> <sub><i>Ventura</i></sub> <sub>Intel</sub>
<b>Linux</b>
<kbd>Ubuntu 26.04 LTS</kbd> <sub><i>Resolute Racoon</i></sub>
<kbd>Ubuntu 24.04 LTS</kbd> <sub><i>Noble Numbat</i></sub>
<kbd>Ubuntu 22.04 LTS</kbd> <sub><i>Jammy Jellyfish</i></sub>
<kbd>Debian 13</kbd> <sub><i>Trixie</i></sub>
@ -28,8 +28,6 @@ Available pre-compiled binaries for installation:
<sub>We are also packaged in <kbd>Arch Linux</kbd>'s <a href="https://archlinux.org/packages/extra/x86_64/cockatrice">official extra repository</a>, courtesy of @FFY00.</sub>
<sub>General Linux support is available via a <kbd>flatpak</kbd> package at <a href="https://flathub.org/apps/io.github.Cockatrice.cockatrice">Flathub</a>!</sub>
<sub>We provide a <kbd>Docker</kbd> image for "Servatrice" in <a href="https://github.com/Cockatrice/Cockatrice/pkgs/container/servatrice">GHCR</a>. You can docker pull it or use our Docker Compose files!</sub>
</pre>

View file

@ -27,16 +27,7 @@ if ! hash aqt; then
fi
# Resolve latest patch
if [[ $RUNNER_OS == macOS ]]; then
if ! qt_resolved=$(aqt list-qt mac desktop --spec "$qt_spec" --latest-version); then
exit 1
fi
elif [[ $RUNNER_OS == Windows ]]; then
if ! qt_resolved=$(aqt list-qt windows desktop --spec "$qt_spec" --latest-version); then
exit 1
fi
else
echo "aqt command for $RUNNER_OS not defined."
if ! qt_resolved=$(aqt list-qt mac desktop --spec "$qt_spec" --latest-version); then
exit 1
fi

View file

@ -461,11 +461,7 @@ revoke the tag by doing the following:
git push --delete upstream $TAG_NAME
git tag -d $TAG_NAME
```
You can also do this on GitHub.
> [!NOTE]
> If you want to push a new release to replace it immediately with the same
> name you have to delete the automatically created release first!
You can also do this on GitHub, you'll also want to delete the false release.
In the first lines of [CMakeLists.txt](
https://github.com/Cockatrice/Cockatrice/blob/master/CMakeLists.txt)

View file

@ -1,12 +1,9 @@
blank_issues_enabled: false
contact_links:
- name: 💬 Discord Community (Get help with server issues, e.g. Login)
url: https://discord.com/invite/3Z9yzmA
url: https://discord.gg/3Z9yzmA
about: Need help with using the client? Want to find some games? Try the Discord server!
- name: 🌐 Translations (Help improve the localization of the app)
url: https://explore.transifex.com/cockatrice/cockatrice/
# it is not possible to add a link to the wiki to this description
about: For more information and guidance check our Translation FAQ on our wiki!
- name: 📖 Code Documentation
url: https://cockatrice.github.io/docs/
about: Helpful source focusing on developers, but there are also references for users!

View file

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

View file

@ -38,15 +38,15 @@ on:
- 'vcpkg.json'
- 'vcpkg'
# Cancel earlier, unfinished runs of this workflow on the same branch (unless on release)
# Cancel earlier, unfinished runs of this workflow on the same branch (unless on master)
concurrency:
group: "${{ github.workflow }} @ ${{ github.ref_name }}"
cancel-in-progress: ${{ github.ref_type != 'tag' }}
cancel-in-progress: ${{ github.ref_name != 'master' }}
jobs:
configure:
name: Configure
runs-on: ubuntu-slim
runs-on: ubuntu-latest
outputs:
tag: ${{steps.configure.outputs.tag}}
sha: ${{steps.configure.outputs.sha}}
@ -142,28 +142,21 @@ jobs:
- distro: Ubuntu
version: 22.04
package: DEB
test: skip # Running tests on all distros is superfluous
- distro: Ubuntu
version: 24.04
package: DEB
- distro: Ubuntu
version: 26.04
package: DEB
name: ${{matrix.distro}} ${{matrix.version}}
needs: configure
runs-on: ubuntu-latest
continue-on-error: ${{matrix.allow-failure == 'yes'}}
timeout-minutes: 70
env:
NAME: ${{matrix.distro}}${{matrix.version}}
CACHE: ${{github.workspace}}/.cache/${{matrix.distro}}${{matrix.version}} # directory for caching docker image and ccache
# Cache size over the entire repo is 10Gi:
# https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#usage-limits-and-eviction-policy
CCACHE_SIZE: 550M
CCACHE_EVICTION_AGE: 7d
CCACHE_SIZE: 500M
CMAKE_GENERATOR: 'Ninja'
steps:
@ -187,45 +180,29 @@ jobs:
- name: Build debug and test
if: matrix.test != 'skip'
shell: bash
env:
CMAKE_GENERATOR: '${{env.CMAKE_GENERATOR}}'
run: |
source .ci/docker.sh
RUN --server --debug --test --ccache "$CCACHE_SIZE" \
--cmake-generator "$CMAKE_GENERATOR"
RUN --server --debug --test --ccache "$CCACHE_SIZE"
- name: Build release package
id: build
if: matrix.package != 'skip'
shell: bash
env:
BUILD_DIR: build
SUFFIX: '-${{matrix.distro}}${{matrix.version}}'
package: '${{matrix.package}}'
server_only: '${{matrix.server_only}}'
CMAKE_GENERATOR: '${{env.CMAKE_GENERATOR}}'
NO_CLIENT: ${{matrix.server_only == 'yes' && '--no-client' || '' }}
run: |
source .ci/docker.sh
args=()
if [[ $server_only == yes ]]; then
args+=(--no-client)
fi
if [[ $GITHUB_REF == "refs/heads/master" ]]; then
args+=(--evict-ccache "$CCACHE_EVICTION_AGE")
fi
args+=(--ccache "$CCACHE_SIZE")
args+=(--cmake-generator "$CMAKE_GENERATOR")
args+=(--suffix "$SUFFIX")
RUN --server --release --package "$package" "${args[@]}"
RUN --server --release --package "$package" --dir "$BUILD_DIR" \
--ccache "$CCACHE_SIZE" $NO_CLIENT
.ci/name_build.sh
# Delete used cache to emulate a ccache update. See https://github.com/actions/cache/issues/342
- name: Delete remote compiler cache (ccache)
if: github.ref == 'refs/heads/master' && steps.ccache_restore.outputs.cache-hit
continue-on-error: true
env:
GH_TOKEN: ${{ github.token }}
run: |
if gh cache delete --repo ${{ github.repository }} ${{ steps.ccache_restore.outputs.cache-primary-key }}; then
echo "Cache deleted successfully"
fi
- name: Save updated compiler cache (ccache)
- name: Save compiler cache (ccache)
if: github.ref == 'refs/heads/master'
uses: actions/cache/save@v5
with:
@ -235,10 +212,10 @@ jobs:
- name: Upload artifact
id: upload_artifact
if: matrix.package != 'skip'
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@v6
with:
name: ${{matrix.distro}}${{matrix.version}}-package
path: ${{steps.build.outputs.path}}
archive: false
if-no-files-found: error
- name: Upload to release
@ -248,24 +225,24 @@ jobs:
env:
GH_TOKEN: ${{github.token}}
tag_name: ${{needs.configure.outputs.tag}}
asset_name: ${{steps.build.outputs.fullname}}
asset_path: ${{steps.build.outputs.path}}
asset_name: ${{steps.build.outputs.name}}
run: gh release upload "$tag_name" "$asset_path#$asset_name"
- name: Attest binary provenance
id: attestation
if: steps.upload_release.outcome == 'success'
uses: actions/attest@v4
uses: actions/attest-build-provenance@v3
with:
subject-path: ${{steps.build.outputs.path}}
show-summary: false
subject-name: ${{steps.build.outputs.name}}
subject-digest: sha256:${{ steps.upload_artifact.outputs.artifact-digest }}
- name: Verify binary attestation
if: steps.attestation.outcome == 'success'
shell: bash
env:
GH_TOKEN: ${{github.token}}
run: gh attestation verify ${{steps.build.outputs.path}} --repo Cockatrice/Cockatrice
run: gh attestation verify ${{steps.build.outputs.path}} -R Cockatrice/Cockatrice
build-vcpkg:
strategy:
@ -281,12 +258,12 @@ jobs:
override_target: 13
make_package: 1
package_suffix: "-macOS13_Intel"
qt_version: 6.11.*
artifact_name: macOS13_Intel-package
qt_version: 6.6.*
qt_arch: clang_64
qt_modules: qtimageformats qtmultimedia qtwebsockets
cmake_generator: Ninja
use_ccache: 1
ccache_eviction_age: 7d
- os: macOS
target: 14
@ -296,12 +273,12 @@ jobs:
type: Release
make_package: 1
package_suffix: "-macOS14"
qt_version: 6.11.*
artifact_name: macOS14-package
qt_version: 6.6.*
qt_arch: clang_64
qt_modules: qtimageformats qtmultimedia qtwebsockets
cmake_generator: Ninja
use_ccache: 1
ccache_eviction_age: 7d
- os: macOS
target: 15
@ -311,12 +288,12 @@ jobs:
type: Release
make_package: 1
package_suffix: "-macOS15"
qt_version: 6.11.*
artifact_name: macOS15-package
qt_version: 6.6.*
qt_arch: clang_64
qt_modules: qtimageformats qtmultimedia qtwebsockets
cmake_generator: Ninja
use_ccache: 1
ccache_eviction_age: 7d
- os: macOS
target: 15
@ -324,21 +301,33 @@ jobs:
soc: Apple
xcode: "16.4"
type: Debug
qt_version: 6.11.*
qt_version: 6.6.*
qt_arch: clang_64
qt_modules: qtimageformats qtmultimedia qtwebsockets
cmake_generator: Ninja
use_ccache: 1
ccache_eviction_age: 7d
- os: Windows
target: 7
runner: windows-2022
type: Release
make_package: 1
package_suffix: "-Win7"
artifact_name: Windows7-installer
qt_version: 5.15.*
qt_arch: win64_msvc2019_64
cmake_generator: "Visual Studio 17 2022"
cmake_generator_platform: x64
- os: Windows
target: 10
runner: windows-2025
runner: windows-2022
type: Release
make_package: 1
package_suffix: "-Win10"
qt_version: 6.11.*
qt_arch: win64_msvc2022_64
artifact_name: Windows10-installer
qt_version: 6.6.*
qt_arch: win64_msvc2019_64
qt_modules: qtimageformats qtmultimedia qtwebsockets
cmake_generator: "Visual Studio 17 2022"
cmake_generator_platform: x64
@ -346,12 +335,11 @@ jobs:
name: ${{matrix.os}} ${{matrix.target}}${{ matrix.soc == 'Intel' && ' Intel' || '' }}${{ matrix.type == 'Debug' && ' Debug' || '' }}
needs: configure
runs-on: ${{matrix.runner}}
timeout-minutes: 100
env:
CCACHE_DIR: ${{github.workspace}}/.cache/
# Cache size over the entire repo is 10Gi:
# https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#usage-limits-and-eviction-policy
CCACHE_SIZE: 550M
CCACHE_SIZE: 500M
steps:
- name: Checkout
@ -362,7 +350,7 @@ jobs:
- name: Add msbuild to PATH
if: matrix.os == 'Windows'
id: add-msbuild
uses: microsoft/setup-msbuild@v3
uses: microsoft/setup-msbuild@v2
with:
msbuild-architecture: x64
@ -382,12 +370,15 @@ jobs:
restore-keys: ccache-${{matrix.runner}}-${{matrix.soc}}-${{matrix.type}}-
- name: Install aqtinstall
if: matrix.os == 'macOS'
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
# Checking if there's a newer, uncached version of Qt available to install via aqtinstall
- name: Resolve latest Qt patch version
if: matrix.os == 'macOS'
id: resolve_qt_version
shell: bash
# Ouputs the version of Qt to install via aqtinstall
run: .ci/resolve_latest_aqt_qt_version.sh "${{matrix.qt_version}}"
- name: Restore thin Qt ${{ steps.resolve_qt_version.outputs.version }} libraries (${{ matrix.soc }} macOS)
@ -404,10 +395,10 @@ jobs:
if: matrix.os == 'macOS' && steps.restore_qt.outputs.cache-hit != 'true'
uses: jurplel/install-qt-action@v4
with:
cache: false
version: ${{ steps.resolve_qt_version.outputs.version }}
arch: ${{matrix.qt_arch}}
modules: ${{matrix.qt_modules}}
cache: false
dir: ${{github.workspace}}
- name: Thin Qt libraries (${{ matrix.soc }} macOS)
@ -425,18 +416,11 @@ jobs:
if: matrix.os == 'Windows'
uses: jurplel/install-qt-action@v4
with:
# qt 6.11.0 only works with aqtinstall directly from git until aqtinstall 3.4 is released
aqtsource: git+https://github.com/miurahr/aqtinstall.git
version: ${{ steps.resolve_qt_version.outputs.version }}
version: ${{matrix.qt_version}}
arch: ${{matrix.qt_arch}}
modules: ${{matrix.qt_modules}}
cache: true
- name: Install NSIS
if: matrix.os == 'Windows'
shell: bash
run: choco install nsis
- name: Setup vcpkg cache
id: vcpkg-cache
uses: TAServers/vcpkg-cache@v3
@ -463,30 +447,19 @@ jobs:
MACOS_CI_KEYCHAIN_PWD: ${{ secrets.PROD_MACOS_CI_KEYCHAIN_PWD }}
DEVELOPER_DIR: '/Applications/Xcode_${{matrix.xcode}}.app/Contents/Developer'
TARGET_MACOS_VERSION: ${{ matrix.override_target }}
CCACHE_EVICTION_AGE: ${{ matrix.ccache_eviction_age }}
run: .ci/compile.sh --server --test --vcpkg
# Delete used cache to emulate a ccache update. See https://github.com/actions/cache/issues/342
- name: Delete remote compiler cache (ccache)
if: github.ref == 'refs/heads/master' && matrix.use_ccache == 1 && steps.ccache_restore.outputs.cache-hit
continue-on-error: true
env:
GH_TOKEN: ${{ github.token }}
run: |
if gh cache delete --repo ${{ github.repository }} ${{ steps.ccache_restore.outputs.cache-primary-key }}; then
echo "Cache deleted successfully"
fi
- name: Save updated compiler cache (ccache)
- name: Save compiler cache (ccache)
if: github.ref == 'refs/heads/master' && matrix.use_ccache == 1
uses: actions/cache/save@v5
env:
BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
with:
path: ${{env.CCACHE_DIR}}
key: ${{ steps.ccache_restore.outputs.cache-primary-key }}
key: ccache-${{matrix.runner}}-${{matrix.soc}}-${{matrix.type}}-${{env.BRANCH_NAME}}
- name: Sign app bundle
if: matrix.os == 'macOS' && matrix.make_package && needs.configure.outputs.tag != null
id: sign_macos
if: matrix.os == 'macOS' && matrix.make_package && (github.ref == 'refs/heads/master' || needs.configure.outputs.tag != null)
env:
MACOS_CERTIFICATE_NAME: ${{ secrets.PROD_MACOS_CERTIFICATE_NAME }}
MACOS_CI_KEYCHAIN_PWD: ${{ secrets.PROD_MACOS_CI_KEYCHAIN_PWD }}
@ -498,7 +471,7 @@ jobs:
fi
- name: Notarize app bundle
if: steps.sign_macos.outcome == 'success'
if: matrix.os == 'macOS' && matrix.make_package && (github.ref == 'refs/heads/master' || needs.configure.outputs.tag != null)
env:
MACOS_NOTARIZATION_APPLE_ID: ${{ secrets.PROD_MACOS_NOTARIZATION_APPLE_ID }}
MACOS_NOTARIZATION_TEAM_ID: ${{ secrets.PROD_MACOS_NOTARIZATION_TEAM_ID }}
@ -530,47 +503,46 @@ jobs:
fi
- name: Upload artifact
if: matrix.make_package
id: upload_artifact
uses: actions/upload-artifact@v7
if: matrix.make_package
uses: actions/upload-artifact@v6
with:
name: ${{matrix.artifact_name}}
path: ${{steps.build.outputs.path}}
archive: false
if-no-files-found: error
- name: Upload PDBs (Program Databases)
if: matrix.os == 'Windows' && github.ref_type != 'tag'
uses: actions/upload-artifact@v7
- name: Upload pdb database
if: matrix.os == 'Windows'
uses: actions/upload-artifact@v6
with:
name: ${{steps.build.outputs.name}}-PDBs
name: Windows${{matrix.target}}-debug-pdbs
path: |
build/cockatrice/Release/*.pdb
build/oracle/Release/*.pdb
build/servatrice/Release/*.pdb
if-no-files-found: error
- name: Upload to release
if: needs.configure.outputs.tag != null && matrix.make_package == '1'
id: upload_release
if: needs.configure.outputs.tag != null
shell: bash
env:
GH_TOKEN: ${{github.token}}
tag_name: ${{needs.configure.outputs.tag}}
asset_name: ${{steps.build.outputs.fullname}}
asset_path: ${{steps.build.outputs.path}}
asset_name: ${{steps.build.outputs.name}}
run: gh release upload "$tag_name" "$asset_path#$asset_name"
- name: Attest binary provenance
if: steps.upload_release.outcome == 'success'
id: attestation
uses: actions/attest@v4
if: steps.upload_release.outcome == 'success'
uses: actions/attest-build-provenance@v3
with:
subject-path: ${{steps.build.outputs.path}}
show-summary: false
subject-name: ${{steps.build.outputs.name}}
subject-digest: sha256:${{ steps.upload_artifact.outputs.artifact-digest }}
- name: Verify binary attestation
if: steps.attestation.outcome == 'success'
shell: bash
env:
GH_TOKEN: ${{github.token}}
run: gh attestation verify ${{steps.build.outputs.path}} --repo Cockatrice/Cockatrice
run: gh attestation verify ${{steps.build.outputs.path}} -R Cockatrice/Cockatrice

View file

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

View file

@ -13,11 +13,6 @@ on:
- '.github/workflows/docker-release.yml'
- 'Dockerfile'
# Cancel earlier, unfinished runs of this workflow on the same branch (unless on release)
concurrency:
group: "${{ github.workflow }} @ ${{ github.ref_name }}"
cancel-in-progress: ${{ github.ref_type != 'tag' }}
jobs:
docker:
name: amd64 & arm64
@ -32,7 +27,7 @@ jobs:
- name: Docker metadata
id: metadata
uses: docker/metadata-action@v6
uses: docker/metadata-action@v5
with:
images: |
ghcr.io/cockatrice/servatrice
@ -46,27 +41,26 @@ jobs:
org.opencontainers.image.description=Server for Cockatrice, a cross-platform virtual tabletop for multiplayer card games
- name: Set up QEMU
uses: docker/setup-qemu-action@v4
uses: docker/setup-qemu-action@v3
- name: Set up Docker buildx
uses: docker/setup-buildx-action@v4
uses: docker/setup-buildx-action@v3
- name: Login to GitHub Container Registry
if: github.ref_type == 'tag'
uses: docker/login-action@v4
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ github.token }}
- name: Build and push Docker image
uses: docker/build-push-action@v7
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64,linux/arm64
push: ${{ github.ref_type == 'tag' }}
tags: ${{ steps.metadata.outputs.tags }}
labels: ${{ steps.metadata.outputs.labels }}
annotations: ${{ steps.metadata.outputs.annotations }}
cache-from: type=gha,scope=servatrice
cache-to: type=gha,mode=max,scope=servatrice
cache-from: type=gha
cache-to: type=gha,mode=max

View file

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

View file

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

View file

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

View file

@ -74,11 +74,11 @@ endif()
# A project name is needed for CPack
# Version can be overriden by git tags, see cmake/getversion.cmake
project("Cockatrice" VERSION 3.0.0)
project("Cockatrice" VERSION 2.11.0)
# Set release name if not provided via env/cmake var
if(NOT DEFINED GIT_TAG_RELEASENAME)
set(GIT_TAG_RELEASENAME "Graduation Day")
set(GIT_TAG_RELEASENAME "Omenpath")
endif()
# Use c++20 for all targets
@ -254,9 +254,7 @@ endif()
set(CPACK_PACKAGE_CONTACT "Zach Halpern <zach@cockatrice.us>")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "${PROJECT_NAME}")
set(CPACK_PACKAGE_VENDOR "Cockatrice Development Team")
set(CPACK_PACKAGE_DESCRIPTION
"Cockatrice is an open-source, multiplatform application for playing tabletop card games over a network. The program's server design prevents users from manipulating the game for unfair advantage. The client also provides a single-player mode, which allows users to brew while offline."
)
set(CPACK_PACKAGE_DESCRIPTION "Cockatrice is an open-source, multiplatform application for playing tabletop card games over a network. The program's server design prevents users from manipulating the game for unfair advantage. The client also provides a single-player mode, which allows users to brew while offline.")
set(CPACK_RESOURCE_FILE_LICENSE "${PROJECT_SOURCE_DIR}/LICENSE")
set(CPACK_PACKAGE_VERSION_MAJOR "${PROJECT_VERSION_MAJOR}")
set(CPACK_PACKAGE_VERSION_MINOR "${PROJECT_VERSION_MINOR}")
@ -330,12 +328,7 @@ include(CPack)
add_subdirectory(${CMAKE_SOURCE_DIR}/libcockatrice_interfaces ${CMAKE_BINARY_DIR}/libcockatrice_interfaces)
add_subdirectory(${CMAKE_SOURCE_DIR}/libcockatrice_protocol ${CMAKE_BINARY_DIR}/libcockatrice_protocol)
if(WITH_CLIENT
OR WITH_SERVER
OR WITH_ORACLE
)
add_subdirectory(${CMAKE_SOURCE_DIR}/libcockatrice_network ${CMAKE_BINARY_DIR}/libcockatrice_network)
endif()
add_subdirectory(${CMAKE_SOURCE_DIR}/libcockatrice_network ${CMAKE_BINARY_DIR}/libcockatrice_network)
add_subdirectory(${CMAKE_SOURCE_DIR}/libcockatrice_deck_list ${CMAKE_BINARY_DIR}/libcockatrice_deck_list)
add_subdirectory(${CMAKE_SOURCE_DIR}/libcockatrice_rng ${CMAKE_BINARY_DIR}/libcockatrice_rng)
add_subdirectory(${CMAKE_SOURCE_DIR}/libcockatrice_card ${CMAKE_BINARY_DIR}/libcockatrice_card)

View file

@ -52,7 +52,7 @@ Latest <kbd>beta</kbd> version:
# Community Resources [![Discord](https://img.shields.io/discord/314987288398659595?label=Discord&logo=discord&logoColor=white)](https://discord.gg/3Z9yzmA)
Join our [Discord community](https://discord.gg/3Z9yzmA) to connect with other project contributors (`#dev` channel) or fellow users of the app. Come here to talk about the application, features, or just to hang out.
Join our [Discord community](https://discord.gg/3Z9yzmA) to connect with other projet contributors (`#dev` channel) or fellow users of the app. Come here to talk about the application, features, or just to hang out.
- [Official Website](https://cockatrice.github.io)
- [Official Wiki](https://github.com/Cockatrice/Cockatrice/wiki)
- [Official Discord](https://discord.gg/3Z9yzmA)
@ -109,7 +109,7 @@ Cockatrice tries to use the [Google Developer Documentation Style Guide](https:/
Cockatrice uses Transifex to manage translations. You can help us bring <kbd>Cockatrice</kbd>, <kbd>Oracle</kbd> and <kbd>Webatrice</kbd> to your language and just adjust single wordings right from within your browser by visiting our [Transifex project page](https://explore.transifex.com/cockatrice/cockatrice/).<br>
Check out our [Translator FAQ](https://github.com/Cockatrice/Cockatrice/wiki/Translation-FAQ) for more information about getting involved, and join a group of hundreds of others!<br>
Check out our [Translator FAQ](https://github.com/Cockatrice/Cockatrice/wiki/Translation-FAQ) for more information about getting invovled, and join a group of hundreds of others!<br>
# Build [![CI Desktop](https://github.com/Cockatrice/Cockatrice/actions/workflows/desktop-build.yml/badge.svg?branch=master&event=push)](https://github.com/Cockatrice/Cockatrice/actions/workflows/desktop-build.yml?query=branch%3Amaster+event%3Apush) [![CI Docker](https://github.com/Cockatrice/Cockatrice/actions/workflows/docker-release.yml/badge.svg?branch=master&event=push)](https://github.com/Cockatrice/Cockatrice/actions/workflows/docker-release.yml?query=branch%3Amaster+event%3Apush) [![CI Web](https://github.com/Cockatrice/Cockatrice/actions/workflows/web-build.yml/badge.svg?branch=master&event=push)](https://github.com/Cockatrice/Cockatrice/actions/workflows/web-build.yml?query=branch%3Amaster+event%3Apush)

View file

@ -29,9 +29,7 @@ if(WITH_ORACLE)
set(_ORACLE_NEEDED Concurrent Network Svg Widgets)
endif()
if(TEST)
# Union of Qt modules required across all test targets (independent of application targets).
# When adding a new test that needs additional Qt modules, add them here rather than in the test's CMakeLists.txt.
set(_TEST_NEEDED Concurrent Network Svg Widgets)
set(_TEST_NEEDED Widgets)
endif()
set(REQUIRED_QT_COMPONENTS ${REQUIRED_QT_COMPONENTS} ${_SERVATRICE_NEEDED} ${_COCKATRICE_NEEDED} ${_ORACLE_NEEDED}
@ -42,7 +40,7 @@ list(REMOVE_DUPLICATES REQUIRED_QT_COMPONENTS)
if(NOT FORCE_USE_QT5)
# Linguist is now a component in Qt6 instead of an external package
find_package(
Qt6 6.4.2
Qt6 6.2.3
COMPONENTS ${REQUIRED_QT_COMPONENTS} Linguist
QUIET HINTS ${Qt6_DIR}
)

View file

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

View file

@ -39,7 +39,6 @@ set(cockatrice_SOURCES
src/interface/widgets/dialogs/dlg_load_deck_from_clipboard.cpp
src/interface/widgets/dialogs/dlg_load_deck_from_website.cpp
src/interface/widgets/dialogs/dlg_load_remote_deck.cpp
src/interface/widgets/dialogs/dlg_local_game_options.cpp
src/interface/widgets/dialogs/dlg_manage_sets.cpp
src/interface/widgets/dialogs/dlg_register.cpp
src/interface/widgets/dialogs/dlg_select_set_for_cards.cpp
@ -564,9 +563,6 @@ if(WIN32)
PATTERN "styles/qopensslbackend.dll"
PATTERN "styles/qschannelbackend.dll"
PATTERN "styles/qwindowsvistastyle.dll"
PATTERN "styles/qwindows11style.dll"
PATTERN "styles/qmodernwindowsstyle.dll"
PATTERN "styles/qmodernwindowsstyled.dll"
PATTERN "tls/qcertonlybackend.dll"
PATTERN "tls/qopensslbackend.dll"
PATTERN "tls/qschannelbackend.dll"

View file

@ -24,7 +24,6 @@
<file>resources/icons/dragon.svg</file>
<file>resources/icons/dropdown_collapsed.svg</file>
<file>resources/icons/dropdown_expanded.svg</file>
<file>resources/icons/filter.svg</file>
<file>resources/icons/floppy_disk.svg</file>
<file>resources/icons/forgot_password.svg</file>
<file>resources/icons/gear.svg</file>

File diff suppressed because it is too large Load diff

View file

@ -47,6 +47,6 @@ searches are case insensitive.
<dd>[t:aggro OR o:control](#t:aggro OR o:control) <small>(Any deck filename that contains either aggro or control)</small></dd>
<dt>Grouping:</dt>
<dd><a href="#red -([[]]:100 OR aggro)">red -([[]]:100 OR aggro)</a> <small>(Any deck that has red in its filename but is not 100 cards or has aggro in its filename)</small></dd>
<dd><a href="#red -([[]]:100 or aggro)">red -([[]]:100 or aggro)</a> <small>(Any deck that has red in its filename but is not 100 cards or has aggro in its filename)</small></dd>
</dl>

View file

@ -51,16 +51,16 @@ In this list of examples below, each entry has an explanation and can be clicked
<dt><u>E</u>dition:</dt>
<dd>[set:lea](#set:lea) <small>(Cards that appear in Alpha, which has the set code LEA)</small></dd>
<dd>[e:lea OR e:leb](#e:lea OR e:leb) <small>(Cards that appear in Alpha or Beta)</small></dd>
<dd>[e:lea or e:leb](#e:lea or e:leb) <small>(Cards that appear in Alpha or Beta)</small></dd>
<dt>Negate:</dt>
<dd>[c:wu -c:m](#c:wu -c:m) <small>(Any card that is white or blue, but not multicolored)</small></dd>
<dt>Branching:</dt>
<dd>[t:sliver OR o:changeling](#t:sliver OR o:changeling) <small>(Any card that is either a sliver or has changeling)</small></dd>
<dd>[t:sliver or o:changeling](#t:sliver or o:changeling) <small>(Any card that is either a sliver or has changeling)</small></dd>
<dt>Grouping:</dt>
<dd><a href="#t:angel -(angel OR c:w)">t:angel -(angel OR c:w)</a> <small>(Any angel that doesn't have angel in its name and isn't white)</small></dd>
<dd><a href="#t:angel -(angel or c:w)">t:angel -(angel or c:w)</a> <small>(Any angel that doesn't have angel in its name and isn't white)</small></dd>
<dt>Regular Expression:</dt>
<dd>[/^fell/](#/^fell/) <small>(Any card name that begins with "fell")</small></dd>

View file

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg fill="#000000" version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg"
width="800px" height="800px" viewBox="0 0 971.986 971.986"
xml:space="preserve">
<g>
<path d="M370.216,459.3c10.2,11.1,15.8,25.6,15.8,40.6v442c0,26.601,32.1,40.101,51.1,21.4l123.3-141.3
c16.5-19.8,25.6-29.601,25.6-49.2V500c0-15,5.7-29.5,15.8-40.601L955.615,75.5c26.5-28.8,6.101-75.5-33.1-75.5h-873
c-39.2,0-59.7,46.6-33.1,75.5L370.216,459.3z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 676 B

View file

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

View file

@ -8,11 +8,10 @@
#define INTERFACE_JSON_DECK_PARSER_H
#include "../../../interface/deck_loader/card_node_function.h"
#include "../../../interface/deck_loader/deck_loader.h"
#include <QJsonArray>
#include <QJsonObject>
#include <libcockatrice/card/import/card_name_normalizer.h>
#include <libcockatrice/deck_list/deck_list.h>
class IJsonDeckParser
{
@ -50,7 +49,7 @@ public:
outStream << quantity << ' ' << cardName << " (" << setName << ") " << collectorNumber << '\n';
}
deckList.loadFromStream_Plain(outStream, false, CardNameNormalizer());
deckList.loadFromStream_Plain(outStream, false);
deckList.forEachCard(CardNodeFunction::ResolveProviderId());
return deckList;
@ -97,7 +96,7 @@ public:
outStream << quantity << ' ' << cardName << " (" << setName << ") " << collectorNumber << '\n';
}
deckList.loadFromStream_Plain(outStream, false, CardNameNormalizer());
deckList.loadFromStream_Plain(outStream, false);
deckList.forEachCard(CardNodeFunction::ResolveProviderId());
QJsonObject commandersObj = obj.value("commanders").toObject();

View file

@ -284,9 +284,6 @@ SettingsCache::SettingsCache()
closeEmptyCardView = settings->value("interface/closeEmptyCardView", true).toBool();
focusCardViewSearchBar = settings->value("interface/focusCardViewSearchBar", true).toBool();
showDragSelectionCount = settings->value("interface/showlassoselectioncount", true).toBool();
showTotalSelectionCount = settings->value("interface/showpersistentselectioncount", true).toBool();
showShortcuts = settings->value("menu/showshortcuts", true).toBool();
showGameSelectorFilterToolbar = settings->value("menu/showgameselectorfiltertoolbar", true).toBool();
displayCardNames = settings->value("cards/displaycardnames", true).toBool();
@ -388,13 +385,6 @@ SettingsCache::SettingsCache()
defaultStartingLifeTotal = settings->value("game/defaultstartinglifetotal", 20).toInt();
shareDecklistsOnLoad = settings->value("game/sharedecklistsonload", false).toBool();
rememberGameSettings = settings->value("game/remembergamesettings", true).toBool();
// Local game settings use "localgameoptions/" prefix to keep them separate
// from server game settings which use "game/" prefix
localGameRememberSettings = settings->value("localgameoptions/remembersettings", false).toBool();
localGameMaxPlayers = settings->value("localgameoptions/maxplayers", 1).toInt();
localGameStartingLifeTotal = settings->value("localgameoptions/startinglifetotal", 20).toInt();
clientID = settings->value("personal/clientid", CLIENT_INFO_NOT_SET).toString();
clientVersion = settings->value("personal/clientversion", CLIENT_INFO_NOT_SET).toString();
}
@ -1125,21 +1115,257 @@ void SettingsCache::setClientVersion(const QString &_clientVersion)
QStringList SettingsCache::getCountries() const
{
static const QStringList countries = {
"ad", "ae", "af", "ag", "ai", "al", "am", "ao", "aq", "ar", "as", "at", "au", "aw", "ax", "az", "ba", "bb",
"bd", "be", "bf", "bg", "bh", "bi", "bj", "bl", "bm", "bn", "bo", "bq", "br", "bs", "bt", "bv", "bw", "by",
"bz", "ca", "cc", "cd", "cf", "cg", "ch", "ci", "ck", "cl", "cm", "cn", "co", "cr", "cu", "cv", "cw", "cx",
"cy", "cz", "de", "dj", "dk", "dm", "do", "dz", "ec", "ee", "eg", "eh", "er", "es", "et", "eu", "fi", "fj",
"fk", "fm", "fo", "fr", "ga", "gb", "gd", "ge", "gf", "gg", "gh", "gi", "gl", "gm", "gn", "gp", "gq", "gr",
"gs", "gt", "gu", "gw", "gy", "hk", "hm", "hn", "hr", "ht", "hu", "id", "ie", "il", "im", "in", "io", "iq",
"ir", "is", "it", "je", "jm", "jo", "jp", "ke", "kg", "kh", "ki", "km", "kn", "kp", "kr", "kw", "ky", "kz",
"la", "lb", "lc", "li", "lk", "lr", "ls", "lt", "lu", "lv", "ly", "ma", "mc", "md", "me", "mf", "mg", "mh",
"mk", "ml", "mm", "mn", "mo", "mp", "mq", "mr", "ms", "mt", "mu", "mv", "mw", "mx", "my", "mz", "na", "nc",
"ne", "nf", "ng", "ni", "nl", "no", "np", "nr", "nu", "nz", "om", "pa", "pe", "pf", "pg", "ph", "pk", "pl",
"pm", "pn", "pr", "ps", "pt", "pw", "py", "qa", "re", "ro", "rs", "ru", "rw", "sa", "sb", "sc", "sd", "se",
"sg", "sh", "si", "sj", "sk", "sl", "sm", "sn", "so", "sr", "ss", "st", "sv", "sx", "sy", "sz", "tc", "td",
"tf", "tg", "th", "tj", "tk", "tl", "tm", "tn", "to", "tr", "tt", "tv", "tw", "tz", "ua", "ug", "um", "us",
"uy", "uz", "va", "vc", "ve", "vg", "vi", "vn", "vu", "wf", "ws", "xk", "ye", "yt", "za", "zm", "zw"};
static QStringList countries = QStringList() << "ad"
<< "ae"
<< "af"
<< "ag"
<< "ai"
<< "al"
<< "am"
<< "ao"
<< "aq"
<< "ar"
<< "as"
<< "at"
<< "au"
<< "aw"
<< "ax"
<< "az"
<< "ba"
<< "bb"
<< "bd"
<< "be"
<< "bf"
<< "bg"
<< "bh"
<< "bi"
<< "bj"
<< "bl"
<< "bm"
<< "bn"
<< "bo"
<< "bq"
<< "br"
<< "bs"
<< "bt"
<< "bv"
<< "bw"
<< "by"
<< "bz"
<< "ca"
<< "cc"
<< "cd"
<< "cf"
<< "cg"
<< "ch"
<< "ci"
<< "ck"
<< "cl"
<< "cm"
<< "cn"
<< "co"
<< "cr"
<< "cu"
<< "cv"
<< "cw"
<< "cx"
<< "cy"
<< "cz"
<< "de"
<< "dj"
<< "dk"
<< "dm"
<< "do"
<< "dz"
<< "ec"
<< "ee"
<< "eg"
<< "eh"
<< "er"
<< "es"
<< "et"
<< "eu"
<< "fi"
<< "fj"
<< "fk"
<< "fm"
<< "fo"
<< "fr"
<< "ga"
<< "gb"
<< "gd"
<< "ge"
<< "gf"
<< "gg"
<< "gh"
<< "gi"
<< "gl"
<< "gm"
<< "gn"
<< "gp"
<< "gq"
<< "gr"
<< "gs"
<< "gt"
<< "gu"
<< "gw"
<< "gy"
<< "hk"
<< "hm"
<< "hn"
<< "hr"
<< "ht"
<< "hu"
<< "id"
<< "ie"
<< "il"
<< "im"
<< "in"
<< "io"
<< "iq"
<< "ir"
<< "is"
<< "it"
<< "je"
<< "jm"
<< "jo"
<< "jp"
<< "ke"
<< "kg"
<< "kh"
<< "ki"
<< "km"
<< "kn"
<< "kp"
<< "kr"
<< "kw"
<< "ky"
<< "kz"
<< "la"
<< "lb"
<< "lc"
<< "li"
<< "lk"
<< "lr"
<< "ls"
<< "lt"
<< "lu"
<< "lv"
<< "ly"
<< "ma"
<< "mc"
<< "md"
<< "me"
<< "mf"
<< "mg"
<< "mh"
<< "mk"
<< "ml"
<< "mm"
<< "mn"
<< "mo"
<< "mp"
<< "mq"
<< "mr"
<< "ms"
<< "mt"
<< "mu"
<< "mv"
<< "mw"
<< "mx"
<< "my"
<< "mz"
<< "na"
<< "nc"
<< "ne"
<< "nf"
<< "ng"
<< "ni"
<< "nl"
<< "no"
<< "np"
<< "nr"
<< "nu"
<< "nz"
<< "om"
<< "pa"
<< "pe"
<< "pf"
<< "pg"
<< "ph"
<< "pk"
<< "pl"
<< "pm"
<< "pn"
<< "pr"
<< "ps"
<< "pt"
<< "pw"
<< "py"
<< "qa"
<< "re"
<< "ro"
<< "rs"
<< "ru"
<< "rw"
<< "sa"
<< "sb"
<< "sc"
<< "sd"
<< "se"
<< "sg"
<< "sh"
<< "si"
<< "sj"
<< "sk"
<< "sl"
<< "sm"
<< "sn"
<< "so"
<< "sr"
<< "ss"
<< "st"
<< "sv"
<< "sx"
<< "sy"
<< "sz"
<< "tc"
<< "td"
<< "tf"
<< "tg"
<< "th"
<< "tj"
<< "tk"
<< "tl"
<< "tm"
<< "tn"
<< "to"
<< "tr"
<< "tt"
<< "tv"
<< "tw"
<< "tz"
<< "ua"
<< "ug"
<< "um"
<< "us"
<< "uy"
<< "uz"
<< "va"
<< "vc"
<< "ve"
<< "vg"
<< "vi"
<< "vn"
<< "vu"
<< "wf"
<< "ws"
<< "xk"
<< "ye"
<< "yt"
<< "za"
<< "zm"
<< "zw";
return countries;
}
@ -1252,24 +1478,6 @@ void SettingsCache::setRememberGameSettings(const bool _rememberGameSettings)
settings->setValue("game/remembergamesettings", rememberGameSettings);
}
void SettingsCache::setLocalGameRememberSettings(bool value)
{
localGameRememberSettings = value;
settings->setValue("localgameoptions/remembersettings", value);
}
void SettingsCache::setLocalGameMaxPlayers(int value)
{
localGameMaxPlayers = value;
settings->setValue("localgameoptions/maxplayers", value);
}
void SettingsCache::setLocalGameStartingLifeTotal(int value)
{
localGameStartingLifeTotal = value;
settings->setValue("localgameoptions/startinglifetotal", value);
}
void SettingsCache::setNotifyAboutUpdate(QT_STATE_CHANGED_T _notifyaboutupdate)
{
notifyAboutUpdates = static_cast<bool>(_notifyaboutupdate);
@ -1311,18 +1519,6 @@ void SettingsCache::setRoundCardCorners(bool _roundCardCorners)
emit roundCardCornersChanged(roundCardCorners);
}
void SettingsCache::setShowDragSelectionCount(QT_STATE_CHANGED_T _showDragSelectionCount)
{
showDragSelectionCount = static_cast<bool>(_showDragSelectionCount);
settings->setValue("interface/showlassoselectioncount", showDragSelectionCount);
}
void SettingsCache::setShowTotalSelectionCount(QT_STATE_CHANGED_T _showTotalSelectionCount)
{
showTotalSelectionCount = static_cast<bool>(_showTotalSelectionCount);
settings->setValue("interface/showpersistentselectioncount", showTotalSelectionCount);
}
void SettingsCache::loadPaths()
{
QString dataPath = getDataPath();

View file

@ -330,18 +330,10 @@ private:
[[nodiscard]] QString getSafeConfigFilePath(QString configEntry, QString defaultPath) const;
void loadPaths();
bool rememberGameSettings;
// Local game settings (separate from server game settings in game/*)
bool localGameRememberSettings;
int localGameMaxPlayers;
int localGameStartingLifeTotal;
QList<ReleaseChannel *> releaseChannels;
bool isPortableBuild;
bool roundCardCorners;
bool showStatusBar;
bool showDragSelectionCount;
bool showTotalSelectionCount;
public:
SettingsCache();
@ -457,14 +449,6 @@ public:
{
return showStatusBar;
}
[[nodiscard]] bool getShowDragSelectionCount() const
{
return showDragSelectionCount;
}
[[nodiscard]] bool getShowTotalSelectionCount() const
{
return showTotalSelectionCount;
}
[[nodiscard]] bool getNotificationsEnabled() const
{
return notificationsEnabled;
@ -878,18 +862,6 @@ public:
{
return rememberGameSettings;
}
[[nodiscard]] bool getLocalGameRememberSettings() const
{
return localGameRememberSettings;
}
[[nodiscard]] int getLocalGameMaxPlayers() const
{
return localGameMaxPlayers;
}
[[nodiscard]] int getLocalGameStartingLifeTotal() const
{
return localGameStartingLifeTotal;
}
[[nodiscard]] int getKeepAlive() const override
{
return keepalive;
@ -1117,9 +1089,6 @@ public slots:
void setDefaultStartingLifeTotal(const int _defaultStartingLifeTotal);
void setShareDecklistsOnLoad(const bool _shareDecklistsOnLoad);
void setRememberGameSettings(const bool _rememberGameSettings);
void setLocalGameRememberSettings(bool value);
void setLocalGameMaxPlayers(int value);
void setLocalGameStartingLifeTotal(int value);
void setCheckUpdatesOnStartup(QT_STATE_CHANGED_T value);
void setStartupCardUpdateCheckPromptForUpdate(bool value);
void setStartupCardUpdateCheckAlwaysUpdate(bool value);
@ -1130,7 +1099,5 @@ public slots:
void setUpdateReleaseChannelIndex(int value);
void setMaxFontSize(int _max);
void setRoundCardCorners(bool _roundCardCorners);
void setShowDragSelectionCount(QT_STATE_CHANGED_T _showDragSelectionCount);
void setShowTotalSelectionCount(QT_STATE_CHANGED_T _showTotalSelectionCount);
};
#endif

View file

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

View file

@ -501,7 +501,7 @@ private:
{"Player/aUntapAll", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Untap All"),
parseSequenceString("Ctrl+U"),
ShortcutGroup::Playing_Area)},
{"Player/aDoesntUntap", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Toggle Skip Untapping"),
{"Player/aDoesntUntap", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Toggle Untap"),
parseSequenceString("Alt+U"),
ShortcutGroup::Playing_Area)},
{"Player/aFlip", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Turn Card Over"),
@ -513,9 +513,6 @@ private:
{"Player/aPlay", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Play Card"),
parseSequenceString(""),
ShortcutGroup::Playing_Area)},
{"Player/aPlayFacedown", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Play Card, Face Down"),
parseSequenceString(""),
ShortcutGroup::Playing_Area)},
{"Player/aAttach", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Attach Card..."),
parseSequenceString("Ctrl+Alt+A"),
ShortcutGroup::Playing_Area)},
@ -563,9 +560,12 @@ private:
{"Player/aMoveToTopLibrary", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Top of Library"),
parseSequenceString(""),
ShortcutGroup::Move_selected)},
{"Player/aMoveToTable", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Battlefield"),
parseSequenceString(""),
ShortcutGroup::Move_selected)},
{"Player/aPlayFacedown", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Battlefield, Face Down"),
parseSequenceString(""),
ShortcutGroup::Move_selected)},
{"Player/aPlay", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Battlefield"),
parseSequenceString(""),
ShortcutGroup::Move_selected)},
{"Player/aViewHand",
ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Hand"), parseSequenceString(""), ShortcutGroup::View)},
{"Player/aViewGraveyard",
@ -598,19 +598,11 @@ private:
{"Player/aMoveTopCardsToGraveyard", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Graveyard (Multiple)"),
parseSequenceString("Alt+M"),
ShortcutGroup::Move_top)},
{"Player/aMoveTopCardsToGraveyardFaceDown",
ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Graveyard (Multiple), Face Down"),
parseSequenceString(""),
ShortcutGroup::Move_top)},
{"Player/aMoveTopCardToExile",
ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Exile"), parseSequenceString(""), ShortcutGroup::Move_top)},
{"Player/aMoveTopCardsToExile", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Exile (Multiple)"),
parseSequenceString(""),
ShortcutGroup::Move_top)},
{"Player/aMoveTopCardsToExileFaceDown",
ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Exile (Multiple), Face Down"),
parseSequenceString(""),
ShortcutGroup::Move_top)},
{"Player/aMoveTopCardsUntil", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Stack Until Found"),
parseSequenceString("Ctrl+Shift+Y"),
ShortcutGroup::Move_top)},
@ -628,19 +620,11 @@ private:
{"Player/aMoveBottomCardsToGrave", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Graveyard (Multiple)"),
parseSequenceString(""),
ShortcutGroup::Move_bottom)},
{"Player/aMoveBottomCardsToGraveFaceDown",
ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Graveyard (Multiple), Face Down"),
parseSequenceString(""),
ShortcutGroup::Move_bottom)},
{"Player/aMoveBottomCardToExile",
ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Exile"), parseSequenceString(""), ShortcutGroup::Move_bottom)},
{"Player/aMoveBottomCardsToExile", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Exile (Multiple)"),
parseSequenceString(""),
ShortcutGroup::Move_bottom)},
{"Player/aMoveBottomCardsToExileFaceDown",
ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Exile (Multiple), Face Down"),
parseSequenceString(""),
ShortcutGroup::Move_bottom)},
{"Player/aMoveBottomCardToTop", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Top of Library"),
parseSequenceString(""),
ShortcutGroup::Move_bottom)},

View file

@ -31,7 +31,7 @@ GenericQuery <- String
NonDoubleQuoteUnlessEscaped <- '\\\"'. / !["].
NonSingleQuoteUnlessEscaped <- "\\\'". / !['].
UnescapedStringListPart <- !['":<>()=! ].
UnescapedStringListPart <- !['":<>=! ].
SingleApostropheString <- (UnescapedStringListPart+ ws*)* ['] (UnescapedStringListPart+ ws*)*
String <- SingleApostropheString / UnescapedStringListPart+ / ["] <NonDoubleQuoteUnlessEscaped*> ["] / ['] <NonSingleQuoteUnlessEscaped*> [']

View file

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

View file

@ -1,13 +1,14 @@
#include "abstract_card_drag_item.h"
#include "../../client/settings/cache_settings.h"
#include "../z_values.h"
#include <QCursor>
#include <QDebug>
#include <QGraphicsSceneMouseEvent>
#include <QPainter>
static const float CARD_WIDTH_HALF = CARD_WIDTH / 2;
static const float CARD_HEIGHT_HALF = CARD_HEIGHT / 2;
const QColor GHOST_MASK = QColor(255, 255, 255, 50);
AbstractCardDragItem::AbstractCardDragItem(AbstractCardItem *_item,
@ -17,19 +18,19 @@ AbstractCardDragItem::AbstractCardDragItem(AbstractCardItem *_item,
{
if (parentDrag) {
parentDrag->addChildDrag(this);
setZValue(ZValues::childDragZValue(hotSpot.x(), hotSpot.y()));
setZValue(2000000007 + hotSpot.x() * 1000000 + hotSpot.y() * 1000 + 1000);
connect(parentDrag, &QObject::destroyed, this, &AbstractCardDragItem::deleteLater);
} else {
hotSpot = QPointF{qBound(0.0, hotSpot.x(), CardDimensions::WIDTH_F - 1),
qBound(0.0, hotSpot.y(), CardDimensions::HEIGHT_F - 1)};
hotSpot = QPointF{qBound(0.0, hotSpot.x(), static_cast<qreal>(CARD_WIDTH - 1)),
qBound(0.0, hotSpot.y(), static_cast<qreal>(CARD_HEIGHT - 1))};
setCursor(Qt::ClosedHandCursor);
setZValue(ZValues::DRAG_ITEM);
setZValue(2000000007);
}
if (item->getTapped())
setTransform(QTransform()
.translate(CardDimensions::WIDTH_HALF_F, CardDimensions::HEIGHT_HALF_F)
.translate(CARD_WIDTH_HALF, CARD_HEIGHT_HALF)
.rotate(90)
.translate(-CardDimensions::WIDTH_HALF_F, -CardDimensions::HEIGHT_HALF_F));
.translate(-CARD_WIDTH_HALF, -CARD_HEIGHT_HALF));
setCacheMode(DeviceCoordinateCache);
@ -46,7 +47,7 @@ AbstractCardDragItem::AbstractCardDragItem(AbstractCardItem *_item,
QPainterPath AbstractCardDragItem::shape() const
{
QPainterPath shape;
qreal cardCornerRadius = SettingsCache::instance().getRoundCardCorners() ? 0.05 * CardDimensions::WIDTH_F : 0.0;
qreal cardCornerRadius = SettingsCache::instance().getRoundCardCorners() ? 0.05 * CARD_WIDTH : 0.0;
shape.addRoundedRect(boundingRect(), cardCornerRadius, cardCornerRadius);
return shape;
}

View file

@ -34,7 +34,7 @@ public:
AbstractCardDragItem(AbstractCardItem *_item, const QPointF &_hotSpot, AbstractCardDragItem *parentDrag = 0);
[[nodiscard]] QRectF boundingRect() const override
{
return QRectF(0, 0, CardDimensions::WIDTH_F, CardDimensions::HEIGHT_F);
return QRectF(0, 0, CARD_WIDTH, CARD_HEIGHT);
}
[[nodiscard]] QPainterPath shape() const override;
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;

View file

@ -3,7 +3,6 @@
#include "../../client/settings/cache_settings.h"
#include "../../interface/card_picture_loader/card_picture_loader.h"
#include "../game_scene.h"
#include "../z_values.h"
#include <QCursor>
#include <QGraphicsScene>
@ -39,13 +38,13 @@ AbstractCardItem::~AbstractCardItem()
QRectF AbstractCardItem::boundingRect() const
{
return QRectF(0, 0, CardDimensions::WIDTH_F, CardDimensions::HEIGHT_F);
return QRectF(0, 0, CARD_WIDTH, CARD_HEIGHT);
}
QPainterPath AbstractCardItem::shape() const
{
QPainterPath shape;
qreal cardCornerRadius = SettingsCache::instance().getRoundCardCorners() ? 0.05 * CardDimensions::WIDTH_F : 0.0;
qreal cardCornerRadius = SettingsCache::instance().getRoundCardCorners() ? 0.05 * CARD_WIDTH : 0.0;
shape.addRoundedRect(boundingRect(), cardCornerRadius, cardCornerRadius);
return shape;
}
@ -216,9 +215,9 @@ void AbstractCardItem::setHovered(bool _hovered)
if (_hovered)
processHoverEvent();
isHovered = _hovered;
setZValue(_hovered ? ZValues::HOVERED_CARD : realZValue);
setZValue(_hovered ? 2000000004 : realZValue);
setScale(_hovered && SettingsCache::instance().getScaleCards() ? 1.1 : 1);
setTransformOriginPoint(_hovered ? CardDimensions::WIDTH_HALF_F : 0, _hovered ? CardDimensions::HEIGHT_HALF_F : 0);
setTransformOriginPoint(_hovered ? CARD_WIDTH / 2 : 0, _hovered ? CARD_HEIGHT / 2 : 0);
update();
}
@ -274,9 +273,9 @@ void AbstractCardItem::setTapped(bool _tapped, bool canAnimate)
else {
tapAngle = tapped ? 90 : 0;
setTransform(QTransform()
.translate(CardDimensions::WIDTH_HALF_F, CardDimensions::HEIGHT_HALF_F)
.translate((float)CARD_WIDTH / 2, (float)CARD_HEIGHT / 2)
.rotate(tapAngle)
.translate(-CardDimensions::WIDTH_HALF_F, -CardDimensions::HEIGHT_HALF_F));
.translate((float)-CARD_WIDTH / 2, (float)-CARD_HEIGHT / 2));
update();
}
}

View file

@ -8,7 +8,6 @@
#define ABSTRACTCARDITEM_H
#include "../../game_graphics/board/graphics_item_type.h"
#include "../card_dimensions.h"
#include "arrow_target.h"
#include <libcockatrice/card/printing/exact_card.h>
@ -16,6 +15,9 @@
class Player;
const int CARD_WIDTH = 72;
const int CARD_HEIGHT = 102;
class AbstractCardItem : public ArrowTarget
{
Q_OBJECT

View file

@ -8,7 +8,6 @@
#define COUNTER_H
#include "../../interface/widgets/menus/tearoff_menu.h"
#include "../player/menu/abstract_player_component.h"
#include <QGraphicsItem>
#include <QInputDialog>
@ -19,7 +18,7 @@ class QKeyEvent;
class QMenu;
class QString;
class AbstractCounter : public QObject, public QGraphicsItem, public AbstractPlayerComponent
class AbstractCounter : public QObject, public QGraphicsItem
{
Q_OBJECT
Q_INTERFACES(QGraphicsItem)
@ -57,10 +56,10 @@ public:
QGraphicsItem *parent = nullptr);
~AbstractCounter() override;
void retranslateUi() override;
void retranslateUi();
void setValue(int _value);
void setShortcutsActive() override;
void setShortcutsInactive() override;
void setShortcutsActive();
void setShortcutsInactive();
void delCounter();
QMenu *getMenu() const

View file

@ -5,7 +5,6 @@
#include "../player/player.h"
#include "../player/player_actions.h"
#include "../player/player_target.h"
#include "../z_values.h"
#include "../zones/card_zone.h"
#include "card_item.h"
@ -19,13 +18,12 @@
#include <libcockatrice/protocol/pb/command_create_arrow.pb.h>
#include <libcockatrice/protocol/pb/command_delete_arrow.pb.h>
#include <libcockatrice/utility/color.h>
#include <libcockatrice/utility/zone_names.h>
ArrowItem::ArrowItem(Player *_player, int _id, ArrowTarget *_startItem, ArrowTarget *_targetItem, const QColor &_color)
: QGraphicsItem(), player(_player), id(_id), startItem(_startItem), targetItem(_targetItem), targetLocked(false),
color(_color), fullColor(true)
{
setZValue(ZValues::ARROWS);
setZValue(2000000005);
if (startItem)
startItem->addArrowFrom(this);
@ -240,16 +238,16 @@ void ArrowDragItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
}
// if the card is in hand then we will move the card to stack or table as part of drawing the arrow
if (startZone->getName() == ZoneNames::HAND) {
if (startZone->getName() == "hand") {
startCard->playCard(false);
CardInfoPtr ci = startCard->getCard().getCardPtr();
bool playToStack = SettingsCache::instance().getPlayToStack();
if (ci && ((!playToStack && ci->getUiAttributes().tableRow == 3) ||
(playToStack && ci->getUiAttributes().tableRow != 0 &&
startCard->getZone()->getName() != ZoneNames::STACK)))
cmd.set_start_zone(ZoneNames::STACK);
if (ci &&
((!playToStack && ci->getUiAttributes().tableRow == 3) ||
(playToStack && ci->getUiAttributes().tableRow != 0 && startCard->getZone()->getName() != "stack")))
cmd.set_start_zone("stack");
else
cmd.set_start_zone(playToStack ? ZoneNames::STACK : ZoneNames::TABLE);
cmd.set_start_zone(playToStack ? "stack" : "table");
}
if (deleteInPhase != 0) {
@ -319,7 +317,7 @@ void ArrowAttachItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
void ArrowAttachItem::attachCards(CardItem *startCard, const CardItem *targetCard)
{
// do nothing if target is already attached to another card or is not in play
if (targetCard->getAttachedTo() || targetCard->getZone()->getName() != ZoneNames::TABLE) {
if (targetCard->getAttachedTo() || targetCard->getZone()->getName() != "table") {
return;
}
@ -327,12 +325,12 @@ void ArrowAttachItem::attachCards(CardItem *startCard, const CardItem *targetCar
CardZoneLogic *targetZone = targetCard->getZone();
// move card onto table first if attaching from some other zone
if (startZone->getName() != ZoneNames::TABLE) {
if (startZone->getName() != "table") {
player->getPlayerActions()->playCardToTable(startCard, false);
}
Command_AttachCard cmd;
cmd.set_start_zone(ZoneNames::TABLE);
cmd.set_start_zone("table");
cmd.set_card_id(startCard->getId());
cmd.set_target_player_id(targetZone->getPlayer()->getPlayerInfo()->getId());
cmd.set_target_zone(targetZone->getName().toStdString());

View file

@ -13,10 +13,9 @@
CardDragItem::CardDragItem(CardItem *_item,
int _id,
const QPointF &_hotSpot,
bool _forceFaceDown,
bool _faceDown,
AbstractCardDragItem *parentDrag)
: AbstractCardDragItem(_item, _hotSpot, parentDrag), id(_id), forceFaceDown(_forceFaceDown), occupied(false),
currentZone(0)
: AbstractCardDragItem(_item, _hotSpot, parentDrag), id(_id), faceDown(_faceDown), occupied(false), currentZone(0)
{
}

View file

@ -16,7 +16,7 @@ class CardDragItem : public AbstractCardDragItem
Q_OBJECT
private:
int id;
bool forceFaceDown;
bool faceDown;
bool occupied;
CardZone *currentZone;
@ -24,15 +24,15 @@ public:
CardDragItem(CardItem *_item,
int _id,
const QPointF &_hotSpot,
bool _forceFaceDown,
bool _faceDown,
AbstractCardDragItem *parentDrag = 0);
int getId() const
{
return id;
}
bool isForceFaceDown() const
bool getFaceDown() const
{
return forceFaceDown;
return faceDown;
}
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
void updatePosition(const QPointF &cursorScenePos) override;

View file

@ -212,12 +212,10 @@ void CardItem::setAttachedTo(CardItem *_attachedTo)
}
}
/**
* @brief Resets the fields that should be reset after a zone transition
*/
void CardItem::resetState(bool keepAnnotations)
{
attacking = false;
facedown = false;
counters.clear();
pt.clear();
if (!keepAnnotations) {
@ -253,10 +251,10 @@ void CardItem::processCardInfo(const ServerInfo_Card &_info)
setDoesntUntap(_info.doesnt_untap());
}
CardDragItem *CardItem::createDragItem(int _id, const QPointF &_pos, const QPointF &_scenePos, bool forceFaceDown)
CardDragItem *CardItem::createDragItem(int _id, const QPointF &_pos, const QPointF &_scenePos, bool faceDown)
{
deleteDragItem();
dragItem = new CardDragItem(this, _id, _pos, forceFaceDown);
dragItem = new CardDragItem(this, _id, _pos, faceDown);
dragItem->setVisible(false);
scene()->addItem(dragItem);
dragItem->updatePosition(_scenePos);
@ -354,7 +352,7 @@ void CardItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
// Use the buttonDownPos to align the hot spot with the position when
// the user originally clicked
createDragItem(id, event->buttonDownPos(Qt::LeftButton), event->scenePos(), forceFaceDown);
createDragItem(id, event->buttonDownPos(Qt::LeftButton), event->scenePos(), facedown || forceFaceDown);
dragItem->grabMouse();
int childIndex = 0;
@ -367,7 +365,7 @@ void CardItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
if (zone->getHasCardAttr())
childPos = card->pos() - pos();
else
childPos = QPointF(childIndex * CardDimensions::WIDTH_HALF_F, 0);
childPos = QPointF(childIndex * CARD_WIDTH / 2, 0);
CardDragItem *drag =
new CardDragItem(card, card->getId(), childPos, card->getFaceDown() || forceFaceDown, dragItem);
drag->setPos(dragItem->pos() + childPos);
@ -476,9 +474,9 @@ bool CardItem::animationEvent()
}
setTransform(QTransform()
.translate(CardDimensions::WIDTH_HALF_F, CardDimensions::HEIGHT_HALF_F)
.translate(CARD_WIDTH_HALF, CARD_HEIGHT_HALF)
.rotate(tapAngle)
.translate(-CardDimensions::WIDTH_HALF_F, -CardDimensions::HEIGHT_HALF_F));
.translate(-CARD_WIDTH_HALF, -CARD_HEIGHT_HALF));
setHovered(false);
update();

View file

@ -21,6 +21,8 @@ class QAction;
class QColor;
const int MAX_COUNTERS_ON_CARD = 999;
const float CARD_WIDTH_HALF = CARD_WIDTH / 2;
const float CARD_HEIGHT_HALF = CARD_HEIGHT / 2;
const int ROTATION_DEGREES_PER_FRAME = 10;
class CardItem : public AbstractCardItem
@ -140,7 +142,7 @@ public:
void processCardInfo(const ServerInfo_Card &_info);
bool animationEvent();
CardDragItem *createDragItem(int _id, const QPointF &_pos, const QPointF &_scenePos, bool forceFaceDown);
CardDragItem *createDragItem(int _id, const QPointF &_pos, const QPointF &_scenePos, bool faceDown);
void deleteDragItem();
void drawArrow(const QColor &arrowColor);
void drawAttachArrow();

View file

@ -1,29 +0,0 @@
#ifndef CARD_DIMENSIONS_H
#define CARD_DIMENSIONS_H
#include <QtGlobal>
/**
* @file card_dimensions.h
* @brief Canonical card dimension constants for layout and Z-value calculations.
*
* These values represent the logical pixel dimensions of a standard card graphic.
* They are used throughout the game scene for layout, rendering, and Z-value computation.
*/
namespace CardDimensions
{
/// Card width in pixels
constexpr int WIDTH = 72;
/// Card height in pixels
constexpr int HEIGHT = 102;
/// Pre-converted for floating-point contexts (Z-value calculations)
constexpr qreal WIDTH_F = static_cast<qreal>(WIDTH);
constexpr qreal HEIGHT_F = static_cast<qreal>(HEIGHT);
/// Half-dimensions for centering and rotation transforms
constexpr qreal WIDTH_HALF_F = WIDTH_F / 2;
constexpr qreal HEIGHT_HALF_F = HEIGHT_F / 2;
} // namespace CardDimensions
#endif // CARD_DIMENSIONS_H

View file

@ -95,9 +95,8 @@ void DeckViewCard::paint(QPainter *painter, const QStyleOptionGraphicsItem *opti
pen.setJoinStyle(Qt::MiterJoin);
pen.setColor(originZone == DECK_ZONE_MAIN ? Qt::green : Qt::red);
painter->setPen(pen);
qreal cardRadius = SettingsCache::instance().getRoundCardCorners() ? 0.05 * (CardDimensions::WIDTH_F - 3) : 0.0;
painter->drawRoundedRect(QRectF(1.5, 1.5, CardDimensions::WIDTH_F - 3, CardDimensions::HEIGHT_F - 3), cardRadius,
cardRadius);
qreal cardRadius = SettingsCache::instance().getRoundCardCorners() ? 0.05 * (CARD_WIDTH - 3) : 0.0;
painter->drawRoundedRect(QRectF(1.5, 1.5, CARD_WIDTH - 3., CARD_HEIGHT - 3.), cardRadius, cardRadius);
painter->restore();
}
@ -123,7 +122,7 @@ void DeckViewCard::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
if (c == this)
continue;
++j;
auto childPos = QPointF(j * CardDimensions::WIDTH_HALF_F, 0);
auto childPos = QPointF(j * CARD_WIDTH / 2, 0);
auto *drag = new DeckViewCardDragItem(c, childPos, dragItem);
drag->setPos(dragItem->pos() + childPos);
scene()->addItem(drag);
@ -205,7 +204,7 @@ void DeckViewCardContainer::paint(QPainter *painter, const QStyleOptionGraphicsI
painter->setPen(QColor(255, 255, 255, 100));
painter->drawLine(QPointF(0, yUntilNow - paddingY / 2), QPointF(width, yUntilNow - paddingY / 2));
}
qreal thisRowHeight = CardDimensions::HEIGHT_F * currentRowsAndCols[i].first;
qreal thisRowHeight = CARD_HEIGHT * currentRowsAndCols[i].first;
QRectF textRect(0, yUntilNow, totalTextWidth, thisRowHeight);
yUntilNow += thisRowHeight + paddingY;
@ -261,9 +260,9 @@ QSizeF DeckViewCardContainer::calculateBoundingRect(const QList<QPair<int, int>>
// Calculate space needed for cards
for (int i = 0; i < rowsAndCols.size(); ++i) {
totalHeight += CardDimensions::HEIGHT_F * rowsAndCols[i].first + paddingY;
if (CardDimensions::WIDTH_F * rowsAndCols[i].second > totalWidth)
totalWidth = CardDimensions::WIDTH_F * rowsAndCols[i].second;
totalHeight += CARD_HEIGHT * rowsAndCols[i].first + paddingY;
if (CARD_WIDTH * rowsAndCols[i].second > totalWidth)
totalWidth = CARD_WIDTH * rowsAndCols[i].second;
}
return QSizeF(getCardTypeTextWidth() + totalWidth, totalHeight + separatorY + paddingY);
@ -290,10 +289,9 @@ void DeckViewCardContainer::rearrangeItems(const QList<QPair<int, int>> &rowsAnd
std::sort(row.begin(), row.end(), DeckViewCardContainer::sortCardsByName);
for (int j = 0; j < row.size(); ++j) {
DeckViewCard *card = row[j];
card->setPos(x + (j % tempCols) * CardDimensions::WIDTH_F,
yUntilNow + (j / tempCols) * CardDimensions::HEIGHT_F);
card->setPos(x + (j % tempCols) * CARD_WIDTH, yUntilNow + (j / tempCols) * CARD_HEIGHT);
}
yUntilNow += tempRows * CardDimensions::HEIGHT_F + paddingY;
yUntilNow += tempRows * CARD_HEIGHT + paddingY;
}
prepareGeometryChange();
@ -394,7 +392,7 @@ void DeckViewScene::applySideboardPlan(const QList<MoveCard_ToZone> &plan)
void DeckViewScene::rearrangeItems()
{
const int spacing = CardDimensions::HEIGHT / 3;
const int spacing = CARD_HEIGHT / 3;
QList<DeckViewCardContainer *> contList = cardContainers.values();
// Initialize space requirements

View file

@ -12,7 +12,8 @@
#include <libcockatrice/card/database/card_database_manager.h>
#include <libcockatrice/filters/filter_string.h>
DlgMoveTopCardsUntil::DlgMoveTopCardsUntil(QWidget *parent, const MoveTopCardsUntilOptions &options) : QDialog(parent)
DlgMoveTopCardsUntil::DlgMoveTopCardsUntil(QWidget *parent, QStringList exprs, uint _numberOfHits, bool autoPlay)
: QDialog(parent)
{
exprLabel = new QLabel(tr("Card name (or search expressions):"));
@ -20,13 +21,13 @@ DlgMoveTopCardsUntil::DlgMoveTopCardsUntil(QWidget *parent, const MoveTopCardsUn
exprComboBox->setFocus();
exprComboBox->setEditable(true);
exprComboBox->setInsertPolicy(QComboBox::InsertAtTop);
exprComboBox->insertItems(0, options.exprs);
exprComboBox->insertItems(0, exprs);
exprLabel->setBuddy(exprComboBox);
numberOfHitsLabel = new QLabel(tr("Number of hits:"));
numberOfHitsEdit = new QSpinBox(this);
numberOfHitsEdit->setRange(1, 99);
numberOfHitsEdit->setValue(options.numberOfHits);
numberOfHitsEdit->setValue(_numberOfHits);
numberOfHitsLabel->setBuddy(numberOfHitsEdit);
auto *grid = new QGridLayout;
@ -34,7 +35,7 @@ DlgMoveTopCardsUntil::DlgMoveTopCardsUntil(QWidget *parent, const MoveTopCardsUn
grid->addWidget(numberOfHitsEdit, 0, 1);
autoPlayCheckBox = new QCheckBox(tr("Auto play hits"));
autoPlayCheckBox->setChecked(options.autoPlay);
autoPlayCheckBox->setChecked(autoPlay);
buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
connect(buttonBox, &QDialogButtonBox::accepted, this, &DlgMoveTopCardsUntil::validateAndAccept);
@ -117,13 +118,6 @@ QString DlgMoveTopCardsUntil::getExpr() const
return exprComboBox->currentText();
}
MoveTopCardsUntilOptions DlgMoveTopCardsUntil::getOptions() const
{
return {.exprs = getExprs(),
.numberOfHits = numberOfHitsEdit->text().toInt(),
.autoPlay = autoPlayCheckBox->isChecked()};
}
QStringList DlgMoveTopCardsUntil::getExprs() const
{
QStringList exprs;
@ -131,4 +125,14 @@ QStringList DlgMoveTopCardsUntil::getExprs() const
exprs.append(exprComboBox->itemText(i));
}
return exprs;
}
}
uint DlgMoveTopCardsUntil::getNumberOfHits() const
{
return numberOfHitsEdit->text().toUInt();
}
bool DlgMoveTopCardsUntil::isAutoPlay() const
{
return autoPlayCheckBox->isChecked();
}

View file

@ -16,13 +16,6 @@
class FilterString;
struct MoveTopCardsUntilOptions
{
QStringList exprs = {};
int numberOfHits = 1;
bool autoPlay = false;
};
class DlgMoveTopCardsUntil : public QDialog
{
Q_OBJECT
@ -36,12 +29,15 @@ class DlgMoveTopCardsUntil : public QDialog
void validateAndAccept();
bool validateMatchExists(const FilterString &filterString);
[[nodiscard]] QStringList getExprs() const;
public:
explicit DlgMoveTopCardsUntil(QWidget *parent = nullptr, const MoveTopCardsUntilOptions &options = {});
explicit DlgMoveTopCardsUntil(QWidget *parent = nullptr,
QStringList exprs = QStringList(),
uint numberOfHits = 1,
bool autoPlay = false);
[[nodiscard]] QString getExpr() const;
[[nodiscard]] MoveTopCardsUntilOptions getOptions() const;
[[nodiscard]] QStringList getExprs() const;
[[nodiscard]] uint getNumberOfHits() const;
[[nodiscard]] bool isAutoPlay() const;
};
#endif // DLG_MOVE_TOP_CARDS_UNTIL_H

View file

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

View file

@ -14,7 +14,6 @@
#include <QGraphicsView>
#include <QSet>
#include <QtMath>
#include <libcockatrice/utility/zone_names.h>
#include <numeric>
/**
@ -411,9 +410,9 @@ void GameScene::toggleZoneView(Player *player, const QString &zoneName, int numb
connect(item, &ZoneViewWidget::closePressed, this, &GameScene::removeZoneView);
addItem(item);
if (zoneName == ZoneNames::GRAVE)
if (zoneName == "grave")
item->setPos(360, 100);
else if (zoneName == ZoneNames::EXILE)
else if (zoneName == "rfg")
item->setPos(380, 120);
else
item->setPos(340, 80);
@ -521,9 +520,9 @@ void GameScene::startRubberBand(const QPointF &selectionOrigin)
emit sigStartRubberBand(selectionOrigin);
}
void GameScene::resizeRubberBand(const QPointF &cursorPoint, int selectedCount)
void GameScene::resizeRubberBand(const QPointF &cursorPoint)
{
emit sigResizeRubberBand(cursorPoint, selectedCount);
emit sigResizeRubberBand(cursorPoint);
}
void GameScene::stopRubberBand()

View file

@ -163,7 +163,7 @@ public:
/** Unregisters a card from animation updates. */
void unregisterAnimationItem(AbstractCardItem *card);
void startRubberBand(const QPointF &selectionOrigin);
void resizeRubberBand(const QPointF &cursorPoint, int selectedCount);
void resizeRubberBand(const QPointF &cursorPoint);
void stopRubberBand();
public slots:
@ -196,7 +196,7 @@ protected:
signals:
void sigStartRubberBand(const QPointF &selectionOrigin);
void sigResizeRubberBand(const QPointF &cursorPoint, int selectedCount);
void sigResizeRubberBand(const QPointF &cursorPoint);
void sigStopRubberBand();
};

View file

@ -4,32 +4,9 @@
#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)));
@ -42,7 +19,6 @@ GameView::GameView(GameScene *scene, QWidget *parent) : QGraphicsView(scene, par
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);
@ -51,23 +27,7 @@ GameView::GameView(GameScene *scene, QWidget *parent) : QGraphicsView(scene, par
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();
rubberBand = new QRubberBand(QRubberBand::Rectangle, this);
}
void GameView::resizeEvent(QResizeEvent *event)
@ -79,7 +39,6 @@ void GameView::resizeEvent(QResizeEvent *event)
s->processViewSizeChange(event->size());
updateSceneRect(scene()->sceneRect());
updateTotalSelectionCount(event->size());
}
void GameView::updateSceneRect(const QRectF &rect)
@ -89,67 +48,20 @@ void GameView::updateSceneRect(const QRectF &rect)
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)
void GameView::resizeRubberBand(const QPointF &cursorPoint)
{
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();
}
if (rubberBand)
rubberBand->setGeometry(QRect(mapFromScene(selectionOrigin), cursorPoint.toPoint()).normalized());
}
void GameView::stopRubberBand()
{
if (!rubberBand)
return;
rubberBand->hide();
dragCountLabel->hide();
}
void GameView::refreshShortcuts()
@ -157,28 +69,3 @@ void GameView::refreshShortcuts()
aCloseMostRecentZoneView->setShortcuts(
SettingsCache::instance().shortcuts().getShortcut("Player/aCloseMostRecentZoneView"));
}
void GameView::updateTotalSelectionCount(const QSize &viewSize)
{
if (!SettingsCache::instance().getShowTotalSelectionCount()) {
totalCountLabel->hide();
return;
}
int count = scene()->selectedItems().count();
if (count > 1) {
totalCountLabel->setText(QString::number(count));
totalCountLabel->adjustSize();
constexpr int kMarginInPixels = 10;
int availableWidth = viewSize.isValid() ? viewSize.width() : viewport()->width();
int availableHeight = viewSize.isValid() ? viewSize.height() : viewport()->height();
int x = availableWidth - totalCountLabel->width() - kMarginInPixels;
int y = availableHeight - totalCountLabel->height() - kMarginInPixels;
totalCountLabel->move(x, y);
totalCountLabel->show();
} else {
totalCountLabel->hide();
}
}

View file

@ -10,7 +10,6 @@
#include <QGraphicsView>
class GameScene;
class QLabel;
class QRubberBand;
class GameView : public QGraphicsView
@ -19,18 +18,15 @@ class GameView : public QGraphicsView
private:
QAction *aCloseMostRecentZoneView;
QRubberBand *rubberBand;
QLabel *dragCountLabel;
QLabel *totalCountLabel;
QPointF selectionOrigin;
protected:
void resizeEvent(QResizeEvent *event) override;
private slots:
void startRubberBand(const QPointF &selectionOrigin);
void resizeRubberBand(const QPointF &cursorPoint, int selectedCount);
void resizeRubberBand(const QPointF &cursorPoint);
void stopRubberBand();
void refreshShortcuts();
void updateTotalSelectionCount(const QSize &viewSize = QSize());
public slots:
void updateSceneRect(const QRectF &rect);

View file

@ -10,9 +10,16 @@
#include <../../client/settings/card_counter_settings.h>
#include <libcockatrice/protocol/pb/context_move_card.pb.h>
#include <libcockatrice/protocol/pb/context_mulligan.pb.h>
#include <libcockatrice/utility/zone_names.h>
#include <utility>
static const QString TABLE_ZONE_NAME = "table";
static const QString GRAVE_ZONE_NAME = "grave";
static const QString EXILE_ZONE_NAME = "rfg";
static const QString HAND_ZONE_NAME = "hand";
static const QString DECK_ZONE_NAME = "deck";
static const QString SIDEBOARD_ZONE_NAME = "sb";
static const QString STACK_ZONE_NAME = "stack";
static QString sanitizeHtml(QString dirty)
{
return dirty.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;").replace("\"", "&quot;");
@ -30,15 +37,15 @@ MessageLogWidget::getFromStr(CardZoneLogic *zone, QString cardName, int position
QString fromStr;
QString zoneName = zone->getName();
if (zoneName == ZoneNames::TABLE) {
if (zoneName == TABLE_ZONE_NAME) {
fromStr = tr(" from play");
} else if (zoneName == ZoneNames::GRAVE) {
} else if (zoneName == GRAVE_ZONE_NAME) {
fromStr = tr(" from their graveyard");
} else if (zoneName == ZoneNames::EXILE) {
} else if (zoneName == EXILE_ZONE_NAME) {
fromStr = tr(" from exile");
} else if (zoneName == ZoneNames::HAND) {
} else if (zoneName == HAND_ZONE_NAME) {
fromStr = tr(" from their hand");
} else if (zoneName == ZoneNames::DECK) {
} else if (zoneName == DECK_ZONE_NAME) {
if (position == 0) {
if (cardName.isEmpty()) {
if (ownerChange) {
@ -54,7 +61,7 @@ MessageLogWidget::getFromStr(CardZoneLogic *zone, QString cardName, int position
fromStr = tr(" from the top of their library");
}
}
} else if (position == zone->getCards().size()) {
} else if (position >= zone->getCards().size() - 1) {
if (cardName.isEmpty()) {
if (ownerChange) {
cardName = tr("the bottom card of %1's library").arg(zone->getPlayer()->getPlayerInfo()->getName());
@ -76,9 +83,9 @@ MessageLogWidget::getFromStr(CardZoneLogic *zone, QString cardName, int position
fromStr = tr(" from their library");
}
}
} else if (zoneName == ZoneNames::SIDEBOARD) {
} else if (zoneName == SIDEBOARD_ZONE_NAME) {
fromStr = tr(" from sideboard");
} else if (zoneName == ZoneNames::STACK) {
} else if (zoneName == STACK_ZONE_NAME) {
fromStr = tr(" from the stack");
} else {
fromStr = tr(" from custom zone '%1'").arg(zoneName);
@ -268,9 +275,9 @@ void MessageLogWidget::logMoveCard(Player *player,
bool ownerChanged = startZone->getPlayer() != targetZone->getPlayer();
// do not log if moved within the same zone
if ((startZoneName == ZoneNames::TABLE && targetZoneName == ZoneNames::TABLE && !ownerChanged) ||
(startZoneName == ZoneNames::HAND && targetZoneName == ZoneNames::HAND) ||
(startZoneName == ZoneNames::EXILE && targetZoneName == ZoneNames::EXILE)) {
if ((startZoneName == TABLE_ZONE_NAME && targetZoneName == TABLE_ZONE_NAME && !ownerChanged) ||
(startZoneName == HAND_ZONE_NAME && targetZoneName == HAND_ZONE_NAME) ||
(startZoneName == EXILE_ZONE_NAME && targetZoneName == EXILE_ZONE_NAME)) {
return;
}
@ -299,28 +306,20 @@ void MessageLogWidget::logMoveCard(Player *player,
QString finalStr;
std::optional<QString> fourthArg;
if (targetZoneName == ZoneNames::TABLE) {
if (targetZoneName == TABLE_ZONE_NAME) {
soundEngine->playSound("play_card");
if (card->getFaceDown()) {
finalStr = tr("%1 puts %2 into play%3 face down.");
} else {
finalStr = tr("%1 puts %2 into play%3.");
}
} else if (targetZoneName == ZoneNames::GRAVE) {
if (card->getFaceDown()) {
finalStr = tr("%1 puts %2%3 into their graveyard face down.");
} else {
finalStr = tr("%1 puts %2%3 into their graveyard.");
}
} else if (targetZoneName == ZoneNames::EXILE) {
if (card->getFaceDown()) {
finalStr = tr("%1 exiles %2%3 face down.");
} else {
finalStr = tr("%1 exiles %2%3.");
}
} else if (targetZoneName == ZoneNames::HAND) {
} else if (targetZoneName == GRAVE_ZONE_NAME) {
finalStr = tr("%1 puts %2%3 into their graveyard.");
} else if (targetZoneName == EXILE_ZONE_NAME) {
finalStr = tr("%1 exiles %2%3.");
} else if (targetZoneName == HAND_ZONE_NAME) {
finalStr = tr("%1 moves %2%3 to their hand.");
} else if (targetZoneName == ZoneNames::DECK) {
} else if (targetZoneName == DECK_ZONE_NAME) {
if (newX == -1) {
finalStr = tr("%1 puts %2%3 into their library.");
} else if (newX >= targetZone->getCards().size()) {
@ -332,22 +331,14 @@ void MessageLogWidget::logMoveCard(Player *player,
fourthArg = QString::number(newX);
finalStr = tr("%1 puts %2%3 into their library %4 cards from the top.");
}
} else if (targetZoneName == ZoneNames::SIDEBOARD) {
} else if (targetZoneName == SIDEBOARD_ZONE_NAME) {
finalStr = tr("%1 moves %2%3 to sideboard.");
} else if (targetZoneName == ZoneNames::STACK) {
} else if (targetZoneName == STACK_ZONE_NAME) {
soundEngine->playSound("play_card");
if (card->getFaceDown()) {
finalStr = tr("%1 plays %2%3 face down.");
} else {
finalStr = tr("%1 plays %2%3.");
}
finalStr = tr("%1 plays %2%3.");
} else {
fourthArg = targetZoneName;
if (card->getFaceDown()) {
finalStr = tr("%1 moves %2%3 to custom zone '%4' face down.");
} else {
finalStr = tr("%1 moves %2%3 to custom zone '%4'.");
}
finalStr = tr("%1 moves %2%3 to custom zone '%4'.");
}
QString message = finalStr.arg(sanitizeHtml(player->getPlayerInfo()->getName()), cardStr, nameFrom.second);

View file

@ -11,7 +11,6 @@
#include <libcockatrice/protocol/pb/command_next_turn.pb.h>
#include <libcockatrice/protocol/pb/command_set_active_phase.pb.h>
#include <libcockatrice/protocol/pb/command_set_card_attr.pb.h>
#include <libcockatrice/utility/zone_names.h>
PhaseButton::PhaseButton(const QString &_name, QGraphicsItem *parent, QAction *_doubleClickAction, bool _highlightable)
: QObject(), QGraphicsItem(parent), name(_name), active(false), highlightable(_highlightable),
@ -260,7 +259,7 @@ void PhasesToolbar::actNextTurn()
void PhasesToolbar::actUntapAll()
{
Command_SetCardAttr cmd;
cmd.set_zone(ZoneNames::TABLE);
cmd.set_zone("table");
cmd.set_attribute(AttrTapped);
cmd.set_attr_value("0");

View file

@ -9,20 +9,17 @@
enum CardMenuActionType
{
// Per-card attribute actions (must be <= cmClone for cardMenuAction() dispatch)
cmTap,
cmUntap,
cmDoesntUntap,
cmFlip,
cmPeek,
cmClone,
// Move actions (must be > cmClone for cardMenuAction() dispatch)
cmMoveToTopLibrary,
cmMoveToBottomLibrary,
cmMoveToHand,
cmMoveToGraveyard,
cmMoveToExile,
cmMoveToTable
cmMoveToExile
};
#endif // COCKATRICE_CARD_MENU_ACTION_TYPE_H

View file

@ -1,32 +0,0 @@
/**
* @file abstract_player_component.h
* @ingroup GameMenusPlayers
* @brief Polymorphic interface for player-bound UI components managed by PlayerMenu.
*/
#ifndef COCKATRICE_ABSTRACT_PLAYER_COMPONENT_H
#define COCKATRICE_ABSTRACT_PLAYER_COMPONENT_H
/**
* @brief Interface for player-bound UI components that need shortcut and translation lifecycle management.
*
* Not a QObject avoids diamond inheritance with Qt's MOC. Each concrete component
* inherits QObject through its Qt base class (QMenu, TearOffMenu, QGraphicsItem, etc.)
* and this interface through regular multiple inheritance.
*/
class AbstractPlayerComponent
{
public:
virtual ~AbstractPlayerComponent() = default;
/// Bind keyboard shortcuts. Called when this player gains focus.
virtual void setShortcutsActive() = 0;
/// Unbind keyboard shortcuts. Called when this player loses focus.
virtual void setShortcutsInactive() = 0;
/// Retranslate all user-visible strings. Called on language change.
virtual void retranslateUi() = 0;
};
#endif // COCKATRICE_ABSTRACT_PLAYER_COMPONENT_H

View file

@ -12,7 +12,6 @@
#include <libcockatrice/card/database/card_database_manager.h>
#include <libcockatrice/card/relation/card_relation.h>
#include <libcockatrice/utility/zone_names.h>
CardMenu::CardMenu(Player *_player, const CardItem *_card, bool _shortcutsActive)
: player(_player), card(_card), shortcutsActive(_shortcutsActive)
@ -35,8 +34,6 @@ CardMenu::CardMenu(Player *_player, const CardItem *_card, bool _shortcutsActive
connect(aTap, &QAction::triggered, playerActions, &PlayerActions::cardMenuAction);
aDoesntUntap = new QAction(this);
aDoesntUntap->setData(cmDoesntUntap);
aDoesntUntap->setCheckable(true);
aDoesntUntap->setChecked(card != nullptr && card->getDoesntUntap());
connect(aDoesntUntap, &QAction::triggered, playerActions, &PlayerActions::cardMenuAction);
aAttach = new QAction(this);
connect(aAttach, &QAction::triggered, playerActions, &PlayerActions::actAttach);
@ -110,26 +107,36 @@ CardMenu::CardMenu(Player *_player, const CardItem *_card, bool _shortcutsActive
if (revealedCard) {
addAction(aHide);
addSeparator();
addAction(aClone);
addSeparator();
addAction(aSelectAll);
addAction(aSelectColumn);
addRelatedCardView();
} else {
} else if (writeableCard) {
if (card->getZone()) {
if (card->getZone()->getName() == ZoneNames::TABLE) {
createTableMenu(writeableCard);
} else if (card->getZone()->getName() == ZoneNames::STACK) {
createStackMenu(writeableCard);
} else if (card->getZone()->getName() == ZoneNames::EXILE ||
card->getZone()->getName() == ZoneNames::GRAVE) {
createGraveyardOrExileMenu(writeableCard);
if (card->getZone()->getName() == "table") {
createTableMenu();
} else if (card->getZone()->getName() == "stack") {
createStackMenu();
} else if (card->getZone()->getName() == "rfg" || card->getZone()->getName() == "grave") {
createGraveyardOrExileMenu();
} else {
createHandOrCustomZoneMenu(writeableCard);
createHandOrCustomZoneMenu();
}
} else {
createZonelessMenu(writeableCard);
addMenu(new MoveMenu(player));
}
} else {
if (card->getZone() && card->getZone()->getName() != "hand") {
addAction(aDrawArrow);
addSeparator();
addRelatedCardView();
addRelatedCardActions();
addSeparator();
addAction(aClone);
addSeparator();
addAction(aSelectAll);
}
}
}
@ -145,18 +152,22 @@ void CardMenu::removePlayer(Player *playerToRemove)
}
}
void CardMenu::createTableMenu(bool canModifyCard)
void CardMenu::createTableMenu()
{
// Card is on the battlefield
bool canModifyCard = player->getPlayerInfo()->judge || card->getOwner() == player;
if (!canModifyCard) {
addRelatedCardView();
addRelatedCardActions();
addSeparator();
addAction(aDrawArrow);
addSeparator();
addAction(aClone);
addSeparator();
addAction(aSelectAll);
addAction(aSelectRow);
addRelatedCardView();
addRelatedCardActions();
return;
}
@ -166,9 +177,10 @@ void CardMenu::createTableMenu(bool canModifyCard)
if (card->getFaceDown()) {
addAction(aPeek);
}
addSeparator();
addAction(aClone);
addMenu(new MoveMenu(player));
addRelatedCardView();
addRelatedCardActions();
addSeparator();
addAction(aAttach);
if (card->getAttachedTo()) {
@ -179,6 +191,9 @@ void CardMenu::createTableMenu(bool canModifyCard)
addMenu(new PtMenu(player));
addAction(aSetAnnotation);
addSeparator();
addAction(aClone);
addMenu(new MoveMenu(player));
addSeparator();
addAction(aSelectAll);
addAction(aSelectRow);
@ -194,81 +209,67 @@ void CardMenu::createTableMenu(bool canModifyCard)
}
addSeparator();
addMenu(mCardCounters);
addRelatedCardView();
addRelatedCardActions();
}
void CardMenu::createStackMenu(bool canModifyCard)
void CardMenu::createStackMenu()
{
bool canModifyCard = player->getPlayerInfo()->judge || card->getOwner() == player;
// Card is on the stack
if (!canModifyCard) {
if (canModifyCard) {
addAction(aAttach);
addAction(aDrawArrow);
addSeparator();
addAction(aClone);
addMenu(new MoveMenu(player));
addSeparator();
addAction(aSelectAll);
} else {
addAction(aDrawArrow);
addSeparator();
addAction(aClone);
addSeparator();
addAction(aSelectAll);
addRelatedCardView();
addRelatedCardActions();
return;
}
addAction(aPlay);
addAction(aPlayFacedown);
addSeparator();
addAction(aClone);
addMenu(new MoveMenu(player));
addSeparator();
addAction(aAttach);
addAction(aDrawArrow);
addSeparator();
addAction(aSelectAll);
addRelatedCardView();
addRelatedCardActions();
}
void CardMenu::createGraveyardOrExileMenu(bool canModifyCard)
void CardMenu::createGraveyardOrExileMenu()
{
bool canModifyCard = player->getPlayerInfo()->judge || card->getOwner() == player;
// Card is in the graveyard or exile
if (!canModifyCard) {
addAction(aDrawArrow);
if (canModifyCard) {
addAction(aPlay);
addAction(aPlayFacedown);
addSeparator();
addAction(aClone);
addMenu(new MoveMenu(player));
addSeparator();
addAction(aSelectAll);
addAction(aSelectColumn);
addSeparator();
addAction(aAttach);
addAction(aDrawArrow);
} else {
addAction(aClone);
addSeparator();
addAction(aSelectAll);
addAction(aSelectColumn);
addRelatedCardView();
addRelatedCardActions();
return;
addSeparator();
addAction(aDrawArrow);
}
addAction(aPlay);
addAction(aPlayFacedown);
addSeparator();
addAction(aClone);
addMenu(new MoveMenu(player));
addSeparator();
addAction(aAttach);
addAction(aDrawArrow);
addSeparator();
addAction(aSelectAll);
addAction(aSelectColumn);
addRelatedCardView();
addRelatedCardActions();
}
void CardMenu::createHandOrCustomZoneMenu(bool canModifyCard)
void CardMenu::createHandOrCustomZoneMenu()
{
if (!canModifyCard) {
addAction(aDrawArrow);
addSeparator();
addAction(aClone);
addSeparator();
addAction(aSelectAll);
addRelatedCardView();
addRelatedCardActions();
return;
}
// Card is in hand or a custom zone specified by server
addAction(aPlay);
addAction(aPlayFacedown);
@ -284,7 +285,7 @@ void CardMenu::createHandOrCustomZoneMenu(bool canModifyCard)
addMenu(new MoveMenu(player));
// actions that are really wonky when done from deck or sideboard
if (card->getZone()->getName() == ZoneNames::HAND) {
if (card->getZone()->getName() == "hand") {
addSeparator();
addAction(aAttach);
addAction(aDrawArrow);
@ -297,18 +298,11 @@ void CardMenu::createHandOrCustomZoneMenu(bool canModifyCard)
}
addRelatedCardView();
if (card->getZone()->getName() == ZoneNames::HAND) {
if (card->getZone()->getName() == "hand") {
addRelatedCardActions();
}
}
void CardMenu::createZonelessMenu(bool canModifyCard)
{
if (canModifyCard) {
addMenu(new MoveMenu(player));
}
}
/**
* @brief Populates the menu with an action for each active player.
*
@ -452,7 +446,7 @@ void CardMenu::retranslateUi()
aRevealToAll->setText(tr("&All players"));
//: Turn sideways or back again
aTap->setText(tr("&Tap / Untap"));
aDoesntUntap->setText(tr("Skip &untapping"));
aDoesntUntap->setText(tr("Toggle &normal untapping"));
//: Turn face up/face down
aFlip->setText(tr("T&urn Over")); // Only the user facing names in client got renamed to "turn over"
// All code and proto bits are still unchanged (flip) for compatibility reasons

View file

@ -18,11 +18,10 @@ class CardMenu : public QMenu
public:
explicit CardMenu(Player *player, const CardItem *card, bool shortcutsActive);
void removePlayer(Player *playerToRemove);
void createTableMenu(bool canModifyCard);
void createStackMenu(bool canModifyCard);
void createGraveyardOrExileMenu(bool canModifyCard);
void createHandOrCustomZoneMenu(bool canModifyCard);
void createZonelessMenu(bool canModifyCard);
void createTableMenu();
void createStackMenu();
void createGraveyardOrExileMenu();
void createHandOrCustomZoneMenu();
QMenu *mCardCounters;

View file

@ -7,23 +7,15 @@
#ifndef COCKATRICE_CUSTOM_ZONE_MENU_H
#define COCKATRICE_CUSTOM_ZONE_MENU_H
#include "abstract_player_component.h"
#include <QMenu>
class Player;
class CustomZoneMenu : public QMenu, public AbstractPlayerComponent
class CustomZoneMenu : public QMenu
{
Q_OBJECT
public:
explicit CustomZoneMenu(Player *player);
void retranslateUi() override;
void setShortcutsActive() override
{
}
void setShortcutsInactive() override
{
}
void retranslateUi();
private:
Player *player;

View file

@ -6,7 +6,6 @@
#include <QAction>
#include <QMenu>
#include <libcockatrice/utility/zone_names.h>
GraveyardMenu::GraveyardMenu(Player *_player, QWidget *parent) : TearOffMenu(parent), player(_player)
{
@ -40,16 +39,16 @@ void GraveyardMenu::createMoveActions()
if (player->getPlayerInfo()->local || player->getPlayerInfo()->judge) {
aMoveGraveToTopLibrary = new QAction(this);
aMoveGraveToTopLibrary->setData(QList<QVariant>() << ZoneNames::DECK << 0);
aMoveGraveToTopLibrary->setData(QList<QVariant>() << "deck" << 0);
aMoveGraveToBottomLibrary = new QAction(this);
aMoveGraveToBottomLibrary->setData(QList<QVariant>() << ZoneNames::DECK << -1);
aMoveGraveToBottomLibrary->setData(QList<QVariant>() << "deck" << -1);
aMoveGraveToHand = new QAction(this);
aMoveGraveToHand->setData(QList<QVariant>() << ZoneNames::HAND << 0);
aMoveGraveToHand->setData(QList<QVariant>() << "hand" << 0);
aMoveGraveToRfg = new QAction(this);
aMoveGraveToRfg->setData(QList<QVariant>() << ZoneNames::EXILE << 0);
aMoveGraveToRfg->setData(QList<QVariant>() << "rfg" << 0);
connect(aMoveGraveToTopLibrary, &QAction::triggered, grave, &PileZoneLogic::moveAllToZone);
connect(aMoveGraveToBottomLibrary, &QAction::triggered, grave, &PileZoneLogic::moveAllToZone);

View file

@ -8,13 +8,12 @@
#define COCKATRICE_GRAVE_MENU_H
#include "../../../interface/widgets/menus/tearoff_menu.h"
#include "abstract_player_component.h"
#include <QAction>
#include <QMenu>
class Player;
class GraveyardMenu : public TearOffMenu, public AbstractPlayerComponent
class GraveyardMenu : public TearOffMenu
{
Q_OBJECT
signals:
@ -26,9 +25,9 @@ public:
void createViewActions();
void populateRevealRandomMenuWithActivePlayers();
void onRevealRandomTriggered();
void retranslateUi() override;
void setShortcutsActive() override;
void setShortcutsInactive() override;
void retranslateUi();
void setShortcutsActive();
void setShortcutsInactive();
QMenu *mRevealRandomGraveyardCard = nullptr;
QMenu *moveGraveMenu = nullptr;

View file

@ -9,7 +9,6 @@
#include <QAction>
#include <QMenu>
#include <libcockatrice/utility/zone_names.h>
HandMenu::HandMenu(Player *_player, PlayerActions *actions, QWidget *parent) : TearOffMenu(parent), player(_player)
{
@ -77,13 +76,13 @@ HandMenu::HandMenu(Player *_player, PlayerActions *actions, QWidget *parent) : T
if (player->getPlayerInfo()->local || player->getPlayerInfo()->judge) {
aMoveHandToTopLibrary = new QAction(this);
aMoveHandToTopLibrary->setData(QList<QVariant>() << ZoneNames::DECK << 0);
aMoveHandToTopLibrary->setData(QList<QVariant>() << "deck" << 0);
aMoveHandToBottomLibrary = new QAction(this);
aMoveHandToBottomLibrary->setData(QList<QVariant>() << ZoneNames::DECK << -1);
aMoveHandToBottomLibrary->setData(QList<QVariant>() << "deck" << -1);
aMoveHandToGrave = new QAction(this);
aMoveHandToGrave->setData(QList<QVariant>() << ZoneNames::GRAVE << 0);
aMoveHandToGrave->setData(QList<QVariant>() << "grave" << 0);
aMoveHandToRfg = new QAction(this);
aMoveHandToRfg->setData(QList<QVariant>() << ZoneNames::EXILE << 0);
aMoveHandToRfg->setData(QList<QVariant>() << "rfg" << 0);
auto hand = player->getHandZone();

View file

@ -8,7 +8,6 @@
#define COCKATRICE_HAND_MENU_H
#include "../../../interface/widgets/menus/tearoff_menu.h"
#include "abstract_player_component.h"
#include <QAction>
#include <QMenu>
@ -16,7 +15,7 @@
class Player;
class PlayerActions;
class HandMenu : public TearOffMenu, public AbstractPlayerComponent
class HandMenu : public TearOffMenu
{
Q_OBJECT
@ -32,9 +31,9 @@ public:
return mRevealRandomHandCard;
}
void retranslateUi() override;
void setShortcutsActive() override;
void setShortcutsInactive() override;
void retranslateUi();
void setShortcutsActive();
void setShortcutsInactive();
private slots:
void populateRevealHandMenuWithActivePlayers();

View file

@ -51,10 +51,8 @@ LibraryMenu::LibraryMenu(Player *_player, QWidget *parent) : TearOffMenu(parent)
topLibraryMenu->addSeparator();
topLibraryMenu->addAction(aMoveTopCardToGraveyard);
topLibraryMenu->addAction(aMoveTopCardsToGraveyard);
topLibraryMenu->addAction(aMoveTopCardsToGraveyardFaceDown);
topLibraryMenu->addAction(aMoveTopCardToExile);
topLibraryMenu->addAction(aMoveTopCardsToExile);
topLibraryMenu->addAction(aMoveTopCardsToExileFaceDown);
topLibraryMenu->addAction(aMoveTopCardsUntil);
topLibraryMenu->addSeparator();
topLibraryMenu->addAction(aShuffleTopCards);
@ -68,10 +66,8 @@ LibraryMenu::LibraryMenu(Player *_player, QWidget *parent) : TearOffMenu(parent)
bottomLibraryMenu->addSeparator();
bottomLibraryMenu->addAction(aMoveBottomCardToGraveyard);
bottomLibraryMenu->addAction(aMoveBottomCardsToGraveyard);
bottomLibraryMenu->addAction(aMoveBottomCardsToGraveyardFaceDown);
bottomLibraryMenu->addAction(aMoveBottomCardToExile);
bottomLibraryMenu->addAction(aMoveBottomCardsToExile);
bottomLibraryMenu->addAction(aMoveBottomCardsToExileFaceDown);
bottomLibraryMenu->addSeparator();
bottomLibraryMenu->addAction(aShuffleBottomCards);
@ -140,14 +136,8 @@ void LibraryMenu::createMoveActions()
connect(aMoveTopCardToExile, &QAction::triggered, playerActions, &PlayerActions::actMoveTopCardToExile);
aMoveTopCardsToGraveyard = new QAction(this);
connect(aMoveTopCardsToGraveyard, &QAction::triggered, playerActions, &PlayerActions::actMoveTopCardsToGrave);
aMoveTopCardsToGraveyardFaceDown = new QAction(this);
connect(aMoveTopCardsToGraveyardFaceDown, &QAction::triggered, playerActions,
&PlayerActions::actMoveTopCardsToGraveFaceDown);
aMoveTopCardsToExile = new QAction(this);
connect(aMoveTopCardsToExile, &QAction::triggered, playerActions, &PlayerActions::actMoveTopCardsToExile);
aMoveTopCardsToExileFaceDown = new QAction(this);
connect(aMoveTopCardsToExileFaceDown, &QAction::triggered, playerActions,
&PlayerActions::actMoveTopCardsToExileFaceDown);
aMoveTopCardsUntil = new QAction(this);
connect(aMoveTopCardsUntil, &QAction::triggered, playerActions, &PlayerActions::actMoveTopCardsUntil);
aMoveTopCardToBottom = new QAction(this);
@ -166,14 +156,8 @@ void LibraryMenu::createMoveActions()
aMoveBottomCardsToGraveyard = new QAction(this);
connect(aMoveBottomCardsToGraveyard, &QAction::triggered, playerActions,
&PlayerActions::actMoveBottomCardsToGrave);
aMoveBottomCardsToGraveyardFaceDown = new QAction(this);
connect(aMoveBottomCardsToGraveyardFaceDown, &QAction::triggered, playerActions,
&PlayerActions::actMoveBottomCardsToGraveFaceDown);
aMoveBottomCardsToExile = new QAction(this);
connect(aMoveBottomCardsToExile, &QAction::triggered, playerActions, &PlayerActions::actMoveBottomCardsToExile);
aMoveBottomCardsToExileFaceDown = new QAction(this);
connect(aMoveBottomCardsToExileFaceDown, &QAction::triggered, playerActions,
&PlayerActions::actMoveBottomCardsToExileFaceDown);
aMoveBottomCardToTop = new QAction(this);
connect(aMoveBottomCardToTop, &QAction::triggered, playerActions, &PlayerActions::actMoveBottomCardToTop);
}
@ -232,9 +216,7 @@ void LibraryMenu::retranslateUi()
aMoveTopCardToGraveyard->setText(tr("Move top card to grave&yard"));
aMoveTopCardToExile->setText(tr("Move top card to e&xile"));
aMoveTopCardsToGraveyard->setText(tr("Move top cards to &graveyard..."));
aMoveTopCardsToGraveyardFaceDown->setText(tr("Move top cards to graveyard face down..."));
aMoveTopCardsToExile->setText(tr("Move top cards to &exile..."));
aMoveTopCardsToExileFaceDown->setText(tr("Move top cards to exile face down..."));
aMoveTopCardsUntil->setText(tr("Put top cards on stack &until..."));
aShuffleTopCards->setText(tr("Shuffle top cards..."));
@ -245,9 +227,7 @@ void LibraryMenu::retranslateUi()
aMoveBottomCardToGraveyard->setText(tr("Move bottom card to grave&yard"));
aMoveBottomCardToExile->setText(tr("Move bottom card to e&xile"));
aMoveBottomCardsToGraveyard->setText(tr("Move bottom cards to &graveyard..."));
aMoveBottomCardsToGraveyardFaceDown->setText(tr("Move bottom cards to graveyard face down..."));
aMoveBottomCardsToExile->setText(tr("Move bottom cards to &exile..."));
aMoveBottomCardsToExileFaceDown->setText(tr("Move bottom cards to exile face down..."));
aMoveBottomCardToTop->setText(tr("Put bottom card on &top"));
aShuffleBottomCards->setText(tr("Shuffle bottom cards..."));
}
@ -355,10 +335,8 @@ void LibraryMenu::setShortcutsActive()
aMoveTopToPlayFaceDown->setShortcuts(shortcuts.getShortcut("Player/aMoveTopToPlayFaceDown"));
aMoveTopCardToGraveyard->setShortcuts(shortcuts.getShortcut("Player/aMoveTopCardToGraveyard"));
aMoveTopCardsToGraveyard->setShortcuts(shortcuts.getShortcut("Player/aMoveTopCardsToGraveyard"));
aMoveTopCardsToGraveyardFaceDown->setShortcuts(shortcuts.getShortcut("Player/aMoveTopCardsToGraveyardFaceDown"));
aMoveTopCardToExile->setShortcuts(shortcuts.getShortcut("Player/aMoveTopCardToExile"));
aMoveTopCardsToExile->setShortcuts(shortcuts.getShortcut("Player/aMoveTopCardsToExile"));
aMoveTopCardsToExileFaceDown->setShortcuts(shortcuts.getShortcut("Player/aMoveTopCardsToExileFaceDown"));
aMoveTopCardsUntil->setShortcuts(shortcuts.getShortcut("Player/aMoveTopCardsUntil"));
aMoveTopCardToBottom->setShortcuts(shortcuts.getShortcut("Player/aMoveTopCardToBottom"));
aDrawBottomCard->setShortcuts(shortcuts.getShortcut("Player/aDrawBottomCard"));
@ -367,10 +345,8 @@ void LibraryMenu::setShortcutsActive()
aMoveBottomToPlayFaceDown->setShortcuts(shortcuts.getShortcut("Player/aMoveBottomToPlayFaceDown"));
aMoveBottomCardToGraveyard->setShortcuts(shortcuts.getShortcut("Player/aMoveBottomCardToGrave"));
aMoveBottomCardsToGraveyard->setShortcuts(shortcuts.getShortcut("Player/aMoveBottomCardsToGrave"));
aMoveBottomCardsToGraveyardFaceDown->setShortcuts(shortcuts.getShortcut("Player/aMoveBottomCardsToGraveFaceDown"));
aMoveBottomCardToExile->setShortcuts(shortcuts.getShortcut("Player/aMoveBottomCardToExile"));
aMoveBottomCardsToExile->setShortcuts(shortcuts.getShortcut("Player/aMoveBottomCardsToExile"));
aMoveBottomCardsToExileFaceDown->setShortcuts(shortcuts.getShortcut("Player/aMoveBottomCardsToExileFaceDown"));
aMoveBottomCardToTop->setShortcuts(shortcuts.getShortcut("Player/aMoveBottomCardToTop"));
}
@ -391,10 +367,8 @@ void LibraryMenu::setShortcutsInactive()
aMoveTopToPlayFaceDown->setShortcut(QKeySequence());
aMoveTopCardToGraveyard->setShortcut(QKeySequence());
aMoveTopCardsToGraveyard->setShortcut(QKeySequence());
aMoveTopCardsToGraveyardFaceDown->setShortcut(QKeySequence());
aMoveTopCardToExile->setShortcut(QKeySequence());
aMoveTopCardsToExile->setShortcut(QKeySequence());
aMoveTopCardsToExileFaceDown->setShortcut(QKeySequence());
aMoveTopCardsUntil->setShortcut(QKeySequence());
aDrawBottomCard->setShortcut(QKeySequence());
aDrawBottomCards->setShortcut(QKeySequence());
@ -402,8 +376,6 @@ void LibraryMenu::setShortcutsInactive()
aMoveBottomToPlayFaceDown->setShortcut(QKeySequence());
aMoveBottomCardToGraveyard->setShortcut(QKeySequence());
aMoveBottomCardsToGraveyard->setShortcut(QKeySequence());
aMoveBottomCardsToGraveyardFaceDown->setShortcut(QKeySequence());
aMoveBottomCardToExile->setShortcut(QKeySequence());
aMoveBottomCardsToExile->setShortcut(QKeySequence());
aMoveBottomCardsToExileFaceDown->setShortcut(QKeySequence());
}

View file

@ -8,7 +8,6 @@
#define COCKATRICE_LIBRARY_MENU_H
#include "../../../interface/widgets/menus/tearoff_menu.h"
#include "abstract_player_component.h"
#include <QAction>
#include <QMenu>
@ -16,7 +15,7 @@
class Player;
class PlayerActions;
class LibraryMenu : public TearOffMenu, public AbstractPlayerComponent
class LibraryMenu : public TearOffMenu
{
Q_OBJECT
public slots:
@ -29,15 +28,15 @@ public:
void createShuffleActions();
void createMoveActions();
void createViewActions();
void retranslateUi() override;
void retranslateUi();
void populateRevealLibraryMenuWithActivePlayers();
void populateLendLibraryMenuWithActivePlayers();
void populateRevealTopCardMenuWithActivePlayers();
void onRevealLibraryTriggered();
void onLendLibraryTriggered();
void onRevealTopCardTriggered();
void setShortcutsActive() override;
void setShortcutsInactive() override;
void setShortcutsActive();
void setShortcutsInactive();
[[nodiscard]] bool isAlwaysRevealTopCardChecked() const
{
@ -89,9 +88,7 @@ public:
QAction *aMoveTopCardToGraveyard = nullptr;
QAction *aMoveTopCardToExile = nullptr;
QAction *aMoveTopCardsToGraveyard = nullptr;
QAction *aMoveTopCardsToGraveyardFaceDown = nullptr;
QAction *aMoveTopCardsToExile = nullptr;
QAction *aMoveTopCardsToExileFaceDown = nullptr;
QAction *aMoveTopCardsUntil = nullptr;
QAction *aShuffleTopCards = nullptr;
@ -103,9 +100,7 @@ public:
QAction *aMoveBottomCardToGraveyard = nullptr;
QAction *aMoveBottomCardToExile = nullptr;
QAction *aMoveBottomCardsToGraveyard = nullptr;
QAction *aMoveBottomCardsToGraveyardFaceDown = nullptr;
QAction *aMoveBottomCardsToExile = nullptr;
QAction *aMoveBottomCardsToExileFaceDown = nullptr;
QAction *aShuffleBottomCards = nullptr;
int defaultNumberTopCards = 1;

View file

@ -11,8 +11,6 @@ MoveMenu::MoveMenu(Player *player) : QMenu(tr("Move to"))
aMoveToBottomLibrary = new QAction(this);
aMoveToBottomLibrary->setData(cmMoveToBottomLibrary);
aMoveToXfromTopOfLibrary = new QAction(this);
aMoveToTable = new QAction(this);
aMoveToTable->setData(cmMoveToTable);
aMoveToGraveyard = new QAction(this);
aMoveToHand = new QAction(this);
aMoveToHand->setData(cmMoveToHand);
@ -24,7 +22,6 @@ MoveMenu::MoveMenu(Player *player) : QMenu(tr("Move to"))
connect(aMoveToBottomLibrary, &QAction::triggered, player->getPlayerActions(), &PlayerActions::cardMenuAction);
connect(aMoveToXfromTopOfLibrary, &QAction::triggered, player->getPlayerActions(),
&PlayerActions::actMoveCardXCardsFromTop);
connect(aMoveToTable, &QAction::triggered, player->getPlayerActions(), &PlayerActions::cardMenuAction);
connect(aMoveToHand, &QAction::triggered, player->getPlayerActions(), &PlayerActions::cardMenuAction);
connect(aMoveToGraveyard, &QAction::triggered, player->getPlayerActions(), &PlayerActions::cardMenuAction);
connect(aMoveToExile, &QAction::triggered, player->getPlayerActions(), &PlayerActions::cardMenuAction);
@ -33,8 +30,6 @@ MoveMenu::MoveMenu(Player *player) : QMenu(tr("Move to"))
addAction(aMoveToXfromTopOfLibrary);
addAction(aMoveToBottomLibrary);
addSeparator();
addAction(aMoveToTable);
addSeparator();
addAction(aMoveToHand);
addSeparator();
addAction(aMoveToGraveyard);
@ -52,7 +47,6 @@ void MoveMenu::setShortcutsActive()
aMoveToTopLibrary->setShortcuts(shortcuts.getShortcut("Player/aMoveToTopLibrary"));
aMoveToBottomLibrary->setShortcuts(shortcuts.getShortcut("Player/aMoveToBottomLibrary"));
aMoveToTable->setShortcuts(shortcuts.getShortcut("Player/aMoveToTable"));
aMoveToHand->setShortcuts(shortcuts.getShortcut("Player/aMoveToHand"));
aMoveToGraveyard->setShortcuts(shortcuts.getShortcut("Player/aMoveToGraveyard"));
aMoveToExile->setShortcuts(shortcuts.getShortcut("Player/aMoveToExile"));
@ -63,8 +57,7 @@ void MoveMenu::retranslateUi()
aMoveToTopLibrary->setText(tr("&Top of library in random order"));
aMoveToXfromTopOfLibrary->setText(tr("X cards from the top of library..."));
aMoveToBottomLibrary->setText(tr("&Bottom of library in random order"));
aMoveToTable->setText(tr("T&able"));
aMoveToHand->setText(tr("&Hand"));
aMoveToGraveyard->setText(tr("&Graveyard"));
aMoveToExile->setText(tr("&Exile"));
}
}

View file

@ -23,7 +23,6 @@ public:
QAction *aMoveToBottomLibrary = nullptr;
QAction *aMoveToHand = nullptr;
QAction *aMoveToTable = nullptr;
QAction *aMoveToGraveyard = nullptr;
QAction *aMoveToExile = nullptr;
};

View file

@ -10,29 +10,38 @@
#include <libcockatrice/protocol/pb/command_reveal_cards.pb.h>
PlayerMenu::PlayerMenu(Player *_player) : QObject(_player), player(_player)
PlayerMenu::PlayerMenu(Player *_player) : player(_player)
{
playerMenu = new TearOffMenu();
if (player->getPlayerInfo()->getLocalOrJudge()) {
handMenu = addManagedMenu<HandMenu>(player, player->getPlayerActions(), playerMenu);
libraryMenu = addManagedMenu<LibraryMenu>(player, playerMenu);
handMenu = new HandMenu(player, player->getPlayerActions(), playerMenu);
playerMenu->addMenu(handMenu);
libraryMenu = new LibraryMenu(player, playerMenu);
playerMenu->addMenu(libraryMenu);
} else {
handMenu = nullptr;
libraryMenu = nullptr;
}
graveMenu = addManagedMenu<GraveyardMenu>(player, playerMenu);
rfgMenu = addManagedMenu<RfgMenu>(player, playerMenu);
graveMenu = new GraveyardMenu(player, playerMenu);
playerMenu->addMenu(graveMenu);
rfgMenu = new RfgMenu(player, playerMenu);
playerMenu->addMenu(rfgMenu);
if (player->getPlayerInfo()->getLocalOrJudge()) {
sideboardMenu = addManagedMenu<SideboardMenu>(player, playerMenu);
customZonesMenu = addManagedMenu<CustomZoneMenu>(player);
sideboardMenu = new SideboardMenu(player, playerMenu);
playerMenu->addMenu(sideboardMenu);
customZonesMenu = new CustomZoneMenu(player);
playerMenu->addMenu(customZonesMenu);
playerMenu->addSeparator();
countersMenu = playerMenu->addMenu(QString());
utilityMenu = createManagedComponent<UtilityMenu>(player, playerMenu);
utilityMenu = new UtilityMenu(player, playerMenu);
} else {
sideboardMenu = nullptr;
customZonesMenu = nullptr;
@ -41,7 +50,8 @@ PlayerMenu::PlayerMenu(Player *_player) : QObject(_player), player(_player)
}
if (player->getPlayerInfo()->getLocal()) {
sayMenu = addManagedMenu<SayMenu>(player);
sayMenu = new SayMenu(player);
playerMenu->addMenu(sayMenu);
} else {
sayMenu = nullptr;
}
@ -89,18 +99,40 @@ void PlayerMenu::retranslateUi()
{
playerMenu->setTitle(tr("Player \"%1\"").arg(player->getPlayerInfo()->getName()));
for (auto *component : managedComponents) {
component->retranslateUi();
if (handMenu) {
handMenu->retranslateUi();
}
if (libraryMenu) {
libraryMenu->retranslateUi();
}
graveMenu->retranslateUi();
rfgMenu->retranslateUi();
if (sideboardMenu) {
sideboardMenu->retranslateUi();
}
if (countersMenu) {
countersMenu->setTitle(tr("&Counters"));
}
if (customZonesMenu) {
customZonesMenu->retranslateUi();
}
QMapIterator<int, AbstractCounter *> counterIterator(player->getCounters());
while (counterIterator.hasNext()) {
counterIterator.next().value()->retranslateUi();
}
if (utilityMenu) {
utilityMenu->retranslateUi();
}
if (sayMenu) {
sayMenu->setTitle(tr("S&ay"));
}
}
void PlayerMenu::refreshShortcuts()
@ -121,29 +153,52 @@ void PlayerMenu::setShortcutsActive()
{
shortcutsActive = true;
for (auto *component : managedComponents) {
component->setShortcutsActive();
if (handMenu) {
handMenu->setShortcutsActive();
}
if (libraryMenu) {
libraryMenu->setShortcutsActive();
}
graveMenu->setShortcutsActive();
// No shortcuts for RfgMenu yet
if (sideboardMenu) {
sideboardMenu->setShortcutsActive();
}
// Counters implement AbstractPlayerComponent but are iterated via Player::counters
// (the authoritative source) rather than managedComponents to avoid a redundant
// list that must stay in sync with the map.
QMapIterator<int, AbstractCounter *> counterIterator(player->getCounters());
while (counterIterator.hasNext()) {
counterIterator.next().value()->setShortcutsActive();
}
if (utilityMenu) {
utilityMenu->setShortcutsActive();
}
}
void PlayerMenu::setShortcutsInactive()
{
shortcutsActive = false;
for (auto *component : managedComponents) {
component->setShortcutsInactive();
if (handMenu) {
handMenu->setShortcutsInactive();
}
if (libraryMenu) {
libraryMenu->setShortcutsInactive();
}
graveMenu->setShortcutsInactive();
// No shortcuts for RfgMenu yet
if (sideboardMenu) {
sideboardMenu->setShortcutsInactive();
}
QMapIterator<int, AbstractCounter *> counterIterator(player->getCounters());
while (counterIterator.hasNext()) {
counterIterator.next().value()->setShortcutsInactive();
}
if (utilityMenu) {
utilityMenu->setShortcutsInactive();
}
}

View file

@ -1,7 +1,7 @@
/**
* @file player_menu.h
* @ingroup GameMenusPlayers
* @brief Orchestrates lifecycle management for all player-bound UI components.
* @brief TODO: Document this.
*/
#ifndef COCKATRICE_PLAYER_MENU_H
@ -18,7 +18,6 @@
#include "sideboard_menu.h"
#include "utility_menu.h"
#include <QList>
#include <QMenu>
#include <QObject>
@ -37,8 +36,7 @@ private slots:
void refreshShortcuts();
public:
explicit PlayerMenu(Player *player);
/// Lifecycle methods: delegate to all managedComponents, plus counters separately via player->getCounters().
PlayerMenu(Player *player);
void retranslateUi();
QMenu *updateCardMenu(const CardItem *card);
@ -68,9 +66,7 @@ public:
return shortcutsActive;
}
/// Delegates to all managedComponents, plus counters separately.
void setShortcutsActive();
/// Delegates to all managedComponents, plus counters separately.
void setShortcutsInactive();
private:
@ -86,26 +82,9 @@ private:
SayMenu *sayMenu;
CustomZoneMenu *customZonesMenu;
/// Drives AbstractPlayerComponent lifecycle delegation. Counters are iterated separately via player->getCounters().
QList<AbstractPlayerComponent *> managedComponents;
bool shortcutsActive = false;
bool shortcutsActive;
/// Creates component, adds it as a submenu of playerMenu, and registers in managedComponents.
template <typename MenuT, typename... Args> MenuT *addManagedMenu(Args &&...args)
{
auto *menu = new MenuT(std::forward<Args>(args)...);
playerMenu->addMenu(menu);
managedComponents.append(menu);
return menu;
}
/// Creates component and registers in managedComponents, but does NOT add it as a submenu.
template <typename ComponentT, typename... Args> ComponentT *createManagedComponent(Args &&...args)
{
auto *component = new ComponentT(std::forward<Args>(args)...);
managedComponents.append(component);
return component;
}
void initSayMenu();
};
#endif // COCKATRICE_PLAYER_MENU_H

View file

@ -3,8 +3,6 @@
#include "../player.h"
#include "../player_actions.h"
#include <libcockatrice/utility/zone_names.h>
RfgMenu::RfgMenu(Player *_player, QWidget *parent) : TearOffMenu(parent), player(_player)
{
createMoveActions();
@ -32,13 +30,13 @@ void RfgMenu::createMoveActions()
auto rfg = player->getRfgZone();
aMoveRfgToTopLibrary = new QAction(this);
aMoveRfgToTopLibrary->setData(QList<QVariant>() << ZoneNames::DECK << 0);
aMoveRfgToTopLibrary->setData(QList<QVariant>() << "deck" << 0);
aMoveRfgToBottomLibrary = new QAction(this);
aMoveRfgToBottomLibrary->setData(QList<QVariant>() << ZoneNames::DECK << -1);
aMoveRfgToBottomLibrary->setData(QList<QVariant>() << "deck" << -1);
aMoveRfgToHand = new QAction(this);
aMoveRfgToHand->setData(QList<QVariant>() << ZoneNames::HAND << 0);
aMoveRfgToHand->setData(QList<QVariant>() << "hand" << 0);
aMoveRfgToGrave = new QAction(this);
aMoveRfgToGrave->setData(QList<QVariant>() << ZoneNames::GRAVE << 0);
aMoveRfgToGrave->setData(QList<QVariant>() << "grave" << 0);
connect(aMoveRfgToTopLibrary, &QAction::triggered, rfg, &PileZoneLogic::moveAllToZone);
connect(aMoveRfgToBottomLibrary, &QAction::triggered, rfg, &PileZoneLogic::moveAllToZone);

View file

@ -8,26 +8,19 @@
#define COCKATRICE_RFG_MENU_H
#include "../../../interface/widgets/menus/tearoff_menu.h"
#include "abstract_player_component.h"
#include <QAction>
#include <QMenu>
class Player;
class RfgMenu : public TearOffMenu, public AbstractPlayerComponent
class RfgMenu : public TearOffMenu
{
Q_OBJECT
public:
explicit RfgMenu(Player *player, QWidget *parent = nullptr);
void createMoveActions();
void createViewActions();
void retranslateUi() override;
void setShortcutsActive() override
{
}
void setShortcutsInactive() override
{
}
void retranslateUi();
QMenu *moveRfgMenu = nullptr;

View file

@ -8,31 +8,6 @@ SayMenu::SayMenu(Player *_player) : player(_player)
{
connect(&SettingsCache::instance().messages(), &MessageSettings::messageMacrosChanged, this, &SayMenu::initSayMenu);
initSayMenu();
retranslateUi();
}
void SayMenu::retranslateUi()
{
setTitle(tr("S&ay"));
}
void SayMenu::setShortcutsActive()
{
shortcutsActive = true;
const auto menuActions = actions();
for (int i = 0; i < menuActions.size() && i < 10; ++i) {
menuActions[i]->setShortcut(QKeySequence("Ctrl+" + QString::number((i + 1) % 10)));
}
}
void SayMenu::setShortcutsInactive()
{
shortcutsActive = false;
for (auto *action : actions()) {
action->setShortcut(QKeySequence());
}
}
void SayMenu::initSayMenu()
@ -44,11 +19,10 @@ void SayMenu::initSayMenu()
for (int i = 0; i < count; ++i) {
auto *newAction = new QAction(SettingsCache::instance().messages().getMessageAt(i), this);
if (i < 10) {
newAction->setShortcut(QKeySequence("Ctrl+" + QString::number((i + 1) % 10)));
}
connect(newAction, &QAction::triggered, player->getPlayerActions(), &PlayerActions::actSayMessage);
addAction(newAction);
}
if (shortcutsActive) {
setShortcutsActive();
}
}
}

View file

@ -7,27 +7,18 @@
#ifndef COCKATRICE_SAY_MENU_H
#define COCKATRICE_SAY_MENU_H
#include "abstract_player_component.h"
#include <QMenu>
class Player;
class SayMenu : public QMenu, public AbstractPlayerComponent
class SayMenu : public QMenu
{
Q_OBJECT
public:
explicit SayMenu(Player *player);
void retranslateUi() override;
void setShortcutsActive() override;
void setShortcutsInactive() override;
private slots:
void initSayMenu();
private:
Player *player;
bool shortcutsActive = false;
};
#endif // COCKATRICE_SAY_MENU_H

View file

@ -7,20 +7,18 @@
#ifndef COCKATRICE_SIDEBOARD_MENU_H
#define COCKATRICE_SIDEBOARD_MENU_H
#include "abstract_player_component.h"
#include <QMenu>
class Player;
class SideboardMenu : public QMenu, public AbstractPlayerComponent
class SideboardMenu : public QMenu
{
Q_OBJECT
public:
explicit SideboardMenu(Player *player, QMenu *playerMenu);
void retranslateUi() override;
void setShortcutsActive() override;
void setShortcutsInactive() override;
void retranslateUi();
void setShortcutsActive();
void setShortcutsInactive();
private:
Player *player;

View file

@ -7,19 +7,17 @@
#ifndef COCKATRICE_UTILITY_MENU_H
#define COCKATRICE_UTILITY_MENU_H
#include "abstract_player_component.h"
#include <QMenu>
class Player;
class UtilityMenu : public QMenu, public AbstractPlayerComponent
class UtilityMenu : public QMenu
{
Q_OBJECT
public slots:
void populatePredefinedTokensMenu();
void retranslateUi() override;
void setShortcutsActive() override;
void setShortcutsInactive() override;
void retranslateUi();
void setShortcutsActive();
void setShortcutsInactive();
public:
explicit UtilityMenu(Player *player, QMenu *playerMenu);

View file

@ -61,15 +61,15 @@ void Player::forwardActionSignalsToEventHandler()
void Player::initializeZones()
{
addZone(new PileZoneLogic(this, ZoneNames::DECK, false, true, false, this));
addZone(new PileZoneLogic(this, ZoneNames::GRAVE, false, false, true, this));
addZone(new PileZoneLogic(this, ZoneNames::EXILE, false, false, true, this));
addZone(new PileZoneLogic(this, ZoneNames::SIDEBOARD, false, false, false, this));
addZone(new TableZoneLogic(this, ZoneNames::TABLE, true, false, true, this));
addZone(new StackZoneLogic(this, ZoneNames::STACK, true, false, true, this));
addZone(new PileZoneLogic(this, "deck", false, true, false, this));
addZone(new PileZoneLogic(this, "grave", false, false, true, this));
addZone(new PileZoneLogic(this, "rfg", false, false, true, this));
addZone(new PileZoneLogic(this, "sb", false, false, false, this));
addZone(new TableZoneLogic(this, "table", true, false, true, this));
addZone(new StackZoneLogic(this, "stack", true, false, true, this));
bool visibleHand = playerInfo->getLocalOrJudge() ||
(game->getPlayerManager()->isSpectator() && game->getGameMetaInfo()->spectatorsOmniscient());
addZone(new HandZoneLogic(this, ZoneNames::HAND, false, false, visibleHand, this));
addZone(new HandZoneLogic(this, "hand", false, false, visibleHand, this));
}
Player::~Player()
@ -119,13 +119,13 @@ void Player::setZoneId(int _zoneId)
void Player::processPlayerInfo(const ServerInfo_Player &info)
{
static QSet<QString> builtinZones{/* PileZones */
ZoneNames::DECK, ZoneNames::GRAVE, ZoneNames::EXILE, ZoneNames::SIDEBOARD,
"deck", "grave", "rfg", "sb",
/* TableZone */
ZoneNames::TABLE,
"table",
/* StackZone */
ZoneNames::STACK,
"stack",
/* HandZone */
ZoneNames::HAND};
"hand"};
clearCounters();
clearArrows();

View file

@ -27,7 +27,6 @@
#include <libcockatrice/filters/filter_string.h>
#include <libcockatrice/protocol/pb/card_attributes.pb.h>
#include <libcockatrice/protocol/pb/game_event.pb.h>
#include <libcockatrice/utility/zone_names.h>
inline Q_LOGGING_CATEGORY(PlayerLog, "player");
@ -156,37 +155,37 @@ public:
PileZoneLogic *getDeckZone()
{
return qobject_cast<PileZoneLogic *>(zones.value(ZoneNames::DECK));
return qobject_cast<PileZoneLogic *>(zones.value("deck"));
}
PileZoneLogic *getGraveZone()
{
return qobject_cast<PileZoneLogic *>(zones.value(ZoneNames::GRAVE));
return qobject_cast<PileZoneLogic *>(zones.value("grave"));
}
PileZoneLogic *getRfgZone()
{
return qobject_cast<PileZoneLogic *>(zones.value(ZoneNames::EXILE));
return qobject_cast<PileZoneLogic *>(zones.value("rfg"));
}
PileZoneLogic *getSideboardZone()
{
return qobject_cast<PileZoneLogic *>(zones.value(ZoneNames::SIDEBOARD));
return qobject_cast<PileZoneLogic *>(zones.value("sb"));
}
TableZoneLogic *getTableZone()
{
return qobject_cast<TableZoneLogic *>(zones.value(ZoneNames::TABLE));
return qobject_cast<TableZoneLogic *>(zones.value("table"));
}
StackZoneLogic *getStackZone()
{
return qobject_cast<StackZoneLogic *>(zones.value(ZoneNames::STACK));
return qobject_cast<StackZoneLogic *>(zones.value("stack"));
}
HandZoneLogic *getHandZone()
{
return qobject_cast<HandZoneLogic *>(zones.value(ZoneNames::HAND));
return qobject_cast<HandZoneLogic *>(zones.value("hand"));
}
AbstractCounter *addCounter(const ServerInfo_Counter &counter);

View file

@ -3,7 +3,6 @@
#include "../../interface/widgets/tabs/tab_game.h"
#include "../../interface/widgets/utility/get_text_with_max.h"
#include "../board/card_item.h"
#include "../client/settings/card_counter_settings.h"
#include "../dialogs/dlg_move_top_cards_until.h"
#include "../dialogs/dlg_roll_dice.h"
#include "../zones/hand_zone.h"
@ -28,15 +27,12 @@
#include <libcockatrice/protocol/pb/command_shuffle.pb.h>
#include <libcockatrice/protocol/pb/command_undo_draw.pb.h>
#include <libcockatrice/protocol/pb/context_move_card.pb.h>
#include <libcockatrice/utility/expression.h>
#include <libcockatrice/utility/trice_limits.h>
#include <libcockatrice/utility/zone_names.h>
// milliseconds in between triggers of the move top cards until action
static constexpr int MOVE_TOP_CARD_UNTIL_INTERVAL = 100;
PlayerActions::PlayerActions(Player *_player)
: QObject(_player), player(_player), lastTokenTableRow(0), movingCardsUntil(false)
PlayerActions::PlayerActions(Player *_player) : player(_player), lastTokenTableRow(0), movingCardsUntil(false)
{
moveTopCardTimer = new QTimer(this);
moveTopCardTimer->setInterval(MOVE_TOP_CARD_UNTIL_INTERVAL);
@ -67,25 +63,25 @@ void PlayerActions::playCard(CardItem *card, bool faceDown)
int tableRow = info.getUiAttributes().tableRow;
bool playToStack = SettingsCache::instance().getPlayToStack();
QString currentZone = card->getZone()->getName();
if (!faceDown && currentZone == ZoneNames::STACK && tableRow == 3) {
cmd.set_target_zone(ZoneNames::GRAVE);
if (currentZone == "stack" && tableRow == 3) {
cmd.set_target_zone("grave");
cmd.set_x(0);
cmd.set_y(0);
} else if (!faceDown && ((!playToStack && tableRow == 3) ||
((playToStack && tableRow != 0) && currentZone != ZoneNames::STACK))) {
cmd.set_target_zone(ZoneNames::STACK);
} else if (!faceDown &&
((!playToStack && tableRow == 3) || ((playToStack && tableRow != 0) && currentZone != "stack"))) {
cmd.set_target_zone("stack");
cmd.set_x(-1);
cmd.set_y(0);
} else {
tableRow = faceDown ? 2 : info.getUiAttributes().tableRow;
QPoint gridPoint = QPoint(-1, TableZone::tableRowToGridY(tableRow));
QPoint gridPoint = QPoint(-1, TableZone::clampValidTableRow(2 - tableRow));
cardToMove->set_face_down(faceDown);
if (!faceDown) {
cardToMove->set_pt(info.getPowTough().toStdString());
}
cardToMove->set_tapped(!faceDown && info.getUiAttributes().cipt);
if (tableRow != 3)
cmd.set_target_zone(ZoneNames::TABLE);
cmd.set_target_zone("table");
cmd.set_x(gridPoint.x());
cmd.set_y(gridPoint.y());
}
@ -117,13 +113,18 @@ void PlayerActions::playCardToTable(const CardItem *card, bool faceDown)
const CardInfo &info = exactCard.getInfo();
int tableRow = faceDown ? 2 : info.getUiAttributes().tableRow;
QPoint gridPoint = QPoint(-1, TableZone::tableRowToGridY(tableRow));
// default instant/sorcery cards to the noncreatures row
if (tableRow > 2) {
tableRow = 1;
}
QPoint gridPoint = QPoint(-1, TableZone::clampValidTableRow(2 - tableRow));
cardToMove->set_face_down(faceDown);
if (!faceDown) {
cardToMove->set_pt(info.getPowTough().toStdString());
}
cardToMove->set_tapped(!faceDown && info.getUiAttributes().cipt);
cmd.set_target_zone(ZoneNames::TABLE);
cmd.set_target_zone("table");
cmd.set_x(gridPoint.x());
cmd.set_y(gridPoint.y());
sendGameCommand(cmd);
@ -131,12 +132,12 @@ void PlayerActions::playCardToTable(const CardItem *card, bool faceDown)
void PlayerActions::actViewLibrary()
{
player->getGameScene()->toggleZoneView(player, ZoneNames::DECK, -1);
player->getGameScene()->toggleZoneView(player, "deck", -1);
}
void PlayerActions::actViewHand()
{
player->getGameScene()->toggleZoneView(player, ZoneNames::HAND, -1);
player->getGameScene()->toggleZoneView(player, "hand", -1);
}
/**
@ -180,7 +181,7 @@ void PlayerActions::actViewTopCards()
deckSize, 1, &ok);
if (ok) {
defaultNumberTopCards = number;
player->getGameScene()->toggleZoneView(player, ZoneNames::DECK, number);
player->getGameScene()->toggleZoneView(player, "deck", number);
}
}
@ -193,14 +194,14 @@ void PlayerActions::actViewBottomCards()
deckSize, 1, &ok);
if (ok) {
defaultNumberBottomCards = number;
player->getGameScene()->toggleZoneView(player, ZoneNames::DECK, number, true);
player->getGameScene()->toggleZoneView(player, "deck", number, true);
}
}
void PlayerActions::actAlwaysRevealTopCard()
{
Command_ChangeZoneProperties cmd;
cmd.set_zone_name(ZoneNames::DECK);
cmd.set_zone_name("deck");
cmd.set_always_reveal_top_card(player->getPlayerMenu()->getLibraryMenu()->isAlwaysRevealTopCardChecked());
sendGameCommand(cmd);
@ -209,7 +210,7 @@ void PlayerActions::actAlwaysRevealTopCard()
void PlayerActions::actAlwaysLookAtTopCard()
{
Command_ChangeZoneProperties cmd;
cmd.set_zone_name(ZoneNames::DECK);
cmd.set_zone_name("deck");
cmd.set_always_look_at_top_card(player->getPlayerMenu()->getLibraryMenu()->isAlwaysLookAtTopCardChecked());
sendGameCommand(cmd);
@ -222,17 +223,17 @@ void PlayerActions::actOpenDeckInDeckEditor()
void PlayerActions::actViewGraveyard()
{
player->getGameScene()->toggleZoneView(player, ZoneNames::GRAVE, -1);
player->getGameScene()->toggleZoneView(player, "grave", -1);
}
void PlayerActions::actViewRfg()
{
player->getGameScene()->toggleZoneView(player, ZoneNames::EXILE, -1);
player->getGameScene()->toggleZoneView(player, "rfg", -1);
}
void PlayerActions::actViewSideboard()
{
player->getGameScene()->toggleZoneView(player, ZoneNames::SIDEBOARD, -1);
player->getGameScene()->toggleZoneView(player, "sb", -1);
}
void PlayerActions::actShuffle()
@ -262,7 +263,7 @@ void PlayerActions::actShuffleTop()
defaultNumberTopCards = number;
Command_Shuffle cmd;
cmd.set_zone_name(ZoneNames::DECK);
cmd.set_zone_name("deck");
cmd.set_start(0);
cmd.set_end(number - 1); // inclusive, the indexed card at end will be shuffled
@ -291,7 +292,7 @@ void PlayerActions::actShuffleBottom()
defaultNumberBottomCards = number;
Command_Shuffle cmd;
cmd.set_zone_name(ZoneNames::DECK);
cmd.set_zone_name("deck");
cmd.set_start(-number);
cmd.set_end(-1);
@ -375,7 +376,7 @@ void PlayerActions::actUndoDraw()
void PlayerActions::cmdSetTopCard(Command_MoveCard &cmd)
{
cmd.set_start_zone(ZoneNames::DECK);
cmd.set_start_zone("deck");
auto *cardToMove = cmd.mutable_cards_to_move()->add_card();
cardToMove->set_card_id(0);
cmd.set_target_player_id(player->getPlayerInfo()->getId());
@ -385,7 +386,7 @@ void PlayerActions::cmdSetBottomCard(Command_MoveCard &cmd)
{
CardZoneLogic *zone = player->getDeckZone();
int lastCard = zone->getCards().size() - 1;
cmd.set_start_zone(ZoneNames::DECK);
cmd.set_start_zone("deck");
auto *cardToMove = cmd.mutable_cards_to_move()->add_card();
cardToMove->set_card_id(lastCard);
cmd.set_target_player_id(player->getPlayerInfo()->getId());
@ -399,7 +400,7 @@ void PlayerActions::actMoveTopCardToGrave()
Command_MoveCard cmd;
cmdSetTopCard(cmd);
cmd.set_target_zone(ZoneNames::GRAVE);
cmd.set_target_zone("grave");
cmd.set_x(0);
cmd.set_y(0);
@ -414,7 +415,7 @@ void PlayerActions::actMoveTopCardToExile()
Command_MoveCard cmd;
cmdSetTopCard(cmd);
cmd.set_target_zone(ZoneNames::EXILE);
cmd.set_target_zone("rfg");
cmd.set_x(0);
cmd.set_y(0);
@ -423,25 +424,37 @@ void PlayerActions::actMoveTopCardToExile()
void PlayerActions::actMoveTopCardsToGrave()
{
moveTopCardsTo(ZoneNames::GRAVE, tr("grave"), false);
}
const int maxCards = player->getDeckZone()->getCards().size();
if (maxCards == 0) {
return;
}
void PlayerActions::actMoveTopCardsToGraveFaceDown()
{
moveTopCardsTo(ZoneNames::GRAVE, tr("grave"), true);
bool ok;
int number = QInputDialog::getInt(player->getGame()->getTab(), tr("Move top cards to grave"),
tr("Number of cards: (max. %1)").arg(maxCards), defaultNumberTopCards, 1,
maxCards, 1, &ok);
if (!ok) {
return;
} else if (number > maxCards) {
number = maxCards;
}
defaultNumberTopCards = number;
Command_MoveCard cmd;
cmd.set_start_zone("deck");
cmd.set_target_player_id(player->getPlayerInfo()->getId());
cmd.set_target_zone("grave");
cmd.set_x(0);
cmd.set_y(0);
for (int i = number - 1; i >= 0; --i) {
cmd.mutable_cards_to_move()->add_card()->set_card_id(i);
}
sendGameCommand(cmd);
}
void PlayerActions::actMoveTopCardsToExile()
{
moveTopCardsTo(ZoneNames::EXILE, tr("exile"), false);
}
void PlayerActions::actMoveTopCardsToExileFaceDown()
{
moveTopCardsTo(ZoneNames::EXILE, tr("exile"), true);
}
void PlayerActions::moveTopCardsTo(const QString &targetZone, const QString &zoneDisplayName, bool faceDown)
{
const int maxCards = player->getDeckZone()->getCards().size();
if (maxCards == 0) {
@ -449,31 +462,25 @@ void PlayerActions::moveTopCardsTo(const QString &targetZone, const QString &zon
}
bool ok;
int number = QInputDialog::getInt(player->getGame()->getTab(), tr("Move top cards to %1").arg(zoneDisplayName),
int number = QInputDialog::getInt(player->getGame()->getTab(), tr("Move top cards to exile"),
tr("Number of cards: (max. %1)").arg(maxCards), defaultNumberTopCards, 1,
maxCards, 1, &ok);
if (!ok) {
return;
}
if (number > maxCards) {
} else if (number > maxCards) {
number = maxCards;
}
defaultNumberTopCards = number;
Command_MoveCard cmd;
cmd.set_start_zone(ZoneNames::DECK);
cmd.set_start_zone("deck");
cmd.set_target_player_id(player->getPlayerInfo()->getId());
cmd.set_target_zone(targetZone.toStdString());
cmd.set_target_zone("rfg");
cmd.set_x(0);
cmd.set_y(0);
for (int i = number - 1; i >= 0; --i) {
auto card = cmd.mutable_cards_to_move()->add_card();
card->set_card_id(i);
if (faceDown) {
card->set_face_down(true);
}
cmd.mutable_cards_to_move()->add_card()->set_card_id(i);
}
sendGameCommand(cmd);
@ -483,19 +490,22 @@ void PlayerActions::actMoveTopCardsUntil()
{
stopMoveTopCardsUntil();
DlgMoveTopCardsUntil dlg(player->getGame()->getTab(), movingCardsUntilOptions);
DlgMoveTopCardsUntil dlg(player->getGame()->getTab(), movingCardsUntilExprs, movingCardsUntilNumberOfHits,
movingCardsUntilAutoPlay);
if (!dlg.exec()) {
return;
}
auto expr = dlg.getExpr();
movingCardsUntilOptions = dlg.getOptions();
movingCardsUntilExprs = dlg.getExprs();
movingCardsUntilNumberOfHits = dlg.getNumberOfHits();
movingCardsUntilAutoPlay = dlg.isAutoPlay();
if (player->getDeckZone()->getCards().empty()) {
stopMoveTopCardsUntil();
} else {
movingCardsUntilFilter = FilterString(expr);
movingCardsUntilCounter = movingCardsUntilOptions.numberOfHits;
movingCardsUntilCounter = movingCardsUntilNumberOfHits;
movingCardsUntil = true;
actMoveTopCardToPlay();
}
@ -507,7 +517,7 @@ void PlayerActions::moveOneCardUntil(CardItem *card)
const bool isMatch = card && movingCardsUntilFilter.check(card->getCard().getCardPtr());
if (isMatch && movingCardsUntilOptions.autoPlay) {
if (isMatch && movingCardsUntilAutoPlay) {
// Directly calling playCard will deadlock, since we are already in the middle of processing an event.
// Use QTimer::singleShot to queue up the playCard on the event loop.
QTimer::singleShot(0, this, [card, this] { playCard(card, false); });
@ -545,7 +555,7 @@ void PlayerActions::actMoveTopCardToBottom()
Command_MoveCard cmd;
cmdSetTopCard(cmd);
cmd.set_target_zone(ZoneNames::DECK);
cmd.set_target_zone("deck");
cmd.set_x(-1); // bottom of deck
cmd.set_y(0);
@ -560,7 +570,7 @@ void PlayerActions::actMoveTopCardToPlay()
Command_MoveCard cmd;
cmdSetTopCard(cmd);
cmd.set_target_zone(ZoneNames::STACK);
cmd.set_target_zone("stack");
cmd.set_x(-1);
cmd.set_y(0);
@ -574,12 +584,12 @@ void PlayerActions::actMoveTopCardToPlayFaceDown()
}
Command_MoveCard cmd;
cmd.set_start_zone(ZoneNames::DECK);
cmd.set_start_zone("deck");
CardToMove *cardToMove = cmd.mutable_cards_to_move()->add_card();
cardToMove->set_card_id(0);
cardToMove->set_face_down(true);
cmd.set_target_player_id(player->getPlayerInfo()->getId());
cmd.set_target_zone(ZoneNames::TABLE);
cmd.set_target_zone("table");
cmd.set_x(-1);
cmd.set_y(0);
@ -594,7 +604,7 @@ void PlayerActions::actMoveBottomCardToGrave()
Command_MoveCard cmd;
cmdSetBottomCard(cmd);
cmd.set_target_zone(ZoneNames::GRAVE);
cmd.set_target_zone("grave");
cmd.set_x(0);
cmd.set_y(0);
@ -609,7 +619,7 @@ void PlayerActions::actMoveBottomCardToExile()
Command_MoveCard cmd;
cmdSetBottomCard(cmd);
cmd.set_target_zone(ZoneNames::EXILE);
cmd.set_target_zone("rfg");
cmd.set_x(0);
cmd.set_y(0);
@ -618,25 +628,37 @@ void PlayerActions::actMoveBottomCardToExile()
void PlayerActions::actMoveBottomCardsToGrave()
{
moveBottomCardsTo(ZoneNames::GRAVE, tr("grave"), false);
}
const int maxCards = player->getDeckZone()->getCards().size();
if (maxCards == 0) {
return;
}
void PlayerActions::actMoveBottomCardsToGraveFaceDown()
{
moveBottomCardsTo(ZoneNames::GRAVE, tr("grave"), true);
bool ok;
int number = QInputDialog::getInt(player->getGame()->getTab(), tr("Move bottom cards to grave"),
tr("Number of cards: (max. %1)").arg(maxCards), defaultNumberBottomCards, 1,
maxCards, 1, &ok);
if (!ok) {
return;
} else if (number > maxCards) {
number = maxCards;
}
defaultNumberBottomCards = number;
Command_MoveCard cmd;
cmd.set_start_zone("deck");
cmd.set_target_player_id(player->getPlayerInfo()->getId());
cmd.set_target_zone("grave");
cmd.set_x(0);
cmd.set_y(0);
for (int i = maxCards - number; i < maxCards; ++i) {
cmd.mutable_cards_to_move()->add_card()->set_card_id(i);
}
sendGameCommand(cmd);
}
void PlayerActions::actMoveBottomCardsToExile()
{
moveBottomCardsTo(ZoneNames::EXILE, tr("exile"), false);
}
void PlayerActions::actMoveBottomCardsToExileFaceDown()
{
moveBottomCardsTo(ZoneNames::EXILE, tr("exile"), true);
}
void PlayerActions::moveBottomCardsTo(const QString &targetZone, const QString &zoneDisplayName, bool faceDown)
{
const int maxCards = player->getDeckZone()->getCards().size();
if (maxCards == 0) {
@ -644,31 +666,25 @@ void PlayerActions::moveBottomCardsTo(const QString &targetZone, const QString &
}
bool ok;
int number = QInputDialog::getInt(player->getGame()->getTab(), tr("Move bottom cards to %1").arg(zoneDisplayName),
int number = QInputDialog::getInt(player->getGame()->getTab(), tr("Move bottom cards to exile"),
tr("Number of cards: (max. %1)").arg(maxCards), defaultNumberBottomCards, 1,
maxCards, 1, &ok);
if (!ok) {
return;
}
if (number > maxCards) {
} else if (number > maxCards) {
number = maxCards;
}
defaultNumberBottomCards = number;
Command_MoveCard cmd;
cmd.set_start_zone(ZoneNames::DECK);
cmd.set_start_zone("deck");
cmd.set_target_player_id(player->getPlayerInfo()->getId());
cmd.set_target_zone(targetZone.toStdString());
cmd.set_target_zone("rfg");
cmd.set_x(0);
cmd.set_y(0);
for (int i = maxCards - number; i < maxCards; ++i) {
auto card = cmd.mutable_cards_to_move()->add_card();
card->set_card_id(i);
if (faceDown) {
card->set_face_down(true);
}
cmd.mutable_cards_to_move()->add_card()->set_card_id(i);
}
sendGameCommand(cmd);
@ -682,7 +698,7 @@ void PlayerActions::actMoveBottomCardToTop()
Command_MoveCard cmd;
cmdSetBottomCard(cmd);
cmd.set_target_zone(ZoneNames::DECK);
cmd.set_target_zone("deck");
cmd.set_x(0); // top of deck
cmd.set_y(0);
@ -752,7 +768,7 @@ void PlayerActions::actDrawBottomCard()
Command_MoveCard cmd;
cmdSetBottomCard(cmd);
cmd.set_target_zone(ZoneNames::HAND);
cmd.set_target_zone("hand");
cmd.set_x(0);
cmd.set_y(0);
@ -778,9 +794,9 @@ void PlayerActions::actDrawBottomCards()
defaultNumberBottomCards = number;
Command_MoveCard cmd;
cmd.set_start_zone(ZoneNames::DECK);
cmd.set_start_zone("deck");
cmd.set_target_player_id(player->getPlayerInfo()->getId());
cmd.set_target_zone(ZoneNames::HAND);
cmd.set_target_zone("hand");
cmd.set_x(0);
cmd.set_y(0);
@ -799,7 +815,7 @@ void PlayerActions::actMoveBottomCardToPlay()
Command_MoveCard cmd;
cmdSetBottomCard(cmd);
cmd.set_target_zone(ZoneNames::STACK);
cmd.set_target_zone("stack");
cmd.set_x(-1);
cmd.set_y(0);
@ -816,13 +832,13 @@ void PlayerActions::actMoveBottomCardToPlayFaceDown()
int lastCard = zone->getCards().size() - 1;
Command_MoveCard cmd;
cmd.set_start_zone(ZoneNames::DECK);
cmd.set_start_zone("deck");
auto *cardToMove = cmd.mutable_cards_to_move()->add_card();
cardToMove->set_card_id(lastCard);
cardToMove->set_face_down(true);
cmd.set_target_player_id(player->getPlayerInfo()->getId());
cmd.set_target_zone(ZoneNames::TABLE);
cmd.set_target_zone("table");
cmd.set_x(-1);
cmd.set_y(0);
@ -832,7 +848,7 @@ void PlayerActions::actMoveBottomCardToPlayFaceDown()
void PlayerActions::actUntapAll()
{
Command_SetCardAttr cmd;
cmd.set_zone(ZoneNames::TABLE);
cmd.set_zone("table");
cmd.set_attribute(AttrTapped);
cmd.set_attr_value("0");
@ -864,7 +880,7 @@ void PlayerActions::actCreateToken()
ExactCard correctedCard = CardDatabaseManager::query()->guessCard({lastTokenInfo.name, lastTokenInfo.providerId});
if (correctedCard) {
lastTokenInfo.name = correctedCard.getName();
lastTokenTableRow = TableZone::tableRowToGridY(correctedCard.getInfo().getUiAttributes().tableRow);
lastTokenTableRow = TableZone::clampValidTableRow(2 - correctedCard.getInfo().getUiAttributes().tableRow);
if (lastTokenInfo.pt.isEmpty()) {
lastTokenInfo.pt = correctedCard.getInfo().getPowTough();
}
@ -882,7 +898,7 @@ void PlayerActions::actCreateAnotherToken()
}
Command_CreateToken cmd;
cmd.set_zone(ZoneNames::TABLE);
cmd.set_zone("table");
cmd.set_card_name(lastTokenInfo.name.toStdString());
cmd.set_card_provider_id(lastTokenInfo.providerId.toStdString());
cmd.set_color(lastTokenInfo.color.toStdString());
@ -915,7 +931,7 @@ void PlayerActions::setLastToken(CardInfoPtr cardInfo)
.providerId =
SettingsCache::instance().cardOverrides().getCardPreferenceOverride(cardInfo->getName())};
lastTokenTableRow = TableZone::tableRowToGridY(cardInfo->getUiAttributes().tableRow);
lastTokenTableRow = TableZone::clampValidTableRow(2 - cardInfo->getUiAttributes().tableRow);
utilityMenu->setAndEnableCreateAnotherTokenAction(tr("C&reate another %1 token").arg(lastTokenInfo.name));
}
@ -1063,7 +1079,7 @@ bool PlayerActions::createRelatedFromRelation(const CardItem *sourceCard, const
// move card onto table first if attaching from some other zone
// we only do this for AttachTo because cross-zone TransformInto is already handled server-side
if (attachType == CardRelationType::AttachTo && sourceCard->getZone()->getName() != ZoneNames::TABLE) {
if (attachType == CardRelationType::AttachTo && sourceCard->getZone()->getName() != "table") {
playCardToTable(sourceCard, false);
}
@ -1083,11 +1099,13 @@ void PlayerActions::createCard(const CardItem *sourceCard,
return;
}
QPoint gridPoint = QPoint(-1, TableZone::tableRowToGridY(cardInfo->getUiAttributes().tableRow));
// get the target token's location
// TODO: Define this QPoint into its own function along with the one below
QPoint gridPoint = QPoint(-1, TableZone::clampValidTableRow(2 - cardInfo->getUiAttributes().tableRow));
// create the token for the related card
Command_CreateToken cmd;
cmd.set_zone(ZoneNames::TABLE);
cmd.set_zone("table");
cmd.set_card_name(cardInfo->getName().toStdString());
switch (cardInfo->getColors().size()) {
case 0:
@ -1116,12 +1134,12 @@ void PlayerActions::createCard(const CardItem *sourceCard,
switch (attachType) {
case CardRelationType::DoesNotAttach:
cmd.set_target_zone(ZoneNames::TABLE);
cmd.set_target_zone("table");
cmd.set_card_provider_id(relatedCard.getPrinting().getUuid().toStdString());
break;
case CardRelationType::AttachTo:
cmd.set_target_zone(ZoneNames::TABLE); // We currently only support creating tokens on the table
cmd.set_target_zone("table"); // We currently only support creating tokens on the table
cmd.set_card_provider_id(relatedCard.getPrinting().getUuid().toStdString());
cmd.set_target_card_id(sourceCard->getId());
cmd.set_target_mode(Command_CreateToken::ATTACH_TO);
@ -1129,7 +1147,7 @@ void PlayerActions::createCard(const CardItem *sourceCard,
case CardRelationType::TransformInto:
// allow cards to directly transform on stack
cmd.set_zone(sourceCard->getZone()->getName() == ZoneNames::STACK ? ZoneNames::STACK : ZoneNames::TABLE);
cmd.set_zone(sourceCard->getZone()->getName() == "stack" ? "stack" : "table");
// Transform card zone changes are handled server-side
cmd.set_target_zone(sourceCard->getZone()->getName().toStdString());
cmd.set_target_card_id(sourceCard->getId());
@ -1244,7 +1262,7 @@ void PlayerActions::actMoveCardXCardsFromTop()
cmd->set_start_zone(startZone.toStdString());
cmd->mutable_cards_to_move()->CopyFrom(idList);
cmd->set_target_player_id(player->getPlayerInfo()->getId());
cmd->set_target_zone(ZoneNames::DECK);
cmd->set_target_zone("deck");
cmd->set_x(number);
cmd->set_y(0);
commandList.append(cmd);
@ -1575,34 +1593,23 @@ void PlayerActions::actCardCounterTrigger()
break;
}
case 11: { // set counter with dialog
bool ok;
player->setDialogSemaphore(true);
// If a single card is selected, we show the old value in the dialog. Otherwise, we show "x"
QList<QGraphicsItem *> sel = player->getGameScene()->selectedItems();
QString oldValueForDlg = "x";
if (sel.size() == 1) {
auto *card = dynamic_cast<CardItem *>(sel.first());
oldValueForDlg = QString::number(card->getCounters().value(counterId, 0));
int oldValue = 0;
if (player->getGameScene()->selectedItems().size() == 1) {
auto *card = static_cast<CardItem *>(player->getGameScene()->selectedItems().first());
oldValue = card->getCounters().value(counterId, 0);
}
auto &cardCounterSettings = SettingsCache::instance().cardCounters();
QString counterName = cardCounterSettings.displayName(counterId);
AbstractCounterDialog dialog(counterName, oldValueForDlg, player->getGame()->getTab());
int ok = dialog.exec();
int number = QInputDialog::getInt(player->getGame()->getTab(), tr("Set counters"), tr("Number:"), oldValue,
0, MAX_COUNTERS_ON_CARD, 1, &ok);
player->setDialogSemaphore(false);
if (player->clearCardsToDelete() || !ok) {
return;
}
for (const auto &item : sel) {
auto *card = dynamic_cast<CardItem *>(item);
int oldValue = card->getCounters().value(counterId, 0);
Expression exp(oldValue);
int number = static_cast<int>(exp.parse(dialog.textValue()));
for (const auto &item : player->getGameScene()->selectedItems()) {
auto *card = static_cast<CardItem *>(item);
auto *cmd = new Command_SetCardCounter;
cmd->set_zone(card->getZone()->getName().toStdString());
cmd->set_card_id(card->getId());
@ -1644,7 +1651,7 @@ void PlayerActions::playSelectedCards(const bool faceDown)
[](const auto &card1, const auto &card2) { return card1->getId() > card2->getId(); });
for (auto &card : selectedCards) {
if (card && !isUnwritableRevealZone(card->getZone()) && card->getZone()->getName() != ZoneNames::TABLE) {
if (card && !isUnwritableRevealZone(card->getZone()) && card->getZone()->getName() != "table") {
playCard(card, faceDown);
}
}
@ -1697,7 +1704,7 @@ void PlayerActions::actRevealHand(int revealToPlayerId)
if (revealToPlayerId != -1) {
cmd.set_player_id(revealToPlayerId);
}
cmd.set_zone_name(ZoneNames::HAND);
cmd.set_zone_name("hand");
sendGameCommand(cmd);
}
@ -1708,7 +1715,7 @@ void PlayerActions::actRevealRandomHandCard(int revealToPlayerId)
if (revealToPlayerId != -1) {
cmd.set_player_id(revealToPlayerId);
}
cmd.set_zone_name(ZoneNames::HAND);
cmd.set_zone_name("hand");
cmd.add_card_id(RANDOM_CARD_FROM_ZONE);
sendGameCommand(cmd);
@ -1720,7 +1727,7 @@ void PlayerActions::actRevealLibrary(int revealToPlayerId)
if (revealToPlayerId != -1) {
cmd.set_player_id(revealToPlayerId);
}
cmd.set_zone_name(ZoneNames::DECK);
cmd.set_zone_name("deck");
sendGameCommand(cmd);
}
@ -1731,7 +1738,7 @@ void PlayerActions::actLendLibrary(int lendToPlayerId)
if (lendToPlayerId != -1) {
cmd.set_player_id(lendToPlayerId);
}
cmd.set_zone_name(ZoneNames::DECK);
cmd.set_zone_name("deck");
cmd.set_grant_write_access(true);
sendGameCommand(cmd);
@ -1744,7 +1751,7 @@ void PlayerActions::actRevealTopCards(int revealToPlayerId, int amount)
cmd.set_player_id(revealToPlayerId);
}
cmd.set_zone_name(ZoneNames::DECK);
cmd.set_zone_name("deck");
cmd.set_top_cards(amount);
// backward compatibility: servers before #1051 only permits to reveal the first card
cmd.add_card_id(0);
@ -1758,7 +1765,7 @@ void PlayerActions::actRevealRandomGraveyardCard(int revealToPlayerId)
if (revealToPlayerId != -1) {
cmd.set_player_id(revealToPlayerId);
}
cmd.set_zone_name(ZoneNames::GRAVE);
cmd.set_zone_name("grave");
cmd.add_card_id(RANDOM_CARD_FROM_ZONE);
sendGameCommand(cmd);
}
@ -1821,7 +1828,7 @@ void PlayerActions::cardMenuAction()
}
case cmClone: {
auto *cmd = new Command_CreateToken;
cmd->set_zone(ZoneNames::TABLE);
cmd->set_zone("table");
cmd->set_card_name(card->getName().toStdString());
cmd->set_card_provider_id(card->getProviderId().toStdString());
cmd->set_color(card->getColor().toStdString());
@ -1863,13 +1870,13 @@ void PlayerActions::cardMenuAction()
cmd->set_start_zone(startZone.toStdString());
cmd->mutable_cards_to_move()->CopyFrom(idList);
cmd->set_target_player_id(player->getPlayerInfo()->getId());
cmd->set_target_zone(ZoneNames::DECK);
cmd->set_target_zone("deck");
cmd->set_x(0);
cmd->set_y(0);
if (idList.card_size() > 1) {
auto *scmd = new Command_Shuffle;
scmd->set_zone_name(ZoneNames::DECK);
scmd->set_zone_name("deck");
scmd->set_start(0);
scmd->set_end(idList.card_size() - 1); // inclusive, the indexed card at end will be shuffled
// Server process events backwards, so...
@ -1885,13 +1892,13 @@ void PlayerActions::cardMenuAction()
cmd->set_start_zone(startZone.toStdString());
cmd->mutable_cards_to_move()->CopyFrom(idList);
cmd->set_target_player_id(player->getPlayerInfo()->getId());
cmd->set_target_zone(ZoneNames::DECK);
cmd->set_target_zone("deck");
cmd->set_x(-1);
cmd->set_y(0);
if (idList.card_size() > 1) {
auto *scmd = new Command_Shuffle;
scmd->set_zone_name(ZoneNames::DECK);
scmd->set_zone_name("deck");
scmd->set_start(-idList.card_size());
scmd->set_end(-1);
// Server process events backwards, so...
@ -1907,7 +1914,7 @@ void PlayerActions::cardMenuAction()
cmd->set_start_zone(startZone.toStdString());
cmd->mutable_cards_to_move()->CopyFrom(idList);
cmd->set_target_player_id(player->getPlayerInfo()->getId());
cmd->set_target_zone(ZoneNames::HAND);
cmd->set_target_zone("hand");
cmd->set_x(0);
cmd->set_y(0);
commandList.append(cmd);
@ -1919,7 +1926,7 @@ void PlayerActions::cardMenuAction()
cmd->set_start_zone(startZone.toStdString());
cmd->mutable_cards_to_move()->CopyFrom(idList);
cmd->set_target_player_id(player->getPlayerInfo()->getId());
cmd->set_target_zone(ZoneNames::GRAVE);
cmd->set_target_zone("grave");
cmd->set_x(0);
cmd->set_y(0);
commandList.append(cmd);
@ -1931,40 +1938,12 @@ void PlayerActions::cardMenuAction()
cmd->set_start_zone(startZone.toStdString());
cmd->mutable_cards_to_move()->CopyFrom(idList);
cmd->set_target_player_id(player->getPlayerInfo()->getId());
cmd->set_target_zone(ZoneNames::EXILE);
cmd->set_target_zone("rfg");
cmd->set_x(0);
cmd->set_y(0);
commandList.append(cmd);
break;
}
case cmMoveToTable: {
// Each card needs its own command because table row, pt, and cipt vary per card
for (const auto &card : cardList) {
auto *cmd = new Command_MoveCard;
cmd->set_start_player_id(startPlayerId);
cmd->set_start_zone(startZone.toStdString());
cmd->set_target_player_id(player->getPlayerInfo()->getId());
cmd->set_target_zone(ZoneNames::TABLE);
cmd->set_x(-1);
CardToMove *ctm = cmd->mutable_cards_to_move()->add_card();
ctm->set_card_id(card->getId());
ctm->set_face_down(false);
int tableRow = 0;
ExactCard exactCard = card->getCard();
if (exactCard) {
const CardInfo &info = exactCard.getInfo();
tableRow = info.getUiAttributes().tableRow;
ctm->set_pt(info.getPowTough().toStdString());
ctm->set_tapped(info.getUiAttributes().cipt);
}
cmd->set_y(TableZone::tableRowToGridY(tableRow));
commandList.append(cmd);
}
break;
}
default:
break;
}

View file

@ -8,7 +8,6 @@
#ifndef COCKATRICE_PLAYER_ACTIONS_H
#define COCKATRICE_PLAYER_ACTIONS_H
#include "../dialogs/dlg_create_token.h"
#include "../dialogs/dlg_move_top_cards_until.h"
#include "event_processing_options.h"
#include "player.h"
@ -99,9 +98,7 @@ public slots:
void actMoveTopCardToGrave();
void actMoveTopCardToExile();
void actMoveTopCardsToGrave();
void actMoveTopCardsToGraveFaceDown();
void actMoveTopCardsToExile();
void actMoveTopCardsToExileFaceDown();
void actMoveTopCardsUntil();
void actMoveTopCardToBottom();
void actDrawBottomCard();
@ -111,9 +108,7 @@ public slots:
void actMoveBottomCardToGrave();
void actMoveBottomCardToExile();
void actMoveBottomCardsToGrave();
void actMoveBottomCardsToGraveFaceDown();
void actMoveBottomCardsToExile();
void actMoveBottomCardsToExileFaceDown();
void actMoveBottomCardToTop();
void actSelectAll();
@ -179,12 +174,11 @@ private:
bool movingCardsUntil;
QTimer *moveTopCardTimer;
QStringList movingCardsUntilExprs = {};
int movingCardsUntilNumberOfHits = 1;
bool movingCardsUntilAutoPlay = false;
FilterString movingCardsUntilFilter;
int movingCardsUntilCounter = 0;
MoveTopCardsUntilOptions movingCardsUntilOptions;
void moveTopCardsTo(const QString &targetZone, const QString &zoneDisplayName, bool faceDown);
void moveBottomCardsTo(const QString &targetZone, const QString &zoneDisplayName, bool faceDown);
void createCard(const CardItem *sourceCard,
const QString &dbCardName,

View file

@ -30,9 +30,8 @@
#include <libcockatrice/protocol/pb/event_set_card_counter.pb.h>
#include <libcockatrice/protocol/pb/event_set_counter.pb.h>
#include <libcockatrice/protocol/pb/event_shuffle.pb.h>
#include <libcockatrice/utility/zone_names.h>
PlayerEventHandler::PlayerEventHandler(Player *_player) : QObject(_player), player(_player)
PlayerEventHandler::PlayerEventHandler(Player *_player) : player(_player)
{
}
@ -60,6 +59,7 @@ void PlayerEventHandler::eventShuffle(const Event_Shuffle &event)
// we want to close empty views as well
if (length == 0 || length > absStart) { // note this assumes views always start at the top of the library
view->close();
break;
}
} else {
qWarning() << zone->getName() << "of" << player->getPlayerInfo()->getName() << "holds empty zoneview!";
@ -321,8 +321,8 @@ void PlayerEventHandler::eventMoveCard(const Event_MoveCard &event, const GameEv
}
player->getPlayerMenu()->updateCardMenu(card);
if (player->getPlayerActions()->isMovingCardsUntil() && startZoneString == ZoneNames::DECK &&
targetZone->getName() == ZoneNames::STACK) {
if (player->getPlayerActions()->isMovingCardsUntil() && startZoneString == "deck" &&
targetZone->getName() == "stack") {
player->getPlayerActions()->moveOneCardUntil(card);
}
}
@ -594,4 +594,4 @@ void PlayerEventHandler::processGameEvent(GameEvent::GameEventType type,
qWarning() << "unhandled game event" << type;
}
}
}
}

View file

@ -19,8 +19,7 @@ PlayerGraphicsItem::PlayerGraphicsItem(Player *_player) : player(_player)
playerArea = new PlayerArea(this);
playerTarget = new PlayerTarget(player, playerArea);
qreal avatarMargin =
(counterAreaWidth + CardDimensions::HEIGHT_F + 15 - playerTarget->boundingRect().width()) / 2.0;
qreal avatarMargin = (counterAreaWidth + CARD_HEIGHT + 15 - playerTarget->boundingRect().width()) / 2.0;
playerTarget->setPos(QPointF(avatarMargin, avatarMargin));
initializeZones();
@ -56,9 +55,8 @@ void PlayerGraphicsItem::onPlayerActiveChanged(bool _active)
void PlayerGraphicsItem::initializeZones()
{
deckZoneGraphicsItem = new PileZone(player->getDeckZone(), this);
auto base = QPointF(counterAreaWidth + (CardDimensions::HEIGHT_F - CardDimensions::WIDTH_F + 15) / 2.0,
10 + playerTarget->boundingRect().height() + 5 -
(CardDimensions::HEIGHT_F - CardDimensions::WIDTH_F) / 2.0);
auto base = QPointF(counterAreaWidth + (CARD_HEIGHT - CARD_WIDTH + 15) / 2.0,
10 + playerTarget->boundingRect().height() + 5 - (CARD_HEIGHT - CARD_WIDTH) / 2.0);
deckZoneGraphicsItem->setPos(base);
qreal h = deckZoneGraphicsItem->boundingRect().width() + 5;
@ -97,7 +95,7 @@ QRectF PlayerGraphicsItem::boundingRect() const
qreal PlayerGraphicsItem::getMinimumWidth() const
{
qreal result = tableZoneGraphicsItem->getMinimumWidth() + CardDimensions::HEIGHT_F + 15 + counterAreaWidth +
qreal result = tableZoneGraphicsItem->getMinimumWidth() + CARD_HEIGHT + 15 + counterAreaWidth +
stackZoneGraphicsItem->boundingRect().width();
if (!SettingsCache::instance().getHorizontalHand()) {
result += handZoneGraphicsItem->boundingRect().width();
@ -114,8 +112,8 @@ void PlayerGraphicsItem::paint(QPainter * /*painter*/,
void PlayerGraphicsItem::processSceneSizeChange(int newPlayerWidth)
{
// Extend table (and hand, if horizontal) to accommodate the new player width.
qreal tableWidth = newPlayerWidth - CardDimensions::HEIGHT_F - 15 - counterAreaWidth -
stackZoneGraphicsItem->boundingRect().width();
qreal tableWidth =
newPlayerWidth - CARD_HEIGHT - 15 - counterAreaWidth - stackZoneGraphicsItem->boundingRect().width();
if (!SettingsCache::instance().getHorizontalHand()) {
tableWidth -= handZoneGraphicsItem->boundingRect().width();
}
@ -154,7 +152,7 @@ void PlayerGraphicsItem::rearrangeCounters()
void PlayerGraphicsItem::rearrangeZones()
{
auto base = QPointF(CardDimensions::HEIGHT_F + counterAreaWidth + 15, 0);
auto base = QPointF(CARD_HEIGHT + counterAreaWidth + 15, 0);
if (SettingsCache::instance().getHorizontalHand()) {
if (mirrored) {
if (player->getHandZone()->contentsKnown()) {
@ -205,7 +203,7 @@ void PlayerGraphicsItem::rearrangeZones()
void PlayerGraphicsItem::updateBoundingRect()
{
prepareGeometryChange();
qreal width = CardDimensions::HEIGHT_F + 15 + counterAreaWidth + stackZoneGraphicsItem->boundingRect().width();
qreal width = CARD_HEIGHT + 15 + counterAreaWidth + stackZoneGraphicsItem->boundingRect().width();
if (SettingsCache::instance().getHorizontalHand()) {
qreal handHeight =
player->getPlayerInfo()->getHandVisible() ? handZoneGraphicsItem->boundingRect().height() : 0;
@ -216,7 +214,7 @@ void PlayerGraphicsItem::updateBoundingRect()
0, 0, width + handZoneGraphicsItem->boundingRect().width() + tableZoneGraphicsItem->boundingRect().width(),
tableZoneGraphicsItem->boundingRect().height());
}
playerArea->setSize(CardDimensions::HEIGHT_F + counterAreaWidth + 15, bRect.height());
playerArea->setSize(CARD_HEIGHT + counterAreaWidth + 15, bRect.height());
emit sizeChanged();
}
}

View file

@ -1,136 +0,0 @@
/**
* @file z_value_layer_manager.h
* @ingroup GameGraphics
* @brief Semantic Z-value layer management for game scene rendering.
*
* This file provides a structured approach to Z-value allocation in the game scene.
* Z-values in Qt determine stacking order - higher values render on top of lower values.
*
* ## Layer Architecture
*
* The game scene is organized into three conceptual layers:
*
* 1. **Zone Layer (0-999)**: Zone backgrounds, containers, and static elements
* - Zone backgrounds (0.5-1.0)
* - Cards within zones (1.0 base + index)
*
* 2. **Card Layer (1-40,000,000)**: Dynamic card rendering on the table zone
* - Cards use formula: (actualY + CardDimensions::HEIGHT) * 100000 + (actualX + 1) * 100
* - Maximum card Z-value: ~40,000,000 (with 3 rows, actualY <= ~289)
*
* 3. **Overlay Layer (2,000,000,000+)**: UI elements that must appear above all cards
* - Hovered cards (+1)
* - Arrows (+3)
* - Zone views (+4)
* - Drag items (+5, +6)
* - Top UI elements (+7)
*
* ## Design Rationale
*
* The large gap between card Z-values (max ~40M) and overlay base (2B) provides
* safety margin for future table zone expansions while ensuring overlays always
* render above cards regardless of table position.
*
* ## Usage
*
* Prefer using the semantic constants from ZValues namespace:
* @code
* card->setZValue(ZValues::HOVERED_CARD);
* arrow->setZValue(ZValues::ARROWS);
* @endcode
*
* Use validation functions to verify card Z-values during development:
* @code
* Q_ASSERT(ZValueLayerManager::isValidCardZValue(cardZ));
* @endcode
*/
#ifndef Z_VALUE_LAYER_MANAGER_H
#define Z_VALUE_LAYER_MANAGER_H
#include <QtGlobal>
/**
* @namespace ZValueLayerManager
* @brief Utilities for Z-value validation and layer management.
*/
namespace ZValueLayerManager
{
/**
* @enum Layer
* @brief Semantic layer identifiers for Z-value allocation.
*
* These represent conceptual rendering layers, not actual Z-values.
* Use the corresponding ZValues constants for actual rendering.
*/
enum class Layer
{
/// Zone-level elements like backgrounds and containers
Zone,
/// Cards rendered in zones (uses sequential Z-values)
Card,
/// Temporary UI elements like hovered cards and drag items
Overlay
};
/**
* @brief Maximum Z-value a card can have on the table zone.
*
* Based on table zone formula: (actualY + CardDimensions::HEIGHT) * 100000 + (actualX + 1) * 100
* With maximum 3 rows and CardDimensions::HEIGHT = 102, actualY <= ~289.
* Maximum: (289 + 102) * 100000 + 100 * 100 = 39,110,000
*
* We use 40,000,000 as a safe upper bound with margin.
*/
constexpr qreal CARD_Z_VALUE_MAX = 40000000.0;
/**
* @brief Base Z-value for overlay elements.
*
* Must exceed CARD_Z_VALUE_MAX to ensure overlays render above all cards.
* The 50x margin (2B vs 40M) provides safety for future expansion.
*/
constexpr qreal OVERLAY_BASE = 2000000000.0;
/**
* @brief Validates that a Z-value is within the valid card range.
*
* Cards should have Z-values between CARD_BASE (1.0) and CARD_Z_VALUE_MAX.
* Values outside this range may interfere with overlay rendering.
*
* @param zValue The Z-value to validate
* @return true if the Z-value is valid for a card
*/
[[nodiscard]] constexpr bool isValidCardZValue(qreal zValue)
{
return zValue >= 1.0 && zValue <= CARD_Z_VALUE_MAX;
}
/**
* @brief Validates that a Z-value is in the overlay layer.
*
* Overlay elements should have Z-values at or above OVERLAY_BASE.
*
* @param zValue The Z-value to validate
* @return true if the Z-value is valid for an overlay element
*/
[[nodiscard]] constexpr bool isOverlayZValue(qreal zValue)
{
return zValue >= OVERLAY_BASE;
}
/**
* @brief Returns the Z-value for a specific overlay element.
*
* @param offset Offset from OVERLAY_BASE (0-7 for current elements)
* @return The absolute Z-value for the overlay element
*/
[[nodiscard]] constexpr qreal overlayZValue(qreal offset)
{
return OVERLAY_BASE + offset;
}
} // namespace ZValueLayerManager
#endif // Z_VALUE_LAYER_MANAGER_H

View file

@ -1,83 +0,0 @@
#ifndef Z_VALUES_H
#define Z_VALUES_H
#include "card_dimensions.h"
#include "z_value_layer_manager.h"
/**
* @file z_values.h
* @ingroup GameGraphics
* @brief Centralized Z-value constants for rendering layer order.
*
* Z-values in Qt determine stacking order. Higher values render on top.
* These constants define the visual layering hierarchy for the game scene.
*
* ## Layer Architecture
*
* See z_value_layer_manager.h for detailed documentation on the three-layer
* architecture (Zone, Card, Overlay) and the rationale for Z-value choices.
*
* ## Quick Reference
*
* | Layer | Z-Value Range | Purpose |
* |----------|------------------|-----------------------------------|
* | Zone | 0.5 - 1.0 | Zone backgrounds, containers |
* | Card | 1.0 - 40,000,000 | Cards on table (position-based) |
* | Overlay | 2,000,000,000+ | UI elements above all cards |
*/
namespace ZValues
{
// Expose base for callers that need it
constexpr qreal OVERLAY_BASE = ZValueLayerManager::OVERLAY_BASE;
// Overlay layer Z-values for items that should appear above normal cards
constexpr qreal HOVERED_CARD = ZValueLayerManager::overlayZValue(1.0);
constexpr qreal ARROWS = ZValueLayerManager::overlayZValue(3.0);
constexpr qreal ZONE_VIEW_WIDGET = ZValueLayerManager::overlayZValue(4.0);
constexpr qreal DRAG_ITEM = ZValueLayerManager::overlayZValue(5.0);
constexpr qreal DRAG_ITEM_CHILD = ZValueLayerManager::overlayZValue(6.0);
constexpr qreal TOP_UI = ZValueLayerManager::overlayZValue(7.0);
/**
* @brief Compute Z-value for child drag items based on hotspot position.
*
* When dragging multiple cards together, each child card needs a unique Z-value
* to prevent Z-fighting (flickering/flashing). The Z-values are derived from
* their position when grabbed to conserve original stacking. The formula encodes
* 2D coordinates into a single value where X has higher weight, ensuring
* deterministic visual stacking.
*
* @param hotSpotX The X coordinate of the grab position
* @param hotSpotY The Y coordinate of the grab position
* @return Unique Z-value for the child drag item
*/
[[nodiscard]] constexpr qreal childDragZValue(qreal hotSpotX, qreal hotSpotY)
{
return DRAG_ITEM_CHILD + hotSpotX * 1000000 + hotSpotY * 1000 + 1000;
}
/**
* @brief Compute Z-value for cards on the table zone based on position.
*
* Cards lower on the table (higher Y) render above cards higher up,
* and cards to the right (higher X) render above cards to the left.
* This creates natural visual stacking for overlapping cards.
*
* @param x The X coordinate of the card position
* @param y The Y coordinate of the card position
* @return Z-value for the card's table position
*/
[[nodiscard]] constexpr qreal tableCardZValue(qreal x, qreal y)
{
return (y + CardDimensions::HEIGHT_F) * 100000.0 + (x + 1) * 100.0;
}
// Card layering (general architecture, not command-zone specific)
constexpr qreal CARD_BASE = 1.0;
constexpr qreal CARD_MAX = ZValueLayerManager::CARD_Z_VALUE_MAX;
} // namespace ZValues
#endif // Z_VALUES_H

View file

@ -56,7 +56,7 @@ void HandZone::handleDropEvent(const QList<CardDragItem *> &dragItems,
QRectF HandZone::boundingRect() const
{
if (SettingsCache::instance().getHorizontalHand())
return QRectF(0, 0, width, CardDimensions::HEIGHT_F + 10);
return QRectF(0, 0, width, CARD_HEIGHT + 10);
else
return QRectF(0, 0, 100, zoneHeight);
}

View file

@ -1,45 +0,0 @@
#ifndef COCKATRICE_CARD_ZONE_ALGORITHMS_H
#define COCKATRICE_CARD_ZONE_ALGORITHMS_H
namespace CardZoneAlgorithms
{
/**
* Shared insertion logic for zones where cards become visible on add and follow
* the standard pattern: clamp index, insert, clear identity if contents unknown,
* reset state, show card.
*
* Zones with different post-add behavior (signal connections, positional resets,
* hidden cards, or coordinate-based placement) should NOT use this implement
* addCardImpl directly instead.
*
* Template parameters allow testing with lightweight mocks that avoid Qt graphics
* dependencies.
*
* @tparam CardList Must provide: size() -> int, insert(int, CardType*),
* getContentsKnown() -> bool
* @tparam CardType Must provide: setId(int), setCardRef(CardRefType),
* resetState(bool), setVisible(bool)
* @param keepAnnotations Forwarded to card->resetState(). Stack-like zones preserve
* annotations across zone transitions; hand-like zones clear them.
*/
template <typename CardList, typename CardType>
void addCardToList(CardList &cards, CardType *card, int x, bool keepAnnotations)
{
if (x < 0 || x >= cards.size()) {
x = static_cast<int>(cards.size());
}
cards.insert(x, card);
if (!cards.getContentsKnown()) {
card->setId(-1);
card->setCardRef({});
}
card->resetState(keepAnnotations);
card->setVisible(true);
}
} // namespace CardZoneAlgorithms
#endif // COCKATRICE_CARD_ZONE_ALGORITHMS_H

View file

@ -10,7 +10,6 @@
#include <QDebug>
#include <libcockatrice/card/database/card_database_manager.h>
#include <libcockatrice/protocol/pb/command_move_card.pb.h>
#include <libcockatrice/utility/zone_names.h>
/**
* @param _player the player that the zone belongs to
@ -44,10 +43,8 @@ void CardZoneLogic::addCard(CardItem *card, const bool reorganize, const int x,
for (auto *view : views) {
if (qobject_cast<ZoneViewZoneLogic *>(view->getLogic())->prepareAddCard(x)) {
auto copy = new CardItem(player, nullptr, card->getCardRef(), card->getId());
copy->setFaceDown(card->getFaceDown());
view->getLogic()->addCard(copy, reorganize, x, y);
view->getLogic()->addCard(new CardItem(player, nullptr, card->getCardRef(), card->getId()), reorganize, x,
y);
}
}
@ -175,9 +172,9 @@ void CardZoneLogic::clearContents()
QString CardZoneLogic::getTranslatedName(bool theirOwn, GrammaticalCase gc) const
{
QString ownerName = player->getPlayerInfo()->getName();
if (name == ZoneNames::HAND)
if (name == "hand")
return (theirOwn ? tr("their hand", "nominative") : tr("%1's hand", "nominative").arg(ownerName));
else if (name == ZoneNames::DECK)
else if (name == "deck")
switch (gc) {
case CaseLookAtZone:
return (theirOwn ? tr("their library", "look at zone")
@ -193,11 +190,11 @@ QString CardZoneLogic::getTranslatedName(bool theirOwn, GrammaticalCase gc) cons
default:
return (theirOwn ? tr("their library", "nominative") : tr("%1's library", "nominative").arg(ownerName));
}
else if (name == ZoneNames::GRAVE)
else if (name == "grave")
return (theirOwn ? tr("their graveyard", "nominative") : tr("%1's graveyard", "nominative").arg(ownerName));
else if (name == ZoneNames::EXILE)
else if (name == "rfg")
return (theirOwn ? tr("their exile", "nominative") : tr("%1's exile", "nominative").arg(ownerName));
else if (name == ZoneNames::SIDEBOARD)
else if (name == "sb")
switch (gc) {
case CaseLookAtZone:
return (theirOwn ? tr("their sideboard", "look at zone")

View file

@ -1,7 +1,6 @@
#include "hand_zone_logic.h"
#include "../../board/card_item.h"
#include "card_zone_algorithms.h"
HandZoneLogic::HandZoneLogic(Player *_player,
const QString &_name,
@ -15,5 +14,16 @@ HandZoneLogic::HandZoneLogic(Player *_player,
void HandZoneLogic::addCardImpl(CardItem *card, int x, int /*y*/)
{
CardZoneAlgorithms::addCardToList(cards, card, x, false);
}
// if x is negative set it to add at end
if (x < 0 || x >= cards.size()) {
x = cards.size();
}
cards.insert(x, card);
if (!cards.getContentsKnown()) {
card->setId(-1);
card->setCardRef({});
}
card->resetState();
card->setVisible(true);
}

View file

@ -1,7 +1,6 @@
#include "stack_zone_logic.h"
#include "../../board/card_item.h"
#include "card_zone_algorithms.h"
StackZoneLogic::StackZoneLogic(Player *_player,
const QString &_name,
@ -15,5 +14,16 @@ StackZoneLogic::StackZoneLogic(Player *_player,
void StackZoneLogic::addCardImpl(CardItem *card, int x, int /*y*/)
{
CardZoneAlgorithms::addCardToList(cards, card, x, true);
}
// if x is negative set it to add at end
if (x < 0 || x >= cards.size()) {
x = static_cast<int>(cards.size());
}
cards.insert(x, card);
if (!cards.getContentsKnown()) {
card->setId(-1);
card->setCardRef({});
}
card->resetState(true);
card->setVisible(true);
}

View file

@ -19,9 +19,9 @@ PileZone::PileZone(PileZoneLogic *_logic, QGraphicsItem *parent) : CardZone(_log
setCursor(Qt::OpenHandCursor);
setTransform(QTransform()
.translate(CardDimensions::WIDTH_HALF_F, CardDimensions::HEIGHT_HALF_F)
.translate((float)CARD_WIDTH / 2, (float)CARD_HEIGHT / 2)
.rotate(90)
.translate(-CardDimensions::WIDTH_HALF_F, -CardDimensions::HEIGHT_HALF_F));
.translate((float)-CARD_WIDTH / 2, (float)-CARD_HEIGHT / 2));
connect(&SettingsCache::instance(), &SettingsCache::roundCardCornersChanged, this, [this](bool _roundCardCorners) {
Q_UNUSED(_roundCardCorners);
@ -33,13 +33,13 @@ PileZone::PileZone(PileZoneLogic *_logic, QGraphicsItem *parent) : CardZone(_log
QRectF PileZone::boundingRect() const
{
return QRectF(0, 0, CardDimensions::WIDTH_F, CardDimensions::HEIGHT_F);
return QRectF(0, 0, CARD_WIDTH, CARD_HEIGHT);
}
QPainterPath PileZone::shape() const
{
QPainterPath shape;
qreal cardCornerRadius = SettingsCache::instance().getRoundCardCorners() ? 0.05 * CardDimensions::WIDTH_F : 0.0;
qreal cardCornerRadius = SettingsCache::instance().getRoundCardCorners() ? 0.05 * CARD_WIDTH : 0.0;
shape.addRoundedRect(boundingRect(), cardCornerRadius, cardCornerRadius);
return shape;
}
@ -52,9 +52,9 @@ void PileZone::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*optio
getLogic()->getCards().at(0)->paintPicture(painter, getLogic()->getCards().at(0)->getTranslatedSize(painter),
90);
painter->translate(CardDimensions::WIDTH_HALF_F, CardDimensions::HEIGHT_HALF_F);
painter->translate((float)CARD_WIDTH / 2, (float)CARD_HEIGHT / 2);
painter->rotate(-90);
painter->translate(-CardDimensions::WIDTH_HALF_F, -CardDimensions::HEIGHT_HALF_F);
painter->translate((float)-CARD_WIDTH / 2, (float)-CARD_HEIGHT / 2);
paintNumberEllipse(getLogic()->getCards().size(), 28, Qt::white, -1, -1, painter);
}
@ -68,13 +68,8 @@ void PileZone::handleDropEvent(const QList<CardDragItem *> &dragItems, CardZoneL
cmd.set_x(0);
cmd.set_y(0);
for (int i = 0; i < dragItems.size(); ++i) {
auto cardToMove = cmd.mutable_cards_to_move()->add_card();
cardToMove->set_card_id(dragItems[i]->getId());
if (dragItems[i]->isForceFaceDown()) {
cardToMove->set_face_down(true);
}
}
for (int i = 0; i < dragItems.size(); ++i)
cmd.mutable_cards_to_move()->add_card()->set_card_id(dragItems[i]->getId());
getLogic()->getPlayer()->getPlayerActions()->sendGameCommand(cmd);
}
@ -106,12 +101,12 @@ void PileZone::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
if (getLogic()->getCards().isEmpty())
return;
bool forceFaceDown = event->modifiers().testFlag(Qt::ShiftModifier);
bool faceDown = event->modifiers().testFlag(Qt::ShiftModifier);
bool bottomCard = event->modifiers().testFlag(Qt::ControlModifier);
CardItem *card = bottomCard ? getLogic()->getCards().last() : getLogic()->getCards().first();
const int cardid =
getLogic()->contentsKnown() ? card->getId() : (bottomCard ? getLogic()->getCards().size() - 1 : 0);
CardDragItem *drag = card->createDragItem(cardid, event->pos(), event->scenePos(), forceFaceDown);
CardDragItem *drag = card->createDragItem(cardid, event->pos(), event->scenePos(), faceDown);
drag->grabMouse();
setCursor(Qt::OpenHandCursor);
}

View file

@ -68,8 +68,7 @@ void SelectZone::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
}
}
static_cast<GameScene *>(scene())->resizeRubberBand(
deviceTransform(static_cast<GameScene *>(scene())->getViewportTransform()).map(pos),
cardsInSelectionRect.size());
deviceTransform(static_cast<GameScene *>(scene())->getViewportTransform()).map(pos));
event->accept();
}
}

View file

@ -78,11 +78,7 @@ void StackZone::handleDropEvent(const QList<CardDragItem *> &dragItems,
for (CardDragItem *item : dragItems) {
if (item) {
auto cardToMove = cmd.mutable_cards_to_move()->add_card();
cardToMove->set_card_id(item->getId());
if (item->isForceFaceDown()) {
cardToMove->set_face_down(true);
}
cmd.mutable_cards_to_move()->add_card()->set_card_id(item->getId());
}
}

View file

@ -7,7 +7,6 @@
#include "../board/card_item.h"
#include "../player/player.h"
#include "../player/player_actions.h"
#include "../z_values.h"
#include "logic/table_zone_logic.h"
#include <QGraphicsScene>
@ -15,7 +14,6 @@
#include <libcockatrice/card/card_info.h>
#include <libcockatrice/protocol/pb/command_move_card.pb.h>
#include <libcockatrice/protocol/pb/command_set_card_attr.pb.h>
#include <libcockatrice/utility/zone_names.h>
const QColor TableZone::BACKGROUND_COLOR = QColor(100, 100, 100);
const QColor TableZone::FADE_MASK = QColor(0, 0, 0, 80);
@ -32,7 +30,7 @@ TableZone::TableZone(TableZoneLogic *_logic, QGraphicsItem *parent) : SelectZone
updateBg();
height = MARGIN_TOP + MARGIN_BOTTOM + TABLEROWS * CardDimensions::HEIGHT + (TABLEROWS - 1) * PADDING_Y;
height = MARGIN_TOP + MARGIN_BOTTOM + TABLEROWS * CARD_HEIGHT + (TABLEROWS - 1) * PADDING_Y;
width = MIN_WIDTH;
currentMinimumWidth = width;
@ -107,7 +105,7 @@ void TableZone::paintLandDivider(QPainter *painter)
{
// Place the line 2 grid heights down then back it off just enough to allow
// some space between a 3-card stack and the land area.
qreal separatorY = MARGIN_TOP + 2 * (CardDimensions::HEIGHT + PADDING_Y) - STACKED_CARD_OFFSET_Y / 2;
qreal separatorY = MARGIN_TOP + 2 * (CARD_HEIGHT + PADDING_Y) - STACKED_CARD_OFFSET_Y / 2;
if (isInverted())
separatorY = height - separatorY;
painter->setPen(QColor(255, 255, 255, 40));
@ -136,10 +134,8 @@ void TableZone::handleDropEventByGrid(const QList<CardDragItem *> &dragItems,
for (const auto &item : dragItems) {
CardToMove *ctm = cmd.mutable_cards_to_move()->add_card();
ctm->set_card_id(item->getId());
if (item->isForceFaceDown()) {
ctm->set_face_down(true);
}
if (startZone->getName() != getLogic()->getName() && !item->isForceFaceDown()) {
ctm->set_face_down(item->getFaceDown());
if (startZone->getName() != getLogic()->getName() && !item->getFaceDown()) {
const auto &card = item->getItem()->getCard();
if (card) {
ctm->set_pt(card.getInfo().getPowTough().toStdString());
@ -171,7 +167,7 @@ void TableZone::reorganizeCards()
actualY += 15;
getLogic()->getCards()[i]->setPos(actualX, actualY);
getLogic()->getCards()[i]->setRealZValue(ZValues::tableCardZValue(actualX, actualY));
getLogic()->getCards()[i]->setRealZValue((actualY + CARD_HEIGHT) * 100000 + (actualX + 1) * 100);
QListIterator<CardItem *> attachedCardIterator(getLogic()->getCards()[i]->getAttachedCards());
int j = 0;
@ -181,7 +177,7 @@ void TableZone::reorganizeCards()
qreal childX = actualX - j * STACKED_CARD_OFFSET_X;
qreal childY = y + 5;
attachedCard->setPos(childX, childY);
attachedCard->setRealZValue(ZValues::tableCardZValue(childX, childY));
attachedCard->setRealZValue((childY + CARD_HEIGHT) * 100000 + (childX + 1) * 100);
}
}
@ -196,7 +192,7 @@ void TableZone::toggleTapped()
auto isCardOnTable = [](const QGraphicsItem *item) {
if (auto card = qgraphicsitem_cast<const CardItem *>(item)) {
return card->getZone()->getName() == ZoneNames::TABLE;
return card->getZone()->getName() == "table";
}
return false;
};
@ -233,7 +229,7 @@ void TableZone::resizeToContents()
// Minimum width is the rightmost card position plus enough room for
// another card with padding, then margin.
currentMinimumWidth = xMax + (2 * CardDimensions::WIDTH) + PADDING_X + MARGIN_RIGHT;
currentMinimumWidth = xMax + (2 * CARD_WIDTH) + PADDING_X + MARGIN_RIGHT;
if (currentMinimumWidth < MIN_WIDTH)
currentMinimumWidth = MIN_WIDTH;
@ -283,10 +279,10 @@ void TableZone::computeCardStackWidths()
const int key = getCardStackMapKey(gridPoint.x() / 3, gridPoint.y());
const int stackCount = cardStackCount.value(key, 0);
if (stackCount == 1)
cardStackWidth.insert(key, CardDimensions::WIDTH + getLogic()->getCards()[i]->getAttachedCards().size() *
STACKED_CARD_OFFSET_X);
cardStackWidth.insert(key, CARD_WIDTH + getLogic()->getCards()[i]->getAttachedCards().size() *
STACKED_CARD_OFFSET_X);
else
cardStackWidth.insert(key, CardDimensions::WIDTH + (stackCount - 1) * STACKED_CARD_OFFSET_X);
cardStackWidth.insert(key, CARD_WIDTH + (stackCount - 1) * STACKED_CARD_OFFSET_X);
}
}
@ -300,7 +296,7 @@ QPointF TableZone::mapFromGrid(QPoint gridPoint) const
// Add in width of card stack plus padding for each column
for (int i = 0; i < gridPoint.x() / 3; ++i) {
const int key = getCardStackMapKey(i, gridPoint.y());
x += cardStackWidth.value(key, CardDimensions::WIDTH) + PADDING_X;
x += cardStackWidth.value(key, CARD_WIDTH) + PADDING_X;
}
if (isInverted())
@ -311,7 +307,7 @@ QPointF TableZone::mapFromGrid(QPoint gridPoint) const
// Add in card size and padding for each row
for (int i = 0; i < gridPoint.y(); ++i)
y += CardDimensions::HEIGHT + PADDING_Y;
y += CARD_HEIGHT + PADDING_Y;
return QPointF(x, y);
}
@ -325,7 +321,7 @@ QPoint TableZone::mapToGrid(const QPointF &mapPoint) const
int y = mapPoint.y() - MARGIN_TOP;
// Below calculation effectively rounds to the nearest grid point.
const int gridPointHeight = CardDimensions::HEIGHT + PADDING_Y;
const int gridPointHeight = CARD_HEIGHT + PADDING_Y;
int gridPointY = (y + PADDING_Y / 2) / gridPointHeight;
gridPointY = clampValidTableRow(gridPointY);
@ -341,7 +337,7 @@ QPoint TableZone::mapToGrid(const QPointF &mapPoint) const
// Maximum value is a card width from the right margin, referenced to the
// grid area.
const int xMax = width - MARGIN_LEFT - MARGIN_RIGHT - CardDimensions::WIDTH;
const int xMax = width - MARGIN_LEFT - MARGIN_RIGHT - CARD_WIDTH;
int xStack = 0;
int xNextStack = 0;
@ -349,7 +345,7 @@ QPoint TableZone::mapToGrid(const QPointF &mapPoint) const
while ((xNextStack <= x) && (xNextStack <= xMax)) {
xStack = xNextStack;
const int key = getCardStackMapKey(nextStackCol, gridPointY);
xNextStack += cardStackWidth.value(key, CardDimensions::WIDTH) + PADDING_X;
xNextStack += cardStackWidth.value(key, CARD_WIDTH) + PADDING_X;
nextStackCol++;
}
int stackCol = qMax(nextStackCol - 1, 0);
@ -382,11 +378,3 @@ int TableZone::clampValidTableRow(const int row)
return TABLEROWS - 1;
return row;
}
int TableZone::tableRowToGridY(int tableRow)
{
if (tableRow > 2) {
tableRow = 1;
}
return clampValidTableRow(2 - tableRow);
}

View file

@ -41,12 +41,12 @@ private:
/*
Minimum width of the table zone including margins.
*/
static const int MIN_WIDTH = MARGIN_LEFT + (5 * CardDimensions::WIDTH) + MARGIN_RIGHT;
static const int MIN_WIDTH = MARGIN_LEFT + (5 * CARD_WIDTH) + MARGIN_RIGHT;
/*
Offset sizes when cards are stacked on each other in the grid
*/
static const int STACKED_CARD_OFFSET_X = CardDimensions::WIDTH / 3;
static const int STACKED_CARD_OFFSET_X = CARD_WIDTH / 3;
static const int STACKED_CARD_OFFSET_Y = PADDING_Y / 3;
/*
@ -151,13 +151,6 @@ public:
static int clampValidTableRow(const int row);
/**
* Converts a card's logical table row (0=creatures, 1=noncreatures, 2=lands)
* to the corresponding grid Y coordinate. Cards with tableRow > 2 (e.g.,
* instants/sorceries) default to the noncreatures row.
*/
static int tableRowToGridY(int tableRow);
/**
Resizes the TableZone in case CardItems are within or
outside of the TableZone constraints.

View file

@ -73,10 +73,7 @@ void ZoneViewZone::initializeCards(const QList<const ServerInfo_Card *> &cardLis
for (int i = 0; i < cardList.size(); ++i) {
auto card = cardList[i];
CardRef cardRef = {QString::fromStdString(card->name()), QString::fromStdString(card->provider_id())};
auto copy = new CardItem(getLogic()->getPlayer(), this, cardRef, card->id());
copy->setFaceDown(card->face_down());
getLogic()->addCard(copy, false, i);
getLogic()->addCard(new CardItem(getLogic()->getPlayer(), this, cardRef, card->id()), false, i);
}
reorganizeCards();
} else if (!qobject_cast<ZoneViewZoneLogic *>(getLogic())->getOriginalZone()->contentsKnown()) {
@ -94,10 +91,8 @@ void ZoneViewZone::initializeCards(const QList<const ServerInfo_Card *> &cardLis
int number = numberCards == -1 ? c.size() : (numberCards < c.size() ? numberCards : c.size());
for (int i = 0; i < number; i++) {
CardItem *card = c.at(i);
auto copy = new CardItem(getLogic()->getPlayer(), this, card->getCardRef(), card->getId());
copy->setFaceDown(card->getFaceDown());
getLogic()->addCard(copy, false, i);
getLogic()->addCard(new CardItem(getLogic()->getPlayer(), this, card->getCardRef(), card->getId()), false,
i);
}
reorganizeCards();
}
@ -112,15 +107,12 @@ void ZoneViewZone::zoneDumpReceived(const Response &r)
auto cardName = QString::fromStdString(cardInfo.name());
auto cardProviderId = QString::fromStdString(cardInfo.provider_id());
auto card = new CardItem(getLogic()->getPlayer(), this, {cardName, cardProviderId}, cardInfo.id(), getLogic());
card->setFaceDown(cardInfo.face_down());
getLogic()->rawInsertCard(card, i);
}
qobject_cast<ZoneViewZoneLogic *>(getLogic())->updateCardIds(ZoneViewZoneLogic::INITIALIZE);
reorganizeCards();
// clang-format off
emit getLogic()->cardCountChanged(); // emit keyword causes spurious spacing around ->
// clang-format on
emit getLogic()->cardCountChanged();
}
// Because of boundingRect(), this function must not be called before the zone was added to a scene.
@ -168,8 +160,8 @@ void ZoneViewZone::reorganizeCards()
// determine bounding rect
qreal aleft = 0;
qreal atop = 0;
qreal awidth = gridSize.cols * CardDimensions::WIDTH_F + CardDimensions::WIDTH_HALF_F + HORIZONTAL_PADDING;
qreal aheight = (gridSize.rows * CardDimensions::HEIGHT_F) / 3 + CardDimensions::HEIGHT_F * 1.3;
qreal awidth = gridSize.cols * CARD_WIDTH + (CARD_WIDTH / 2) + HORIZONTAL_PADDING;
qreal aheight = (gridSize.rows * CARD_HEIGHT) / 3 + CARD_HEIGHT * 1.3;
optimumRect = QRectF(aleft, atop, awidth, aheight);
updateGeometry();
@ -212,8 +204,8 @@ ZoneViewZone::GridSize ZoneViewZone::positionCardsForDisplay(CardList &cards, Ca
}
lastColumnProp = columnProp;
qreal x = col * CardDimensions::WIDTH_F;
qreal y = row * CardDimensions::HEIGHT_F / 3;
qreal x = col * CARD_WIDTH;
qreal y = row * CARD_HEIGHT / 3;
c->setPos(HORIZONTAL_PADDING + x, VERTICAL_PADDING + y);
c->setRealZValue(i);
longestRow = qMax(row, longestRow);
@ -240,8 +232,8 @@ ZoneViewZone::GridSize ZoneViewZone::positionCardsForDisplay(CardList &cards, Ca
for (int i = 0; i < cardCount; i++) {
CardItem *c = cards.at(i);
qreal x = (i / rows) * CardDimensions::WIDTH_F;
qreal y = (i % rows) * CardDimensions::HEIGHT_F / 3;
qreal x = (i / rows) * CARD_WIDTH;
qreal y = (i % rows) * CARD_HEIGHT / 3;
c->setPos(HORIZONTAL_PADDING + x, VERTICAL_PADDING + y);
c->setRealZValue(i);
}
@ -287,13 +279,8 @@ void ZoneViewZone::handleDropEvent(const QList<CardDragItem *> &dragItems,
cmd.set_y(0);
cmd.set_is_reversed(qobject_cast<ZoneViewZoneLogic *>(getLogic())->getIsReversed());
for (int i = 0; i < dragItems.size(); ++i) {
auto cardToMove = cmd.mutable_cards_to_move()->add_card();
cardToMove->set_card_id(dragItems[i]->getId());
if (dragItems[i]->isForceFaceDown()) {
cardToMove->set_face_down(true);
}
}
for (int i = 0; i < dragItems.size(); ++i)
cmd.mutable_cards_to_move()->add_card()->set_card_id(dragItems[i]->getId());
getLogic()->getPlayer()->getPlayerActions()->sendGameCommand(cmd);
}

View file

@ -7,7 +7,6 @@
#include "../game_scene.h"
#include "../player/player.h"
#include "../player/player_actions.h"
#include "../z_values.h"
#include "view_zone.h"
#include <QCheckBox>
@ -48,7 +47,7 @@ ZoneViewWidget::ZoneViewWidget(Player *_player,
{
setAcceptHoverEvents(true);
setAttribute(Qt::WA_DeleteOnClose);
setZValue(ZValues::ZONE_VIEW_WIDGET);
setZValue(2000000006);
setFlag(ItemIgnoresTransformations);
QGraphicsLinearLayout *vbox = new QGraphicsLinearLayout(Qt::Vertical);
@ -72,7 +71,7 @@ ZoneViewWidget::ZoneViewWidget(Player *_player,
QGraphicsProxyWidget *searchEditProxy = new QGraphicsProxyWidget;
searchEditProxy->setWidget(&searchEdit);
searchEditProxy->setZValue(ZValues::DRAG_ITEM);
searchEditProxy->setZValue(2000000007);
vbox->addItem(searchEditProxy);
// top row
@ -81,13 +80,13 @@ ZoneViewWidget::ZoneViewWidget(Player *_player,
// groupBy options
QGraphicsProxyWidget *groupBySelectorProxy = new QGraphicsProxyWidget;
groupBySelectorProxy->setWidget(&groupBySelector);
groupBySelectorProxy->setZValue(ZValues::TOP_UI);
groupBySelectorProxy->setZValue(2000000008);
hTopRow->addItem(groupBySelectorProxy);
// sortBy options
QGraphicsProxyWidget *sortBySelectorProxy = new QGraphicsProxyWidget;
sortBySelectorProxy->setWidget(&sortBySelector);
sortBySelectorProxy->setZValue(ZValues::DRAG_ITEM);
sortBySelectorProxy->setZValue(2000000007);
hTopRow->addItem(sortBySelectorProxy);
vbox->addItem(hTopRow);
@ -458,7 +457,7 @@ void ZoneViewWidget::resizeScrollbar(const qreal newZoneHeight)
*/
static qreal rowsToHeight(int rows)
{
const qreal cardsHeight = (rows + 1) * (CardDimensions::HEIGHT_F / 3);
const qreal cardsHeight = (rows + 1) * (CARD_HEIGHT / 3);
return cardsHeight + 5; // +5 padding to make the cutoff look nicer
}
@ -574,4 +573,4 @@ void ZoneViewWidget::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
if (event->pos().y() <= 0) {
expandWindow();
}
}
}

View file

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

View file

@ -86,7 +86,6 @@ QNetworkReply *CardPictureLoaderWorker::makeRequest(const QUrl &url, CardPicture
QNetworkRequest req(url);
req.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING));
req.setRawHeader("Accept", "image/avif,image/webp,image/apng,image/,/*;q=0.8");
if (!picDownload) {
req.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysCache);
}

View file

@ -12,10 +12,7 @@
#include <QThreadPool>
// Card back returned by gatherer when card is not found
static const QStringList MD5_BLACKLIST = {
"db0c48db407a907c16ade38de048a441", // Old card back hash. Keep around just in case
"fbc7d763c08771c260b39e2115414eeb" // Current card back hash
};
static const QStringList MD5_BLACKLIST = {"db0c48db407a907c16ade38de048a441"};
CardPictureLoaderWorkerWork::CardPictureLoaderWorkerWork(const CardPictureLoaderWorker *worker, const ExactCard &toLoad)
: QObject(nullptr), cardToDownload(CardPictureToLoad(toLoad)),

View file

@ -17,7 +17,6 @@
#include <QtConcurrentRun>
#include <libcockatrice/card/database/card_database.h>
#include <libcockatrice/card/database/card_database_manager.h>
#include <libcockatrice/card/import/card_name_normalizer.h>
#include <libcockatrice/deck_list/deck_list.h>
#include <libcockatrice/deck_list/tree/deck_list_card_node.h>
@ -43,7 +42,7 @@ DeckLoader::loadFromFile(const QString &fileName, DeckFileFormat::Format fmt, bo
DeckList deckList;
switch (fmt) {
case DeckFileFormat::PlainText:
result = deckList.loadFromFile_Plain(&file, CardNameNormalizer());
result = deckList.loadFromFile_Plain(&file);
break;
case DeckFileFormat::Cockatrice: {
result = deckList.loadFromFile_Native(&file);
@ -51,7 +50,7 @@ DeckLoader::loadFromFile(const QString &fileName, DeckFileFormat::Format fmt, bo
qCInfo(DeckLoaderLog) << "Failed to load " << fileName
<< "as cockatrice format; retrying as plain format";
file.seek(0);
result = deckList.loadFromFile_Plain(&file, CardNameNormalizer());
result = deckList.loadFromFile_Plain(&file);
fmt = DeckFileFormat::PlainText;
}
break;

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