diff --git a/.ci/Debian11/Dockerfile b/.ci/Debian11/Dockerfile
deleted file mode 100644
index b994863bf..000000000
--- a/.ci/Debian11/Dockerfile
+++ /dev/null
@@ -1,26 +0,0 @@
-FROM debian:11
-
-RUN apt-get update && \
- DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
- build-essential \
- ccache \
- clang-format \
- cmake \
- file \
- g++ \
- git \
- liblzma-dev \
- libmariadb-dev-compat \
- libprotobuf-dev \
- libqt5multimedia5-plugins \
- libqt5sql5-mysql \
- libqt5svg5-dev \
- libqt5websockets5-dev \
- ninja-build \
- protobuf-compiler \
- qt5-image-formats-plugins \
- qtmultimedia5-dev \
- qttools5-dev \
- qttools5-dev-tools \
- && apt-get clean \
- && rm -rf /var/lib/apt/lists/*
diff --git a/.ci/Fedora42/Dockerfile b/.ci/Fedora44/Dockerfile
similarity index 95%
rename from .ci/Fedora42/Dockerfile
rename to .ci/Fedora44/Dockerfile
index ee4a856f2..e6c8da7f3 100644
--- a/.ci/Fedora42/Dockerfile
+++ b/.ci/Fedora44/Dockerfile
@@ -1,4 +1,4 @@
-FROM fedora:42
+FROM fedora:44
RUN dnf install -y \
ccache \
diff --git a/.ci/Servatrice_Debian11/Dockerfile b/.ci/Servatrice_Debian12/Dockerfile
similarity index 75%
rename from .ci/Servatrice_Debian11/Dockerfile
rename to .ci/Servatrice_Debian12/Dockerfile
index fadc9e0e7..21f6a036e 100644
--- a/.ci/Servatrice_Debian11/Dockerfile
+++ b/.ci/Servatrice_Debian12/Dockerfile
@@ -1,4 +1,4 @@
-FROM debian:11
+FROM debian:12
RUN apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
@@ -11,11 +11,11 @@ RUN apt-get update && \
git \
libmariadb-dev-compat \
libprotobuf-dev \
- libqt5sql5-mysql \
- libqt5websockets5-dev \
+ libqt6sql6-mysql \
ninja-build \
protobuf-compiler \
- qttools5-dev \
- qttools5-dev-tools \
+ qt6-tools-dev \
+ qt6-tools-dev-tools \
+ qt6-websockets-dev \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
diff --git a/.ci/Ubuntu22.04/Dockerfile b/.ci/Ubuntu22.04/Dockerfile
deleted file mode 100644
index 93c8fdea9..000000000
--- a/.ci/Ubuntu22.04/Dockerfile
+++ /dev/null
@@ -1,26 +0,0 @@
-FROM ubuntu:22.04
-
-RUN apt-get update && \
- DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
- build-essential \
- ccache \
- clang-format \
- cmake \
- file \
- g++ \
- git \
- liblzma-dev \
- libmariadb-dev-compat \
- libprotobuf-dev \
- libqt5multimedia5-plugins \
- libqt5sql5-mysql \
- libqt5svg5-dev \
- libqt5websockets5-dev \
- ninja-build \
- protobuf-compiler \
- qt5-image-formats-plugins \
- qtmultimedia5-dev \
- qttools5-dev \
- qttools5-dev-tools \
- && apt-get clean \
- && rm -rf /var/lib/apt/lists/*
diff --git a/.ci/compile.sh b/.ci/compile.sh
index 7ebdd6e4e..ee846897b 100755
--- a/.ci/compile.sh
+++ b/.ci/compile.sh
@@ -203,7 +203,11 @@ if [[ $RUNNER_OS == macOS ]]; then
arch="x64"
fi
mkdir -p "$triplets_dir"
- cp "../vcpkg/triplets/$arch-osx.cmake" "$triplet_file"
+ triplet_source="../vcpkg/triplets/$arch-osx.cmake"
+ if [[ ! -f "$triplet_source" ]]; then
+ triplet_source="../vcpkg/triplets/community/$arch-osx.cmake"
+ fi
+ cp "$triplet_source" "$triplet_file"
echo "set(VCPKG_CMAKE_SYSTEM_VERSION $TARGET_MACOS_VERSION)" >>"$triplet_file"
echo "set(VCPKG_OSX_DEPLOYMENT_TARGET $TARGET_MACOS_VERSION)" >>"$triplet_file"
flags+=("-DVCPKG_OVERLAY_TRIPLETS=$triplets_dir")
diff --git a/.ci/lint_cpp.sh b/.ci/lint_cpp.sh
index cfb1e1f07..9786a83fc 100755
--- a/.ci/lint_cpp.sh
+++ b/.ci/lint_cpp.sh
@@ -13,17 +13,9 @@ fi
# Check formatting using format.sh
echo "Checking your code using format.sh..."
-diff="$(./format.sh --diff --cmake --shell --print-version --branch origin/master)"
+./format.sh --color-diff --cmake --shell --print-version --branch origin/master
err=$?
-sep="
-----------
-"
-used_version="${diff%%"$sep"*}"
-diff="${diff#*"$sep"}"
-changes_to_make="${diff%%"$sep"*}"
-files_to_edit="${diff#*"$sep"}"
-
case $err in
1)
cat < is the markdown comment escape sequence, emojis are way better
generated_list="${generated_list//-->/→}"
+ # Escape & to preserve it from commit message into markdown output
+ generated_list="${generated_list//&/\\&}"
body="${body//--REPLACE-WITH-GENERATED-LIST--/$generated_list}"
body="${body//--REPLACE-WITH-COMMIT-COUNT--/$count}"
body="${body//--REPLACE-WITH-PREVIOUS-RELEASE-TAG--/$previous}"
diff --git a/.ci/release_template.md b/.ci/release_template.md
index 1ce9f4bf7..ac78a193a 100644
--- a/.ci/release_template.md
+++ b/.ci/release_template.md
@@ -19,12 +19,10 @@ Available pre-compiled binaries for installation:
Linux
• Ubuntu 26.04 LTSResolute Racoon
• Ubuntu 24.04 LTSNoble Numbat
- • Ubuntu 22.04 LTSJammy Jellyfish
• Debian 13Trixie
• Debian 12Bookworm
- • Debian 11Bullseye
+ • Fedora 44
• Fedora 43
- • Fedora 42We are also packaged in Arch Linux's official extra repository, courtesy of @FFY00.General Linux support is available via a flatpak package at Flathub!
@@ -85,7 +83,6 @@ Remove empty headers when done.
### Under the Hood
### Oracle
### Servatrice
-### Webatrice
diff --git a/.clang-format b/.clang-format
index 1ef3ca6db..1db97481a 100644
--- a/.clang-format
+++ b/.clang-format
@@ -3,7 +3,9 @@ AccessModifierOffset: -4
ColumnLimit: 120
---
Language: Cpp
-BreakBeforeBraces: Custom
+AllowAllParametersOfDeclarationOnNextLine: false
+AllowShortFunctionsOnASingleLine: None
+BinPackParameters: false
BraceWrapping:
AfterClass: true
AfterControlStatement: false
@@ -18,16 +20,14 @@ BraceWrapping:
SplitEmptyFunction: true
SplitEmptyRecord: true
SplitEmptyNamespace: true
-AllowShortFunctionsOnASingleLine: None
-BinPackParameters: false
-AllowAllParametersOfDeclarationOnNextLine: false
-IndentCaseLabels: true
-PointerAlignment: Right
-SortIncludes: true
+BreakBeforeBraces: Custom
IncludeBlocks: Regroup
+IndentCaseLabels: true
+InsertBraces: true
+PointerAlignment: Right
+RemoveSemicolon: true
+SortIncludes: true
StatementAttributeLikeMacros: [emit]
-# requires clang-format 16
-# RemoveSemicolon: true
---
Language: Proto
AllowShortFunctionsOnASingleLine: None
diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
index b293b3937..56ad64283 100644
--- a/.github/CONTRIBUTING.md
+++ b/.github/CONTRIBUTING.md
@@ -209,6 +209,16 @@ nowadays and clean it up for you.
Lines should be 120 characters or less. Please break up lines that are too long
into smaller parts, for example at spaces or after opening a brace.
+### Documentation Comments ###
+
+Use [Doxygen](https://www.doxygen.nl/) for code documentation:
+
+- **Doc blocks**: Use `/** @brief Description */` (Javadoc-style), not `///`
+- **Member comments**: Use trailing `///<` for inline member documentation
+- **TODOs**: Use `//! \todo Description` (Qt-style), Doxygen collects them into a Todo List
+ (uses [Qt-style comments](https://www.doxygen.nl/manual/docblocks.html) with
+ Doxygen's [\todo command](https://www.doxygen.nl/manual/commands.html#cmdtodo))
+
### Memory Management ###
New code should be written using references over pointers and stack allocation
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 88bed3663..404cac62e 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -10,7 +10,7 @@ updates:
# 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)
+ # Ignore updates for vcpkg (Bump to latest tag not working (no SemVer used)
- dependency-name: "vcpkg"
# Check for updates once a month
schedule:
@@ -39,13 +39,3 @@ updates:
interval: "weekly"
# Limit the amout of open PR's (default = 5, disabled = 0, security updates are not impacted)
open-pull-requests-limit: 2
-
- # # Enable version updates for npm
- # - package-ecosystem: "npm"
- # # Look for `package.json` and `lock` files in the `webclient` subdirectory
- # directory: "/webclient"
- # # Check the npm registry for updates once a week
- # schedule:
- # interval: "weekly"
- # # Limit the amout of open PR's (default = 5, disabled = 0, security updates are not impacted)
- # open-pull-requests-limit: 5
diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml
index aeed5da81..19c9a15e3 100644
--- a/.github/workflows/desktop-build.yml
+++ b/.github/workflows/desktop-build.yml
@@ -1,10 +1,10 @@
name: Build Desktop
permissions:
+ actions: write # needed to delete entries in GHA cache (update ccache)
+ attestations: write # needed to persist the attestation.
contents: write
- id-token: write
- attestations: write
- actions: write # needed for ccache action to be able to delete gha caches
+ id-token: write # needed for signing certificate in attestation
on:
push:
@@ -14,14 +14,12 @@ on:
- '*/**' # matches all files not in root
- '!**.md'
- '!.github/**'
- - '!.husky/**'
- '!.tx/**'
- '!doc/**'
- - '!webclient/**'
- '.github/workflows/desktop-build.yml'
- 'CMakeLists.txt'
- 'vcpkg.json'
- - 'vcpkg'
+ - 'vcpkg' # needed to match submodule bumps (gitlink)
tags:
- '*'
pull_request:
@@ -29,14 +27,12 @@ on:
- '*/**' # matches all files not in root
- '!**.md'
- '!.github/**'
- - '!.husky/**'
- '!.tx/**'
- '!doc/**'
- - '!webclient/**'
- '.github/workflows/desktop-build.yml'
- 'CMakeLists.txt'
- 'vcpkg.json'
- - 'vcpkg'
+ - 'vcpkg' # needed to match submodule bumps (gitlink)
# Cancel earlier, unfinished runs of this workflow on the same branch (unless on release)
concurrency:
@@ -46,13 +42,13 @@ concurrency:
jobs:
configure:
name: Configure
- runs-on: ubuntu-latest
+ runs-on: ubuntu-slim
outputs:
- tag: ${{steps.configure.outputs.tag}}
- sha: ${{steps.configure.outputs.sha}}
+ tag: ${{ steps.configure.outputs.tag }}
+ sha: ${{ steps.configure.outputs.sha }}
steps:
- - name: Configure
+ - name: "Configure"
id: configure
shell: bash
run: |
@@ -68,154 +64,150 @@ jobs:
fi
echo "sha=$sha" >>"$GITHUB_OUTPUT"
- - name: Checkout
+ - name: "Checkout"
if: steps.configure.outputs.tag != null
uses: actions/checkout@v6
with:
- fetch-depth: 0
+ fetch-depth: 0 # fetch all history for all branches and tags
- - name: Prepare release parameters
+ - name: "Prepare release parameters"
id: prepare
if: steps.configure.outputs.tag != null
shell: bash
env:
- TAG: ${{steps.configure.outputs.tag}}
+ TAG: ${{ steps.configure.outputs.tag }}
run: .ci/prep_release.sh
- - name: Create release
+ - name: "Create release"
if: steps.configure.outputs.tag != null
id: create_release
shell: bash
env:
- GH_TOKEN: ${{github.token}}
- tag_name: ${{steps.configure.outputs.tag}}
- target: ${{steps.configure.outputs.sha}}
- release_name: ${{steps.prepare.outputs.title}}
- body_path: ${{steps.prepare.outputs.body_path}}
- prerelease: ${{steps.prepare.outputs.is_beta}}
+ GH_TOKEN: ${{ github.token }}
+ tag_name: ${{ steps.configure.outputs.tag }}
+ target: ${{ steps.configure.outputs.sha }}
+ release_name: ${{ steps.prepare.outputs.title }}
+ body_path: ${{ steps.prepare.outputs.body_path }}
+ prerelease: ${{ steps.prepare.outputs.is_beta }}
run: |
- if [[ $prerelease == yes ]]; then
- args="--prerelease"
- fi
- gh release create "$tag_name" --draft --verify-tag $args \
- --target "$target" --title "$release_name" \
- --notes-file "$body_path"
+ args=()
+ [[ $prerelease == yes ]] && args+=(--prerelease)
+
+ gh release create "$tag_name" --verify-tag --draft "${args[@]}" \
+ --target "$target" \
+ --title "$release_name" \
+ --notes-file "$body_path"
build-linux:
strategy:
fail-fast: false
matrix:
- # These names correspond to the files in ".ci/$distro$version"
+ # The files in ".ci/$distro$version" correspond to the values given here
include:
- distro: Arch
- package: skip # We are packaged in Arch already
+
allow-failure: yes
-
- - distro: Debian
- version: 11
- package: DEB
+ package: skip # We are packaged in Arch already
- distro: Servatrice_Debian
- version: 11
+ version: 12
+
package: DEB
- test: skip
server_only: yes
+ test: skip
- distro: Debian
version: 12
+
package: DEB
test: skip # Running tests on all distros is superfluous
- distro: Debian
version: 13
+
package: DEB
- - distro: Fedora
- version: 42
- package: RPM
- test: skip # Running tests on all distros is superfluous
-
- distro: Fedora
version: 43
+
+ package: RPM
+ test: skip # Running tests on all distros is superfluous
+
+ - distro: Fedora
+ version: 44
+
package: RPM
- distro: Ubuntu
- version: 22.04
+ version: 24.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}}
+ name: ${{ matrix.distro }} ${{ matrix.version }}
needs: configure
runs-on: ubuntu-latest
- continue-on-error: ${{matrix.allow-failure == 'yes'}}
+ continue-on-error: ${{ matrix.allow-failure == 'yes' }}
timeout-minutes: 70
env:
- 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
+ CACHE: ${{ github.workspace }}/.cache/${{ matrix.distro }}${{ matrix.version }} # directory for caching docker image and ccache
CCACHE_EVICTION_AGE: 7d
+ CCACHE_SIZE: 550M # space of all repo is 10Gi: https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#usage-limits-and-eviction-policy
CMAKE_GENERATOR: 'Ninja'
+ NAME: ${{ matrix.distro }}${{ matrix.version }}
steps:
- - name: Checkout
+ - name: "Checkout"
uses: actions/checkout@v6
- - name: Restore compiler cache (ccache)
+ - name: "Restore compiler cache (ccache)"
id: ccache_restore
uses: actions/cache/restore@v5
env:
BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
with:
- path: ${{env.CACHE}}
- key: ccache-${{matrix.distro}}${{matrix.version}}-${{env.BRANCH_NAME}}
- restore-keys: ccache-${{matrix.distro}}${{matrix.version}}-
+ key: ccache-${{ matrix.distro }}${{ matrix.version }}-${{ env.BRANCH_NAME }}
+ path: ${{ env.CACHE }}
+ restore-keys: ccache-${{ matrix.distro }}${{ matrix.version }}-
- - name: Build ${{matrix.distro}} ${{matrix.version}} Docker image
+ - name: "Build ${{ matrix.distro }} ${{ matrix.version }} Docker image"
shell: bash
run: source .ci/docker.sh --build
- - name: Build debug and test
+ - name: "Build debug and test"
if: matrix.test != 'skip'
shell: bash
run: |
source .ci/docker.sh
RUN --server --debug --test --ccache "$CCACHE_SIZE" \
- --cmake-generator "$CMAKE_GENERATOR"
+ --cmake-generator "$CMAKE_GENERATOR"
- - name: Build release package
+ - name: "Build release package"
id: build
if: matrix.package != 'skip'
shell: bash
env:
- SUFFIX: '-${{matrix.distro}}${{matrix.version}}'
- package: '${{matrix.package}}'
- server_only: '${{matrix.server_only}}'
+ SUFFIX: '-${{ matrix.distro }}${{ matrix.version }}'
+ package: '${{ matrix.package }}'
+ server_only: '${{ matrix.server_only }}'
run: |
source .ci/docker.sh
args=()
- if [[ $server_only == yes ]]; then
- args+=(--no-client)
- fi
- if [[ $GITHUB_REF == "refs/heads/master" ]]; then
- args+=(--evict-ccache "$CCACHE_EVICTION_AGE")
- fi
+ [[ $server_only == yes ]] && args+=(--no-client)
+ [[ $GITHUB_REF == "refs/heads/master" ]] && args+=(--evict-ccache "$CCACHE_EVICTION_AGE")
args+=(--ccache "$CCACHE_SIZE")
args+=(--cmake-generator "$CMAKE_GENERATOR")
args+=(--suffix "$SUFFIX")
+
RUN --server --release --package "$package" "${args[@]}"
# Delete used cache to emulate a ccache update. See https://github.com/actions/cache/issues/342
- - name: Delete remote compiler cache (ccache)
+ - name: "Delete remote compiler cache (ccache)"
if: github.ref == 'refs/heads/master' && steps.ccache_restore.outputs.cache-hit
continue-on-error: true
env:
@@ -225,47 +217,47 @@ jobs:
echo "Cache deleted successfully"
fi
- - name: Save updated compiler cache (ccache)
+ - name: "Save updated compiler cache (ccache)"
if: github.ref == 'refs/heads/master'
uses: actions/cache/save@v5
with:
- path: ${{env.CACHE}}
key: ${{ steps.ccache_restore.outputs.cache-primary-key }}
+ path: ${{ env.CACHE }}
- - name: Upload artifact
+ - name: "Upload artifact"
id: upload_artifact
if: matrix.package != 'skip'
uses: actions/upload-artifact@v7
with:
- path: ${{steps.build.outputs.path}}
archive: false
if-no-files-found: error
+ path: ${{ steps.build.outputs.path }}
- - name: Upload to release
+ - name: "Upload to release"
id: upload_release
if: matrix.package != 'skip' && needs.configure.outputs.tag != null
shell: bash
env:
- 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.fullname }}
+ asset_path: ${{ steps.build.outputs.path }}
+ GH_TOKEN: ${{ github.token }}
+ tag_name: ${{ needs.configure.outputs.tag }}
run: gh release upload "$tag_name" "$asset_path#$asset_name"
- - name: Attest binary provenance
+ - name: "Attest binary provenance"
id: attestation
if: steps.upload_release.outcome == 'success'
uses: actions/attest@v4
with:
- subject-path: ${{steps.build.outputs.path}}
show-summary: false
+ subject-path: ${{ steps.build.outputs.path }}
- - name: Verify binary attestation
+ - name: "Verify binary attestation"
if: steps.attestation.outcome == 'success'
shell: bash
env:
- GH_TOKEN: ${{github.token}}
- run: gh attestation verify ${{steps.build.outputs.path}} --repo Cockatrice/Cockatrice
+ GH_TOKEN: ${{ github.token }}
+ run: gh attestation verify "${{ steps.build.outputs.path }}" --repo Cockatrice/Cockatrice
build-vcpkg:
strategy:
@@ -275,200 +267,202 @@ jobs:
- os: macOS
target: 13
runner: macos-15-intel
- soc: Intel
- xcode: "16.4"
- type: Release
- override_target: 13
+
+ ccache_eviction_age: 7d
+ cmake_generator: Ninja
make_package: 1
+ override_target: 13
package_suffix: "-macOS13_Intel"
- qt_version: 6.11.*
+ qt_version: 6.11.0
qt_arch: clang_64
qt_modules: qtimageformats qtmultimedia qtwebsockets
- cmake_generator: Ninja
+ soc: Intel
+ type: Release
use_ccache: 1
- ccache_eviction_age: 7d
+ xcode: "16.4"
- os: macOS
target: 14
runner: macos-14
- soc: Apple
- xcode: "15.4"
- type: Release
+
+ ccache_eviction_age: 7d
+ cmake_generator: Ninja
make_package: 1
package_suffix: "-macOS14"
- qt_version: 6.11.*
+ qt_version: 6.11.0
qt_arch: clang_64
qt_modules: qtimageformats qtmultimedia qtwebsockets
- cmake_generator: Ninja
+ soc: Apple
+ type: Release
use_ccache: 1
- ccache_eviction_age: 7d
+ xcode: "15.4"
- os: macOS
target: 15
runner: macos-15
- soc: Apple
- xcode: "16.4"
- type: Release
+
+ ccache_eviction_age: 7d
+ cmake_generator: Ninja
make_package: 1
package_suffix: "-macOS15"
- qt_version: 6.11.*
+ qt_version: 6.11.0
qt_arch: clang_64
qt_modules: qtimageformats qtmultimedia qtwebsockets
- cmake_generator: Ninja
+ soc: Apple
+ type: Release
use_ccache: 1
- ccache_eviction_age: 7d
+ xcode: "16.4"
- os: macOS
target: 15
runner: macos-15
- soc: Apple
- xcode: "16.4"
- type: Debug
- qt_version: 6.11.*
+
+ ccache_eviction_age: 7d
+ cmake_generator: Ninja
+ qt_version: 6.11.0
qt_arch: clang_64
qt_modules: qtimageformats qtmultimedia qtwebsockets
- cmake_generator: Ninja
+ soc: Apple
+ type: Debug
use_ccache: 1
- ccache_eviction_age: 7d
+ xcode: "16.4"
- os: Windows
target: 10
runner: windows-2025
- type: Release
- make_package: 1
- package_suffix: "-Win10"
- qt_version: 6.11.*
- qt_arch: win64_msvc2022_64
- qt_modules: qtimageformats qtmultimedia qtwebsockets
+
cmake_generator: "Visual Studio 17 2022"
cmake_generator_platform: x64
+ make_package: 1
+ package_suffix: "-Win10"
+ qt_version: 6.11.0
+ qt_arch: win64_msvc2022_64
+ qt_modules: qtimageformats qtmultimedia qtwebsockets
+ type: Release
- name: ${{matrix.os}} ${{matrix.target}}${{ matrix.soc == 'Intel' && ' Intel' || '' }}${{ matrix.type == 'Debug' && ' Debug' || '' }}
+ name: ${{ matrix.os }} ${{ matrix.target }}${{ matrix.soc == 'Intel' && ' Intel' || '' }}${{ matrix.type == 'Debug' && ' Debug' || '' }}
needs: configure
- runs-on: ${{matrix.runner}}
+ runs-on: ${{ matrix.runner }}
timeout-minutes: 100
env:
- CCACHE_DIR: ${{github.workspace}}/.cache/
- # 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_DIR: ${{ github.workspace }}/.cache/
+ CCACHE_SIZE: 550M # space of all repo is 10Gi: https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#usage-limits-and-eviction-policy
steps:
- - name: Checkout
+ - name: "Checkout"
uses: actions/checkout@v6
with:
submodules: recursive
- - name: Add msbuild to PATH
+ - name: "[Windows] Add msbuild to PATH"
if: matrix.os == 'Windows'
id: add-msbuild
uses: microsoft/setup-msbuild@v3
with:
msbuild-architecture: x64
- - name: Setup ccache
- if: matrix.use_ccache == 1 && matrix.os == 'macOS'
+ - name: "[macOS] Setup ccache"
+ if: matrix.os == 'macOS' && matrix.use_ccache == 1
run: brew install ccache
- - name: Restore compiler cache (ccache)
- if: matrix.use_ccache == 1
+ - name: "[macOS] Restore compiler cache (ccache)"
+ if: matrix.os == 'macOS' && matrix.use_ccache == 1
id: ccache_restore
uses: actions/cache/restore@v5
env:
BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
with:
- path: ${{env.CCACHE_DIR}}
- key: ccache-${{matrix.runner}}-${{matrix.soc}}-${{matrix.type}}-${{env.BRANCH_NAME}}
- restore-keys: ccache-${{matrix.runner}}-${{matrix.soc}}-${{matrix.type}}-
+ key: ccache-${{ matrix.runner }}-${{ matrix.soc }}-${{ matrix.type }}-${{ env.BRANCH_NAME }}
+ path: ${{ env.CCACHE_DIR }}
+ restore-keys: ccache-${{ matrix.runner }}-${{ matrix.soc }}-${{ matrix.type }}-
- - name: Install aqtinstall
+ - name: "Install aqtinstall"
run: pipx install aqtinstall
# Resolve given wildcard versions (e.g. Qt 6.6.*) to latest version via aqtinstall to avoid stale caches on new releases
- - name: Resolve latest Qt patch version
+ - name: "Resolve latest Qt patch version"
id: resolve_qt_version
shell: bash
- run: .ci/resolve_latest_aqt_qt_version.sh "${{matrix.qt_version}}"
+ run: .ci/resolve_latest_aqt_qt_version.sh "${{ matrix.qt_version }}"
- - name: Restore thin Qt ${{ steps.resolve_qt_version.outputs.version }} libraries (${{ matrix.soc }} macOS)
+ - name: "[macOS] Restore thin Qt ${{ steps.resolve_qt_version.outputs.version }} libraries"
if: matrix.os == 'macOS'
id: restore_qt
uses: actions/cache/restore@v5
with:
- path: ${{ github.workspace }}/Qt
key: thin-qt-macos-${{ matrix.soc }}-${{ steps.resolve_qt_version.outputs.version }}
+ path: ${{ github.workspace }}/Qt
# Using jurplel/install-qt-action to install Qt without using brew
- # qt build using vcpkg either just fails or takes too long to build
- - name: Install fat Qt ${{ steps.resolve_qt_version.outputs.version }} (${{ matrix.soc }} macOS)
+ # Qt build using vcpkg either just fails or takes too long to build
+ - name: "[macOS] Install fat Qt ${{ steps.resolve_qt_version.outputs.version }}"
if: matrix.os == 'macOS' && steps.restore_qt.outputs.cache-hit != 'true'
uses: jurplel/install-qt-action@v4
with:
- version: ${{ steps.resolve_qt_version.outputs.version }}
- arch: ${{matrix.qt_arch}}
- modules: ${{matrix.qt_modules}}
+ arch: ${{ matrix.qt_arch }}
cache: false
- dir: ${{github.workspace}}
+ dir: ${{ github.workspace }}
+ modules: ${{ matrix.qt_modules }}
+ version: ${{ steps.resolve_qt_version.outputs.version }}
- - name: Thin Qt libraries (${{ matrix.soc }} macOS)
+ - name: "[macOS] Create thin Qt libraries"
if: matrix.os == 'macOS' && steps.restore_qt.outputs.cache-hit != 'true'
run: .ci/thin_macos_qtlib.sh
- - name: Cache thin Qt libraries (${{ matrix.soc }} macOS)
+ - name: "[macOS] Cache thin Qt libraries"
if: matrix.os == 'macOS' && steps.restore_qt.outputs.cache-hit != 'true'
uses: actions/cache/save@v5
with:
- path: ${{ github.workspace }}/Qt
key: thin-qt-macos-${{ matrix.soc }}-${{ steps.resolve_qt_version.outputs.version }}
+ path: ${{ github.workspace }}/Qt
- - name: Install Qt ${{matrix.qt_version}} (Windows)
+ - name: "[Windows] Install Qt ${{ matrix.qt_version }}"
if: matrix.os == 'Windows'
uses: jurplel/install-qt-action@v4
with:
- # qt 6.11.0 only works with aqtinstall directly from git until aqtinstall 3.4 is released
+ # Qt 6.11.0 only works with aqtinstall directly from git until aqtinstall 3.4 is released
aqtsource: git+https://github.com/miurahr/aqtinstall.git
- version: ${{ steps.resolve_qt_version.outputs.version }}
- arch: ${{matrix.qt_arch}}
- modules: ${{matrix.qt_modules}}
+ arch: ${{ matrix.qt_arch }}
cache: true
+ modules: ${{ matrix.qt_modules }}
+ version: ${{ steps.resolve_qt_version.outputs.version }}
- - name: Install NSIS
+ - name: "[Windows] Install NSIS"
if: matrix.os == 'Windows'
shell: bash
run: choco install nsis
- - name: Setup vcpkg cache
+ - name: "Setup vcpkg cache"
id: vcpkg-cache
uses: TAServers/vcpkg-cache@v3
with:
token: ${{ secrets.GITHUB_TOKEN }}
- # uses environment variables, see compile.sh for more details
- - name: Build Cockatrice
+ # Uses environment variables, see compile.sh for more details
+ - name: "Build Cockatrice"
id: build
shell: bash
env:
- BUILDTYPE: '${{matrix.type}}'
- MAKE_PACKAGE: '${{matrix.make_package}}'
- PACKAGE_SUFFIX: '${{matrix.package_suffix}}'
- CMAKE_GENERATOR: ${{matrix.cmake_generator}}
- CMAKE_GENERATOR_PLATFORM: ${{matrix.cmake_generator_platform}}
- USE_CCACHE: ${{matrix.use_ccache}}
- VCPKG_DISABLE_METRICS: 1
- VCPKG_BINARY_SOURCES: 'clear;files,${{ steps.vcpkg-cache.outputs.path }},readwrite'
- # macOS-specific environment variables, will be ignored on Windows
- MACOS_CERTIFICATE: ${{ secrets.PROD_MACOS_CERTIFICATE }}
- MACOS_CERTIFICATE_PWD: ${{ secrets.PROD_MACOS_CERTIFICATE_PWD }}
- MACOS_CERTIFICATE_NAME: ${{ secrets.PROD_MACOS_CERTIFICATE_NAME }}
- MACOS_CI_KEYCHAIN_PWD: ${{ secrets.PROD_MACOS_CI_KEYCHAIN_PWD }}
- DEVELOPER_DIR: '/Applications/Xcode_${{matrix.xcode}}.app/Contents/Developer'
- TARGET_MACOS_VERSION: ${{ matrix.override_target }}
+ BUILDTYPE: '${{ matrix.type }}'
CCACHE_EVICTION_AGE: ${{ matrix.ccache_eviction_age }}
+ CMAKE_GENERATOR: ${{ matrix.cmake_generator }}
+ CMAKE_GENERATOR_PLATFORM: ${{ matrix.cmake_generator_platform }}
+ DEVELOPER_DIR: '/Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer'
+ MACOS_CERTIFICATE: ${{ secrets.PROD_MACOS_CERTIFICATE }}
+ MACOS_CERTIFICATE_NAME: ${{ secrets.PROD_MACOS_CERTIFICATE_NAME }}
+ MACOS_CERTIFICATE_PWD: ${{ secrets.PROD_MACOS_CERTIFICATE_PWD }}
+ MACOS_CI_KEYCHAIN_PWD: ${{ secrets.PROD_MACOS_CI_KEYCHAIN_PWD }}
+ MAKE_PACKAGE: '${{ matrix.make_package }}'
+ PACKAGE_SUFFIX: '${{ matrix.package_suffix }}'
+ TARGET_MACOS_VERSION: ${{ matrix.override_target }}
+ USE_CCACHE: ${{ matrix.use_ccache }}
+ VCPKG_BINARY_SOURCES: 'clear;files,${{ steps.vcpkg-cache.outputs.path }},readwrite'
+ VCPKG_DISABLE_METRICS: 1
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
+ - name: "[macOS] Delete remote compiler cache (ccache)"
+ if: matrix.os == 'macOS' && matrix.use_ccache == 1 && github.ref == 'refs/heads/master' && steps.ccache_restore.outputs.cache-hit
continue-on-error: true
env:
GH_TOKEN: ${{ github.token }}
@@ -477,14 +471,14 @@ jobs:
echo "Cache deleted successfully"
fi
- - name: Save updated compiler cache (ccache)
- if: github.ref == 'refs/heads/master' && matrix.use_ccache == 1
+ - name: "[macOS] Save updated compiler cache (ccache)"
+ if: matrix.os == 'macOS' && matrix.use_ccache == 1 && github.ref == 'refs/heads/master'
uses: actions/cache/save@v5
with:
- path: ${{env.CCACHE_DIR}}
key: ${{ steps.ccache_restore.outputs.cache-primary-key }}
+ path: ${{ env.CCACHE_DIR }}
- - name: Sign app bundle
+ - name: "[macOS] Sign app bundle"
if: matrix.os == 'macOS' && matrix.make_package && needs.configure.outputs.tag != null
id: sign_macos
env:
@@ -494,15 +488,15 @@ jobs:
if [[ -n "$MACOS_CERTIFICATE_NAME" ]]
then
security unlock-keychain -p "$MACOS_CI_KEYCHAIN_PWD" build.keychain
- /usr/bin/codesign --sign="$MACOS_CERTIFICATE_NAME" --entitlements=".ci/macos.entitlements" --options=runtime --force --deep --timestamp --verbose ${{steps.build.outputs.path}}
+ /usr/bin/codesign --sign="$MACOS_CERTIFICATE_NAME" --entitlements=".ci/macos.entitlements" --options=runtime --force --deep --timestamp --verbose "${{ steps.build.outputs.path }}"
fi
- - name: Notarize app bundle
- if: steps.sign_macos.outcome == 'success'
+ - name: "[macOS] Notarize app bundle"
+ if: matrix.os == 'macOS' && steps.sign_macos.outcome == 'success'
env:
MACOS_NOTARIZATION_APPLE_ID: ${{ secrets.PROD_MACOS_NOTARIZATION_APPLE_ID }}
- MACOS_NOTARIZATION_TEAM_ID: ${{ secrets.PROD_MACOS_NOTARIZATION_TEAM_ID }}
MACOS_NOTARIZATION_PWD: ${{ secrets.PROD_MACOS_NOTARIZATION_PWD }}
+ MACOS_NOTARIZATION_TEAM_ID: ${{ secrets.PROD_MACOS_NOTARIZATION_TEAM_ID }}
run: |
if [[ -n "$MACOS_NOTARIZATION_APPLE_ID" ]]
then
@@ -514,7 +508,7 @@ jobs:
# Therefore, we create a zip file containing our app bundle, so that we can send it to the
# notarization service
echo "Creating temp notarization archive"
- ditto -c -k --keepParent ${{steps.build.outputs.path}} "notarization.zip"
+ ditto -c -k --keepParent "${{ steps.build.outputs.path }}" "notarization.zip"
# Here we send the notarization request to the Apple's Notarization service, waiting for the result.
# This typically takes a few seconds inside a CI environment, but it might take more depending on the App
@@ -526,51 +520,51 @@ jobs:
# Finally, we need to "attach the staple" to our executable, which will allow our app to be
# validated by macOS even when an internet connection is not available.
echo "Attach staple"
- xcrun stapler staple ${{steps.build.outputs.path}}
+ xcrun stapler staple "${{ steps.build.outputs.path }}"
fi
- - name: Upload artifact
+ - name: "Upload artifact"
if: matrix.make_package
id: upload_artifact
uses: actions/upload-artifact@v7
with:
- path: ${{steps.build.outputs.path}}
archive: false
if-no-files-found: error
+ path: ${{ steps.build.outputs.path }}
- - name: Upload PDBs (Program Databases)
+ - name: "[Windows] Upload PDBs (Program Databases)"
if: matrix.os == 'Windows' && github.ref_type != 'tag'
uses: actions/upload-artifact@v7
with:
- name: ${{steps.build.outputs.name}}-PDBs
+ if-no-files-found: error
+ name: ${{ steps.build.outputs.name }}-PDBs
path: |
build/cockatrice/Release/*.pdb
build/oracle/Release/*.pdb
build/servatrice/Release/*.pdb
- if-no-files-found: error
- - name: Upload to release
+ - name: "Upload to release"
if: needs.configure.outputs.tag != null && matrix.make_package == '1'
id: upload_release
shell: bash
env:
- 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.fullname }}
+ asset_path: ${{ steps.build.outputs.path }}
+ GH_TOKEN: ${{ github.token }}
+ tag_name: ${{ needs.configure.outputs.tag }}
run: gh release upload "$tag_name" "$asset_path#$asset_name"
- - name: Attest binary provenance
+ - name: "Attest binary provenance"
if: steps.upload_release.outcome == 'success'
id: attestation
uses: actions/attest@v4
with:
- subject-path: ${{steps.build.outputs.path}}
show-summary: false
+ subject-path: ${{ steps.build.outputs.path }}
- - name: Verify binary attestation
+ - name: "Verify binary attestation"
if: steps.attestation.outcome == 'success'
shell: bash
env:
- GH_TOKEN: ${{github.token}}
- run: gh attestation verify ${{steps.build.outputs.path}} --repo Cockatrice/Cockatrice
+ GH_TOKEN: ${{ github.token }}
+ run: gh attestation verify "${{ steps.build.outputs.path }}" --repo Cockatrice/Cockatrice
diff --git a/.github/workflows/desktop-lint.yml b/.github/workflows/desktop-lint.yml
index 433f302a5..54931933c 100644
--- a/.github/workflows/desktop-lint.yml
+++ b/.github/workflows/desktop-lint.yml
@@ -1,17 +1,15 @@
name: Code Style (C++)
on:
- # push trigger not needed for linting, we do not allow direct pushes to master
+ # Push trigger not needed for linting, we do not allow direct pushes to master
pull_request:
paths:
- '*/**' # matches all files not in root
- '!**.md'
- '!.ci/**'
- '!.github/**'
- - '!.husky/**'
- '!.tx/**'
- '!doc/**'
- - '!webclient/**'
- '.ci/lint_cpp.sh'
- '.github/workflows/desktop-lint.yml'
- '.clang-format'
@@ -20,20 +18,23 @@ on:
jobs:
format:
- runs-on: ubuntu-22.04
+ runs-on: ubuntu-slim
steps:
- - name: Checkout
+ - 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
+ - name: "Install dependencies"
shell: bash
run: |
sudo apt-get update
- sudo apt-get install -y --no-install-recommends clang-format cmake-format shellcheck
+ sudo apt-get install -y --no-install-recommends \
+ clang-format \
+ cmake-format \
+ shellcheck
- - name: Check code formatting
+ - name: "Check code formatting"
shell: bash
run: ./.ci/lint_cpp.sh
diff --git a/.github/workflows/docker-release.yml b/.github/workflows/docker-release.yml
index fca1e97d4..d9ff06282 100644
--- a/.github/workflows/docker-release.yml
+++ b/.github/workflows/docker-release.yml
@@ -1,9 +1,11 @@
name: Build Docker Image
+permissions:
+ contents: read
+ packages: write
+
on:
push:
- tags:
- - '*Release*'
branches:
- master
pull_request:
@@ -12,61 +14,64 @@ on:
paths:
- '.github/workflows/docker-release.yml'
- 'Dockerfile'
+ release:
+ types:
+ - released # publishing of stable releases
# Cancel earlier, unfinished runs of this workflow on the same branch (unless on release)
concurrency:
group: "${{ github.workflow }} @ ${{ github.ref_name }}"
- cancel-in-progress: ${{ github.ref_type != 'tag' }}
+ cancel-in-progress: ${{ github.event_name != 'release' }}
jobs:
docker:
name: amd64 & arm64
+ if: ${{ github.repository_owner == 'Cockatrice' }}
runs-on: ubuntu-latest
- permissions:
- contents: read
- packages: write
-
+
steps:
- - name: Checkout
+ - name: "Checkout"
uses: actions/checkout@v6
- - name: Docker metadata
+ - name: "Docker metadata"
id: metadata
uses: docker/metadata-action@v6
+ env:
+ DOCKER_METADATA_ANNOTATIONS_LEVELS: index # needed for GHCR
with:
+ annotations: |
+ org.opencontainers.image.title=Servatrice
+ org.opencontainers.image.url=https://cockatrice.github.io/
+ org.opencontainers.image.description=Server for Cockatrice, a cross-platform virtual tabletop for multiplayer card games
images: |
ghcr.io/cockatrice/servatrice
labels: |
org.opencontainers.image.title=Servatrice
org.opencontainers.image.url=https://cockatrice.github.io/
org.opencontainers.image.description=Server for Cockatrice, a cross-platform virtual tabletop for multiplayer card games
- annotations: |
- org.opencontainers.image.title=Servatrice
- org.opencontainers.image.url=https://cockatrice.github.io/
- org.opencontainers.image.description=Server for Cockatrice, a cross-platform virtual tabletop for multiplayer card games
- - name: Set up QEMU
+ - name: "Set up QEMU"
uses: docker/setup-qemu-action@v4
- - name: Set up Docker buildx
+ - name: "Set up Docker buildx"
uses: docker/setup-buildx-action@v4
- - name: Login to GitHub Container Registry
- if: github.ref_type == 'tag'
+ - name: "Login to GitHub Container Registry"
+ if: contains(github.event.release.tag_name, 'Release') && github.event.release.target_commitish == 'master'
uses: docker/login-action@v4
with:
+ password: ${{ github.token }}
registry: ghcr.io
username: ${{ github.actor }}
- password: ${{ github.token }}
- - name: Build and push Docker image
+ - name: "Build and push Docker image"
uses: docker/build-push-action@v7
with:
- context: .
- platforms: linux/amd64,linux/arm64
- push: ${{ github.ref_type == 'tag' }}
- tags: ${{ steps.metadata.outputs.tags }}
- labels: ${{ steps.metadata.outputs.labels }}
annotations: ${{ steps.metadata.outputs.annotations }}
cache-from: type=gha,scope=servatrice
cache-to: type=gha,mode=max,scope=servatrice
+ context: .
+ labels: ${{ steps.metadata.outputs.labels }}
+ platforms: linux/amd64,linux/arm64
+ push: ${{ github.ref_type == 'tag' }}
+ tags: ${{ steps.metadata.outputs.tags }}
diff --git a/.github/workflows/documentation-build.yml b/.github/workflows/documentation-build.yml
index ce331d113..717999d5a 100644
--- a/.github/workflows/documentation-build.yml
+++ b/.github/workflows/documentation-build.yml
@@ -1,18 +1,18 @@
name: Generate Docs
on:
- push:
- tags:
- - '*' # Only re-generate docs when a new tagged version is pushed
pull_request:
paths:
- 'doc/doxygen/**'
- '.github/workflows/documentation-build.yml'
- 'Doxyfile'
+ release:
+ types:
+ - published # publishing of stable releases and pre-releases
workflow_dispatch:
env:
- COCKATRICE_REF: ${{ github.ref_name }} # Tag name if the commit is tagged, otherwise branch name
+ COCKATRICE_REF: ${{ github.ref_name }} # tag name if the commit is tagged, otherwise branch name
jobs:
docs:
@@ -20,22 +20,22 @@ jobs:
runs-on: ubuntu-latest
steps:
- - name: Checkout code
+ - name: "Checkout code"
uses: actions/checkout@v6
with:
submodules: recursive
- - name: Install Graphviz
+ - name: "Install Graphviz"
run: |
sudo apt-get install -y graphviz
dot -V
- - name: Install Doxygen
+ - name: "Install Doxygen"
uses: ssciwr/doxygen-install@v2
with:
- version: "1.14.0"
+ version: "1.16.1"
- - name: Update Doxygen Configuration
+ - name: "Update Doxygen Configuration"
run: |
git diff Doxyfile
doxygen -u Doxyfile
@@ -48,16 +48,16 @@ jobs:
exit 1
fi
- - name: Generate Documentation
+ - name: "Generate Documentation"
if: always()
run: doxygen Doxyfile
- - name: Deploy to cockatrice.github.io
- if: github.event_name != 'pull_request'
+ - name: "Deploy to cockatrice.github.io"
+ if: github.event_name == 'release' || github.event_name == 'workflow_dispatch'
uses: peaceiris/actions-gh-pages@v4
with:
deploy_key: ${{ secrets.DOCS_DEPLOY_KEY }}
+ destination_dir: docs # docs will be available at https://cockatrice.github.io/docs/
external_repository: Cockatrice/cockatrice.github.io
publish_branch: master
publish_dir: ./docs/html
- destination_dir: docs # Docs will live under https://cockatrice.github.io/docs/
diff --git a/.github/workflows/translations-pull.yml b/.github/workflows/translations-pull.yml
index ed61e3b19..057381f8a 100644
--- a/.github/workflows/translations-pull.yml
+++ b/.github/workflows/translations-pull.yml
@@ -1,14 +1,14 @@
name: Update Translations
on:
- workflow_dispatch:
- schedule:
- # runs in the middle of each month starting a quarter (UTC) = two weeks after new strings are built
- - cron: '0 0 15 1,4,7,10 *'
pull_request:
paths:
- '.tx/**'
- '.github/workflows/translations-pull.yml'
+ schedule:
+ # Runs in the middle of each month starting a quarter (UTC) = two weeks after new strings are built
+ - cron: '0 0 15 1,4,7,10 *'
+ workflow_dispatch:
jobs:
translations:
@@ -16,21 +16,21 @@ jobs:
if: github.event_name != 'schedule' || github.repository_owner == 'Cockatrice'
name: Pull languages
- runs-on: ubuntu-latest
+ runs-on: ubuntu-slim
steps:
- - name: Checkout repo
+ - name: "Checkout repo"
uses: actions/checkout@v6
- - name: Pull translated strings from Transifex
+ - name: "Pull translated strings from Transifex"
uses: transifex/cli-action@v2
with:
- # used config file: https://github.com/Cockatrice/Cockatrice/blob/master/.tx/config
- # https://github.com/transifex/cli#pulling-files-from-transifex
- token: ${{ secrets.TX_TOKEN }}
+ # Used config file: https://github.com/Cockatrice/Cockatrice/blob/master/.tx/config
+ # Docs: https://github.com/transifex/cli#pulling-files-from-transifex
args: pull --force --all
+ token: ${{ secrets.TX_TOKEN }}
- - name: Create pull request
+ - name: "Create pull request"
if: github.event_name != 'pull_request'
id: create_pr
uses: peter-evans/create-pull-request@v8
@@ -38,13 +38,7 @@ jobs:
add-paths: |
cockatrice/translations/*.ts
oracle/translations/*.ts
- webclient/public/locales/*/translation.json
- commit-message: Update translation files
- # author is the owner of the commit
- author: github-actions
- branch: ci-update_translations
- delete-branch: true
- title: 'Update translations'
+ author: github-actions # owner of the commit
body: |
Pulled all translated strings from [Transifex][1].
@@ -54,12 +48,16 @@ jobs:
[1]: https://explore.transifex.com/cockatrice/cockatrice/
[2]: https://github.com/Cockatrice/Cockatrice/actions/workflows/translations-pull.yml?query=branch%3Amaster
+ branch: ci-update_translations
+ commit-message: Update translation files
+ delete-branch: true
+ draft: false
labels: |
CI
Translation
- draft: false
+ title: 'Update translations'
- - name: PR Status
+ - name: "PR Status"
if: github.event_name != 'pull_request'
shell: bash
env:
diff --git a/.github/workflows/translations-push.yml b/.github/workflows/translations-push.yml
index 777e9e6ac..4adcaf4a4 100644
--- a/.github/workflows/translations-push.yml
+++ b/.github/workflows/translations-push.yml
@@ -1,14 +1,14 @@
name: Update Translation Source
on:
- workflow_dispatch:
- schedule:
- # runs at the start of each quarter (UTC)
- - cron: '0 0 1 1,4,7,10 *'
pull_request:
paths:
- '.ci/update_translation_source_strings.sh'
- '.github/workflows/translations-push.yml'
+ schedule:
+ # Runs at the start of each quarter (UTC)
+ - cron: '0 0 1 1,4,7,10 *'
+ workflow_dispatch:
jobs:
translations:
@@ -16,19 +16,19 @@ jobs:
if: github.event_name != 'schedule' || github.repository_owner == 'Cockatrice'
name: Push strings
- runs-on: ubuntu-latest
+ runs-on: ubuntu-slim
steps:
- - name: Checkout repo
+ - name: "Checkout repo"
uses: actions/checkout@v6
- - name: Install lupdate
+ - name: "Install lupdate"
shell: bash
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends qttools5-dev-tools
- - name: Update Cockatrice translation source
+ - name: "Update Cockatrice translation source"
id: cockatrice
shell: bash
run: |
@@ -36,17 +36,17 @@ jobs:
export DIRS="cockatrice/src $(find . -maxdepth 1 -type d -name 'libcockatrice_*')"
FILE="$FILE" DIRS="$DIRS" .ci/update_translation_source_strings.sh
- - name: Update Oracle translation source
+ - name: "Update Oracle translation source"
id: oracle
shell: bash
env:
- FILE: 'oracle/oracle_en@source.ts'
DIRS: 'oracle/src'
+ FILE: 'oracle/oracle_en@source.ts'
run: .ci/update_translation_source_strings.sh
- - name: Render template
+ - name: "Render template"
id: template
- uses: chuhlomin/render-template@v1
+ uses: chuhlomin/render-template/binary@v1
with:
template: .ci/update_translation_source_strings_template.md
vars: |
@@ -54,7 +54,7 @@ jobs:
oracle_output: ${{ steps.oracle.outputs.output }}
commit: ${{ github.sha }}
- - name: Create pull request
+ - name: "Create pull request"
if: github.event_name != 'pull_request'
id: create_pr
uses: peter-evans/create-pull-request@v8
@@ -62,19 +62,18 @@ jobs:
add-paths: |
cockatrice/cockatrice_en@source.ts
oracle/oracle_en@source.ts
- commit-message: Update translation source strings
- # author is the owner of the commit
- author: github-actions
- branch: ci-update_translation_source
- delete-branch: true
- title: 'Update source strings'
+ author: github-actions # owner of the commit
body: ${{ steps.template.outputs.result }}
+ branch: ci-update_translation_source
+ commit-message: Update translation source strings
+ delete-branch: true
+ draft: false
labels: |
CI
Translation
- draft: false
+ title: 'Update source strings'
- - name: PR Status
+ - name: "PR Status"
if: github.event_name != 'pull_request'
shell: bash
env:
diff --git a/.github/workflows/web-build.yml b/.github/workflows/web-build.yml
deleted file mode 100644
index 8d756da02..000000000
--- a/.github/workflows/web-build.yml
+++ /dev/null
@@ -1,54 +0,0 @@
-name: Build Web
-
-on:
- push:
- branches:
- - master
- paths:
- - '.husky/**'
- - 'webclient/**'
- - '!**.md'
- - '.github/workflows/web-build.yml'
- pull_request:
- paths:
- - '.husky/**'
- - 'webclient/**'
- - '!**.md'
- - '.github/workflows/web-build.yml'
-
-jobs:
- build-web:
- name: React (Node ${{matrix.node_version}})
-
- runs-on: ubuntu-latest
-
- defaults:
- run:
- working-directory: webclient
-
- strategy:
- fail-fast: false
- matrix:
- node_version:
- - 16
- - lts/*
-
- steps:
- - name: Checkout
- uses: actions/checkout@v6
-
- - name: Setup Node.js
- uses: actions/setup-node@v6
- with:
- node-version: ${{matrix.node_version}}
- cache: 'npm'
- cache-dependency-path: 'webclient/package-lock.json'
-
- - name: Install dependencies
- run: npm clean-install
-
- - name: Build app
- run: npm run build
-
- - name: Test app
- run: npm run test
diff --git a/.github/workflows/web-lint.yml b/.github/workflows/web-lint.yml
deleted file mode 100644
index 8a90325e7..000000000
--- a/.github/workflows/web-lint.yml
+++ /dev/null
@@ -1,33 +0,0 @@
-name: Code Style (TypeScript)
-
-on:
- # push trigger not needed for linting, we do not allow direct pushes to master
- pull_request:
- paths:
- - 'webclient/**'
- - '!**.md'
- - '.github/workflows/web-lint.yml'
-
-jobs:
- ESLint:
- runs-on: ubuntu-latest
-
- defaults:
- run:
- working-directory: webclient
-
- steps:
- - name: Checkout
- uses: actions/checkout@v6
-
- - name: Setup Node.js
- uses: actions/setup-node@v6
- with:
- cache: 'npm'
- cache-dependency-path: 'webclient/package-lock.json'
-
- - name: Install ESLint
- run: npm clean-install --ignore-scripts
-
- - name: Run ESLint
- run: npm run lint
diff --git a/.husky/pre-commit b/.husky/pre-commit
deleted file mode 100755
index 369435d6b..000000000
--- a/.husky/pre-commit
+++ /dev/null
@@ -1,7 +0,0 @@
-#!/bin/sh
-. "$(dirname "$0")/_/husky.sh"
-
-cd webclient
-npm run translate
-
-git add src/i18n-default.json
diff --git a/.tx/config b/.tx/config
index 9bc5ce950..8174a4dfd 100644
--- a/.tx/config
+++ b/.tx/config
@@ -16,11 +16,3 @@ source_file = oracle/oracle_en@source.ts
file_filter = oracle/translations/oracle_.ts
type = QT
minimum_perc = 10
-
-[o:cockatrice:p:cockatrice:r:webclient-src-i18n-default-json--master]
-resource_name = Webclient
-source_lang = en
-source_file = webclient/src/i18n-default.json
-file_filter = webclient/public/locales//translation.json
-type = KEYVALUEJSON
-minimum_perc = 10
diff --git a/CMakeLists.txt b/CMakeLists.txt
index fe808a652..c10e1db68 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -74,11 +74,11 @@ endif()
# A project name is needed for CPack
# Version can be overriden by git tags, see cmake/getversion.cmake
-project("Cockatrice" VERSION 2.11.0)
+project("Cockatrice" VERSION 3.1.0)
# Set release name if not provided via env/cmake var
if(NOT DEFINED GIT_TAG_RELEASENAME)
- set(GIT_TAG_RELEASENAME "Omenpath")
+ set(GIT_TAG_RELEASENAME "Graduation Day")
endif()
# Use c++20 for all targets
@@ -174,6 +174,7 @@ elseif(CMAKE_COMPILER_IS_GNUCXX)
-Wno-error=delete-non-virtual-dtor
-Wno-error=sign-compare
-Wno-error=missing-declarations
+ -Wno-error=sfinae-incomplete # GCC 16+: Qt MOC + protobuf forward decls trigger this
)
foreach(FLAG ${ADDITIONAL_DEBUG_FLAGS})
diff --git a/Dockerfile b/Dockerfile
index d185b746a..7c5c773c9 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,5 +1,5 @@
# -------- Build Stage --------
-FROM ubuntu:24.04 AS build
+FROM ubuntu:26.04 AS build
ARG DEBIAN_FRONTEND=noninteractive
@@ -26,7 +26,7 @@ RUN mkdir build && cd build && \
# -------- Runtime Stage (clean) --------
-FROM ubuntu:24.04
+FROM ubuntu:26.04
RUN apt-get update && apt-get install -y --no-install-recommends \
libprotobuf32t64 \
diff --git a/Doxyfile b/Doxyfile
index 0f2642fd8..fc96b5f22 100644
--- a/Doxyfile
+++ b/Doxyfile
@@ -1,4 +1,4 @@
-# Doxyfile 1.14.0
+# Doxyfile 1.16.1
# This file describes the settings to be used by the documentation system
# Doxygen (www.doxygen.org) for a project.
@@ -361,6 +361,20 @@ EXTENSION_MAPPING =
MARKDOWN_SUPPORT = YES
+# If the MARKDOWN_STRICT tag is enabled then Doxygen treats text in comments as
+# Markdown formatted also in cases where Doxygen's native markup format
+# conflicts with that of Markdown. This is only relevant in cases where
+# backticks are used. Doxygen's native markup style allows a single quote to end
+# a text fragment started with a backtick and then treat it as a piece of quoted
+# text, whereas in Markdown such text fragment is treated as verbatim and only
+# ends when a second matching backtick is found. Also, Doxygen's native markup
+# format requires double quotes to be escaped when they appear in a backtick
+# section, whereas this is not needed for Markdown.
+# The default value is: YES.
+# This tag requires that the tag MARKDOWN_SUPPORT is set to YES.
+
+MARKDOWN_STRICT = YES
+
# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up
# to that level are automatically included in the table of contents, even if
# they do not have an id attribute.
@@ -392,8 +406,8 @@ AUTOLINK_SUPPORT = YES
# This tag specifies a list of words that, when matching the start of a word in
# the documentation, will suppress auto links generation, if it is enabled via
-# AUTOLINK_SUPPORT. This list does not affect links explicitly created using \#
-# or the \link or commands.
+# AUTOLINK_SUPPORT. This list does not affect links explicitly created using #
+# or the \link or \ref commands.
# This tag requires that the tag AUTOLINK_SUPPORT is set to YES.
AUTOLINK_IGNORE_WORDS =
@@ -510,9 +524,9 @@ LOOKUP_CACHE_SIZE = 0
# which effectively disables parallel processing. Please report any issues you
# encounter. Generating dot graphs in parallel is controlled by the
# DOT_NUM_THREADS setting.
-# Minimum value: 0, maximum value: 32, default value: 1.
+# Minimum value: 0, maximum value: 512, default value: 1.
-NUM_PROC_THREADS = 1
+NUM_PROC_THREADS = 0
# If the TIMESTAMP tag is set different from NO then each generated page will
# contain the date or date and time when the page was generated. Setting this to
@@ -779,6 +793,27 @@ GENERATE_BUGLIST = YES
GENERATE_DEPRECATEDLIST= YES
+# The GENERATE_REQUIREMENTS tag can be used to enable (YES) or disable (NO) the
+# requirements page. When enabled, this page is automatically created when at
+# least one comment block with a \requirement command appears in the input.
+# The default value is: YES.
+
+GENERATE_REQUIREMENTS = YES
+
+# The REQ_TRACEABILITY_INFO tag controls if traceability information is shown on
+# the requirements page (only relevant when using \requirement comment blocks).
+# The setting NO will disable the traceablility information altogether. The
+# setting UNSATISFIED_ONLY will show a list of requirements that are missing a
+# satisfies relation (through the command: \satisfies). Similarly the setting
+# UNVERIFIED_ONLY will show a list of requirements that are missing a verifies
+# relation (through the command: \verifies). Setting the tag to YES (the
+# default) will show both lists if applicable.
+# Possible values are: YES, NO, UNSATISFIED_ONLY and UNVERIFIED_ONLY.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_REQUIREMENTS is set to YES.
+
+REQ_TRACEABILITY_INFO = YES
+
# The ENABLED_SECTIONS tag can be used to enable conditional documentation
# sections, marked by \if ... \endif and \cond
# ... \endcond blocks.
@@ -1070,8 +1105,7 @@ EXCLUDE = build/ \
cmake/ \
doc/doxygen/theme/docs/ \
doc/doxygen/theme/include/ \
- vcpkg/ \
- webclient/
+ vcpkg/
# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
# directories that are symbolic links (a Unix file system feature) are excluded
@@ -1887,7 +1921,7 @@ USE_MATHJAX = NO
# regards to the different settings, so it is possible that also other MathJax
# settings have to be changed when switching between the different MathJax
# versions.
-# Possible values are: MathJax_2 and MathJax_3.
+# Possible values are: MathJax_2, MathJax_3 and MathJax_4.
# The default value is: MathJax_2.
# This tag requires that the tag USE_MATHJAX is set to YES.
@@ -1896,9 +1930,10 @@ MATHJAX_VERSION = MathJax_2
# When MathJax is enabled you can set the default output format to be used for
# the MathJax output. For more details about the output format see MathJax
# version 2 (see:
-# http://docs.mathjax.org/en/v2.7-latest/output.html) and MathJax version 3
+# https://docs.mathjax.org/en/v2.7/output.html), MathJax version 3 (see:
+# https://docs.mathjax.org/en/v3.2/output/index.html) and MathJax version 4
# (see:
-# http://docs.mathjax.org/en/latest/web/components/output.html).
+# https://docs.mathjax.org/en/v4.0/output/index.htm).
# Possible values are: HTML-CSS (which is slower, but has the best
# compatibility. This is the name for Mathjax version 2, for MathJax version 3
# this will be translated into chtml), NativeMML (i.e. MathML. Only supported
@@ -1911,36 +1946,50 @@ MATHJAX_VERSION = MathJax_2
MATHJAX_FORMAT = HTML-CSS
# When MathJax is enabled you need to specify the location relative to the HTML
-# output directory using the MATHJAX_RELPATH option. The destination directory
-# should contain the MathJax.js script. For instance, if the mathjax directory
-# is located at the same level as the HTML output directory, then
-# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax
-# Content Delivery Network so you can quickly see the result without installing
-# MathJax. However, it is strongly recommended to install a local copy of
-# MathJax from https://www.mathjax.org before deployment. The default value is:
+# output directory using the MATHJAX_RELPATH option. For Mathjax version 2 the
+# destination directory should contain the MathJax.js script. For instance, if
+# the mathjax directory is located at the same level as the HTML output
+# directory, then MATHJAX_RELPATH should be ../mathjax.s For Mathjax versions 3
+# and 4 the destination directory should contain the tex-.js script
+# (where is either chtml or svg). The default value points to the
+# MathJax Content Delivery Network so you can quickly see the result without
+# installing MathJax. However, it is strongly recommended to install a local
+# copy of MathJax from https://www.mathjax.org before deployment. The default
+# value is:
# - in case of MathJax version 2: https://cdn.jsdelivr.net/npm/mathjax@2
# - in case of MathJax version 3: https://cdn.jsdelivr.net/npm/mathjax@3
+# - in case of MathJax version 4: https://cdn.jsdelivr.net/npm/mathjax@4
# This tag requires that the tag USE_MATHJAX is set to YES.
MATHJAX_RELPATH =
# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax
# extension names that should be enabled during MathJax rendering. For example
-# for MathJax version 2 (see
-# https://docs.mathjax.org/en/v2.7-latest/tex.html#tex-and-latex-extensions):
+# for MathJax version 2 (see https://docs.mathjax.org/en/v2.7/tex.html):
# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols
# For example for MathJax version 3 (see
-# http://docs.mathjax.org/en/latest/input/tex/extensions/index.html):
+# https://docs.mathjax.org/en/v3.2/input/tex/extensions/):
# MATHJAX_EXTENSIONS = ams
+# For example for MathJax version 4 (see
+# https://docs.mathjax.org/en/v4.0/input/tex/extensions/):
+# MATHJAX_EXTENSIONS = units
+# Note that for Mathjax version 4 quite a few extensions are already
+# automatically loaded. To disable a package in Mathjax version 4 one can use
+# the package name prepended with a minus sign (- like MATHJAX_EXTENSIONS +=
+# -textmacros)
# This tag requires that the tag USE_MATHJAX is set to YES.
MATHJAX_EXTENSIONS =
# The MATHJAX_CODEFILE tag can be used to specify a file with JavaScript pieces
-# of code that will be used on startup of the MathJax code. See the MathJax site
-# (see:
-# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an
-# example see the documentation.
+# of code that will be used on startup of the MathJax code. See the Mathjax site
+# for more details:
+# - MathJax version 2 (see:
+# https://docs.mathjax.org/en/v2.7/)
+# - MathJax version 3 (see:
+# https://docs.mathjax.org/en/v3.2/)
+# - MathJax version 4 (see:
+# https://docs.mathjax.org/en/v4.0/) For an example see the documentation.
# This tag requires that the tag USE_MATHJAX is set to YES.
MATHJAX_CODEFILE =
@@ -2601,7 +2650,7 @@ HAVE_DOT = YES
# processors available in the system. You can set it explicitly to a value
# larger than 0 to get control over the balance between CPU load and processing
# speed.
-# Minimum value: 0, maximum value: 32, default value: 0.
+# Minimum value: 0, maximum value: 512, default value: 0.
# This tag requires that the tag HAVE_DOT is set to YES.
DOT_NUM_THREADS = 0
diff --git a/README.md b/README.md
index 8ad9a092c..9a27601cd 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,7 @@
Related|Community|Contribute|
- Build|
+ Build|Run
@@ -25,7 +25,6 @@
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.
This project uses C++ and the Qt libraries.
-First work on a webclient with Typescript was started as well.
# Download [](https://tooomm.github.io/github-release-stats/?username=Cockatrice&repository=Cockatrice&search=0)
@@ -48,6 +47,7 @@ Latest beta version:
- [Magic-Spoiler](https://github.com/Cockatrice/Magic-Spoiler): Code to generate MtG spoiler data from [MTGJSON](https://github.com/mtgjson/mtgjson) for use in Cockatrice
- [cockatrice.github.io](https://github.com/Cockatrice/cockatrice.github.io): Code of the official Cockatrice webpage
- [io.github.Cockatrice.cockatrice](https://github.com/flathub/io.github.Cockatrice.cockatrice): Configuration of our Linux `flatpak` package hosted at [Flathub](https://flathub.org/en/apps/io.github.Cockatrice.cockatrice)
+- [Webatrice](https://github.com/Cockatrice/Webatrice): Web client for Cockatrice servers (TypeScript / React)
# Community Resources [](https://discord.gg/3Z9yzmA)
@@ -107,12 +107,12 @@ Cockatrice tries to use the [Google Developer Documentation Style Guide](https:/
### Translation [](https://explore.transifex.com/cockatrice/cockatrice/)
-Cockatrice uses Transifex to manage translations. You can help us bring Cockatrice, Oracle and Webatrice 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/).
+Cockatrice uses Transifex to manage translations. You can help us bring Cockatrice and Oracle 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/). The [Webatrice](https://github.com/seavor/Webatrice) web client manages its own translations in its repo.
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!
-# Build [](https://github.com/Cockatrice/Cockatrice/actions/workflows/desktop-build.yml?query=branch%3Amaster+event%3Apush) [](https://github.com/Cockatrice/Cockatrice/actions/workflows/docker-release.yml?query=branch%3Amaster+event%3Apush) [](https://github.com/Cockatrice/Cockatrice/actions/workflows/web-build.yml?query=branch%3Amaster+event%3Apush)
+# Build [](https://github.com/Cockatrice/Cockatrice/actions/workflows/desktop-build.yml?query=branch%3Amaster+event%3Apush) [](https://github.com/Cockatrice/Cockatrice/actions/workflows/docker-release.yml?query=branch%3Amaster+event%3Apush)
Dependencies: *(for minimum versions search our [CMake file](https://github.com/Cockatrice/Cockatrice/blob/master/CMakeLists.txt))*
- [Qt](https://www.qt.io/developers/)
diff --git a/cmake/NSIS.template.in b/cmake/NSIS.template.in
index 2fdc61fb9..7b52b7bcc 100644
--- a/cmake/NSIS.template.in
+++ b/cmake/NSIS.template.in
@@ -11,6 +11,7 @@ SetCompressor LZMA
Var NormalDestDir
Var PortableDestDir
Var PortableMode
+Var ReinstallMode
!include LogicLib.nsh
!include FileFunc.nsh
@@ -28,13 +29,23 @@ Var PortableMode
!define MUI_FINISHPAGE_RUN_TEXT "Run 'Cockatrice' now"
!define MUI_ICON "${NSIS_SOURCE_PATH}\cockatrice\resources\appicon.ico"
+!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfReinstall
!insertmacro MUI_PAGE_WELCOME
+
+!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfReinstall
!insertmacro MUI_PAGE_LICENSE "${NSIS_SOURCE_PATH}\LICENSE"
+
Page Custom PortableModePageCreate PortableModePageLeave
!define MUI_PAGE_CUSTOMFUNCTION_PRE componentsPagePre
!insertmacro MUI_PAGE_COMPONENTS
+
+!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfReinstall
!insertmacro MUI_PAGE_DIRECTORY
+
+!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfReinstall
!insertmacro MUI_PAGE_INSTFILES
+
+!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfReinstall
!insertmacro MUI_PAGE_FINISH
!insertmacro MUI_UNPAGE_CONFIRM
@@ -73,6 +84,7 @@ ${IfNot} ${Errors}
MessageBox MB_ICONINFORMATION|MB_SETFOREGROUND "\
/PORTABLE : Install in portable mode$\n\
/S : Silent install$\n\
+ /R : Silent upgrade$\n\
/D=%directory% : Specify destination directory$\n"
Quit
${EndIf}
@@ -90,6 +102,16 @@ ${Else}
${EndIf}
${EndIf}
+ClearErrors
+${GetOptions} $9 "/R" $8
+${IfNot} ${Errors}
+ StrCpy $ReinstallMode 1
+ SetSilent silent
+ SetAutoClose true
+${Else}
+ StrCpy $ReinstallMode 0
+${EndIf}
+
${If} $InstDir == ""
; User did not use /D to specify a directory,
; we need to set a default based on the install mode
@@ -97,6 +119,22 @@ ${If} $InstDir == ""
${EndIf}
Call SetModeDestinationFromInstdir
+; --- Detect portable install when using /R ---
+${If} $ReinstallMode = 1
+ IfFileExists "$InstDir\portable.dat" 0 not_portable
+ StrCpy $PortableMode 1
+ Goto portable_done
+
+ not_portable:
+ StrCpy $PortableMode 0
+
+ portable_done:
+${EndIf}
+
+${If} $ReinstallMode = 1
+ Call AutoUninstallIfNeeded
+${EndIf}
+
FunctionEnd
Function un.onInit
@@ -126,8 +164,46 @@ ${Else}
${EndIf}
FunctionEnd
+Function SkipIfReinstall
+${If} $ReinstallMode = 1
+ Abort
+${EndIf}
+FunctionEnd
+
+Function AutoUninstallIfNeeded
+
+SetShellVarContext all
+
+; --- 32-bit uninstall ---
+SetRegView 32
+ReadRegStr $R0 HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Cockatrice" "QuietUninstallString"
+
+StrCmp $R0 "" done32
+DetailPrint "Removing previous version (32-bit)..."
+ExecWait '$R0'
+
+done32:
+
+; --- 64-bit uninstall ---
+${If} ${RunningX64}
+ SetRegView 64
+ ReadRegStr $R0 HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Cockatrice" "QuietUninstallString"
+
+ StrCmp $R0 "" done64
+ DetailPrint "Removing previous version (64-bit)..."
+ ExecWait '$R0'
+
+ done64:
+${EndIf}
+
+FunctionEnd
Function PortableModePageCreate
+
+${If} $ReinstallMode = 1
+ Abort
+${EndIf}
+
Call SetModeDestinationFromInstdir ; If the user clicks BACK on the directory page we will remember their mode specific directory
!insertmacro MUI_HEADER_TEXT "Install Mode" "Choose how you want to install Cockatrice."
nsDialogs::Create 1018
@@ -159,6 +235,11 @@ ${EndIf}
FunctionEnd
Function componentsPagePre
+
+${If} $ReinstallMode = 1
+ Return
+${EndIf}
+
${If} $PortableMode = 0
SetShellVarContext all
@@ -168,8 +249,12 @@ ${If} $PortableMode = 0
ReadRegStr $R0 HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Cockatrice" "UninstallString"
StrCmp $R0 "" done32
- MessageBox MB_OKCANCEL|MB_ICONEXCLAMATION "A previous version of Cockatrice must be uninstalled before installing the new one." IDOK uninst32
- Abort
+ ${If} $ReinstallMode = 0
+ MessageBox MB_OKCANCEL|MB_ICONEXCLAMATION "A previous version of Cockatrice must be uninstalled before installing the new one." IDOK uninst32
+ Abort
+ ${Else}
+ Goto uninst32
+ ${EndIf}
uninst32:
ClearErrors
@@ -184,8 +269,12 @@ ${If} $PortableMode = 0
ReadRegStr $R0 HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Cockatrice" "UninstallString"
StrCmp $R0 "" done64
- MessageBox MB_OKCANCEL|MB_ICONEXCLAMATION "A previous version of Cockatrice must be uninstalled before installing the new one." IDOK uninst64
- Abort
+ ${If} $ReinstallMode = 0
+ MessageBox MB_OKCANCEL|MB_ICONEXCLAMATION "A previous version of Cockatrice must be uninstalled before installing the new one." IDOK uninst64
+ Abort
+ ${Else}
+ Goto uninst64
+ ${EndIf}
uninst64:
ClearErrors
@@ -277,6 +366,12 @@ ${Else}
FileWrite $0 "PORTABLE"
FileClose $0
${EndIf}
+
+${If} $ReinstallMode = 1
+ IfFileExists "$INSTDIR\cockatrice.exe" 0 +2
+ Exec '"$INSTDIR\cockatrice.exe"'
+${EndIf}
+
SectionEnd
Section "Start menu item" SecStartMenu
diff --git a/cmake/getversion.cmake b/cmake/getversion.cmake
index cb27ebd87..b24b520d5 100644
--- a/cmake/getversion.cmake
+++ b/cmake/getversion.cmake
@@ -213,7 +213,8 @@ set(PROJECT_VERSION_FRIENDLY "${PROJECT_VERSION} (${GIT_COMMIT_DATE_FRIENDLY})")
# Format: [-ReleaseName]-MAJ.MIN.PATCH[-prerelease_label]
set(PROJECT_VERSION_FILENAME "${PROJECT_NAME}")
if(PROJECT_VERSION_RELEASENAME)
- set(PROJECT_VERSION_FILENAME "${PROJECT_VERSION_FILENAME}-${PROJECT_VERSION_RELEASENAME}")
+ string(REPLACE " " "-" PROJECT_VERSION_RELEASENAME_SAFE "${PROJECT_VERSION_RELEASENAME}")
+ set(PROJECT_VERSION_FILENAME "${PROJECT_VERSION_FILENAME}-${PROJECT_VERSION_RELEASENAME_SAFE}")
endif()
set(PROJECT_VERSION_FILENAME "${PROJECT_VERSION_FILENAME}-${PROJECT_VERSION}")
diff --git a/cmake/gtest-CMakeLists.txt.in b/cmake/gtest-CMakeLists.txt.in
index 2d71a55e5..2062d7f8c 100644
--- a/cmake/gtest-CMakeLists.txt.in
+++ b/cmake/gtest-CMakeLists.txt.in
@@ -1,15 +1,16 @@
-cmake_minimum_required(VERSION 3.2)
+cmake_minimum_required(VERSION 3.10)
project(gtest-download LANGUAGES NONE)
include(ExternalProject)
-ExternalProject_Add(googletest
- URL https://github.com/google/googletest/archive/release-1.11.0.zip
- URL_HASH SHA1=9ffb7b5923f4a8fcdabf2f42c6540cce299f44c0
+externalproject_add(
+ googletest
+ URL https://github.com/google/googletest/archive/refs/tags/v1.17.0.zip
+ URL_HASH SHA1=f638fa0e724760e2ba07ff8cfba32cd644e1ce28
SOURCE_DIR "${CMAKE_BINARY_DIR}/gtest-src"
BINARY_DIR "${CMAKE_BINARY_DIR}/gtest-build"
CONFIGURE_COMMAND ""
- BUILD_COMMAND ""
- INSTALL_COMMAND ""
- TEST_COMMAND ""
+ BUILD_COMMAND ""
+ INSTALL_COMMAND ""
+ TEST_COMMAND ""
)
diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt
index 12733afe6..bd99d08bf 100644
--- a/cockatrice/CMakeLists.txt
+++ b/cockatrice/CMakeLists.txt
@@ -7,6 +7,7 @@ project(Cockatrice VERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${
set(cockatrice_SOURCES
${VERSION_STRING_CPP}
# sort by alphabetical order, so that there is no debate about where to add new sources to the list
+ src/client/network/connection_controller/remote_connection_controller.cpp
src/client/network/update/client/update_downloader.cpp
src/client/network/interfaces/deck_stats_interface.cpp
src/client/network/interfaces/tapped_out_interface.cpp
@@ -55,68 +56,73 @@ set(cockatrice_SOURCES
src/filters/filter_tree_model.cpp
src/filters/syntax_help.cpp
src/game/abstract_game.cpp
- src/game/board/abstract_card_drag_item.cpp
- src/game/board/abstract_card_item.cpp
- src/game/board/abstract_counter.cpp
- src/game/board/arrow_item.cpp
- src/game/board/arrow_target.cpp
- src/game/board/card_drag_item.cpp
- src/game/board/card_item.cpp
+ src/game/arrow_registry.cpp
+ src/game_graphics/board/abstract_card_drag_item.cpp
+ src/game_graphics/board/abstract_card_item.cpp
+ src/game_graphics/board/abstract_counter.cpp
+ src/game/board/arrow_data.cpp
+ src/game_graphics/board/arrow_item.cpp
+ src/game_graphics/board/arrow_target.cpp
+ src/game_graphics/board/card_drag_item.cpp
+ src/game_graphics/board/card_item.cpp
src/game/board/card_list.cpp
- src/game/board/counter_general.cpp
- src/game/board/translate_counter_name.cpp
- src/game/deckview/deck_view.cpp
- src/game/deckview/deck_view_container.cpp
- src/game/deckview/tabbed_deck_view_container.cpp
- src/game/dialogs/dlg_create_token.cpp
- src/game/dialogs/dlg_move_top_cards_until.cpp
- src/game/dialogs/dlg_roll_dice.cpp
+ src/game/board/card_state.cpp
+ src/game_graphics/board/counter_general.cpp
+ src/game/board/counter_state.cpp
+ src/game_graphics/board/translate_counter_name.cpp
+ src/game_graphics/deckview/deck_view.cpp
+ src/game_graphics/deckview/deck_view_container.cpp
+ src/game_graphics/deckview/tabbed_deck_view_container.cpp
+ src/game_graphics/dialogs/dlg_create_token.cpp
+ src/game_graphics/dialogs/dlg_move_top_cards_until.cpp
+ src/game_graphics/dialogs/dlg_roll_dice.cpp
src/game/game.cpp
src/game/game_event_handler.cpp
src/game/game_meta_info.cpp
- src/game/game_scene.cpp
+ src/game_graphics/game_scene.cpp
src/game/game_state.cpp
- src/game/game_view.cpp
- src/game/hand_counter.cpp
- src/game/log/message_log_widget.cpp
+ src/game_graphics/game_view.cpp
+ src/game_graphics/hand_counter.cpp
+ src/game_graphics/log/message_log_widget.cpp
src/game/phase.cpp
- src/game/phases_toolbar.cpp
- src/game/player/menu/card_menu.cpp
- src/game/player/menu/custom_zone_menu.cpp
- src/game/player/menu/grave_menu.cpp
- src/game/player/menu/hand_menu.cpp
- src/game/player/menu/library_menu.cpp
- src/game/player/menu/move_menu.cpp
- src/game/player/menu/player_menu.cpp
- src/game/player/menu/pt_menu.cpp
- src/game/player/menu/rfg_menu.cpp
- src/game/player/menu/say_menu.cpp
- src/game/player/menu/sideboard_menu.cpp
- src/game/player/menu/utility_menu.cpp
- src/game/player/player.cpp
+ src/game_graphics/phases_toolbar.cpp
+ src/game_graphics/player/menu/card_menu.cpp
+ src/game_graphics/player/menu/custom_zone_menu.cpp
+ src/game_graphics/player/menu/grave_menu.cpp
+ src/game_graphics/player/menu/hand_menu.cpp
+ src/game_graphics/player/menu/library_menu.cpp
+ src/game_graphics/player/menu/move_menu.cpp
+ src/game_graphics/player/menu/player_menu.cpp
+ src/game_graphics/player/menu/pt_menu.cpp
+ src/game_graphics/player/menu/rfg_menu.cpp
+ src/game_graphics/player/menu/say_menu.cpp
+ src/game_graphics/player/menu/sideboard_menu.cpp
+ src/game_graphics/player/menu/utility_menu.cpp
src/game/player/player_actions.cpp
- src/game/player/player_area.cpp
+ src/game_graphics/player/player_area.cpp
+ src/game_graphics/player/player_dialogs.cpp
src/game/player/player_event_handler.cpp
- src/game/player/player_graphics_item.cpp
+ src/game_graphics/player/player_graphics_item.cpp
src/game/player/player_info.cpp
- src/game/player/player_list_widget.cpp
+ src/game_graphics/player/player_list_widget.cpp
+ src/game/player/player_logic.cpp
src/game/player/player_manager.cpp
- src/game/player/player_target.cpp
+ src/game_graphics/player/player_target.cpp
src/game/replay.cpp
- src/game/zones/card_zone.cpp
- src/game/zones/hand_zone.cpp
- src/game/zones/logic/card_zone_logic.cpp
- src/game/zones/logic/hand_zone_logic.cpp
- src/game/zones/logic/pile_zone_logic.cpp
- src/game/zones/logic/stack_zone_logic.cpp
- src/game/zones/logic/table_zone_logic.cpp
- src/game/zones/logic/view_zone_logic.cpp
- src/game/zones/pile_zone.cpp
- src/game/zones/select_zone.cpp
- src/game/zones/stack_zone.cpp
- src/game/zones/table_zone.cpp
- src/game/zones/view_zone.cpp
- src/game/zones/view_zone_widget.cpp
+ src/game/zones/card_zone_logic.cpp
+ src/game/zones/hand_zone_logic.cpp
+ src/game/zones/pile_zone_logic.cpp
+ src/game/zones/stack_zone_logic.cpp
+ src/game/zones/table_zone_logic.cpp
+ src/game/zones/view_zone_logic.cpp
+ src/game_graphics/zones/card_zone.cpp
+ src/game_graphics/zones/hand_zone.cpp
+ src/game_graphics/zones/pile_zone.cpp
+ src/game_graphics/zones/select_zone.cpp
+ src/game_graphics/zones/stack_zone.cpp
+ src/game_graphics/zones/table_zone.cpp
+ src/game_graphics/zones/view_zone.cpp
+ src/game_graphics/zones/view_zone_widget.cpp
src/game_graphics/board/abstract_graphics_item.cpp
src/interface/card_picture_loader/card_picture_loader.cpp
src/interface/card_picture_loader/card_picture_loader_local.cpp
@@ -129,7 +135,13 @@ set(cockatrice_SOURCES
src/interface/layouts/overlap_layout.cpp
src/interface/widgets/utility/line_edit_completer.cpp
src/interface/pixel_map_generator.cpp
+ src/interface/theme_config.cpp
src/interface/theme_manager.cpp
+ src/interface/palette_editor/color_button.cpp
+ src/interface/palette_editor/palette_generator.cpp
+ src/interface/palette_editor/quick_setup_panel.cpp
+ src/interface/palette_editor/palette_grid_widget.cpp
+ src/interface/palette_editor/palette_editor_dialog.cpp
src/interface/widgets/cards/additional_info/color_identity_widget.cpp
src/interface/widgets/cards/additional_info/mana_cost_widget.cpp
src/interface/widgets/cards/additional_info/mana_symbol_widget.cpp
@@ -171,6 +183,7 @@ set(cockatrice_SOURCES
src/interface/widgets/deck_analytics/analyzer_modules/mana_distribution/mana_distribution_single_display_widget.cpp
src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_total_widget.cpp
src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_category_widget.cpp
+ src/interface/widgets/deck_editor/card_database_view.cpp
src/interface/widgets/deck_editor/deck_list_history_manager_widget.cpp
src/interface/widgets/deck_editor/deck_editor_card_database_dock_widget.cpp
src/interface/widgets/deck_editor/deck_editor_card_info_dock_widget.cpp
@@ -216,6 +229,7 @@ set(cockatrice_SOURCES
src/interface/widgets/replay/replay_manager.cpp
src/interface/widgets/replay/replay_timeline_widget.cpp
src/interface/widgets/server/chat_view/chat_view.cpp
+ src/interface/widgets/server/game_filter_configs.cpp
src/interface/widgets/server/game_selector.cpp
src/interface/widgets/server/game_selector_quick_filter_toolbar.cpp
src/interface/widgets/server/games_model.cpp
@@ -227,6 +241,14 @@ set(cockatrice_SOURCES
src/interface/widgets/server/user/user_info_connection.cpp
src/interface/widgets/server/user/user_list_manager.cpp
src/interface/widgets/server/user/user_list_widget.cpp
+ src/interface/widgets/settings_page/appearance_settings_page.cpp
+ src/interface/widgets/settings_page/deck_editor_settings_page.cpp
+ src/interface/widgets/settings_page/general_settings_page.cpp
+ src/interface/widgets/settings_page/messages_settings_page.cpp
+ src/interface/widgets/settings_page/shortcut_settings_page.cpp
+ src/interface/widgets/settings_page/sound_settings_page.cpp
+ src/interface/widgets/settings_page/storage_settings_page.cpp
+ src/interface/widgets/settings_page/user_interface_settings_page.cpp
src/interface/widgets/utility/custom_line_edit.cpp
src/interface/widgets/utility/get_text_with_max.cpp
src/interface/widgets/utility/sequence_edit.cpp
@@ -325,6 +347,8 @@ set(cockatrice_SOURCES
src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_bracket_navigation_widget.h
src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_budget_navigation_widget.cpp
src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_budget_navigation_widget.h
+ src/interface/widgets/utility/compact_push_button.cpp
+ src/interface/widgets/utility/compact_push_button.h
)
add_subdirectory(sounds)
@@ -539,6 +563,7 @@ if(WIN32)
DIRECTORY "${CMAKE_BINARY_DIR}/cockatrice/"
DESTINATION ./
FILES_MATCHING
+ PATTERN "CMakeFiles" EXCLUDE
PATTERN "*.ini"
)
diff --git a/cockatrice/cockatrice.qrc b/cockatrice/cockatrice.qrc
index 37fb145f0..9c34929b7 100644
--- a/cockatrice/cockatrice.qrc
+++ b/cockatrice/cockatrice.qrc
@@ -55,6 +55,7 @@
resources/icons/view.svgresources/icons/mana/B.svg
+ resources/icons/mana/C.svgresources/icons/mana/G.svgresources/icons/mana/R.svgresources/icons/mana/U.svg
@@ -69,6 +70,7 @@
resources/config/interface.svgresources/config/messages.svgresources/config/deckeditor.svg
+ resources/config/storage.svgresources/config/shorcuts.svgresources/config/sound.svgresources/config/debug.ini
diff --git a/cockatrice/resources/config/qtlogging.ini b/cockatrice/resources/config/qtlogging.ini
index 20aa206ce..7ac0d9ca4 100644
--- a/cockatrice/resources/config/qtlogging.ini
+++ b/cockatrice/resources/config/qtlogging.ini
@@ -28,6 +28,8 @@
#dlg_tip_of_the_day = true
#dlg_update = true
+#general_settings_page = true
+
#settings_cache = true
#servers_settings = true
#shortcuts_settings = true
diff --git a/cockatrice/resources/config/storage.svg b/cockatrice/resources/config/storage.svg
new file mode 100644
index 000000000..de85228dc
--- /dev/null
+++ b/cockatrice/resources/config/storage.svg
@@ -0,0 +1,799 @@
+
+
+
+
diff --git a/cockatrice/resources/icons/mana/C.svg b/cockatrice/resources/icons/mana/C.svg
new file mode 100644
index 000000000..eb09fb872
--- /dev/null
+++ b/cockatrice/resources/icons/mana/C.svg
@@ -0,0 +1,72 @@
+
+
+
+
diff --git a/cockatrice/src/client/network/connection_controller/remote_connection_controller.cpp b/cockatrice/src/client/network/connection_controller/remote_connection_controller.cpp
new file mode 100644
index 000000000..75cedcafc
--- /dev/null
+++ b/cockatrice/src/client/network/connection_controller/remote_connection_controller.cpp
@@ -0,0 +1,587 @@
+#include "remote_connection_controller.h"
+
+#include "../../settings/cache_settings.h"
+#include "../interface/widgets/dialogs/dlg_connect.h"
+#include "../interface/widgets/dialogs/dlg_forgot_password_challenge.h"
+#include "../interface/widgets/dialogs/dlg_forgot_password_request.h"
+#include "../interface/widgets/dialogs/dlg_forgot_password_reset.h"
+#include "../interface/widgets/dialogs/dlg_register.h"
+#include "../interface/widgets/utility/get_text_with_max.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+ConnectionController::ConnectionController(QWidget *dialogParent, QObject *parent)
+ : QObject(parent), dialogParent(dialogParent)
+{
+ remoteClient = new RemoteClient(nullptr, &SettingsCache::instance());
+
+ clientThread = new QThread(this);
+ remoteClient->moveToThread(clientThread);
+ clientThread->start();
+
+ wireClientSignals();
+}
+
+ConnectionController::~ConnectionController()
+{
+ remoteClient->deleteLater();
+ clientThread->wait();
+}
+
+void ConnectionController::wireClientSignals()
+{
+ connect(remoteClient, &RemoteClient::connectionClosedEventReceived, this,
+ &ConnectionController::onConnectionClosedEvent);
+
+ connect(remoteClient, &RemoteClient::serverShutdownEventReceived, this,
+ &ConnectionController::onServerShutdownEvent);
+
+ connect(remoteClient, &RemoteClient::statusChanged, this, &ConnectionController::onStatusChanged);
+
+ connect(remoteClient, &RemoteClient::userInfoChanged, this, &ConnectionController::onUserInfoReceived,
+ Qt::BlockingQueuedConnection);
+
+ connect(remoteClient, &RemoteClient::loginError, this,
+ [this](Response::ResponseCode r, QString rs, quint32 et, QList mf) {
+ onLoginError(static_cast(r), rs, et, mf);
+ });
+
+ connect(remoteClient, &RemoteClient::registerError, this,
+ [this](Response::ResponseCode r, QString rs, quint32 et) { onRegisterError(static_cast(r), rs, et); });
+
+ connect(remoteClient, &RemoteClient::activateError, this, &ConnectionController::onActivateError);
+ connect(remoteClient, &RemoteClient::socketError, this, &ConnectionController::onSocketError);
+ connect(remoteClient, &RemoteClient::serverTimeout, this, &ConnectionController::onServerTimeout);
+
+ connect(remoteClient, &RemoteClient::protocolVersionMismatch, this,
+ &ConnectionController::onProtocolVersionMismatch);
+
+ connect(remoteClient, &RemoteClient::registerAccepted, this, &ConnectionController::onRegisterAccepted);
+
+ connect(remoteClient, &RemoteClient::registerAcceptedNeedsActivate, this,
+ &ConnectionController::onRegisterAcceptedNeedsActivate);
+
+ connect(remoteClient, &RemoteClient::activateAccepted, this, &ConnectionController::onActivateAccepted);
+
+ connect(remoteClient, &RemoteClient::notifyUserAboutUpdate, this, &ConnectionController::onNotifyUserAboutUpdate);
+
+ connect(remoteClient, &RemoteClient::sigForgotPasswordSuccess, this,
+ &ConnectionController::onForgotPasswordSuccess);
+
+ connect(remoteClient, &RemoteClient::sigForgotPasswordError, this, &ConnectionController::onForgotPasswordError);
+
+ connect(remoteClient, &RemoteClient::sigPromptForForgotPasswordReset, this,
+ &ConnectionController::onPromptForgotPasswordReset);
+
+ connect(remoteClient, &RemoteClient::sigPromptForForgotPasswordChallenge, this,
+ &ConnectionController::onPromptForgotPasswordChallenge);
+}
+
+void ConnectionController::connectToServer()
+{
+ dlgConnect = new DlgConnect(dialogParent);
+ connect(dlgConnect, &DlgConnect::sigStartForgotPasswordRequest, this, &ConnectionController::forgotPasswordRequest);
+
+ if (dlgConnect->exec()) {
+ remoteClient->connectToServer(dlgConnect->getHost(), static_cast(dlgConnect->getPort()),
+ dlgConnect->getPlayerName(), dlgConnect->getPassword());
+ }
+}
+
+void ConnectionController::connectToServerDirect(const QString &host,
+ unsigned int port,
+ const QString &playerName,
+ const QString &password)
+{
+ remoteClient->connectToServer(host, port, playerName, password);
+}
+
+void ConnectionController::disconnectFromServer()
+{
+ remoteClient->disconnectFromServer();
+}
+
+void ConnectionController::registerToServer()
+{
+ DlgRegister dlg(dialogParent);
+ if (dlg.exec()) {
+ remoteClient->registerToServer(dlg.getHost(), static_cast(dlg.getPort()), dlg.getPlayerName(),
+ dlg.getPassword(), dlg.getEmail(), dlg.getCountry(), dlg.getRealName());
+ }
+}
+
+void ConnectionController::forgotPasswordRequest()
+{
+ DlgForgotPasswordRequest dlg(dialogParent);
+ if (dlg.exec()) {
+ remoteClient->requestForgotPasswordToServer(dlg.getHost(), static_cast(dlg.getPort()),
+ dlg.getPlayerName());
+ }
+}
+
+void ConnectionController::onConnectionClosedEvent(const Event_ConnectionClosed &event)
+{
+ remoteClient->disconnectFromServer();
+
+ QString reasonStr;
+ switch (event.reason()) {
+ case Event_ConnectionClosed::USER_LIMIT_REACHED: {
+ reasonStr = tr("The server has reached its maximum user capacity, please check back later.");
+ break;
+ }
+ case Event_ConnectionClosed::TOO_MANY_CONNECTIONS: {
+ reasonStr = tr("There are too many concurrent connections from your address.");
+ break;
+ }
+ case Event_ConnectionClosed::BANNED: {
+ reasonStr = tr("Banned by moderator");
+ if (event.has_end_time()) {
+ reasonStr.append(
+ "\n" + tr("Expected end time: %1").arg(QDateTime::fromSecsSinceEpoch(event.end_time()).toString()));
+ } else {
+ reasonStr.append("\n" + tr("This ban lasts indefinitely."));
+ }
+ if (event.has_reason_str()) {
+ reasonStr.append("\n\n" + QString::fromStdString(event.reason_str()));
+ }
+ break;
+ }
+ case Event_ConnectionClosed::SERVER_SHUTDOWN: {
+ reasonStr = tr("Scheduled server shutdown.");
+ break;
+ }
+ case Event_ConnectionClosed::USERNAMEINVALID: {
+ reasonStr = tr("Invalid username.");
+ break;
+ }
+ case Event_ConnectionClosed::LOGGEDINELSEWERE: {
+ reasonStr = tr("You have been logged out due to logging in at another location.");
+ break;
+ }
+ default:
+ reasonStr = QString::fromStdString(event.reason_str());
+ }
+
+ QMessageBox::critical(dialogParent, tr("Connection closed"),
+ tr("The server has terminated your connection.\nReason: %1").arg(reasonStr));
+}
+
+void ConnectionController::onServerShutdownEvent(const Event_ServerShutdown &event)
+{
+ serverShutdownMessageBox.setInformativeText(tr("The server is going to be restarted in %n minute(s).\nAll running "
+ "games will be lost.\nReason for shutdown: %1",
+ "", event.minutes())
+ .arg(QString::fromStdString(event.reason())));
+ serverShutdownMessageBox.setIconPixmap(QPixmap("theme:cockatrice").scaled(64, 64));
+ serverShutdownMessageBox.setText(tr("Scheduled server shutdown"));
+ serverShutdownMessageBox.setWindowModality(Qt::ApplicationModal);
+ serverShutdownMessageBox.setVisible(true);
+}
+
+void ConnectionController::onStatusChanged(ClientStatus status)
+{
+ // Update the window title first, then let MainWindow handle its own UI
+ // state via the forwarded signal
+ updateWindowTitle();
+ emit statusChanged(status);
+
+ // TabSupervisor::stop() needs calling on disconnect; start() is driven by
+ // onUserInfoReceived → tabSupervisorStartRequested.
+ if (status == StatusDisconnected) {
+ emit tabSupervisorStopRequested();
+ }
+}
+
+void ConnectionController::onUserInfoReceived(const ServerInfo_User &info)
+{
+ emit tabSupervisorStartRequested(info);
+}
+
+void ConnectionController::onLoginError(int r,
+ QString reasonStr,
+ quint32 endTime,
+ const QList &missingFeatures)
+{
+ switch (static_cast(r)) {
+ case Response::RespClientUpdateRequired: {
+ QString formatted = "Missing Features: ";
+ for (int i = 0; i < missingFeatures.size(); ++i) {
+ formatted.append(QString("\n %1").arg(QChar(0x2022)) + " " + missingFeatures.value(i));
+ }
+
+ QMessageBox msgBox(dialogParent);
+ msgBox.setIcon(QMessageBox::Critical);
+ msgBox.setWindowTitle(tr("Failed Login"));
+ msgBox.setText(tr("Your client seems to be missing features this server requires for connection.") +
+ "\n\n" + tr("To update your client, go to 'Help -> Check for Client Updates'."));
+ msgBox.setDetailedText(formatted);
+ msgBox.exec();
+ break;
+ }
+
+ case Response::RespWrongPassword: {
+ QMessageBox::critical(dialogParent, tr("Error"),
+ tr("Incorrect username or password. "
+ "Please check your authentication information and try again."));
+ break;
+ }
+
+ case Response::RespWouldOverwriteOldSession: {
+ QMessageBox::critical(dialogParent, tr("Error"),
+ tr("There is already an active session using this user name.\n"
+ "Please close that session first and re-login."));
+ break;
+ }
+
+ case Response::RespUserIsBanned: {
+ QString bannedStr =
+ endTime ? tr("You are banned until %1.").arg(QDateTime::fromSecsSinceEpoch(endTime).toString())
+ : tr("You are banned indefinitely.");
+ if (!reasonStr.isEmpty()) {
+ bannedStr.append("\n\n" + reasonStr);
+ }
+ QMessageBox::critical(dialogParent, tr("Error"), bannedStr);
+ break;
+ }
+
+ case Response::RespUsernameInvalid: {
+ QMessageBox::critical(dialogParent, tr("Error"), extractInvalidUsernameMessage(reasonStr));
+ break;
+ }
+
+ case Response::RespRegistrationRequired: {
+ if (QMessageBox::question(dialogParent, tr("Error"),
+ tr("This server requires user registration. Do you want to register now?"),
+ QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
+ registerToServer();
+ }
+ return; // don't re-prompt connect
+ }
+
+ case Response::RespClientIdRequired: {
+ QMessageBox::critical(dialogParent, tr("Error"),
+ tr("This server requires client IDs. Your client is either failing to generate an "
+ "ID or you are running a modified client.\n"
+ "Please close and reopen your client to try again."));
+ break;
+ }
+
+ case Response::RespContextError: {
+ QMessageBox::critical(dialogParent, tr("Error"),
+ tr("An internal error has occurred, please close and reopen Cockatrice before "
+ "trying again.\nIf the error persists, ensure you are running the latest "
+ "version of the software and if needed contact the software developers."));
+ break;
+ }
+
+ case Response::RespAccountNotActivated: {
+ bool ok = false;
+ QString token =
+ getTextWithMax(dialogParent, tr("Account activation"),
+ tr("Your account has not been activated yet.\n"
+ "You need to provide the activation token received in the activation email."),
+ QLineEdit::Normal, QString(), &ok);
+
+ if (ok && !token.isEmpty()) {
+ remoteClient->activateToServer(token);
+ return;
+ }
+ remoteClient->disconnectFromServer();
+ return;
+ }
+
+ case Response::RespServerFull: {
+ QMessageBox::critical(dialogParent, tr("Server Full"),
+ tr("The server has reached its maximum user capacity, please check back later."));
+ break;
+ }
+
+ default: {
+ QMessageBox::critical(dialogParent, tr("Error"),
+ tr("Unknown login error: %1").arg(r) +
+ tr("\nThis usually means that your client version is out of date, and the server "
+ "sent a reply your client doesn't understand."));
+ break;
+ }
+ }
+
+ // Re-open the connect dialog after any handled error
+ connectToServer();
+}
+
+void ConnectionController::onRegisterError(int r, QString reasonStr, quint32 endTime)
+{
+ switch (static_cast(r)) {
+ case Response::RespRegistrationDisabled: {
+ QMessageBox::critical(dialogParent, tr("Registration denied"),
+ tr("Registration is currently disabled on this server"));
+ break;
+ }
+ case Response::RespUserAlreadyExists: {
+ QMessageBox::critical(dialogParent, tr("Registration denied"),
+ tr("There is already an existing account with the same user name."));
+ break;
+ }
+ case Response::RespEmailRequiredToRegister: {
+ QMessageBox::critical(dialogParent, tr("Registration denied"),
+ tr("It's mandatory to specify a valid email address when registering."));
+ break;
+ }
+ case Response::RespEmailBlackListed: {
+ if (reasonStr.isEmpty()) {
+ reasonStr =
+ "The email address provider used during registration has been blocked from use on this server.";
+ }
+ QMessageBox::critical(dialogParent, tr("Registration denied"), reasonStr);
+ break;
+ }
+ case Response::RespTooManyRequests: {
+ QMessageBox::critical(dialogParent, tr("Registration denied"),
+ tr("It appears you are attempting to register a new account on this server yet you "
+ "already have an account registered with the email provided. This server "
+ "restricts the number of accounts a user can register per address. Please "
+ "contact the server operator for further assistance or to obtain your "
+ "credential information."));
+ break;
+ }
+ case Response::RespPasswordTooShort: {
+ QMessageBox::critical(dialogParent, tr("Registration denied"), tr("Password too short."));
+ break;
+ }
+ case Response::RespUserIsBanned: {
+ QString bannedStr =
+ endTime ? tr("You are banned until %1.").arg(QDateTime::fromSecsSinceEpoch(endTime).toString())
+ : tr("You are banned indefinitely.");
+ if (!reasonStr.isEmpty()) {
+ bannedStr.append("\n\n" + reasonStr);
+ }
+ QMessageBox::critical(dialogParent, tr("Error"), bannedStr);
+ break;
+ }
+ case Response::RespUsernameInvalid: {
+ QMessageBox::critical(dialogParent, tr("Error"), extractInvalidUsernameMessage(reasonStr));
+ break;
+ }
+ case Response::RespRegistrationFailed: {
+ QMessageBox::critical(dialogParent, tr("Error"),
+ tr("Registration failed for a technical problem on the server."));
+ break;
+ }
+ case Response::RespNotConnected: {
+ QMessageBox::critical(dialogParent, tr("Error"), tr("The connection to the server has been lost."));
+ break;
+ }
+ default: {
+ QMessageBox::critical(dialogParent, tr("Error"),
+ tr("Unknown registration error: %1").arg(r) +
+ tr("\nThis usually means that your client version is out of date, and the server "
+ "sent a reply your client doesn't understand."));
+ break;
+ }
+ }
+
+ registerToServer();
+}
+
+void ConnectionController::onActivateError()
+{
+ QMessageBox::critical(dialogParent, tr("Error"), tr("Account activation failed"));
+ remoteClient->disconnectFromServer();
+ connectToServer();
+}
+
+void ConnectionController::onSocketError(const QString &errorStr)
+{
+ QMessageBox::critical(dialogParent, tr("Error"), tr("Socket error: %1").arg(errorStr));
+ connectToServer();
+}
+
+void ConnectionController::onServerTimeout()
+{
+ QMessageBox::critical(dialogParent, tr("Error"), tr("Server timeout"));
+ connectToServer();
+}
+
+void ConnectionController::onProtocolVersionMismatch(int localVersion, int remoteVersion)
+{
+ if (localVersion > remoteVersion) {
+ QMessageBox::critical(dialogParent, tr("Error"),
+ tr("You are trying to connect to an obsolete server. Please downgrade your Cockatrice "
+ "version or connect to a suitable server.\n"
+ "Local version is %1, remote version is %2.")
+ .arg(localVersion)
+ .arg(remoteVersion));
+ } else {
+ QMessageBox::critical(dialogParent, tr("Error"),
+ tr("Your Cockatrice client is obsolete. Please update your Cockatrice version.\n"
+ "Local version is %1, remote version is %2.")
+ .arg(localVersion)
+ .arg(remoteVersion));
+ }
+}
+
+void ConnectionController::onRegisterAccepted()
+{
+ QMessageBox::information(dialogParent, tr("Success"), tr("Registration accepted.\nWill now login."));
+}
+
+void ConnectionController::onRegisterAcceptedNeedsActivate()
+{
+ // Server will send activation email; nothing to display here.
+}
+
+void ConnectionController::onActivateAccepted()
+{
+ QMessageBox::information(dialogParent, tr("Success"), tr("Account activation accepted.\nWill now login."));
+}
+
+void ConnectionController::onNotifyUserAboutUpdate()
+{
+ QMessageBox::information(
+ dialogParent, tr("Information"),
+ tr("This server supports additional features that your client doesn't have.\n"
+ "This is most likely not a problem, but this message might mean there is a new version of "
+ "Cockatrice available or this server is running a custom or pre-release version.\n\n"
+ "To update your client, go to Help -> Check for Updates."));
+}
+
+void ConnectionController::onForgotPasswordSuccess()
+{
+ QMessageBox::information(
+ dialogParent, tr("Reset Password"),
+ tr("Your password has been reset successfully, you can now log in using the new credentials."));
+ SettingsCache::instance().servers().setFPHostName("");
+ SettingsCache::instance().servers().setFPPort("");
+ SettingsCache::instance().servers().setFPPlayerName("");
+}
+
+void ConnectionController::onForgotPasswordError()
+{
+ QMessageBox::warning(
+ dialogParent, tr("Reset Password"),
+ tr("Failed to reset user account password, please contact the server operator to reset your password."));
+ SettingsCache::instance().servers().setFPHostName("");
+ SettingsCache::instance().servers().setFPPort("");
+ SettingsCache::instance().servers().setFPPlayerName("");
+}
+
+void ConnectionController::onPromptForgotPasswordReset()
+{
+ QMessageBox::information(dialogParent, tr("Reset Password"),
+ tr("Activation request received, please check your email for an activation token."));
+ DlgForgotPasswordReset dlg(dialogParent);
+ if (dlg.exec()) {
+ remoteClient->submitForgotPasswordResetToServer(dlg.getHost(), static_cast(dlg.getPort()),
+ dlg.getPlayerName(), dlg.getToken(), dlg.getPassword());
+ }
+}
+
+void ConnectionController::onPromptForgotPasswordChallenge()
+{
+ DlgForgotPasswordChallenge dlg(dialogParent);
+ if (dlg.exec()) {
+ remoteClient->submitForgotPasswordChallengeToServer(dlg.getHost(), static_cast(dlg.getPort()),
+ dlg.getPlayerName(), dlg.getEmail());
+ }
+}
+
+void ConnectionController::updateWindowTitle()
+{
+ const QString appName = QStringLiteral("Cockatrice");
+ QString title;
+
+ switch (remoteClient->getStatus()) {
+ case StatusConnecting: {
+ title = appName + " - " + tr("Connecting to %1...").arg(remoteClient->peerName());
+ break;
+ }
+ case StatusRegistering: {
+ title = appName + " - " +
+ tr("Registering to %1 as %2...").arg(remoteClient->peerName()).arg(remoteClient->getUserName());
+ break;
+ }
+ case StatusDisconnected: {
+ title = appName + " - " + tr("Disconnected");
+ break;
+ }
+ case StatusLoggingIn: {
+ title = appName + " - " + tr("Connected, logging in at %1").arg(remoteClient->peerName());
+ break;
+ }
+ case StatusLoggedIn: {
+ title = remoteClient->getUserName() + "@" + remoteClient->peerName();
+ break;
+ }
+ case StatusRequestingForgotPassword:
+ case StatusSubmitForgotPasswordChallenge:
+ case StatusSubmitForgotPasswordReset:
+ title = appName + " - " +
+ tr("Requesting forgotten password to %1 as %2...")
+ .arg(remoteClient->peerName())
+ .arg(remoteClient->getUserName());
+ break;
+ default:
+ title = appName;
+ }
+
+ emit windowTitleChanged(title);
+}
+
+// static
+QString ConnectionController::extractInvalidUsernameMessage(QString &in)
+{
+ QString out = tr("Invalid username.") + " ";
+ QStringList rules = in.split(QChar('|'));
+
+ if (rules.size() == 7 || rules.size() == 9) {
+ out += tr("Your username must respect these rules:") + "
" + tr("can contain the following punctuation: %1").arg(rules.at(6).toHtmlEscaped()) + "
";
+ }
+
+ out += "
" +
+ tr("first character can %1 be a punctuation mark").arg((rules.at(5).toInt() > 0) ? "" : tr("NOT")) +
+ "
";
+
+ if (rules.size() == 9) {
+ if (rules.at(7).size() > 0) {
+ QString words = rules.at(7).toHtmlEscaped();
+ if (words.startsWith("\n")) {
+ out += tr("no unacceptable language as specified by these server rules:",
+ "note that the following lines will not be translated");
+ for (QString &line : words.split("\n", Qt::SkipEmptyParts)) {
+ out += "
" + line + "
";
+ }
+ } else {
+ out += "
" + tr("can not contain any of the following words: %1").arg(words) + "
";
+ }
+ }
+
+ if (rules.at(8).size() > 0) {
+ out += "
" +
+ tr("can not match any of the following expressions: %1").arg(rules.at(8).toHtmlEscaped()) +
+ "
";
+ }
+ }
+
+ out += "
";
+ } else {
+ out += tr("You may only use A-Z, a-z, 0-9, _, ., and - in your username.");
+ }
+
+ return out;
+}
\ No newline at end of file
diff --git a/cockatrice/src/client/network/connection_controller/remote_connection_controller.h b/cockatrice/src/client/network/connection_controller/remote_connection_controller.h
new file mode 100644
index 000000000..7486bc81a
--- /dev/null
+++ b/cockatrice/src/client/network/connection_controller/remote_connection_controller.h
@@ -0,0 +1,98 @@
+#ifndef COCKATRICE_REMOTE_CONNECTION_CONTROLLER_H
+#define COCKATRICE_REMOTE_CONNECTION_CONTROLLER_H
+
+#include "abstract_client.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+class RemoteClient;
+class ServerInfo_User;
+class DlgConnect;
+
+/**
+ * Owns the RemoteClient and its worker thread.
+ * Encapsulates all connection, authentication, and registration logic so that
+ * MainWindow only needs to react to high-level signals.
+ */
+class ConnectionController : public QObject
+{
+ Q_OBJECT
+
+public:
+ explicit ConnectionController(QWidget *dialogParent, QObject *parent = nullptr);
+ ~ConnectionController() override;
+
+ RemoteClient *client() const
+ {
+ return remoteClient;
+ }
+
+ void registerToServer();
+ void forgotPasswordRequest();
+ void connectToServer();
+ void
+ connectToServerDirect(const QString &host, unsigned int port, const QString &playerName, const QString &password);
+ void disconnectFromServer();
+
+ void refreshWindowTitle()
+ {
+ updateWindowTitle();
+ }
+
+signals:
+ void windowTitleChanged(const QString &title);
+
+ void tabSupervisorStartRequested(const ServerInfo_User &info);
+ void tabSupervisorStopRequested();
+
+ // Passes the raw ClientStatus through so MainWindow can drive its own
+ // action enable/disable logic
+ void statusChanged(ClientStatus status);
+
+private slots:
+ // Slots wired directly to RemoteClient signals
+ void onStatusChanged(ClientStatus status);
+ void onUserInfoReceived(const ServerInfo_User &info);
+ void onLoginError(int r, QString reasonStr, quint32 endTime, const QList &missingFeatures);
+ void onRegisterAccepted();
+ void onRegisterAcceptedNeedsActivate();
+ void onRegisterError(int r, QString reasonStr, quint32 endTime);
+ void onActivateAccepted();
+ void onActivateError();
+ void onProtocolVersionMismatch(int localVersion, int remoteVersion);
+ void onNotifyUserAboutUpdate();
+ void onConnectionClosedEvent(const Event_ConnectionClosed &event);
+ void onServerShutdownEvent(const Event_ServerShutdown &event);
+ void onSocketError(const QString &errorStr);
+ void onServerTimeout();
+
+ // Forgot-password flow
+ void onForgotPasswordSuccess();
+ void onForgotPasswordError();
+ void onPromptForgotPasswordReset();
+ void onPromptForgotPasswordChallenge();
+
+private:
+ void wireClientSignals();
+ void updateWindowTitle();
+
+ /** Parse the server's pipe-delimited username-rule string into HTML. */
+ static QString extractInvalidUsernameMessage(QString &in);
+
+ RemoteClient *remoteClient{nullptr};
+ QThread *clientThread{nullptr};
+ QWidget *dialogParent{nullptr}; // used as parent for QMessageBox / dialog calls
+
+ // Persistent so it can be updated in-place by onServerShutdownEvent
+ QMessageBox serverShutdownMessageBox;
+
+ // Kept as a member so the forgot-password signal can be wired to it
+ DlgConnect *dlgConnect{nullptr};
+};
+
+#endif // COCKATRICE_REMOTE_CONNECTION_CONTROLLER_H
diff --git a/cockatrice/src/client/network/interfaces/deck_stats_interface.cpp b/cockatrice/src/client/network/interfaces/deck_stats_interface.cpp
index 0298daa6b..8689a19e9 100644
--- a/cockatrice/src/client/network/interfaces/deck_stats_interface.cpp
+++ b/cockatrice/src/client/network/interfaces/deck_stats_interface.cpp
@@ -6,12 +6,12 @@
#include
#include
#include
+#include
#include
#include
#include
-DeckStatsInterface::DeckStatsInterface(CardDatabase &_cardDatabase, QObject *parent)
- : QObject(parent), cardDatabase(_cardDatabase)
+DeckStatsInterface::DeckStatsInterface(QObject *parent) : QObject(parent)
{
manager = new QNetworkAccessManager(this);
connect(manager, &QNetworkAccessManager::finished, this, &DeckStatsInterface::queryFinished);
@@ -70,8 +70,8 @@ void DeckStatsInterface::analyzeDeck(const DeckList &deck)
void DeckStatsInterface::copyDeckWithoutTokens(const DeckList &source, DeckList &destination)
{
- auto copyIfNotAToken = [this, &destination](const auto node, const auto card) {
- CardInfoPtr dbCard = cardDatabase.query()->getCardInfo(card->getName());
+ auto copyIfNotAToken = [&destination](const auto node, const auto card) {
+ CardInfoPtr dbCard = CardDatabaseManager::query()->getCardInfo(card->getName());
if (dbCard && !dbCard->getIsToken()) {
DecklistCardNode *addedCard = destination.addCard(card->getName(), node->getName(), -1);
addedCard->setNumber(card->getNumber());
diff --git a/cockatrice/src/client/network/interfaces/deck_stats_interface.h b/cockatrice/src/client/network/interfaces/deck_stats_interface.h
index 7dc841027..09bf998de 100644
--- a/cockatrice/src/client/network/interfaces/deck_stats_interface.h
+++ b/cockatrice/src/client/network/interfaces/deck_stats_interface.h
@@ -1,13 +1,12 @@
/**
* @file deck_stats_interface.h
* @ingroup ApiInterfaces
- * @brief TODO: Document this.
*/
+//! \todo Document this file.
#ifndef DECKSTATS_INTERFACE_H
#define DECKSTATS_INTERFACE_H
-#include
#include
class QByteArray;
@@ -21,8 +20,6 @@ class DeckStatsInterface : public QObject
private:
QNetworkAccessManager *manager;
- CardDatabase &cardDatabase;
-
/**
* Deckstats doesn't recognize token cards, and instead tries to find the
* closest non-token card instead. So we construct a new deck which has no
@@ -35,7 +32,7 @@ private slots:
void getAnalyzeRequestData(const DeckList &deck, QByteArray &data);
public:
- explicit DeckStatsInterface(CardDatabase &_cardDatabase, QObject *parent = nullptr);
+ explicit DeckStatsInterface(QObject *parent = nullptr);
void analyzeDeck(const DeckList &deck);
};
diff --git a/cockatrice/src/client/network/interfaces/tapped_out_interface.cpp b/cockatrice/src/client/network/interfaces/tapped_out_interface.cpp
index a30a7f531..5dc77fa2c 100644
--- a/cockatrice/src/client/network/interfaces/tapped_out_interface.cpp
+++ b/cockatrice/src/client/network/interfaces/tapped_out_interface.cpp
@@ -6,12 +6,12 @@
#include
#include
#include
+#include
#include
#include
#include
-TappedOutInterface::TappedOutInterface(CardDatabase &_cardDatabase, QObject *parent)
- : QObject(parent), cardDatabase(_cardDatabase)
+TappedOutInterface::TappedOutInterface(QObject *parent) : QObject(parent)
{
manager = new QNetworkAccessManager(this);
connect(manager, &QNetworkAccessManager::finished, this, &TappedOutInterface::queryFinished);
@@ -89,22 +89,26 @@ 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);
}
void TappedOutInterface::copyDeckSplitMainAndSide(const DeckList &source, DeckList &mainboard, DeckList &sideboard)
{
- auto copyMainOrSide = [this, &mainboard, &sideboard](const auto node, const auto card) {
- CardInfoPtr dbCard = cardDatabase.query()->getCardInfo(card->getName());
- if (!dbCard || dbCard->getIsToken())
+ auto copyMainOrSide = [&mainboard, &sideboard](const auto node, const auto card) {
+ CardInfoPtr dbCard = CardDatabaseManager::query()->getCardInfo(card->getName());
+ if (!dbCard || dbCard->getIsToken()) {
return;
+ }
DecklistCardNode *addedCard;
- if (node->getName() == DECK_ZONE_SIDE)
+ if (node->getName() == DECK_ZONE_SIDE) {
addedCard = sideboard.addCard(card->getName(), node->getName(), -1);
- else
+ } else {
addedCard = mainboard.addCard(card->getName(), node->getName(), -1);
+ }
addedCard->setNumber(card->getNumber());
};
diff --git a/cockatrice/src/client/network/interfaces/tapped_out_interface.h b/cockatrice/src/client/network/interfaces/tapped_out_interface.h
index 0ea9c8358..32f9369d5 100644
--- a/cockatrice/src/client/network/interfaces/tapped_out_interface.h
+++ b/cockatrice/src/client/network/interfaces/tapped_out_interface.h
@@ -1,14 +1,14 @@
/**
* @file tapped_out_interface.h
* @ingroup ApiInterfaces
- * @brief TODO: Document this.
*/
+//! \todo Document this file.
#ifndef TAPPEDOUT_INTERFACE_H
#define TAPPEDOUT_INTERFACE_H
-#include
-#include
+#include
+#include
inline Q_LOGGING_CATEGORY(TappedOutInterfaceLog, "tapped_out_interface");
@@ -29,14 +29,13 @@ class TappedOutInterface : public QObject
private:
QNetworkAccessManager *manager;
- CardDatabase &cardDatabase;
void copyDeckSplitMainAndSide(const DeckList &source, DeckList &mainboard, DeckList &sideboard);
private slots:
void queryFinished(QNetworkReply *reply);
void getAnalyzeRequestData(const DeckList &deck, QByteArray &data);
public:
- explicit TappedOutInterface(CardDatabase &_cardDatabase, QObject *parent = nullptr);
+ explicit TappedOutInterface(QObject *parent = nullptr);
void analyzeDeck(const DeckList &deck);
};
diff --git a/cockatrice/src/client/network/parsers/deck_link_to_api_transformer.h b/cockatrice/src/client/network/parsers/deck_link_to_api_transformer.h
index 1e2372fd1..bbdd70c97 100644
--- a/cockatrice/src/client/network/parsers/deck_link_to_api_transformer.h
+++ b/cockatrice/src/client/network/parsers/deck_link_to_api_transformer.h
@@ -1,8 +1,8 @@
/**
* @file deck_link_to_api_transformer.h
* @ingroup ApiInterfaces
- * @brief TODO: Document this.
*/
+//! \todo Document this file.
#ifndef DECK_LINK_TO_API_TRANSFORMER_H
#define DECK_LINK_TO_API_TRANSFORMER_H
diff --git a/cockatrice/src/client/network/parsers/interface_json_deck_parser.h b/cockatrice/src/client/network/parsers/interface_json_deck_parser.h
index 1818aa35c..87fde4d54 100644
--- a/cockatrice/src/client/network/parsers/interface_json_deck_parser.h
+++ b/cockatrice/src/client/network/parsers/interface_json_deck_parser.h
@@ -1,8 +1,8 @@
/**
* @file interface_json_deck_parser.h
* @ingroup ApiInterfaces
- * @brief TODO: Document this.
*/
+//! \todo Document this file.
#ifndef INTERFACE_JSON_DECK_PARSER_H
#define INTERFACE_JSON_DECK_PARSER_H
diff --git a/cockatrice/src/client/network/update/card_spoiler/spoiler_background_updater.h b/cockatrice/src/client/network/update/card_spoiler/spoiler_background_updater.h
index a2feb5ccf..03d4897a2 100644
--- a/cockatrice/src/client/network/update/card_spoiler/spoiler_background_updater.h
+++ b/cockatrice/src/client/network/update/card_spoiler/spoiler_background_updater.h
@@ -1,8 +1,8 @@
/**
* @file spoiler_background_updater.h
* @ingroup Client
- * @brief TODO: Document this.
*/
+//! \todo Document this file.
#ifndef COCKATRICE_SPOILER_DOWNLOADER_H
#define COCKATRICE_SPOILER_DOWNLOADER_H
diff --git a/cockatrice/src/client/network/update/client/client_update_checker.h b/cockatrice/src/client/network/update/client/client_update_checker.h
index 4e6f279c3..1a89de533 100644
--- a/cockatrice/src/client/network/update/client/client_update_checker.h
+++ b/cockatrice/src/client/network/update/client/client_update_checker.h
@@ -1,8 +1,8 @@
/**
* @file client_update_checker.h
* @ingroup ClientUpdate
- * @brief TODO: Document this.
*/
+//! \todo Document this file.
#ifndef CLIENT_UPDATE_CHECKER_H
#define CLIENT_UPDATE_CHECKER_H
diff --git a/cockatrice/src/client/network/update/client/release_channel.cpp b/cockatrice/src/client/network/update/client/release_channel.cpp
index 9edfe7caf..260167bc8 100644
--- a/cockatrice/src/client/network/update/client/release_channel.cpp
+++ b/cockatrice/src/client/network/update/client/release_channel.cpp
@@ -129,8 +129,9 @@ void StableReleaseChannel::releaseListFinished()
return;
}
- if (!lastRelease)
+ if (!lastRelease) {
lastRelease = new Release;
+ }
lastRelease->setName(resultMap["name"].toString());
lastRelease->setDescriptionUrl(resultMap["html_url"].toString());
@@ -246,8 +247,9 @@ void BetaReleaseChannel::releaseListFinished()
return;
}
- if (lastRelease == nullptr)
+ if (lastRelease == nullptr) {
lastRelease = new Release;
+ }
lastRelease->setCommitHash(resultMap["target_commitish"].toString());
lastRelease->setPublishDate(resultMap["published_at"].toDate());
diff --git a/cockatrice/src/client/network/update/client/release_channel.h b/cockatrice/src/client/network/update/client/release_channel.h
index 93e6b985d..c56d0cfce 100644
--- a/cockatrice/src/client/network/update/client/release_channel.h
+++ b/cockatrice/src/client/network/update/client/release_channel.h
@@ -1,8 +1,8 @@
/**
* @file release_channel.h
* @ingroup ClientUpdate
- * @brief TODO: Document this.
*/
+//! \todo Document this file.
#ifndef RELEASECHANNEL_H
#define RELEASECHANNEL_H
diff --git a/cockatrice/src/client/network/update/client/update_downloader.cpp b/cockatrice/src/client/network/update/client/update_downloader.cpp
index c0f3e945c..a71bcf8f8 100644
--- a/cockatrice/src/client/network/update/client/update_downloader.cpp
+++ b/cockatrice/src/client/network/update/client/update_downloader.cpp
@@ -10,8 +10,9 @@ UpdateDownloader::UpdateDownloader(QObject *parent) : QObject(parent), response(
void UpdateDownloader::beginDownload(QUrl downloadUrl)
{
// Save the original URL because we need it for the filename
- if (originalUrl.isEmpty())
+ if (originalUrl.isEmpty()) {
originalUrl = downloadUrl;
+ }
response = netMan->get(QNetworkRequest(downloadUrl));
connect(response, &QNetworkReply::finished, this, &UpdateDownloader::fileFinished);
@@ -21,8 +22,9 @@ void UpdateDownloader::beginDownload(QUrl downloadUrl)
void UpdateDownloader::downloadError(QNetworkReply::NetworkError)
{
- if (response == nullptr)
+ if (response == nullptr) {
return;
+ }
emit error(response->errorString().toUtf8());
}
diff --git a/cockatrice/src/client/network/update/client/update_downloader.h b/cockatrice/src/client/network/update/client/update_downloader.h
index d70759038..9a0caccbc 100644
--- a/cockatrice/src/client/network/update/client/update_downloader.h
+++ b/cockatrice/src/client/network/update/client/update_downloader.h
@@ -1,8 +1,8 @@
/**
* @file update_downloader.h
* @ingroup ClientUpdate
- * @brief TODO: Document this.
*/
+//! \todo Document this file.
#ifndef COCKATRICE_UPDATEDOWNLOADER_H
#define COCKATRICE_UPDATEDOWNLOADER_H
diff --git a/cockatrice/src/client/settings/cache_settings.cpp b/cockatrice/src/client/settings/cache_settings.cpp
index a66897b4a..73e5a98a1 100644
--- a/cockatrice/src/client/settings/cache_settings.cpp
+++ b/cockatrice/src/client/settings/cache_settings.cpp
@@ -1,5 +1,7 @@
#include "cache_settings.h"
+#include "../../interface/card_picture_loader/card_picture_loader_cache_method.h"
+#include "../../interface/card_picture_loader/card_picture_loader_local_schemes.h"
#include "../network/update/client/release_channel.h"
#include "card_counter_settings.h"
#include "version_string.h"
@@ -24,10 +26,11 @@ SettingsCache &SettingsCache::instance()
QString SettingsCache::getDataPath()
{
- if (isPortableBuild)
+ if (isPortableBuild) {
return qApp->applicationDirPath() + "/data";
- else
+ } else {
return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation);
+ }
}
QString SettingsCache::getSettingsPath()
@@ -37,10 +40,11 @@ QString SettingsCache::getSettingsPath()
QString SettingsCache::getCachePath() const
{
- if (isPortableBuild)
+ if (isPortableBuild) {
return qApp->applicationDirPath() + "/cache";
- else
+ } else {
return QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
+ }
}
QString SettingsCache::getNetworkCachePath() const
@@ -50,14 +54,17 @@ QString SettingsCache::getNetworkCachePath() const
void SettingsCache::translateLegacySettings()
{
- if (isPortableBuild)
+ if (isPortableBuild) {
return;
+ }
// Layouts
QFile layoutFile(getSettingsPath() + "layouts/deckLayout.ini");
- if (layoutFile.exists())
- if (layoutFile.copy(getSettingsPath() + "layouts.ini"))
+ if (layoutFile.exists()) {
+ if (layoutFile.copy(getSettingsPath() + "layouts.ini")) {
layoutFile.remove();
+ }
+ }
QStringList usedKeys;
QSettings legacySetting;
@@ -116,10 +123,11 @@ void SettingsCache::translateLegacySettings()
gameFilters().setHideIgnoredUserGames(legacySetting.value("hide_ignored_user_games").toBool());
gameFilters().setMinPlayers(legacySetting.value("min_players").toInt());
- if (legacySetting.value("max_players").toInt() > 1)
+ if (legacySetting.value("max_players").toInt() > 1) {
gameFilters().setMaxPlayers(legacySetting.value("max_players").toInt());
- else
+ } else {
gameFilters().setMaxPlayers(99); // This prevents a bug where no games will show if max was not set before
+ }
QStringList allFilters = legacySetting.allKeys();
for (int i = 0; i < allFilters.size(); ++i) {
@@ -135,8 +143,9 @@ void SettingsCache::translateLegacySettings()
QStringList allLegacyKeys = legacySetting.allKeys();
for (int i = 0; i < allLegacyKeys.size(); ++i) {
- if (usedKeys.contains(allLegacyKeys.at(i)))
+ if (usedKeys.contains(allLegacyKeys.at(i))) {
continue;
+ }
settings->setValue(allLegacyKeys.at(i), legacySetting.value(allLegacyKeys.at(i)));
}
}
@@ -147,8 +156,9 @@ QString SettingsCache::getSafeConfigPath(QString configEntry, QString defaultPat
// if the config settings is empty or refers to a not-existing folder,
// ensure that the defaut path exists and return it
if (tmp.isEmpty() || !QDir(tmp).exists()) {
- if (!QDir().mkpath(defaultPath))
+ if (!QDir().mkpath(defaultPath)) {
qCInfo(SettingsCacheLog) << "[SettingsCache] Could not create folder:" << defaultPath;
+ }
tmp = defaultPath;
}
return tmp;
@@ -159,8 +169,9 @@ QString SettingsCache::getSafeConfigFilePath(QString configEntry, QString defaul
QString tmp = settings->value(configEntry).toString();
// if the config settings is empty or refers to a not-existing file,
// return the default Path
- if (!QFile::exists(tmp) || tmp.isEmpty())
+ if (!QFile::exists(tmp) || tmp.isEmpty()) {
tmp = std::move(defaultPath);
+ }
return tmp;
}
@@ -168,8 +179,9 @@ SettingsCache::SettingsCache()
{
// first, figure out if we are running in portable mode
isPortableBuild = QFile::exists(qApp->applicationDirPath() + "/portable.dat");
- if (isPortableBuild)
+ if (isPortableBuild) {
qCInfo(SettingsCacheLog) << "Portable mode enabled";
+ }
// define a dummy context that will be used where needed
QString dummy = QT_TRANSLATE_NOOP("i18n", "English");
@@ -189,8 +201,9 @@ SettingsCache::SettingsCache()
cardCounterSettings = new CardCounterSettings(settingsPath, this);
- if (!QFile(settingsPath + "global.ini").exists())
+ if (!QFile(settingsPath + "global.ini").exists()) {
translateLegacySettings();
+ }
// updates - don't reorder them or their index in the settings won't match
// append channels one by one, or msvc will add them in the wrong order.
@@ -211,6 +224,7 @@ SettingsCache::SettingsCache()
startupCardUpdateCheckAlwaysUpdate = settings->value("personal/startupCardUpdateCheckAlwaysUpdate", false).toBool();
cardUpdateCheckInterval = settings->value("personal/cardUpdateCheckInterval", 7).toInt();
lastCardUpdateCheck = settings->value("personal/lastCardUpdateCheck", QDateTime::currentDateTime().date()).toDate();
+ alwaysEnableNewSets = settings->value("personal/alwaysEnableNewSets", false).toBool();
notifyAboutUpdates = settings->value("personal/updatenotification", true).toBool();
notifyAboutNewVersion = settings->value("personal/newversionnotification", true).toBool();
@@ -256,14 +270,26 @@ SettingsCache::SettingsCache()
settings->setValue("personal/pixmapCacheSize", pixmapCacheSize);
settings->setValue("personal/picturedownloadhq", false);
settings->setValue("revert/pixmapCacheSize", true);
- } else
+ } else {
pixmapCacheSize = settings->value("personal/pixmapCacheSize", PIXMAPCACHE_SIZE_DEFAULT).toInt();
+ }
// sanity check
- if (pixmapCacheSize < PIXMAPCACHE_SIZE_MIN || pixmapCacheSize > PIXMAPCACHE_SIZE_MAX)
+ if (pixmapCacheSize < PIXMAPCACHE_SIZE_MIN || pixmapCacheSize > PIXMAPCACHE_SIZE_MAX) {
pixmapCacheSize = PIXMAPCACHE_SIZE_DEFAULT;
+ }
networkCacheSize = settings->value("personal/networkCacheSize", NETWORK_CACHE_SIZE_DEFAULT).toInt();
redirectCacheTtl = settings->value("personal/redirectCacheTtl", NETWORK_REDIRECT_CACHE_TTL_DEFAULT).toInt();
+ cardPictureLoaderCacheMethod =
+ settings
+ ->value("personal/cardPictureLoaderCacheMethod",
+ static_cast(CardPictureLoaderCacheMethod::CacheMethod::NETWORK_CACHE))
+ .toInt();
+ localCardImageStorageNamingScheme =
+ settings
+ ->value("personal/localCardImageStorageNamingScheme",
+ static_cast(CardPictureLoaderLocalSchemes::NamingScheme::Set_Folder_Name_Set_Collector))
+ .toInt();
picDownload = settings->value("personal/picturedownload", true).toBool();
showStatusBar = settings->value("personal/showStatusBar", false).toBool();
@@ -362,6 +388,7 @@ SettingsCache::SettingsCache()
ignoreUnregisteredUsers = settings->value("chat/ignore_unregistered", false).toBool();
ignoreUnregisteredUserMessages = settings->value("chat/ignore_unregistered_messages", false).toBool();
+ ignoreNonBuddyUserMessages = settings->value("chat/ignore_nonbuddy_messages", false).toBool();
scaleCards = settings->value("cards/scaleCards", true).toBool();
verticalCardOverlapPercent = settings->value("cards/verticalCardOverlapPercent", 33).toInt();
@@ -769,8 +796,9 @@ void SettingsCache::setPrintingSelectorCardSize(int _printingSelectorCardSize)
void SettingsCache::setIncludeRebalancedCards(bool _includeRebalancedCards)
{
- if (includeRebalancedCards == _includeRebalancedCards)
+ if (includeRebalancedCards == _includeRebalancedCards) {
return;
+ }
includeRebalancedCards = _includeRebalancedCards;
settings->setValue("cards/includerebalancedcards", includeRebalancedCards);
@@ -1090,6 +1118,12 @@ void SettingsCache::setIgnoreUnregisteredUserMessages(QT_STATE_CHANGED_T _ignore
settings->setValue("chat/ignore_unregistered_messages", ignoreUnregisteredUserMessages);
}
+void SettingsCache::setIgnoreNonBuddyUserMessages(QT_STATE_CHANGED_T _ignoreNonBuddyUserMessages)
+{
+ ignoreNonBuddyUserMessages = static_cast(_ignoreNonBuddyUserMessages);
+ settings->setValue("chat/ignore_nonbuddy_messages", ignoreNonBuddyUserMessages);
+}
+
void SettingsCache::setPixmapCacheSize(const int _pixmapCacheSize)
{
pixmapCacheSize = _pixmapCacheSize;
@@ -1097,6 +1131,13 @@ void SettingsCache::setPixmapCacheSize(const int _pixmapCacheSize)
emit pixmapCacheSizeChanged(pixmapCacheSize);
}
+void SettingsCache::setCardImageCacheMethod(const CardPictureLoaderCacheMethod::CacheMethod _cardImageCachingMethod)
+{
+ cardPictureLoaderCacheMethod = static_cast(_cardImageCachingMethod);
+ settings->setValue("personal/cardPictureLoaderCacheMethod", cardPictureLoaderCacheMethod);
+ emit cardPictureLoaderCacheMethodChanged(cardPictureLoaderCacheMethod);
+}
+
void SettingsCache::setNetworkCacheSizeInMB(const int _networkCacheSize)
{
networkCacheSize = _networkCacheSize;
@@ -1111,6 +1152,14 @@ void SettingsCache::setNetworkRedirectCacheTtl(const int _redirectCacheTtl)
emit redirectCacheTtlChanged(redirectCacheTtl);
}
+void SettingsCache::setLocalCardImageStorageNamingScheme(
+ const CardPictureLoaderLocalSchemes::NamingScheme _localCardImageStorageNamingScheme)
+{
+ localCardImageStorageNamingScheme = static_cast(_localCardImageStorageNamingScheme);
+ settings->setValue("personal/localCardImageStorageNamingScheme", localCardImageStorageNamingScheme);
+ emit localCardImageStorageNamingSchemeChanged(localCardImageStorageNamingScheme);
+}
+
void SettingsCache::setClientID(const QString &_clientID)
{
clientID = _clientID;
@@ -1246,6 +1295,12 @@ void SettingsCache::setLastCardUpdateCheck(QDate value)
settings->setValue("personal/lastCardUpdateCheck", lastCardUpdateCheck);
}
+void SettingsCache::setAlwaysEnableNewSets(bool value)
+{
+ alwaysEnableNewSets = value;
+ settings->setValue("personal/alwaysEnableNewSets", alwaysEnableNewSets);
+}
+
void SettingsCache::setRememberGameSettings(const bool _rememberGameSettings)
{
rememberGameSettings = _rememberGameSettings;
@@ -1303,8 +1358,9 @@ void SettingsCache::setMaxFontSize(int _max)
void SettingsCache::setRoundCardCorners(bool _roundCardCorners)
{
- if (_roundCardCorners == roundCardCorners)
+ if (_roundCardCorners == roundCardCorners) {
return;
+ }
roundCardCorners = _roundCardCorners;
settings->setValue("cards/roundcardcorners", _roundCardCorners);
diff --git a/cockatrice/src/client/settings/cache_settings.h b/cockatrice/src/client/settings/cache_settings.h
index ece61487f..8ee372766 100644
--- a/cockatrice/src/client/settings/cache_settings.h
+++ b/cockatrice/src/client/settings/cache_settings.h
@@ -1,12 +1,14 @@
/**
* @file cache_settings.h
* @ingroup Settings
- * @brief TODO: Document this.
*/
+//! \todo Document this file.
#ifndef SETTINGSCACHE_H
#define SETTINGSCACHE_H
+#include "../../interface/card_picture_loader/card_picture_loader_cache_method.h"
+#include "../../interface/card_picture_loader/card_picture_loader_local_schemes.h"
#include "shortcuts_settings.h"
#include
@@ -181,9 +183,12 @@ signals:
void soundThemeChanged();
void ignoreUnregisteredUsersChanged();
void ignoreUnregisteredUserMessagesChanged();
+ void ignoreNonBuddyUserMessagesChanged();
void pixmapCacheSizeChanged(int newSizeInMBs);
void networkCacheSizeChanged(int newSizeInMBs);
void redirectCacheTtlChanged(int newTtl);
+ void cardPictureLoaderCacheMethodChanged(int cardPictureLoaderCacheMethod);
+ void localCardImageStorageNamingSchemeChanged(int localCardImageStorageNamingScheme);
void masterVolumeChanged(int value);
void chatMentionCompleterChanged();
void downloadSpoilerTimeIndexChanged();
@@ -216,6 +221,7 @@ private:
bool checkCardUpdatesOnStartup;
int cardUpdateCheckInterval;
QDate lastCardUpdateCheck;
+ bool alwaysEnableNewSets;
bool notifyAboutUpdates;
bool notifyAboutNewVersion;
bool showTipsOnStartup;
@@ -289,6 +295,7 @@ private:
QString soundThemeName;
bool ignoreUnregisteredUsers;
bool ignoreUnregisteredUserMessages;
+ bool ignoreNonBuddyUserMessages;
QString picUrl;
QString picUrlFallback;
QString clientID;
@@ -302,6 +309,8 @@ private:
int pixmapCacheSize;
int networkCacheSize;
int redirectCacheTtl;
+ int cardPictureLoaderCacheMethod;
+ int localCardImageStorageNamingScheme;
bool scaleCards;
int verticalCardOverlapPercent;
bool showMessagePopups;
@@ -502,6 +511,10 @@ public:
return getLastCardUpdateCheck().daysTo(QDateTime::currentDateTime().date()) >= getCardUpdateCheckInterval() &&
getLastCardUpdateCheck() != QDateTime::currentDateTime().date();
}
+ [[nodiscard]] bool getAlwaysEnableNewSets() const
+ {
+ return alwaysEnableNewSets;
+ }
[[nodiscard]] bool getNotifyAboutUpdates() const override
{
return notifyAboutUpdates;
@@ -777,10 +790,18 @@ public:
{
return ignoreUnregisteredUserMessages;
}
+ [[nodiscard]] bool getIgnoreNonBuddyUserMessages() const
+ {
+ return ignoreNonBuddyUserMessages;
+ }
[[nodiscard]] int getPixmapCacheSize() const
{
return pixmapCacheSize;
}
+ [[nodiscard]] CardPictureLoaderCacheMethod::CacheMethod getCardPictureLoaderCacheMethod() const
+ {
+ return static_cast(cardPictureLoaderCacheMethod);
+ }
[[nodiscard]] int getNetworkCacheSizeInMB() const
{
return networkCacheSize;
@@ -789,6 +810,10 @@ public:
{
return redirectCacheTtl;
}
+ [[nodiscard]] CardPictureLoaderLocalSchemes::NamingScheme getLocalCardImageStorageNamingScheme() const
+ {
+ return static_cast(localCardImageStorageNamingScheme);
+ }
[[nodiscard]] bool getScaleCards() const
{
return scaleCards;
@@ -1092,9 +1117,13 @@ public slots:
void setSoundThemeName(const QString &_soundThemeName);
void setIgnoreUnregisteredUsers(QT_STATE_CHANGED_T _ignoreUnregisteredUsers);
void setIgnoreUnregisteredUserMessages(QT_STATE_CHANGED_T _ignoreUnregisteredUserMessages);
+ void setIgnoreNonBuddyUserMessages(QT_STATE_CHANGED_T _ignoreNonBuddyUserMessages);
void setPixmapCacheSize(const int _pixmapCacheSize);
+ void setCardImageCacheMethod(CardPictureLoaderCacheMethod::CacheMethod _cardImageCachingMethod);
void setNetworkCacheSizeInMB(const int _networkCacheSize);
void setNetworkRedirectCacheTtl(const int _redirectCacheTtl);
+ void setLocalCardImageStorageNamingScheme(
+ const CardPictureLoaderLocalSchemes::NamingScheme _localCardImageStorageNamingScheme);
void setCardScaling(const QT_STATE_CHANGED_T _scaleCards);
void setStackCardOverlapPercent(const int _verticalCardOverlapPercent);
void setShowMessagePopups(const QT_STATE_CHANGED_T _showMessagePopups);
@@ -1125,6 +1154,7 @@ public slots:
void setStartupCardUpdateCheckAlwaysUpdate(bool value);
void setCardUpdateCheckInterval(int value);
void setLastCardUpdateCheck(QDate value);
+ void setAlwaysEnableNewSets(bool value);
void setNotifyAboutUpdate(QT_STATE_CHANGED_T _notifyaboutupdate);
void setNotifyAboutNewVersion(QT_STATE_CHANGED_T _notifyaboutnewversion);
void setUpdateReleaseChannelIndex(int value);
diff --git a/cockatrice/src/client/settings/card_counter_settings.cpp b/cockatrice/src/client/settings/card_counter_settings.cpp
index 399365c99..662ae0c7d 100644
--- a/cockatrice/src/client/settings/card_counter_settings.cpp
+++ b/cockatrice/src/client/settings/card_counter_settings.cpp
@@ -15,8 +15,9 @@ void CardCounterSettings::setColor(int counterId, const QColor &color)
QString key = QString("cards/counters/%1/color").arg(counterId);
- if (settings.value(key).value() == color)
+ if (settings.value(key).value() == color) {
return;
+ }
settings.setValue(key, color);
emit colorChanged(counterId, color);
diff --git a/cockatrice/src/client/settings/card_counter_settings.h b/cockatrice/src/client/settings/card_counter_settings.h
index b1d467d67..2ac658177 100644
--- a/cockatrice/src/client/settings/card_counter_settings.h
+++ b/cockatrice/src/client/settings/card_counter_settings.h
@@ -1,8 +1,8 @@
/**
* @file card_counter_settings.h
* @ingroup GameSettings
- * @brief TODO: Document this.
*/
+//! \todo Document this file.
#ifndef CARD_COUNTER_SETTINGS_H
#define CARD_COUNTER_SETTINGS_H
diff --git a/cockatrice/src/client/settings/shortcut_treeview.h b/cockatrice/src/client/settings/shortcut_treeview.h
index 8d74a6f1e..afaf7e7ed 100644
--- a/cockatrice/src/client/settings/shortcut_treeview.h
+++ b/cockatrice/src/client/settings/shortcut_treeview.h
@@ -1,8 +1,8 @@
/**
* @file shortcut_treeview.h
* @ingroup CoreSettings
- * @brief TODO: Document this.
*/
+//! \todo Document this file.
#ifndef SHORTCUT_TREEVIEW_H
#define SHORTCUT_TREEVIEW_H
diff --git a/cockatrice/src/client/settings/shortcuts_settings.cpp b/cockatrice/src/client/settings/shortcuts_settings.cpp
index 53c6f6a23..2ff65ab6f 100644
--- a/cockatrice/src/client/settings/shortcuts_settings.cpp
+++ b/cockatrice/src/client/settings/shortcuts_settings.cpp
@@ -64,8 +64,13 @@ ShortcutsSettings::ShortcutsSettings(const QString &settingsPath, QObject *paren
}
}
-/// PR 5079 changes Textbox/unfocusTextBox to Player/unfocusTextBox and tab_game/aFocusChat to Player/aFocusChat.
-/// A migration is necessary to let players keep their already configured shortcuts.
+/**
+ * @brief Migrates legacy shortcut key names to current naming scheme.
+ *
+ * PR 5079 changed Textbox/unfocusTextBox to Player/unfocusTextBox and
+ * tab_game/aFocusChat to Player/aFocusChat. This migration allows players
+ * to keep their already configured shortcuts.
+ */
void ShortcutsSettings::migrateShortcuts()
{
if (QFile(settingsFilePath).exists()) {
@@ -236,9 +241,7 @@ bool ShortcutsSettings::isValid(const QString &name, const QString &sequences) c
return findOverlaps(name, sequences).isEmpty();
}
-/**
- * Checks if the shortcut is a shortcut that is active in all windows
- */
+/** @brief Checks if the shortcut is a shortcut that is active in all windows. */
static bool isAlwaysActiveShortcut(const QString &shortcutName)
{
return shortcutName.startsWith("MainWindow") || shortcutName.startsWith("Tabs");
diff --git a/cockatrice/src/client/settings/shortcuts_settings.h b/cockatrice/src/client/settings/shortcuts_settings.h
index 1de73c165..45e2c4fca 100644
--- a/cockatrice/src/client/settings/shortcuts_settings.h
+++ b/cockatrice/src/client/settings/shortcuts_settings.h
@@ -1,8 +1,8 @@
/**
* @file shortcuts_settings.h
* @ingroup CoreSettings
- * @brief TODO: Document this.
*/
+//! \todo Document this file.
#ifndef SHORTCUTSSETTINGS_H
#define SHORTCUTSSETTINGS_H
@@ -537,6 +537,9 @@ private:
{"Player/aSetAnnotation", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Set Annotation..."),
parseSequenceString("Alt+N"),
ShortcutGroup::Playing_Area)},
+ {"Player/aReduceLifeByPower", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Reduce Life by Power"),
+ parseSequenceString("Ctrl+Shift+L"),
+ ShortcutGroup::Playing_Area)},
{"Player/aSelectAll", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Select All Cards in Zone"),
parseSequenceString("Ctrl+A"),
ShortcutGroup::Playing_Area)},
@@ -664,6 +667,9 @@ private:
{"Player/aRollDie", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Roll Dice..."),
parseSequenceString("Ctrl+I"),
ShortcutGroup::Gameplay)},
+ {"Player/aFlipCoin", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Flip Coin"),
+ parseSequenceString("Ctrl+Shift+I"),
+ ShortcutGroup::Gameplay)},
{"Player/aShuffle", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Shuffle Library"),
parseSequenceString("Ctrl+S"),
ShortcutGroup::Gameplay)},
diff --git a/cockatrice/src/client/sound_engine.cpp b/cockatrice/src/client/sound_engine.cpp
index 31cb2a35e..e592b5ea0 100644
--- a/cockatrice/src/client/sound_engine.cpp
+++ b/cockatrice/src/client/sound_engine.cpp
@@ -105,8 +105,9 @@ QStringMap &SoundEngine::getAvailableThemes()
dir.setPath(SettingsCache::instance().getDataPath() + "/sounds");
for (const QString &themeName : dir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot, QDir::Name)) {
- if (!availableThemes.contains(themeName))
+ if (!availableThemes.contains(themeName)) {
availableThemes.insert(themeName, dir.absoluteFilePath(themeName));
+ }
}
// load themes from cockatrice system dir
@@ -121,8 +122,9 @@ QStringMap &SoundEngine::getAvailableThemes()
);
for (const QString &themeName : dir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot, QDir::Name)) {
- if (!availableThemes.contains(themeName))
+ if (!availableThemes.contains(themeName)) {
availableThemes.insert(themeName, dir.absoluteFilePath(themeName));
+ }
}
return availableThemes;
diff --git a/cockatrice/src/client/sound_engine.h b/cockatrice/src/client/sound_engine.h
index f45cccf7d..0c201523a 100644
--- a/cockatrice/src/client/sound_engine.h
+++ b/cockatrice/src/client/sound_engine.h
@@ -1,8 +1,8 @@
/**
* @file sound_engine.h
* @ingroup Core
- * @brief TODO: Document this.
*/
+//! \todo Document this file.
#ifndef SOUNDENGINE_H
#define SOUNDENGINE_H
diff --git a/cockatrice/src/filters/deck_filter_string.cpp b/cockatrice/src/filters/deck_filter_string.cpp
index 6b671831d..dd873cfa5 100644
--- a/cockatrice/src/filters/deck_filter_string.cpp
+++ b/cockatrice/src/filters/deck_filter_string.cpp
@@ -88,20 +88,27 @@ static void setupParserRules()
const auto arg = std::any_cast(sv[1]);
const auto op = std::any_cast(sv[0]);
- if (op == ">")
+ if (op == ">") {
return [=](const int s) { return s > arg; };
- if (op == ">=")
+ }
+ if (op == ">=") {
return [=](const int s) { return s >= arg; };
- if (op == "<")
+ }
+ if (op == "<") {
return [=](const int s) { return s < arg; };
- if (op == "<=")
+ }
+ if (op == "<=") {
return [=](const int s) { return s <= arg; };
- if (op == "=")
+ }
+ if (op == "=") {
return [=](const int s) { return s == arg; };
- if (op == ":")
+ }
+ if (op == ":") {
return [=](const int s) { return s == arg; };
- if (op == "!=")
+ }
+ if (op == "!=") {
return [=](const int s) { return s != arg; };
+ }
return [](int) { return false; };
};
diff --git a/cockatrice/src/filters/deck_filter_string.h b/cockatrice/src/filters/deck_filter_string.h
index 1b43d770d..916b629ee 100644
--- a/cockatrice/src/filters/deck_filter_string.h
+++ b/cockatrice/src/filters/deck_filter_string.h
@@ -1,8 +1,8 @@
/**
* @file deck_filter_string.h
* @ingroup DeckStorageWidgets
- * @brief TODO: Document this.
*/
+//! \todo Document this file.
#ifndef DECK_FILTER_STRING_H
#define DECK_FILTER_STRING_H
diff --git a/cockatrice/src/filters/filter_builder.cpp b/cockatrice/src/filters/filter_builder.cpp
index 7178ce95a..785f753e7 100644
--- a/cockatrice/src/filters/filter_builder.cpp
+++ b/cockatrice/src/filters/filter_builder.cpp
@@ -11,13 +11,15 @@ FilterBuilder::FilterBuilder(QWidget *parent) : QWidget(parent)
{
filterCombo = new QComboBox;
filterCombo->setObjectName("filterCombo");
- for (int i = 0; i < CardFilter::AttrEnd; i++)
+ for (int i = 0; i < CardFilter::AttrEnd; i++) {
filterCombo->addItem(CardFilter::attrName(static_cast(i)), QVariant(i));
+ }
typeCombo = new QComboBox;
typeCombo->setObjectName("typeCombo");
- for (int i = 0; i < CardFilter::TypeEnd; i++)
+ for (int i = 0; i < CardFilter::TypeEnd; i++) {
typeCombo->addItem(CardFilter::typeName(static_cast(i)), QVariant(i));
+ }
QPushButton *ok = new QPushButton(QPixmap("theme:icons/increment"), QString());
ok->setObjectName("ok");
@@ -53,8 +55,9 @@ FilterBuilder::~FilterBuilder()
void FilterBuilder::destroyFilter()
{
- if (fltr)
+ if (fltr) {
delete fltr;
+ }
}
static int comboCurrentIntData(const QComboBox *combo)
@@ -67,8 +70,9 @@ void FilterBuilder::emit_add()
QString txt;
txt = edit->text();
- if (txt.length() < 1)
+ if (txt.length() < 1) {
return;
+ }
destroyFilter();
fltr = new CardFilter(txt, static_cast(comboCurrentIntData(typeCombo)),
diff --git a/cockatrice/src/filters/filter_builder.h b/cockatrice/src/filters/filter_builder.h
index 74872e3cd..98fe3fd02 100644
--- a/cockatrice/src/filters/filter_builder.h
+++ b/cockatrice/src/filters/filter_builder.h
@@ -1,8 +1,8 @@
/**
* @file filter_builder.h
* @ingroup CardDatabaseModelFilters
- * @brief TODO: Document this.
*/
+//! \todo Document this file.
#ifndef FILTERBUILDER_H
#define FILTERBUILDER_H
diff --git a/cockatrice/src/filters/filter_tree_model.cpp b/cockatrice/src/filters/filter_tree_model.cpp
index f0a02ec79..33b54530e 100644
--- a/cockatrice/src/filters/filter_tree_model.cpp
+++ b/cockatrice/src/filters/filter_tree_model.cpp
@@ -23,8 +23,9 @@ void FilterTreeModel::proxyBeginInsertRow(const FilterTreeNode *node, int i)
int idx;
idx = node->index();
- if (idx >= 0)
+ if (idx >= 0) {
beginInsertRows(createIndex(idx, 0, (void *)node), i, i);
+ }
}
void FilterTreeModel::proxyEndInsertRow(const FilterTreeNode *node, int)
@@ -32,8 +33,9 @@ void FilterTreeModel::proxyEndInsertRow(const FilterTreeNode *node, int)
int idx;
idx = node->index();
- if (idx >= 0)
+ if (idx >= 0) {
endInsertRows();
+ }
}
void FilterTreeModel::proxyBeginRemoveRow(const FilterTreeNode *node, int i)
@@ -41,8 +43,9 @@ void FilterTreeModel::proxyBeginRemoveRow(const FilterTreeNode *node, int i)
int idx;
idx = node->index();
- if (idx >= 0)
+ if (idx >= 0) {
beginRemoveRows(createIndex(idx, 0, (void *)node), i, i);
+ }
}
void FilterTreeModel::proxyEndRemoveRow(const FilterTreeNode *node, int)
@@ -50,8 +53,9 @@ void FilterTreeModel::proxyEndRemoveRow(const FilterTreeNode *node, int)
int idx;
idx = node->index();
- if (idx >= 0)
+ if (idx >= 0) {
endRemoveRows();
+ }
}
FilterTreeNode *FilterTreeModel::indexToNode(const QModelIndex &idx) const
@@ -59,12 +63,14 @@ FilterTreeNode *FilterTreeModel::indexToNode(const QModelIndex &idx) const
void *ip;
FilterTreeNode *node;
- if (!idx.isValid())
+ if (!idx.isValid()) {
return fTree;
+ }
ip = idx.internalPointer();
- if (ip == NULL)
+ if (ip == NULL) {
return fTree;
+ }
node = static_cast(ip);
return node;
@@ -145,14 +151,16 @@ int FilterTreeModel::rowCount(const QModelIndex &parent) const
const FilterTreeNode *node;
int result;
- if (parent.column() > 0)
+ if (parent.column() > 0) {
return 0;
+ }
node = indexToNode(parent);
- if (node)
+ if (node) {
result = node->childCount();
- else
+ } else {
result = 0;
+ }
return result;
}
@@ -166,14 +174,17 @@ QVariant FilterTreeModel::data(const QModelIndex &index, int role) const
{
const FilterTreeNode *node;
- if (!index.isValid())
+ if (!index.isValid()) {
return QVariant();
- if (index.column() >= columnCount())
+ }
+ if (index.column() >= columnCount()) {
return QVariant();
+ }
node = indexToNode(index);
- if (node == NULL)
+ if (node == NULL) {
return QVariant();
+ }
switch (role) {
case Qt::FontRole:
@@ -190,10 +201,11 @@ QVariant FilterTreeModel::data(const QModelIndex &index, int role) const
case Qt::WhatsThisRole:
return node->text();
case Qt::CheckStateRole:
- if (node->isEnabled())
+ if (node->isEnabled()) {
return Qt::Checked;
- else
+ } else {
return Qt::Unchecked;
+ }
default:
return QVariant();
}
@@ -205,22 +217,27 @@ bool FilterTreeModel::setData(const QModelIndex &index, const QVariant &value, i
{
FilterTreeNode *node;
- if (!index.isValid())
+ if (!index.isValid()) {
return false;
- if (index.column() >= columnCount())
+ }
+ if (index.column() >= columnCount()) {
return false;
- if (role != Qt::CheckStateRole)
+ }
+ if (role != Qt::CheckStateRole) {
return false;
+ }
node = indexToNode(index);
- if (node == NULL || node == fTree)
+ if (node == NULL || node == fTree) {
return false;
+ }
Qt::CheckState state = static_cast(value.toInt());
- if (state == Qt::Checked)
+ if (state == Qt::Checked) {
node->enable();
- else
+ } else {
node->disable();
+ }
emit dataChanged(index, index);
return true;
@@ -231,16 +248,19 @@ Qt::ItemFlags FilterTreeModel::flags(const QModelIndex &index) const
const FilterTreeNode *node;
Qt::ItemFlags result;
- if (!index.isValid())
+ if (!index.isValid()) {
return Qt::NoItemFlags;
+ }
node = indexToNode(index);
- if (node == NULL)
+ if (node == NULL) {
return Qt::NoItemFlags;
+ }
result = Qt::ItemIsEnabled;
- if (node == fTree)
+ if (node == fTree) {
return result;
+ }
result |= Qt::ItemIsSelectable;
result |= Qt::ItemIsUserCheckable;
@@ -252,8 +272,9 @@ QModelIndex FilterTreeModel::nodeIndex(const FilterTreeNode *node, int row, int
{
FilterTreeNode *child;
- if (column > 0 || row >= node->childCount())
+ if (column > 0 || row >= node->childCount()) {
return QModelIndex();
+ }
child = node->nodeAt(row);
return createIndex(row, column, child);
@@ -263,12 +284,14 @@ QModelIndex FilterTreeModel::index(int row, int column, const QModelIndex &paren
{
const FilterTreeNode *node;
- if (!hasIndex(row, column, parent))
+ if (!hasIndex(row, column, parent)) {
return QModelIndex();
+ }
node = indexToNode(parent);
- if (node == NULL)
+ if (node == NULL) {
return QModelIndex();
+ }
return nodeIndex(node, row, column);
}
@@ -279,18 +302,21 @@ QModelIndex FilterTreeModel::parent(const QModelIndex &ind) const
FilterTreeNode *parent;
QModelIndex idx;
- if (!ind.isValid())
+ if (!ind.isValid()) {
return QModelIndex();
+ }
node = indexToNode(ind);
- if (node == NULL || node == fTree)
+ if (node == NULL || node == fTree) {
return QModelIndex();
+ }
parent = node->parent();
if (parent) {
int row = parent->index();
- if (row < 0)
+ if (row < 0) {
return QModelIndex();
+ }
idx = createIndex(row, 0, parent);
return idx;
}
@@ -304,18 +330,22 @@ bool FilterTreeModel::removeRows(int row, int count, const QModelIndex &parent)
int i, last;
last = row + count - 1;
- if (!parent.isValid() || count < 1 || row < 0)
+ if (!parent.isValid() || count < 1 || row < 0) {
return false;
+ }
node = indexToNode(parent);
- if (node == NULL || last >= node->childCount())
+ if (node == NULL || last >= node->childCount()) {
return false;
+ }
- for (i = 0; i < count; i++)
+ for (i = 0; i < count; i++) {
node->deleteAt(row);
+ }
- if (node != fTree && node->childCount() < 1)
+ if (node != fTree && node->childCount() < 1) {
return removeRow(parent.row(), parent.parent());
+ }
return true;
}
diff --git a/cockatrice/src/filters/filter_tree_model.h b/cockatrice/src/filters/filter_tree_model.h
index c6666a838..7452f7a61 100644
--- a/cockatrice/src/filters/filter_tree_model.h
+++ b/cockatrice/src/filters/filter_tree_model.h
@@ -1,8 +1,8 @@
/**
* @file filter_tree_model.h
* @ingroup CardDatabaseModelFilters
- * @brief TODO: Document this.
*/
+//! \todo Document this file.
#ifndef FILTERTREEMODEL_H
#define FILTERTREEMODEL_H
diff --git a/cockatrice/src/filters/syntax_help.h b/cockatrice/src/filters/syntax_help.h
index 7e5ef3e0e..d06fe03e5 100644
--- a/cockatrice/src/filters/syntax_help.h
+++ b/cockatrice/src/filters/syntax_help.h
@@ -2,8 +2,8 @@
* @file syntax_help.h
* @ingroup CardDatabaseModelFilters
* @ingroup DeckStorageWidgets
- * @brief TODO: Document this.
*/
+//! \todo Document this file.
#ifndef SEARCH_SYNTAX_HELP_H
#define SEARCH_SYNTAX_HELP_H
diff --git a/cockatrice/src/game/abstract_game.cpp b/cockatrice/src/game/abstract_game.cpp
index 9216f9174..c20003ece 100644
--- a/cockatrice/src/game/abstract_game.cpp
+++ b/cockatrice/src/game/abstract_game.cpp
@@ -1,9 +1,9 @@
#include "abstract_game.h"
#include "../interface/widgets/tabs/tab_game.h"
-#include "player/player.h"
+#include "player/player_logic.h"
-AbstractGame::AbstractGame(TabGame *_tab) : QObject(_tab), tab(_tab)
+AbstractGame::AbstractGame(QObject *_parent) : QObject(_parent)
{
gameMetaInfo = new GameMetaInfo(this);
gameEventHandler = new GameEventHandler(this);
@@ -24,10 +24,11 @@ AbstractClient *AbstractGame::getClientForPlayer(int playerId) const
}
return gameState->getClients().at(playerId);
- } else if (gameState->getClients().isEmpty())
+ } else if (gameState->getClients().isEmpty()) {
return nullptr;
- else
+ } else {
return gameState->getClients().first();
+ }
}
void AbstractGame::loadReplay(GameReplay *replay)
@@ -43,13 +44,15 @@ void AbstractGame::setActiveCard(CardItem *card)
CardItem *AbstractGame::getCard(int playerId, const QString &zoneName, int cardId) const
{
- Player *player = playerManager->getPlayer(playerId);
- if (!player)
+ PlayerLogic *player = playerManager->getPlayer(playerId);
+ if (!player) {
return nullptr;
+ }
CardZoneLogic *zone = player->getZones().value(zoneName, 0);
- if (!zone)
+ if (!zone) {
return nullptr;
+ }
return zone->getCard(cardId);
}
\ No newline at end of file
diff --git a/cockatrice/src/game/abstract_game.h b/cockatrice/src/game/abstract_game.h
index cb4dbac2a..5115ed5ca 100644
--- a/cockatrice/src/game/abstract_game.h
+++ b/cockatrice/src/game/abstract_game.h
@@ -1,8 +1,8 @@
/**
* @file abstract_game.h
* @ingroup GameLogic
- * @brief TODO: Document this.
*/
+//! \todo Document this file.
#ifndef COCKATRICE_ABSTRACT_GAME_H
#define COCKATRICE_ABSTRACT_GAME_H
@@ -16,26 +16,19 @@
#include
class CardItem;
-class TabGame;
class AbstractGame : public QObject
{
Q_OBJECT
public:
- explicit AbstractGame(TabGame *tab);
+ explicit AbstractGame(QObject *parent);
- TabGame *tab;
GameMetaInfo *gameMetaInfo;
GameState *gameState;
GameEventHandler *gameEventHandler;
PlayerManager *playerManager;
CardItem *activeCard;
- TabGame *getTab() const
- {
- return tab;
- }
-
GameMetaInfo *getGameMetaInfo()
{
return gameMetaInfo;
diff --git a/cockatrice/src/game/arrow_registry.cpp b/cockatrice/src/game/arrow_registry.cpp
new file mode 100644
index 000000000..286764b3b
--- /dev/null
+++ b/cockatrice/src/game/arrow_registry.cpp
@@ -0,0 +1,48 @@
+#include "arrow_registry.h"
+
+#include "../game_graphics/board/arrow_item.h"
+
+void ArrowRegistry::insert(QSharedPointer data, ArrowItem *arrow)
+{
+ const ArrowKey key{data->creatorId, data->id};
+
+ if (auto *existing = take(data->creatorId, data->id)) {
+ existing->delArrow();
+ }
+
+ dataStore.insert(key, data);
+ items.insert(key, arrow);
+ byPlayer[data->creatorId].insert(data->id);
+}
+
+ArrowItem *ArrowRegistry::take(int creatorId, int arrowId)
+{
+ const ArrowKey key{creatorId, arrowId};
+ dataStore.remove(key);
+ auto &playerSet = byPlayer[creatorId];
+ playerSet.remove(arrowId);
+ if (playerSet.isEmpty()) {
+ byPlayer.remove(creatorId);
+ }
+ return items.take(key);
+}
+
+ArrowItem *ArrowRegistry::get(int creatorId, int arrowId) const
+{
+ return items.value(ArrowKey{creatorId, arrowId}, nullptr);
+}
+
+bool ArrowRegistry::contains(int creatorId, int arrowId) const
+{
+ return items.contains(ArrowKey{creatorId, arrowId});
+}
+
+QSet ArrowRegistry::idsForPlayer(int playerId) const
+{
+ return byPlayer.value(playerId);
+}
+
+QList ArrowRegistry::all() const
+{
+ return items.values();
+}
\ No newline at end of file
diff --git a/cockatrice/src/game/arrow_registry.h b/cockatrice/src/game/arrow_registry.h
new file mode 100644
index 000000000..ef98229a2
--- /dev/null
+++ b/cockatrice/src/game/arrow_registry.h
@@ -0,0 +1,43 @@
+#ifndef COCKATRICE_ARROW_REGISTRY_H
+#define COCKATRICE_ARROW_REGISTRY_H
+
+#include "board/arrow_data.h"
+
+#include
+#include
+#include
+
+class ArrowItem;
+
+struct ArrowKey
+{
+ int creatorId;
+ int arrowId;
+
+ bool operator<(const ArrowKey &other) const
+ {
+ if (creatorId != other.creatorId) {
+ return creatorId < other.creatorId;
+ }
+ return arrowId < other.arrowId;
+ }
+};
+
+class ArrowRegistry
+{
+public:
+ void insert(QSharedPointer data, ArrowItem *arrow);
+ ArrowItem *take(int creatorId, int arrowId);
+
+ [[nodiscard]] ArrowItem *get(int creatorId, int arrowId) const;
+ [[nodiscard]] bool contains(int creatorId, int arrowId) const;
+ [[nodiscard]] QSet idsForPlayer(int playerId) const;
+ [[nodiscard]] QList all() const;
+
+private:
+ QMap> dataStore;
+ QMap items;
+ QMap> byPlayer;
+};
+
+#endif
\ No newline at end of file
diff --git a/cockatrice/src/game/board/arrow_data.cpp b/cockatrice/src/game/board/arrow_data.cpp
new file mode 100644
index 000000000..9e89deed0
--- /dev/null
+++ b/cockatrice/src/game/board/arrow_data.cpp
@@ -0,0 +1,21 @@
+#include "arrow_data.h"
+
+ArrowData ArrowData::fromProto(const ServerInfo_Arrow &arrow, int creatorId, bool isLocalCreator)
+{
+ ArrowData data;
+ data.creatorId = creatorId;
+ data.isLocalCreator = isLocalCreator;
+ data.id = arrow.id();
+ data.startPlayerId = arrow.start_player_id();
+ data.startZone = QString::fromStdString(arrow.start_zone());
+ data.startCardId = arrow.start_card_id();
+ data.targetPlayerId = arrow.target_player_id();
+ data.color = convertColorToQColor(arrow.arrow_color());
+
+ if (arrow.has_target_zone()) {
+ data.targetZone = QString::fromStdString(arrow.target_zone());
+ data.targetCardId = arrow.target_card_id();
+ }
+
+ return data;
+}
\ No newline at end of file
diff --git a/cockatrice/src/game/board/arrow_data.h b/cockatrice/src/game/board/arrow_data.h
new file mode 100644
index 000000000..2752f97e3
--- /dev/null
+++ b/cockatrice/src/game/board/arrow_data.h
@@ -0,0 +1,30 @@
+#ifndef COCKATRICE_ARROW_DATA_H
+#define COCKATRICE_ARROW_DATA_H
+
+#include
+#include
+#include
+#include
+
+struct ArrowData
+{
+ int creatorId = -1;
+ bool isLocalCreator = false;
+ int id = -1;
+ int startPlayerId = -1;
+ QString startZone = "";
+ int startCardId = -1;
+ int targetPlayerId = -1;
+ QString targetZone = "";
+ int targetCardId = -1;
+ QColor color = "";
+
+ static ArrowData fromProto(const ServerInfo_Arrow &arrow, int creatorId, bool isLocalCreator);
+
+ bool isPlayerTargeted() const
+ {
+ return targetZone.isEmpty();
+ }
+};
+
+#endif // COCKATRICE_ARROW_DATA_H
diff --git a/cockatrice/src/game/board/arrow_target.cpp b/cockatrice/src/game/board/arrow_target.cpp
deleted file mode 100644
index 2dbd913fa..000000000
--- a/cockatrice/src/game/board/arrow_target.cpp
+++ /dev/null
@@ -1,41 +0,0 @@
-#include "arrow_target.h"
-
-#include "../player/player.h"
-#include "arrow_item.h"
-
-ArrowTarget::ArrowTarget(Player *_owner, QGraphicsItem *parent)
- : AbstractGraphicsItem(parent), owner(_owner), beingPointedAt(false)
-{
- setFlag(ItemSendsScenePositionChanges);
-}
-
-ArrowTarget::~ArrowTarget()
-{
- for (int i = 0; i < arrowsFrom.size(); ++i) {
- arrowsFrom[i]->setStartItem(0);
- arrowsFrom[i]->delArrow();
- }
- for (int i = 0; i < arrowsTo.size(); ++i) {
- arrowsTo[i]->setTargetItem(0);
- arrowsTo[i]->delArrow();
- }
-}
-
-void ArrowTarget::setBeingPointedAt(bool _beingPointedAt)
-{
- beingPointedAt = _beingPointedAt;
- update();
-}
-
-QVariant ArrowTarget::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value)
-{
- if (change == ItemScenePositionHasChanged && scene()) {
- for (auto *arrow : arrowsFrom)
- arrow->updatePath();
-
- for (auto *arrow : arrowsTo)
- arrow->updatePath();
- }
-
- return QGraphicsItem::itemChange(change, value);
-}
diff --git a/cockatrice/src/game/board/arrow_target.h b/cockatrice/src/game/board/arrow_target.h
deleted file mode 100644
index 55f4ef678..000000000
--- a/cockatrice/src/game/board/arrow_target.h
+++ /dev/null
@@ -1,70 +0,0 @@
-/**
- * @file arrow_target.h
- * @ingroup GameGraphics
- * @brief TODO: Document this.
- */
-
-#ifndef ARROWTARGET_H
-#define ARROWTARGET_H
-
-#include "../../game_graphics/board/abstract_graphics_item.h"
-
-#include
-
-class Player;
-class ArrowItem;
-
-class ArrowTarget : public AbstractGraphicsItem
-{
- Q_OBJECT
-protected:
- Player *owner;
-
-private:
- bool beingPointedAt;
- QList arrowsFrom, arrowsTo;
-
-public:
- explicit ArrowTarget(Player *_owner, QGraphicsItem *parent = nullptr);
- ~ArrowTarget() override;
-
- [[nodiscard]] Player *getOwner() const
- {
- return owner;
- }
-
- void setBeingPointedAt(bool _beingPointedAt);
- [[nodiscard]] bool getBeingPointedAt() const
- {
- return beingPointedAt;
- }
-
- [[nodiscard]] const QList &getArrowsFrom() const
- {
- return arrowsFrom;
- }
- void addArrowFrom(ArrowItem *arrow)
- {
- arrowsFrom.append(arrow);
- }
- void removeArrowFrom(ArrowItem *arrow)
- {
- arrowsFrom.removeOne(arrow);
- }
- [[nodiscard]] const QList &getArrowsTo() const
- {
- return arrowsTo;
- }
- void addArrowTo(ArrowItem *arrow)
- {
- arrowsTo.append(arrow);
- }
- void removeArrowTo(ArrowItem *arrow)
- {
- arrowsTo.removeOne(arrow);
- }
-
-protected:
- QVariant itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) override;
-};
-#endif
diff --git a/cockatrice/src/game/board/card_list.cpp b/cockatrice/src/game/board/card_list.cpp
index c324ca10a..0080b5ae6 100644
--- a/cockatrice/src/game/board/card_list.cpp
+++ b/cockatrice/src/game/board/card_list.cpp
@@ -1,6 +1,6 @@
#include "card_list.h"
-#include "card_item.h"
+#include "../../game_graphics/board/card_item.h"
#include
#include
diff --git a/cockatrice/src/game/board/card_list.h b/cockatrice/src/game/board/card_list.h
index 07be33b32..85a6848b7 100644
--- a/cockatrice/src/game/board/card_list.h
+++ b/cockatrice/src/game/board/card_list.h
@@ -1,8 +1,8 @@
/**
* @file card_list.h
* @ingroup GameLogicCards
- * @brief TODO: Document this.
*/
+//! \todo Document this file.
#ifndef CARDLIST_H
#define CARDLIST_H
diff --git a/cockatrice/src/game/board/card_state.cpp b/cockatrice/src/game/board/card_state.cpp
new file mode 100644
index 000000000..4319400d7
--- /dev/null
+++ b/cockatrice/src/game/board/card_state.cpp
@@ -0,0 +1,111 @@
+#include "card_state.h"
+
+void CardState::resetState(bool keepAnnotations)
+{
+ attacking = false;
+ counters.clear();
+ pt.clear();
+ if (!keepAnnotations) {
+ annotation.clear();
+ }
+ attachedTo = nullptr;
+}
+
+void CardState::setZone(CardZoneLogic *_zone)
+{
+ if (zone == _zone) {
+ return;
+ }
+
+ zone = _zone;
+ emit zoneChanged(this, zone);
+ emit stateChanged();
+}
+
+void CardState::setAttacking(bool _attacking)
+{
+ if (attacking == _attacking) {
+ return;
+ }
+ attacking = _attacking;
+ emit attackingChanged(_attacking);
+ emit stateChanged();
+}
+
+void CardState::insertCounter(int id, int value)
+{
+ counters.insert(id, value);
+
+ emit countersChanged(counters);
+ emit stateChanged();
+}
+
+void CardState::setCounter(int id, int value)
+{
+ if (value) {
+ counters[id] = value;
+ } else {
+ counters.remove(id);
+ }
+
+ emit countersChanged(counters);
+ emit stateChanged();
+}
+
+void CardState::clearCounters()
+{
+ counters.clear();
+ emit countersChanged(counters);
+ emit stateChanged();
+}
+
+void CardState::setAnnotation(const QString &_annotation)
+{
+ if (annotation == _annotation) {
+ return;
+ }
+ annotation = _annotation;
+ emit annotationChanged(annotation);
+ emit stateChanged();
+}
+
+void CardState::setPT(const QString &_pt)
+{
+ if (pt == _pt) {
+ return;
+ }
+ pt = _pt;
+ emit ptChanged(pt);
+ emit stateChanged();
+}
+
+void CardState::setDoesntUntap(bool _doesntUntap)
+{
+ if (doesntUntap == _doesntUntap) {
+ return;
+ }
+ doesntUntap = _doesntUntap;
+ emit doesntUntapChanged(_doesntUntap);
+ emit stateChanged();
+}
+
+void CardState::setDestroyOnZoneChange(bool _destroyOnZoneChange)
+{
+ if (destroyOnZoneChange == _destroyOnZoneChange) {
+ return;
+ }
+
+ destroyOnZoneChange = _destroyOnZoneChange;
+ emit destroyOnZoneChangeChanged(_destroyOnZoneChange);
+ emit stateChanged();
+}
+
+void CardState::setAttachedTo(CardItem *_attachedTo)
+{
+ if (attachedTo == _attachedTo) {
+ return;
+ }
+ attachedTo = _attachedTo;
+ emit attachedToChanged(_attachedTo);
+ emit stateChanged();
+}
\ No newline at end of file
diff --git a/cockatrice/src/game/board/card_state.h b/cockatrice/src/game/board/card_state.h
new file mode 100644
index 000000000..0498b1aa2
--- /dev/null
+++ b/cockatrice/src/game/board/card_state.h
@@ -0,0 +1,103 @@
+#ifndef COCKATRICE_CARD_STATE_H
+#define COCKATRICE_CARD_STATE_H
+
+#include
+#include
+
+class CardZoneLogic;
+class CardItem;
+class CardState : public QObject
+{
+ Q_OBJECT
+
+private:
+ bool attacking = false;
+ QMap counters;
+ QString annotation;
+ QString pt;
+ bool doesntUntap = false;
+ bool destroyOnZoneChange = false;
+
+ CardItem *attachedTo = nullptr;
+ CardZoneLogic *zone = nullptr;
+
+signals:
+ void stateChanged();
+
+ void attackingChanged(bool newValue);
+ void countersChanged(const QMap &newCounters);
+ void annotationChanged(const QString &newAnnotation);
+ void ptChanged(const QString &newPt);
+ void doesntUntapChanged(bool newValue);
+ void destroyOnZoneChangeChanged(bool newValue);
+ void attachedToChanged(CardItem *newAttachedTo);
+ void zoneChanged(CardState *changedCard, CardZoneLogic *newZone);
+
+public:
+ explicit CardState(QObject *parent, CardZoneLogic *_zone) : QObject(parent), zone(_zone)
+ {
+ }
+
+ void resetState(bool keepAnnotations);
+
+ CardZoneLogic *getZone() const
+ {
+ return zone;
+ }
+
+ void setZone(CardZoneLogic *_zone);
+
+ bool getAttacking() const
+ {
+ return attacking;
+ }
+ void setAttacking(bool _attacking);
+
+ const QMap &getCounters() const
+ {
+ return counters;
+ }
+
+ void insertCounter(int id, int value);
+
+ void setCounter(int id, int value);
+
+ void clearCounters();
+
+ QString getAnnotation() const
+ {
+ return annotation;
+ }
+
+ void setAnnotation(const QString &_annotation);
+
+ QString getPT() const
+ {
+ return pt;
+ }
+
+ void setPT(const QString &_pt);
+
+ bool getDoesntUntap() const
+ {
+ return doesntUntap;
+ }
+
+ void setDoesntUntap(bool _doesntUntap);
+
+ bool getDestroyOnZoneChange() const
+ {
+ return destroyOnZoneChange;
+ }
+
+ void setDestroyOnZoneChange(bool _destroyOnZoneChange);
+
+ CardItem *getAttachedTo() const
+ {
+ return attachedTo;
+ }
+
+ void setAttachedTo(CardItem *_attachedTo);
+};
+
+#endif // COCKATRICE_CARD_STATE_H
diff --git a/cockatrice/src/game/board/counter_state.cpp b/cockatrice/src/game/board/counter_state.cpp
new file mode 100644
index 000000000..6da18b662
--- /dev/null
+++ b/cockatrice/src/game/board/counter_state.cpp
@@ -0,0 +1,24 @@
+#include "counter_state.h"
+
+#include
+
+CounterState::CounterState(int id, const QString &name, const QColor &color, int radius, int value, QObject *parent)
+ : QObject(parent), id(id), name(name), color(color), radius(radius), value(value)
+{
+}
+
+CounterState *CounterState::fromProto(const ServerInfo_Counter &counter, QObject *parent)
+{
+ return new CounterState(counter.id(), QString::fromStdString(counter.name()),
+ convertColorToQColor(counter.counter_color()), counter.radius(), counter.count(), parent);
+}
+
+void CounterState::setValue(int newValue)
+{
+ if (newValue == value) {
+ return;
+ }
+ int old = value;
+ value = newValue;
+ emit valueChanged(old, newValue);
+}
\ No newline at end of file
diff --git a/cockatrice/src/game/board/counter_state.h b/cockatrice/src/game/board/counter_state.h
new file mode 100644
index 000000000..0f2f16b55
--- /dev/null
+++ b/cockatrice/src/game/board/counter_state.h
@@ -0,0 +1,51 @@
+#ifndef COCKATRICE_COUNTER_STATE_H
+#define COCKATRICE_COUNTER_STATE_H
+
+#include
+#include
+#include
+#include
+
+class CounterState : public QObject
+{
+ Q_OBJECT
+public:
+ CounterState(int id, const QString &name, const QColor &color, int radius, int value, QObject *parent = nullptr);
+
+ static CounterState *fromProto(const ServerInfo_Counter &counter, QObject *parent = nullptr);
+
+ int getId() const
+ {
+ return id;
+ }
+ QString getName() const
+ {
+ return name;
+ }
+ QColor getColor() const
+ {
+ return color;
+ }
+ int getRadius() const
+ {
+ return radius;
+ }
+ int getValue() const
+ {
+ return value;
+ }
+
+ void setValue(int newValue);
+
+signals:
+ void valueChanged(int oldValue, int newValue);
+
+private:
+ int id;
+ QString name;
+ QColor color;
+ int radius;
+ int value;
+};
+
+#endif // COCKATRICE_COUNTER_STATE_H
diff --git a/cockatrice/src/game/game.cpp b/cockatrice/src/game/game.cpp
index 38477f7f7..4c8b109c2 100644
--- a/cockatrice/src/game/game.cpp
+++ b/cockatrice/src/game/game.cpp
@@ -4,16 +4,16 @@
#include
-Game::Game(TabGame *_tab,
+Game::Game(QObject *_parent,
+ bool isLocalGame,
QList &_clients,
const Event_GameJoined &event,
const QMap &_roomGameTypes)
- : AbstractGame(_tab)
+ : AbstractGame(_parent)
{
gameMetaInfo->setFromProto(event.game_info());
gameMetaInfo->setRoomGameTypes(_roomGameTypes);
- gameState = new GameState(this, 0, event.host_id(), tab->getTabSupervisor()->getIsLocalGame(), _clients, false,
- event.resuming(), -1, false);
+ gameState = new GameState(this, 0, event.host_id(), isLocalGame, _clients, false, event.resuming(), -1, false);
connect(gameMetaInfo, &GameMetaInfo::startedChanged, gameState, &GameState::onStartedChanged);
playerManager = new PlayerManager(this, event.player_id(), event.judge(), event.spectator());
gameMetaInfo->setStarted(false);
diff --git a/cockatrice/src/game/game.h b/cockatrice/src/game/game.h
index 96ebbae4d..4f912664c 100644
--- a/cockatrice/src/game/game.h
+++ b/cockatrice/src/game/game.h
@@ -1,8 +1,8 @@
/**
* @file game.h
* @ingroup GameLogic
- * @brief TODO: Document this.
*/
+//! \todo Document this file.
#ifndef COCKATRICE_GAME_H
#define COCKATRICE_GAME_H
@@ -16,7 +16,8 @@ class Game : public AbstractGame
Q_OBJECT
public:
- Game(TabGame *tab,
+ Game(QObject *parent,
+ bool isLocalGame,
QList &_clients,
const Event_GameJoined &event,
const QMap &_roomGameTypes);
diff --git a/cockatrice/src/game/game_event_handler.cpp b/cockatrice/src/game/game_event_handler.cpp
index 42dd49458..4a96eebdb 100644
--- a/cockatrice/src/game/game_event_handler.cpp
+++ b/cockatrice/src/game/game_event_handler.cpp
@@ -1,8 +1,8 @@
#include "game_event_handler.h"
+#include "../game_graphics/log/message_log_widget.h"
#include "../interface/widgets/tabs/tab_game.h"
#include "abstract_game.h"
-#include "log/message_log_widget.h"
#include
#include
@@ -36,8 +36,9 @@ GameEventHandler::GameEventHandler(AbstractGame *_game) : QObject(_game), game(_
void GameEventHandler::sendGameCommand(PendingCommand *pend, int playerId)
{
AbstractClient *client = game->getClientForPlayer(playerId);
- if (!client)
+ if (!client) {
return;
+ }
connect(pend, &PendingCommand::finished, this, &GameEventHandler::commandFinished);
client->sendCommand(pend);
@@ -46,8 +47,9 @@ void GameEventHandler::sendGameCommand(PendingCommand *pend, int playerId)
void GameEventHandler::sendGameCommand(const google::protobuf::Message &command, int playerId)
{
AbstractClient *client = game->getClientForPlayer(playerId);
- if (!client)
+ if (!client) {
return;
+ }
PendingCommand *pend = prepareGameCommand(command);
connect(pend, &PendingCommand::finished, this, &GameEventHandler::commandFinished);
@@ -56,8 +58,9 @@ void GameEventHandler::sendGameCommand(const google::protobuf::Message &command,
void GameEventHandler::commandFinished(const Response &response)
{
- if (response.response_code() == Response::RespChatFlood)
+ if (response.response_code() == Response::RespChatFlood) {
emit gameFlooded();
+ }
}
PendingCommand *GameEventHandler::prepareGameCommand(const ::google::protobuf::Message &cmd)
@@ -96,7 +99,7 @@ void GameEventHandler::processGameEventContainer(const GameEventContainer &cont,
if (cont.has_forced_by_judge()) {
auto id = cont.forced_by_judge();
- Player *judgep = game->getPlayerManager()->getPlayers().value(id, nullptr);
+ PlayerLogic *judgep = game->getPlayerManager()->getPlayers().value(id, nullptr);
if (judgep) {
emit setContextJudgeName(judgep->getPlayerInfo()->getName());
} else if (game->getPlayerManager()->getSpectators().contains(id)) {
@@ -117,9 +120,11 @@ void GameEventHandler::processGameEventContainer(const GameEventContainer &cont,
break;
}
} else {
- if ((game->getGameState()->getClients().size() > 1) && (playerId != -1))
- if (game->getGameState()->getClients().at(playerId) != client)
+ if ((game->getGameState()->getClients().size() > 1) && (playerId != -1)) {
+ if (game->getGameState()->getClients().at(playerId) != client) {
continue;
+ }
+ }
switch (eventType) {
case GameEvent::GAME_STATE_CHANGED:
@@ -155,7 +160,7 @@ void GameEventHandler::processGameEventContainer(const GameEventContainer &cont,
break;
default: {
- Player *player = game->getPlayerManager()->getPlayers().value(playerId, 0);
+ PlayerLogic *player = game->getPlayerManager()->getPlayers().value(playerId, 0);
if (!player) {
qCWarning(GameEventHandlerLog) << "unhandled game event: invalid player id";
break;
@@ -208,11 +213,25 @@ void GameEventHandler::handleChatMessageSent(const QString &chatMessage)
sendGameCommand(cmd);
}
-void GameEventHandler::handleArrowDeletion(int arrowId)
+void GameEventHandler::handleArrowDeletion(int creatorId, int arrowId)
{
Command_DeleteArrow cmd;
cmd.set_arrow_id(arrowId);
- sendGameCommand(cmd);
+
+ auto preparedCommand = prepareGameCommand(cmd);
+
+ connect(preparedCommand, &PendingCommand::finished, this, [creatorId, arrowId, this](const Response &response) {
+ handleArrowDeletionFinished(response, creatorId, arrowId);
+ });
+
+ sendGameCommand(preparedCommand);
+}
+
+void GameEventHandler::handleArrowDeletionFinished(const Response &response, int creatorId, int arrowId)
+{
+ if (response.response_code() == Response::RespNameNotFound) {
+ emit arrowDeleted(creatorId, arrowId);
+ }
}
void GameEventHandler::eventSpectatorSay(const Event_GameSay &event,
@@ -256,7 +275,7 @@ void GameEventHandler::eventGameStateChanged(const Event_GameStateChanged &event
emit spectatorJoined(prop);
}
} else {
- Player *player = game->getPlayerManager()->getPlayers().value(playerId, 0);
+ PlayerLogic *player = game->getPlayerManager()->getPlayers().value(playerId, 0);
if (!player) {
player = game->getPlayerManager()->addPlayer(playerId, prop.user_info());
emit playerJoined(prop);
@@ -284,8 +303,9 @@ void GameEventHandler::eventGameStateChanged(const Event_GameStateChanged &event
if (event.game_started() && !game->getGameMetaInfo()->started()) {
game->getGameState()->setResuming(!game->getGameState()->isGameStateKnown());
game->getGameMetaInfo()->setStarted(event.game_started());
- if (game->getGameState()->isGameStateKnown())
+ if (game->getGameState()->isGameStateKnown()) {
emit logGameStart();
+ }
game->getGameState()->setActivePlayer(event.active_player_id());
game->getGameState()->setCurrentPhase(event.active_phase());
} else if (!event.game_started() && game->getGameMetaInfo()->started()) {
@@ -304,9 +324,10 @@ void GameEventHandler::processCardAttachmentsForPlayers(const Event_GameStateCha
const ServerInfo_Player &playerInfo = event.player_list(i);
const ServerInfo_PlayerProperties &prop = playerInfo.properties();
if (!prop.spectator()) {
- Player *player = game->getPlayerManager()->getPlayers().value(prop.player_id(), 0);
- if (!player)
+ PlayerLogic *player = game->getPlayerManager()->getPlayers().value(prop.player_id(), 0);
+ if (!player) {
continue;
+ }
player->processCardAttachment(playerInfo);
}
}
@@ -316,9 +337,10 @@ void GameEventHandler::eventPlayerPropertiesChanged(const Event_PlayerProperties
int eventPlayerId,
const GameEventContext &context)
{
- Player *player = game->getPlayerManager()->getPlayers().value(eventPlayerId, 0);
- if (!player)
+ PlayerLogic *player = game->getPlayerManager()->getPlayers().value(eventPlayerId, 0);
+ if (!player) {
return;
+ }
const ServerInfo_PlayerProperties &prop = event.player_properties();
emit playerPropertiesChanged(prop, eventPlayerId);
@@ -326,8 +348,9 @@ void GameEventHandler::eventPlayerPropertiesChanged(const Event_PlayerProperties
switch (contextType) {
case GameEventContext::READY_START: {
bool ready = prop.ready_start();
- if (player->getPlayerInfo()->getLocal())
+ if (player->getPlayerInfo()->getLocal()) {
emit localPlayerReadyStateChanged(player->getPlayerInfo()->getId(), ready);
+ }
if (ready) {
emit logReadyStart(player);
} else {
@@ -338,9 +361,10 @@ void GameEventHandler::eventPlayerPropertiesChanged(const Event_PlayerProperties
case GameEventContext::CONCEDE: {
player->setConceded(true);
- QMapIterator playerIterator(game->getPlayerManager()->getPlayers());
- while (playerIterator.hasNext())
+ QMapIterator playerIterator(game->getPlayerManager()->getPlayers());
+ while (playerIterator.hasNext()) {
playerIterator.next().value()->updateZones();
+ }
emit logConcede(eventPlayerId);
@@ -349,9 +373,10 @@ void GameEventHandler::eventPlayerPropertiesChanged(const Event_PlayerProperties
case GameEventContext::UNCONCEDE: {
player->setConceded(false);
- QMapIterator playerIterator(game->getPlayerManager()->getPlayers());
- while (playerIterator.hasNext())
+ QMapIterator playerIterator(game->getPlayerManager()->getPlayers());
+ while (playerIterator.hasNext()) {
playerIterator.next().value()->updateZones();
+ }
emit logUnconcede(eventPlayerId);
@@ -389,15 +414,16 @@ void GameEventHandler::eventJoin(const Event_Join &event, int /*eventPlayerId*/,
QString playerName = QString::fromStdString(playerInfo.user_info().name());
emit addPlayerToAutoCompleteList(playerName);
- if (game->getPlayerManager()->getPlayers().contains(playerId))
+ if (game->getPlayerManager()->getPlayers().contains(playerId)) {
return;
+ }
if (playerInfo.spectator()) {
game->getPlayerManager()->addSpectator(playerId, playerInfo);
emit logJoinSpectator(playerName);
emit spectatorJoined(playerInfo);
} else {
- Player *newPlayer = game->getPlayerManager()->addPlayer(playerId, playerInfo.user_info());
+ PlayerLogic *newPlayer = game->getPlayerManager()->addPlayer(playerId, playerInfo.user_info());
emit logJoinPlayer(newPlayer);
emit playerJoined(playerInfo);
}
@@ -425,9 +451,10 @@ QString GameEventHandler::getLeaveReason(Event_Leave::LeaveReason reason)
}
void GameEventHandler::eventLeave(const Event_Leave &event, int eventPlayerId, const GameEventContext & /*context*/)
{
- Player *player = game->getPlayerManager()->getPlayers().value(eventPlayerId, 0);
- if (!player)
+ PlayerLogic *player = game->getPlayerManager()->getPlayers().value(eventPlayerId, 0);
+ if (!player) {
return;
+ }
player->clear();
emit playerLeft(eventPlayerId);
@@ -439,9 +466,10 @@ void GameEventHandler::eventLeave(const Event_Leave &event, int eventPlayerId, c
player->deleteLater();
// Rearrange all remaining zones so that attachment relationship updates take place
- QMapIterator playerIterator(game->getPlayerManager()->getPlayers());
- while (playerIterator.hasNext())
+ QMapIterator playerIterator(game->getPlayerManager()->getPlayers());
+ while (playerIterator.hasNext()) {
playerIterator.next().value()->updateZones();
+ }
emitUserEvent();
}
@@ -460,9 +488,10 @@ void GameEventHandler::eventReverseTurn(const Event_ReverseTurn &event,
int eventPlayerId,
const GameEventContext & /*context*/)
{
- Player *player = game->getPlayerManager()->getPlayers().value(eventPlayerId, 0);
- if (!player)
+ PlayerLogic *player = game->getPlayerManager()->getPlayers().value(eventPlayerId, 0);
+ if (!player) {
return;
+ }
emit logTurnReversed(player, event.reversed());
}
@@ -490,9 +519,10 @@ void GameEventHandler::eventSetActivePlayer(const Event_SetActivePlayer &event,
const GameEventContext & /*context*/)
{
game->getGameState()->setActivePlayer(event.active_player_id());
- Player *player = game->getPlayerManager()->getPlayer(event.active_player_id());
- if (!player)
+ PlayerLogic *player = game->getPlayerManager()->getPlayer(event.active_player_id());
+ if (!player) {
return;
+ }
emit logActivePlayer(player);
emitUserEvent();
}
diff --git a/cockatrice/src/game/game_event_handler.h b/cockatrice/src/game/game_event_handler.h
index 302e7a6ff..f47116949 100644
--- a/cockatrice/src/game/game_event_handler.h
+++ b/cockatrice/src/game/game_event_handler.h
@@ -1,8 +1,8 @@
/**
* @file game_event_handler.h
* @ingroup GameLogic
- * @brief TODO: Document this.
*/
+//! \todo Document this file.
#ifndef COCKATRICE_GAME_EVENT_HANDLER_H
#define COCKATRICE_GAME_EVENT_HANDLER_H
@@ -38,7 +38,7 @@ class Event_Kicked;
class Event_ReverseTurn;
class AbstractGame;
class PendingCommand;
-class Player;
+class PlayerLogic;
inline Q_LOGGING_CATEGORY(GameEventHandlerLog, "game_event_handler");
@@ -60,7 +60,8 @@ public:
void handleActivePhaseChanged(int phase);
void handleGameLeft();
void handleChatMessageSent(const QString &chatMessage);
- void handleArrowDeletion(int arrowId);
+ void handleArrowDeletion(int creatorId, int arrowId);
+ void handleArrowDeletionFinished(const Response &response, int creatorId, int arrowId);
void eventSpectatorSay(const Event_GameSay &event, int eventPlayerId, const GameEventContext &context);
void eventSpectatorLeave(const Event_Leave &event, int eventPlayerId, const GameEventContext &context);
@@ -95,7 +96,7 @@ public slots:
signals:
void emitUserEvent();
void addPlayerToAutoCompleteList(QString playerName);
- void localPlayerDeckSelected(Player *localPlayer, int playerId, ServerInfo_Player playerInfo);
+ void localPlayerDeckSelected(PlayerLogic *localPlayer, int playerId, ServerInfo_Player playerInfo);
void remotePlayerDeckSelected(QString deckList, int playerId, QString playerName);
void remotePlayersDecksSelected(QVector>> opponentDecks);
void localPlayerSideboardLocked(int playerId, bool sideboardLocked);
@@ -112,21 +113,22 @@ signals:
void containerProcessingStarted(GameEventContext context);
void setContextJudgeName(QString judgeName);
void containerProcessingDone();
+ void arrowDeleted(int creatorId, int arrowId);
void logSpectatorSay(ServerInfo_User userInfo, QString message);
void logSpectatorLeave(QString name, QString reason);
void logGameStart();
- void logReadyStart(Player *player);
- void logNotReadyStart(Player *player);
- void logDeckSelect(Player *player, QString deckHash, int sideboardSize);
- void logSideboardLockSet(Player *player, bool sideboardLocked);
- void logConnectionStateChanged(Player *player, bool connected);
+ void logReadyStart(PlayerLogic *player);
+ void logNotReadyStart(PlayerLogic *player);
+ void logDeckSelect(PlayerLogic *player, QString deckHash, int sideboardSize);
+ void logSideboardLockSet(PlayerLogic *player, bool sideboardLocked);
+ void logConnectionStateChanged(PlayerLogic *player, bool connected);
void logJoinSpectator(QString spectatorName);
- void logJoinPlayer(Player *player);
- void logLeave(Player *player, QString reason);
+ void logJoinPlayer(PlayerLogic *player);
+ void logLeave(PlayerLogic *player, QString reason);
void logKicked();
- void logTurnReversed(Player *player, bool reversed);
+ void logTurnReversed(PlayerLogic *player, bool reversed);
void logGameClosed();
- void logActivePlayer(Player *activePlayer);
+ void logActivePlayer(PlayerLogic *activePlayer);
void logActivePhaseChanged(int activePhase);
void logConcede(int playerId);
void logUnconcede(int playerId);
diff --git a/cockatrice/src/game/game_meta_info.h b/cockatrice/src/game/game_meta_info.h
index b5f5bfe4f..cdba1605f 100644
--- a/cockatrice/src/game/game_meta_info.h
+++ b/cockatrice/src/game/game_meta_info.h
@@ -1,8 +1,8 @@
/**
* @file game_meta_info.h
* @ingroup GameLogic
- * @brief TODO: Document this.
*/
+//! \todo Document this file.
#ifndef GAME_META_INFO_H
#define GAME_META_INFO_H
@@ -87,15 +87,17 @@ public:
public slots:
void setStarted(bool s)
{
- if (gameInfo_.started() == s)
+ if (gameInfo_.started() == s) {
return;
+ }
gameInfo_.set_started(s);
emit startedChanged(s);
}
void setSpectatorsOmniscient(bool v)
{
- if (gameInfo_.spectators_omniscient() == v)
+ if (gameInfo_.spectators_omniscient() == v) {
return;
+ }
gameInfo_.set_spectators_omniscient(v);
emit spectatorsOmniscienceChanged(v);
}
diff --git a/cockatrice/src/game/game_state.h b/cockatrice/src/game/game_state.h
index 54f6d9276..5a57d4321 100644
--- a/cockatrice/src/game/game_state.h
+++ b/cockatrice/src/game/game_state.h
@@ -1,8 +1,8 @@
/**
* @file game_state.h
* @ingroup GameLogic
- * @brief TODO: Document this.
*/
+//! \todo Document this file.
#ifndef COCKATRICE_GAME_STATE_H
#define COCKATRICE_GAME_STATE_H
diff --git a/cockatrice/src/game/log/message_log_widget.h b/cockatrice/src/game/log/message_log_widget.h
deleted file mode 100644
index 369debd33..000000000
--- a/cockatrice/src/game/log/message_log_widget.h
+++ /dev/null
@@ -1,108 +0,0 @@
-/**
- * @file message_log_widget.h
- * @ingroup GameWidgets
- * @brief TODO: Document this.
- */
-
-#ifndef MESSAGELOGWIDGET_H
-#define MESSAGELOGWIDGET_H
-
-#include "../../interface/widgets/server/chat_view/chat_view.h"
-#include "../zones/logic/card_zone_logic.h"
-
-class AbstractGame;
-class CardItem;
-class GameEventContext;
-class Player;
-class PlayerEventHandler;
-
-class MessageLogWidget : public ChatView
-{
- Q_OBJECT
-private:
- enum MessageContext
- {
- MessageContext_None,
- MessageContext_MoveCard,
- MessageContext_Mulligan
- };
-
- MessageContext currentContext;
- QString messagePrefix, messageSuffix;
-
- static QPair getFromStr(CardZoneLogic *zone, QString cardName, int position, bool ownerChange);
-
-public:
- void connectToPlayerEventHandler(PlayerEventHandler *player);
- MessageLogWidget(TabSupervisor *_tabSupervisor, AbstractGame *_game, QWidget *parent = nullptr);
-
-public slots:
- void containerProcessingDone();
- void containerProcessingStarted(const GameEventContext &context);
- void logAlwaysRevealTopCard(Player *player, CardZoneLogic *zone, bool reveal);
- void logAlwaysLookAtTopCard(Player *player, CardZoneLogic *zone, bool reveal);
- void logAttachCard(Player *player, QString cardName, Player *targetPlayer, QString targetCardName);
- void logConcede(int playerId);
- void logUnconcede(int playerId);
- void logConnectionStateChanged(Player *player, bool connectionState);
- void logCreateArrow(Player *player,
- Player *startPlayer,
- QString startCard,
- Player *targetPlayer,
- QString targetCard,
- bool playerTarget);
- void logCreateToken(Player *player, QString cardName, QString pt, bool faceDown);
- void logDeckSelect(Player *player, QString deckHash, int sideboardSize);
- void logDestroyCard(Player *player, QString cardName);
- void logDrawCards(Player *player, int number, bool deckIsEmpty);
- void logDumpZone(Player *player, CardZoneLogic *zone, int numberCards, bool isReversed = false);
- void logFlipCard(Player *player, QString cardName, bool faceDown);
- void logGameClosed();
- void logGameStart();
- void logGameFlooded();
- void logJoin(Player *player);
- void logJoinSpectator(QString name);
- void logKicked();
- void logLeave(Player *player, QString reason);
- void logLeaveSpectator(QString name, QString reason);
- void logNotReadyStart(Player *player);
- void logMoveCard(Player *player,
- CardItem *card,
- CardZoneLogic *startZone,
- int oldX,
- CardZoneLogic *targetZone,
- int newX);
- void logMulligan(Player *player, int number);
- void logReplayStarted(int gameId);
- void logReadyStart(Player *player);
- void logRevealCards(Player *player,
- CardZoneLogic *zone,
- int cardId,
- QString cardName,
- Player *otherPlayer,
- bool faceDown,
- int amount,
- bool isLentToAnotherPlayer);
- void logReverseTurn(Player *player, bool reversed);
- void logRollDie(Player *player, int sides, const QList &rolls);
- void logSay(Player *player, QString message);
- void logSetActivePhase(int phase);
- void logSetActivePlayer(Player *player);
- void logSetAnnotation(Player *player, CardItem *card, QString newAnnotation);
- void logSetCardCounter(Player *player, QString cardName, int counterId, int value, int oldValue);
- void logSetCounter(Player *player, QString counterName, int value, int oldValue);
- void logSetDoesntUntap(Player *player, CardItem *card, bool doesntUntap);
- void logSetPT(Player *player, CardItem *card, QString newPT);
- void logSetSideboardLock(Player *player, bool locked);
- void logSetTapped(Player *player, CardItem *card, bool tapped);
- void logShuffle(Player *player, CardZoneLogic *zone, int start, int end);
- void logSpectatorSay(const ServerInfo_User &spectator, QString message);
- void logUnattachCard(Player *player, QString cardName);
- void logUndoDraw(Player *player, QString cardName);
- void setContextJudgeName(QString player);
- void appendHtmlServerMessage(const QString &html,
- bool optionalIsBold = false,
- QString optionalFontColor = QString()) override;
-};
-
-#endif
diff --git a/cockatrice/src/game/phase.h b/cockatrice/src/game/phase.h
index 2e712932f..a888ad43b 100644
--- a/cockatrice/src/game/phase.h
+++ b/cockatrice/src/game/phase.h
@@ -1,8 +1,8 @@
/**
* @file phase.h
* @ingroup GameLogic
- * @brief TODO: Document this.
*/
+//! \todo Document this file.
#ifndef PHASE_H
#define PHASE_H
diff --git a/cockatrice/src/game/player/event_processing_options.h b/cockatrice/src/game/player/event_processing_options.h
index 3e743bdb3..4c7663789 100644
--- a/cockatrice/src/game/player/event_processing_options.h
+++ b/cockatrice/src/game/player/event_processing_options.h
@@ -1,8 +1,8 @@
/**
* @file event_processing_options.h
* @ingroup GameLogicPlayers
- * @brief TODO: Document this.
*/
+//! \todo Document this file.
#ifndef COCKATRICE_EVENT_PROCESSING_OPTIONS_H
#define COCKATRICE_EVENT_PROCESSING_OPTIONS_H
diff --git a/cockatrice/src/game/player/menu/player_menu.cpp b/cockatrice/src/game/player/menu/player_menu.cpp
deleted file mode 100644
index 0dc381e28..000000000
--- a/cockatrice/src/game/player/menu/player_menu.cpp
+++ /dev/null
@@ -1,149 +0,0 @@
-#include "player_menu.h"
-
-#include "../../../interface/widgets/tabs/tab_game.h"
-#include "../../board/card_item.h"
-#include "../../zones/hand_zone.h"
-#include "../../zones/pile_zone.h"
-#include "../../zones/table_zone.h"
-#include "card_menu.h"
-#include "hand_menu.h"
-
-#include
-
-PlayerMenu::PlayerMenu(Player *_player) : QObject(_player), player(_player)
-{
- playerMenu = new TearOffMenu();
-
- if (player->getPlayerInfo()->getLocalOrJudge()) {
- handMenu = addManagedMenu(player, player->getPlayerActions(), playerMenu);
- libraryMenu = addManagedMenu(player, playerMenu);
- } else {
- handMenu = nullptr;
- libraryMenu = nullptr;
- }
-
- graveMenu = addManagedMenu(player, playerMenu);
- rfgMenu = addManagedMenu(player, playerMenu);
-
- if (player->getPlayerInfo()->getLocalOrJudge()) {
- sideboardMenu = addManagedMenu(player, playerMenu);
- customZonesMenu = addManagedMenu(player);
- playerMenu->addSeparator();
-
- countersMenu = playerMenu->addMenu(QString());
-
- utilityMenu = createManagedComponent(player, playerMenu);
- } else {
- sideboardMenu = nullptr;
- customZonesMenu = nullptr;
- countersMenu = nullptr;
- utilityMenu = nullptr;
- }
-
- if (player->getPlayerInfo()->getLocal()) {
- sayMenu = addManagedMenu(player);
- } else {
- sayMenu = nullptr;
- }
-
- connect(&SettingsCache::instance().shortcuts(), &ShortcutsSettings::shortCutChanged, this,
- &PlayerMenu::refreshShortcuts);
- refreshShortcuts();
-
- retranslateUi();
-}
-
-void PlayerMenu::setMenusForGraphicItems()
-{
- player->getGraphicsItem()->getTableZoneGraphicsItem()->setMenu(playerMenu);
- player->getGraphicsItem()->getGraveyardZoneGraphicsItem()->setMenu(graveMenu, graveMenu->aViewGraveyard);
- player->getGraphicsItem()->getRfgZoneGraphicsItem()->setMenu(rfgMenu, rfgMenu->aViewRfg);
- if (player->getPlayerInfo()->getLocalOrJudge()) {
- player->getGraphicsItem()->getHandZoneGraphicsItem()->setMenu(handMenu);
- player->getGraphicsItem()->getDeckZoneGraphicsItem()->setMenu(libraryMenu, libraryMenu->aDrawCard);
- player->getGraphicsItem()->getSideboardZoneGraphicsItem()->setMenu(sideboardMenu);
- }
-}
-
-QMenu *PlayerMenu::updateCardMenu(const CardItem *card)
-{
- if (!card) {
- emit cardMenuUpdated(nullptr);
- return nullptr;
- }
-
- // If is spectator (as spectators don't need card menus), return
- // only update the menu if the card is actually selected
- if ((player->getGame()->getPlayerManager()->isSpectator() && !player->getGame()->getPlayerManager()->isJudge()) ||
- player->getGame()->getActiveCard() != card) {
- return nullptr;
- }
-
- QMenu *menu = new CardMenu(player, card, shortcutsActive);
- emit cardMenuUpdated(menu);
-
- return menu;
-}
-
-void PlayerMenu::retranslateUi()
-{
- playerMenu->setTitle(tr("Player \"%1\"").arg(player->getPlayerInfo()->getName()));
-
- for (auto *component : managedComponents) {
- component->retranslateUi();
- }
-
- if (countersMenu) {
- countersMenu->setTitle(tr("&Counters"));
- }
-
- QMapIterator counterIterator(player->getCounters());
- while (counterIterator.hasNext()) {
- counterIterator.next().value()->retranslateUi();
- }
-}
-
-void PlayerMenu::refreshShortcuts()
-{
- if (shortcutsActive) {
- // Judges get access to every player's menus but only want shortcuts to be set for their own.
- if (player->getPlayerInfo()->getLocalOrJudge() && !player->getPlayerInfo()->getLocal()) {
- setShortcutsInactive();
- } else {
- setShortcutsActive();
- }
- } else {
- setShortcutsInactive();
- }
-}
-
-void PlayerMenu::setShortcutsActive()
-{
- shortcutsActive = true;
-
- for (auto *component : managedComponents) {
- component->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 counterIterator(player->getCounters());
- while (counterIterator.hasNext()) {
- counterIterator.next().value()->setShortcutsActive();
- }
-}
-
-void PlayerMenu::setShortcutsInactive()
-{
- shortcutsActive = false;
-
- for (auto *component : managedComponents) {
- component->setShortcutsInactive();
- }
-
- QMapIterator counterIterator(player->getCounters());
- while (counterIterator.hasNext()) {
- counterIterator.next().value()->setShortcutsInactive();
- }
-}
\ No newline at end of file
diff --git a/cockatrice/src/game/player/player_actions.cpp b/cockatrice/src/game/player/player_actions.cpp
index 20034df16..de909ca5e 100644
--- a/cockatrice/src/game/player/player_actions.cpp
+++ b/cockatrice/src/game/player/player_actions.cpp
@@ -1,15 +1,13 @@
#include "player_actions.h"
+#include "../../game_graphics/dialogs/dlg_move_top_cards_until.h"
+#include "../../game_graphics/dialogs/dlg_roll_dice.h"
+#include "../../game_graphics/player/card_menu_action_type.h"
+#include "../../game_graphics/zones/hand_zone.h"
+#include "../../game_graphics/zones/table_zone.h"
#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"
-#include "../zones/logic/view_zone_logic.h"
-#include "../zones/table_zone.h"
-#include "card_menu_action_type.h"
+#include "../zones/view_zone_logic.h"
#include
#include
@@ -19,6 +17,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -35,9 +34,11 @@
// milliseconds in between triggers of the move top cards until action
static constexpr int MOVE_TOP_CARD_UNTIL_INTERVAL = 100;
-PlayerActions::PlayerActions(Player *_player)
+PlayerActions::PlayerActions(PlayerLogic *_player)
: QObject(_player), player(_player), lastTokenTableRow(0), movingCardsUntil(false)
{
+ connect(this, &PlayerActions::requestZoneViewToggle, player, &PlayerLogic::onRequestZoneViewToggle);
+
moveTopCardTimer = new QTimer(this);
moveTopCardTimer->setInterval(MOVE_TOP_CARD_UNTIL_INTERVAL);
moveTopCardTimer->setSingleShot(true);
@@ -84,8 +85,9 @@ void PlayerActions::playCard(CardItem *card, bool faceDown)
cardToMove->set_pt(info.getPowTough().toStdString());
}
cardToMove->set_tapped(!faceDown && info.getUiAttributes().cipt);
- if (tableRow != 3)
+ if (tableRow != 3) {
cmd.set_target_zone(ZoneNames::TABLE);
+ }
cmd.set_x(gridPoint.x());
cmd.set_y(gridPoint.y());
}
@@ -131,12 +133,12 @@ void PlayerActions::playCardToTable(const CardItem *card, bool faceDown)
void PlayerActions::actViewLibrary()
{
- player->getGameScene()->toggleZoneView(player, ZoneNames::DECK, -1);
+ emit requestZoneViewToggle(ZoneNames::DECK, -1);
}
void PlayerActions::actViewHand()
{
- player->getGameScene()->toggleZoneView(player, ZoneNames::HAND, -1);
+ emit requestZoneViewToggle(ZoneNames::HAND, -1);
}
/**
@@ -168,49 +170,45 @@ void PlayerActions::actSortHand()
static QList defaultOptions = {CardList::SortByName, CardList::SortByPrinting};
- player->getGraphicsItem()->getHandZoneGraphicsItem()->sortHand(sortOptions + defaultOptions);
+ emit requestSortHand(sortOptions + defaultOptions);
}
-void PlayerActions::actViewTopCards()
+void PlayerActions::actRequestViewTopCardsDialog()
{
- int deckSize = player->getDeckZone()->getCards().size();
- bool ok;
- int number = QInputDialog::getInt(player->getGame()->getTab(), tr("View top cards of library"),
- tr("Number of cards: (max. %1)").arg(deckSize), defaultNumberTopCards, 1,
- deckSize, 1, &ok);
- if (ok) {
- defaultNumberTopCards = number;
- player->getGameScene()->toggleZoneView(player, ZoneNames::DECK, number);
- }
+ emit requestViewTopCardsDialog(defaultNumberTopCards, player->getDeckZone()->getCards().size());
}
-void PlayerActions::actViewBottomCards()
+void PlayerActions::actViewTopCards(int number)
{
- int deckSize = player->getDeckZone()->getCards().size();
- bool ok;
- int number = QInputDialog::getInt(player->getGame()->getTab(), tr("View bottom cards of library"),
- tr("Number of cards: (max. %1)").arg(deckSize), defaultNumberBottomCards, 1,
- deckSize, 1, &ok);
- if (ok) {
- defaultNumberBottomCards = number;
- player->getGameScene()->toggleZoneView(player, ZoneNames::DECK, number, true);
- }
+ defaultNumberTopCards = number;
+ emit requestZoneViewToggle(ZoneNames::DECK, number);
}
-void PlayerActions::actAlwaysRevealTopCard()
+void PlayerActions::actRequestViewBottomCardsDialog()
+{
+ emit requestViewBottomCardsDialog(defaultNumberBottomCards, player->getDeckZone()->getCards().size());
+}
+
+void PlayerActions::actViewBottomCards(int number)
+{
+ defaultNumberBottomCards = number;
+ emit requestZoneViewToggle(ZoneNames::DECK, number, true);
+}
+
+void PlayerActions::actAlwaysRevealTopCard(bool alwaysRevealTopCard)
{
Command_ChangeZoneProperties cmd;
cmd.set_zone_name(ZoneNames::DECK);
- cmd.set_always_reveal_top_card(player->getPlayerMenu()->getLibraryMenu()->isAlwaysRevealTopCardChecked());
+ cmd.set_always_reveal_top_card(alwaysRevealTopCard);
sendGameCommand(cmd);
}
-void PlayerActions::actAlwaysLookAtTopCard()
+void PlayerActions::actAlwaysLookAtTopCard(bool alwaysRevealTopCard)
{
Command_ChangeZoneProperties cmd;
cmd.set_zone_name(ZoneNames::DECK);
- cmd.set_always_look_at_top_card(player->getPlayerMenu()->getLibraryMenu()->isAlwaysLookAtTopCardChecked());
+ cmd.set_always_look_at_top_card(alwaysRevealTopCard);
sendGameCommand(cmd);
}
@@ -222,17 +220,17 @@ void PlayerActions::actOpenDeckInDeckEditor()
void PlayerActions::actViewGraveyard()
{
- player->getGameScene()->toggleZoneView(player, ZoneNames::GRAVE, -1);
+ emit requestZoneViewToggle(ZoneNames::GRAVE, -1);
}
void PlayerActions::actViewRfg()
{
- player->getGameScene()->toggleZoneView(player, ZoneNames::EXILE, -1);
+ emit requestZoneViewToggle(ZoneNames::EXILE, -1);
}
void PlayerActions::actViewSideboard()
{
- player->getGameScene()->toggleZoneView(player, ZoneNames::SIDEBOARD, -1);
+ emit requestZoneViewToggle(ZoneNames::SIDEBOARD, -1);
}
void PlayerActions::actShuffle()
@@ -240,18 +238,20 @@ void PlayerActions::actShuffle()
sendGameCommand(Command_Shuffle());
}
-void PlayerActions::actShuffleTop()
+void PlayerActions::actRequestShuffleTopDialog()
{
const int maxCards = player->getDeckZone()->getCards().size();
if (maxCards == 0) {
return;
}
- bool ok;
- int number = QInputDialog::getInt(player->getGame()->getTab(), tr("Shuffle top cards of library"),
- tr("Number of cards: (max. %1)").arg(maxCards), defaultNumberTopCards, 1,
- maxCards, 1, &ok);
- if (!ok) {
+ emit requestShuffleTopDialog(defaultNumberTopCards, maxCards);
+}
+
+void PlayerActions::actShuffleTop(int number)
+{
+ const int maxCards = player->getDeckZone()->getCards().size();
+ if (maxCards == 0) {
return;
}
@@ -269,18 +269,20 @@ void PlayerActions::actShuffleTop()
sendGameCommand(cmd);
}
-void PlayerActions::actShuffleBottom()
+void PlayerActions::actRequestShuffleBottomDialog()
{
const int maxCards = player->getDeckZone()->getCards().size();
if (maxCards == 0) {
return;
}
- bool ok;
- int number = QInputDialog::getInt(player->getGame()->getTab(), tr("Shuffle bottom cards of library"),
- tr("Number of cards: (max. %1)").arg(maxCards), defaultNumberBottomCards, 1,
- maxCards, 1, &ok);
- if (!ok) {
+ emit requestShuffleBottomDialog(defaultNumberBottomCards, maxCards);
+}
+
+void PlayerActions::actShuffleBottom(int number)
+{
+ const int maxCards = player->getDeckZone()->getCards().size();
+ if (maxCards == 0) {
return;
}
@@ -305,21 +307,18 @@ void PlayerActions::actDrawCard()
sendGameCommand(cmd);
}
-void PlayerActions::actMulligan()
+void PlayerActions::actRequestMulliganDialog()
{
int startSize = SettingsCache::instance().getStartingHandSize();
int handSize = player->getHandZone()->getCards().size();
int deckSize = player->getDeckZone()->getCards().size() + handSize;
- bool ok;
- int number = QInputDialog::getInt(player->getGame()->getTab(), tr("Draw hand"),
- tr("Number of cards: (max. %1)").arg(deckSize) + '\n' +
- tr("0 and lower are in comparison to current hand size"),
- startSize, -handSize, deckSize, 1, &ok);
+ emit requestMulliganDialog(startSize, handSize, deckSize);
+}
- if (!ok) {
- return;
- }
+void PlayerActions::actMulligan(int number)
+{
+ int handSize = player->getHandZone()->getCards().size();
if (number < 1) {
number = handSize + number;
@@ -353,19 +352,19 @@ void PlayerActions::doMulligan(int number)
sendGameCommand(cmd);
}
-void PlayerActions::actDrawCards()
+void PlayerActions::actRequestDrawCardsDialog()
{
int deckSize = player->getDeckZone()->getCards().size();
- bool ok;
- int number = QInputDialog::getInt(player->getGame()->getTab(), tr("Draw cards"),
- tr("Number of cards: (max. %1)").arg(deckSize), defaultNumberTopCards, 1,
- deckSize, 1, &ok);
- if (ok) {
- defaultNumberTopCards = number;
- Command_DrawCards cmd;
- cmd.set_number(static_cast(number));
- sendGameCommand(cmd);
- }
+
+ emit requestDrawCardsDialog(defaultNumberTopCards, deckSize);
+}
+
+void PlayerActions::actDrawCards(int number)
+{
+ defaultNumberTopCards = number;
+ Command_DrawCards cmd;
+ cmd.set_number(static_cast(number));
+ sendGameCommand(cmd);
}
void PlayerActions::actUndoDraw()
@@ -423,36 +422,40 @@ void PlayerActions::actMoveTopCardToExile()
void PlayerActions::actMoveTopCardsToGrave()
{
- moveTopCardsTo(ZoneNames::GRAVE, tr("grave"), false);
+ actRequestMoveTopCardsToDialog(ZoneNames::GRAVE, tr("grave"), false);
}
void PlayerActions::actMoveTopCardsToGraveFaceDown()
{
- moveTopCardsTo(ZoneNames::GRAVE, tr("grave"), true);
+ actRequestMoveTopCardsToDialog(ZoneNames::GRAVE, tr("grave"), true);
}
void PlayerActions::actMoveTopCardsToExile()
{
- moveTopCardsTo(ZoneNames::EXILE, tr("exile"), false);
+ actRequestMoveTopCardsToDialog(ZoneNames::EXILE, tr("exile"), false);
}
void PlayerActions::actMoveTopCardsToExileFaceDown()
{
- moveTopCardsTo(ZoneNames::EXILE, tr("exile"), true);
+ actRequestMoveTopCardsToDialog(ZoneNames::EXILE, tr("exile"), true);
}
-void PlayerActions::moveTopCardsTo(const QString &targetZone, const QString &zoneDisplayName, bool faceDown)
+void PlayerActions::actRequestMoveTopCardsToDialog(const QString &targetZone,
+ const QString &zoneDisplayName,
+ bool faceDown)
{
const int maxCards = player->getDeckZone()->getCards().size();
if (maxCards == 0) {
return;
}
- bool ok;
- int number = QInputDialog::getInt(player->getGame()->getTab(), tr("Move top cards to %1").arg(zoneDisplayName),
- tr("Number of cards: (max. %1)").arg(maxCards), defaultNumberTopCards, 1,
- maxCards, 1, &ok);
- if (!ok) {
+ emit requestMoveTopCardsToDialog(defaultNumberTopCards, maxCards, targetZone, zoneDisplayName, faceDown);
+}
+
+void PlayerActions::moveTopCardsTo(int number, const QString &targetZone, bool faceDown)
+{
+ const int maxCards = player->getDeckZone()->getCards().size();
+ if (maxCards == 0) {
return;
}
@@ -479,17 +482,16 @@ void PlayerActions::moveTopCardsTo(const QString &targetZone, const QString &zon
sendGameCommand(cmd);
}
-void PlayerActions::actMoveTopCardsUntil()
+void PlayerActions::actRequestMoveTopCardsUntilDialog()
{
stopMoveTopCardsUntil();
- DlgMoveTopCardsUntil dlg(player->getGame()->getTab(), movingCardsUntilOptions);
- if (!dlg.exec()) {
- return;
- }
+ emit requestMoveTopCardsUntilDialog(movingCardsUntilOptions);
+}
- auto expr = dlg.getExpr();
- movingCardsUntilOptions = dlg.getOptions();
+void PlayerActions::moveTopCardsUntil(const QString &expr, MoveTopCardsUntilOptions options)
+{
+ movingCardsUntilOptions = options;
if (player->getDeckZone()->getCards().empty()) {
stopMoveTopCardsUntil();
@@ -618,36 +620,40 @@ void PlayerActions::actMoveBottomCardToExile()
void PlayerActions::actMoveBottomCardsToGrave()
{
- moveBottomCardsTo(ZoneNames::GRAVE, tr("grave"), false);
+ actRequestMoveBottomCardsToDialog(ZoneNames::GRAVE, tr("grave"), false);
}
void PlayerActions::actMoveBottomCardsToGraveFaceDown()
{
- moveBottomCardsTo(ZoneNames::GRAVE, tr("grave"), true);
+ actRequestMoveBottomCardsToDialog(ZoneNames::GRAVE, tr("grave"), true);
}
void PlayerActions::actMoveBottomCardsToExile()
{
- moveBottomCardsTo(ZoneNames::EXILE, tr("exile"), false);
+ actRequestMoveBottomCardsToDialog(ZoneNames::EXILE, tr("exile"), false);
}
void PlayerActions::actMoveBottomCardsToExileFaceDown()
{
- moveBottomCardsTo(ZoneNames::EXILE, tr("exile"), true);
+ actRequestMoveBottomCardsToDialog(ZoneNames::EXILE, tr("exile"), true);
}
-void PlayerActions::moveBottomCardsTo(const QString &targetZone, const QString &zoneDisplayName, bool faceDown)
+void PlayerActions::actRequestMoveBottomCardsToDialog(const QString &targetZone,
+ const QString &zoneDisplayName,
+ bool faceDown)
{
const int maxCards = player->getDeckZone()->getCards().size();
if (maxCards == 0) {
return;
}
- bool ok;
- int number = QInputDialog::getInt(player->getGame()->getTab(), tr("Move bottom cards to %1").arg(zoneDisplayName),
- tr("Number of cards: (max. %1)").arg(maxCards), defaultNumberBottomCards, 1,
- maxCards, 1, &ok);
- if (!ok) {
+ emit requestMoveBottomCardsToDialog(defaultNumberBottomCards, maxCards, targetZone, zoneDisplayName, faceDown);
+}
+
+void PlayerActions::moveBottomCardsTo(int number, const QString &targetZone, bool faceDown)
+{
+ const int maxCards = player->getDeckZone()->getCards().size();
+ if (maxCards == 0) {
return;
}
@@ -759,20 +765,24 @@ void PlayerActions::actDrawBottomCard()
sendGameCommand(cmd);
}
-void PlayerActions::actDrawBottomCards()
+void PlayerActions::actRequestDrawBottomCardsDialog()
{
const int maxCards = player->getDeckZone()->getCards().size();
if (maxCards == 0) {
return;
}
- bool ok;
- int number = QInputDialog::getInt(player->getGame()->getTab(), tr("Draw bottom cards"),
- tr("Number of cards: (max. %1)").arg(maxCards), defaultNumberBottomCards, 1,
- maxCards, 1, &ok);
- if (!ok) {
+ emit requestDrawBottomCardsDialog(defaultNumberBottomCards, maxCards);
+}
+
+void PlayerActions::actDrawBottomCards(int number)
+{
+ const int maxCards = player->getDeckZone()->getCards().size();
+ if (maxCards == 0) {
return;
- } else if (number > maxCards) {
+ }
+
+ if (number > maxCards) {
number = maxCards;
}
defaultNumberBottomCards = number;
@@ -839,27 +849,35 @@ void PlayerActions::actUntapAll()
sendGameCommand(cmd);
}
-void PlayerActions::actRollDie()
+void PlayerActions::actRequestRollDieDialog()
{
- DlgRollDice dlg(player->getGame()->getTab());
- if (!dlg.exec()) {
- return;
- }
+ emit requestRollDieDialog();
+}
+void PlayerActions::actRollDie(int sides, int count)
+{
Command_RollDie cmd;
- cmd.set_sides(dlg.getDieSideCount());
- cmd.set_count(dlg.getDiceToRollCount());
+ cmd.set_sides(sides);
+ cmd.set_count(count);
sendGameCommand(cmd);
}
-void PlayerActions::actCreateToken()
+void PlayerActions::actFlipCoin()
{
- DlgCreateToken dlg(player->getPlayerMenu()->getUtilityMenu()->getPredefinedTokens(), player->getGame()->getTab());
- if (!dlg.exec()) {
- return;
- }
+ Command_RollDie cmd;
+ cmd.set_sides(2);
+ cmd.set_count(1);
+ sendGameCommand(cmd);
+}
- lastTokenInfo = dlg.getTokenInfo();
+void PlayerActions::actRequestCreateTokenDialog(const QStringList &predefinedTokens)
+{
+ emit requestCreateTokenDialog(predefinedTokens);
+}
+
+void PlayerActions::actCreateToken(TokenInfo tokenToCreate)
+{
+ lastTokenInfo = tokenToCreate;
ExactCard correctedCard = CardDatabaseManager::query()->guessCard({lastTokenInfo.name, lastTokenInfo.providerId});
if (correctedCard) {
@@ -870,8 +888,7 @@ void PlayerActions::actCreateToken()
}
}
- player->getPlayerMenu()->getUtilityMenu()->setAndEnableCreateAnotherTokenAction(
- tr("C&reate another %1 token").arg(lastTokenInfo.name));
+ emit requestEnableAndSetCreateAnotherTokenAction(lastTokenInfo.name);
actCreateAnotherToken();
}
@@ -902,8 +919,12 @@ void PlayerActions::setLastToken(CardInfoPtr cardInfo)
return;
}
- UtilityMenu *utilityMenu = player->getPlayerMenu()->getUtilityMenu();
- if (utilityMenu == nullptr || !utilityMenu->createAnotherTokenActionExists()) {
+ emit requestSetLastToken(cardInfo);
+}
+
+void PlayerActions::setLastTokenInfo(CardInfoPtr cardInfo)
+{
+ if (cardInfo == nullptr) {
return;
}
@@ -917,7 +938,7 @@ void PlayerActions::setLastToken(CardInfoPtr cardInfo)
lastTokenTableRow = TableZone::tableRowToGridY(cardInfo->getUiAttributes().tableRow);
- utilityMenu->setAndEnableCreateAnotherTokenAction(tr("C&reate another %1 token").arg(lastTokenInfo.name));
+ emit requestEnableAndSetCreateAnotherTokenAction(lastTokenInfo.name);
}
void PlayerActions::actCreatePredefinedToken()
@@ -936,23 +957,17 @@ void PlayerActions::actCreatePredefinedToken()
void PlayerActions::actCreateRelatedCard()
{
const CardItem *sourceCard = player->getGame()->getActiveCard();
+
if (!sourceCard) {
return;
}
+
auto *action = static_cast(sender());
// If there is a better way of passing a CardRelation through a QAction, please add it here.
auto relatedCards = sourceCard->getCardInfo().getAllRelatedCards();
- CardRelation *cardRelation = relatedCards.at(action->data().toInt());
- /*
- * If we make a token via "Token: TokenName"
- * then let's allow it to be created via "create another token"
- */
- if (createRelatedFromRelation(sourceCard, cardRelation) && cardRelation->getCanCreateAnother()) {
- ExactCard relatedCard = CardDatabaseManager::query()->getCardFromSameSet(cardRelation->getName(),
- sourceCard->getCard().getPrinting());
- setLastToken(relatedCard.getCardPtr());
- }
+ CardRelation *cardRelation = relatedCards.at(action->data().toInt());
+ actRequestCreateRelatedFromRelationDialog(sourceCard, cardRelation);
}
void PlayerActions::actCreateAllRelatedCards()
@@ -972,7 +987,9 @@ void PlayerActions::actCreateAllRelatedCards()
if (relatedCards.length() == 1) {
cardRelation = relatedCards.at(0);
- if (createRelatedFromRelation(sourceCard, cardRelation)) {
+ lastRelatedCreationSucceeded = false; // reset before emit
+ actRequestCreateRelatedFromRelationDialog(sourceCard, cardRelation);
+ if (lastRelatedCreationSucceeded) {
++tokensTypesCreated;
}
} else {
@@ -984,15 +1001,18 @@ void PlayerActions::actCreateAllRelatedCards()
}
}
switch (nonExcludedRelatedCards.length()) {
- case 1: // if nonExcludedRelatedCards == 1
+ case 1:
cardRelation = nonExcludedRelatedCards.at(0);
- if (createRelatedFromRelation(sourceCard, cardRelation)) {
+ lastRelatedCreationSucceeded = false; // reset before emit
+ actRequestCreateRelatedFromRelationDialog(sourceCard, cardRelation);
+ if (lastRelatedCreationSucceeded) {
++tokensTypesCreated;
}
break;
+
// If all are marked "Exclude", then treat the situation as if none of them are.
// We won't accept "garbage in, garbage out", here.
- case 0: // else if nonExcludedRelatedCards == 0
+ case 0:
for (CardRelation *cardRelationAll : relatedCards) {
if (!cardRelationAll->getDoesAttach() && !cardRelationAll->getIsVariable()) {
dbName = cardRelationAll->getName();
@@ -1007,7 +1027,8 @@ void PlayerActions::actCreateAllRelatedCards()
}
}
break;
- default: // else
+
+ default:
for (CardRelation *cardRelationNotExcluded : nonExcludedRelatedCards) {
if (!cardRelationNotExcluded->getDoesAttach() && !cardRelationNotExcluded->getIsVariable()) {
dbName = cardRelationNotExcluded->getName();
@@ -1035,43 +1056,83 @@ void PlayerActions::actCreateAllRelatedCards()
}
}
-bool PlayerActions::createRelatedFromRelation(const CardItem *sourceCard, const CardRelation *cardRelation)
+void PlayerActions::actRequestCreateRelatedFromRelationDialog(const CardItem *sourceCard,
+ const CardRelation *cardRelation)
+{
+ emit requestCreateRelatedFromRelationDialog(sourceCard, cardRelation);
+}
+
+bool PlayerActions::createRelatedFromRelation(const CardItem *sourceCard,
+ const CardRelation *cardRelation,
+ int variableCount)
{
if (sourceCard == nullptr || cardRelation == nullptr) {
return false;
}
- QString dbName = cardRelation->getName();
- bool persistent = cardRelation->getIsPersistent();
+
+ const QString dbName = cardRelation->getName();
+ const bool persistent = cardRelation->getIsPersistent();
+
+ // Variable relations always use DoesNotAttach, regardless of the count the user
+ // entered.
if (cardRelation->getIsVariable()) {
- bool ok;
- player->setDialogSemaphore(true);
- int count = QInputDialog::getInt(player->getGame()->getTab(), tr("Create tokens"), tr("Number:"),
- cardRelation->getDefaultCount(), 1, MAX_TOKENS_PER_DIALOG, 1, &ok);
- player->setDialogSemaphore(false);
- if (!ok) {
+ if (variableCount <= 0) {
return false;
}
+ for (int i = 0; i < variableCount; ++i) {
+ createCard(sourceCard, dbName, CardRelationType::DoesNotAttach, persistent);
+ }
+ return true;
+ }
+
+ const int count = cardRelation->getDefaultCount();
+
+ if (count > 1) {
for (int i = 0; i < count; ++i) {
createCard(sourceCard, dbName, CardRelationType::DoesNotAttach, persistent);
}
- } else if (cardRelation->getDefaultCount() > 1) {
- for (int i = 0; i < cardRelation->getDefaultCount(); ++i) {
- createCard(sourceCard, dbName, CardRelationType::DoesNotAttach, persistent);
- }
- } else {
- auto attachType = cardRelation->getAttachType();
-
- // 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) {
- playCardToTable(sourceCard, false);
- }
-
- createCard(sourceCard, dbName, attachType, persistent);
+ return true;
}
+
+ CardRelationType attachType;
+ // do not attempt to attach to another player's cards, this causes the card to attempt to attach to the same
+ // cardid on the local player's field instead, which is an entirely different card!
+ if (player->getPlayerInfo()->getLocalOrJudge()) {
+ attachType = cardRelation->getAttachType();
+ } else {
+ attachType = CardRelationType::DoesNotAttach;
+ }
+
+ // 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) {
+ playCardToTable(sourceCard, false);
+ }
+
+ createCard(sourceCard, dbName, attachType, persistent);
return true;
}
+void PlayerActions::onRelatedCardCreated(const CardItem *sourceCard, const CardRelation *cardRelation)
+{
+ if (sourceCard == nullptr || cardRelation == nullptr) {
+ return;
+ }
+
+ /*
+ * If we make a token via "Token: TokenName"
+ * then let's allow it to be created via "create another token"
+ */
+ if (!cardRelation->getCanCreateAnother()) {
+ return;
+ }
+
+ ExactCard relatedCard =
+ CardDatabaseManager::query()->getCardFromSameSet(cardRelation->getName(), sourceCard->getCard().getPrinting());
+
+ setLastToken(relatedCard.getCardPtr());
+}
+
void PlayerActions::createCard(const CardItem *sourceCard,
const QString &dbCardName,
CardRelationType attachType,
@@ -1149,95 +1210,29 @@ void PlayerActions::actSayMessage()
sendGameCommand(cmd);
}
-void PlayerActions::setCardAttrHelper(const GameEventContext &context,
- CardItem *card,
- CardAttribute attribute,
- const QString &avalue,
- bool allCards,
- EventProcessingOptions options)
-{
- if (card == nullptr) {
- return;
- }
-
- bool moveCardContext = context.HasExtension(Context_MoveCard::ext);
- switch (attribute) {
- case AttrTapped: {
- bool tapped = avalue == "1";
- if (!(!tapped && card->getDoesntUntap() && allCards)) {
- if (!allCards) {
- emit logSetTapped(player, card, tapped);
- }
- bool canAnimate = !options.testFlag(SKIP_TAP_ANIMATION) && !moveCardContext;
- card->setTapped(tapped, canAnimate);
- }
- break;
- }
- case AttrAttacking: {
- card->setAttacking(avalue == "1");
- break;
- }
- case AttrFaceDown: {
- card->setFaceDown(avalue == "1");
- break;
- }
- case AttrColor: {
- card->setColor(avalue);
- break;
- }
- case AttrAnnotation: {
- emit logSetAnnotation(player, card, avalue);
- card->setAnnotation(avalue);
- break;
- }
- case AttrDoesntUntap: {
- bool value = (avalue == "1");
- emit logSetDoesntUntap(player, card, value);
- card->setDoesntUntap(value);
- break;
- }
- case AttrPT: {
- emit logSetPT(player, card, avalue);
- card->setPT(avalue);
- break;
- }
- }
-}
-
-void PlayerActions::actMoveCardXCardsFromTop()
+void PlayerActions::actRequestMoveCardXCardsFromTopDialog()
{
int deckSize = player->getDeckZone()->getCards().size() + 1; // add the card to move to the deck
- bool ok;
- int number =
- QInputDialog::getInt(player->getGame()->getTab(), tr("Place card X cards from top of library"),
- tr("Which position should this card be placed:") + "\n" + tr("(max. %1)").arg(deckSize),
- defaultNumberTopCardsToPlaceBelow, 1, deckSize, 1, &ok);
- number -= 1; // indexes start at 0
- if (!ok) {
- return;
- }
+ emit requestMoveCardXCardsFromTopDialog(defaultNumberTopCardsToPlaceBelow, deckSize);
+}
+void PlayerActions::actMoveCardXCardsFromTop(QList selectedCards, int number)
+{
defaultNumberTopCardsToPlaceBelow = number;
- QList sel = player->getGameScene()->selectedItems();
- if (sel.isEmpty()) {
+ if (selectedCards.isEmpty()) {
return;
}
- QList cardList;
- while (!sel.isEmpty()) {
- cardList.append(qgraphicsitem_cast(sel.takeFirst()));
- }
-
QList commandList;
ListOfCardsToMove idList;
- for (const auto &i : cardList) {
+ for (const auto &i : selectedCards) {
idList.add_card()->set_card_id(i->getId());
}
- int startPlayerId = cardList[0]->getZone()->getPlayer()->getPlayerInfo()->getId();
- QString startZone = cardList[0]->getZone()->getName();
+ int startPlayerId = selectedCards[0]->getZone()->getPlayer()->getPlayerInfo()->getId();
+ QString startZone = selectedCards[0]->getZone()->getName();
auto *cmd = new Command_MoveCard;
cmd->set_start_player_id(startPlayerId);
@@ -1256,15 +1251,14 @@ void PlayerActions::actMoveCardXCardsFromTop()
}
}
-void PlayerActions::actIncPT(int deltaP, int deltaT)
+void PlayerActions::actIncPT(QList selectedCards, int deltaP, int deltaT)
{
int playerid = player->getPlayerInfo()->getId();
QList commandList;
- for (const auto &item : player->getGameScene()->selectedItems()) {
- auto *card = static_cast(item);
+ for (auto card : selectedCards) {
QString pt = card->getPT();
- const auto ptList = parsePT(pt);
+ const auto ptList = CardItem::parsePT(pt);
QString newpt;
if (ptList.isEmpty()) {
newpt = QString::number(deltaP) + (deltaT ? "/" + QString::number(deltaT) : "");
@@ -1290,12 +1284,11 @@ void PlayerActions::actIncPT(int deltaP, int deltaT)
player->getGame()->getGameEventHandler()->sendGameCommand(prepareGameCommand(commandList), playerid);
}
-void PlayerActions::actResetPT()
+void PlayerActions::actResetPT(QList selectedCards)
{
int playerid = player->getPlayerInfo()->getId();
QList commandList;
- for (const auto &item : player->getGameScene()->selectedItems()) {
- auto *card = static_cast(item);
+ for (auto card : selectedCards) {
QString ptString;
if (!card->getFaceDown()) { // leave the pt empty if the card is face down
ExactCard ec = card->getCard();
@@ -1324,68 +1317,32 @@ void PlayerActions::actResetPT()
}
}
-QVariantList PlayerActions::parsePT(const QString &pt)
-{
- QVariantList ptList = QVariantList();
- if (!pt.isEmpty()) {
- int sep = pt.indexOf('/');
- if (sep == 0) {
- ptList.append(QVariant(pt.mid(1))); // cut off starting '/' and take full string
- } else {
- int start = 0;
- for (;;) {
- QString item = pt.mid(start, sep - start);
- if (item.isEmpty()) {
- ptList.append(QVariant(QString()));
- } else if (item[0] == '+') {
- ptList.append(QVariant(item.mid(1).toInt())); // add as int
- } else if (item[0] == '-') {
- ptList.append(QVariant(item.toInt())); // add as int
- } else {
- ptList.append(QVariant(item)); // add as qstring
- }
- if (sep == -1) {
- break;
- }
- start = sep + 1;
- sep = pt.indexOf('/', start);
- }
- }
- }
- return ptList;
-}
-
-void PlayerActions::actSetPT()
+void PlayerActions::actRequestSetPTDialog(QList selectedCards)
{
QString oldPT;
- int playerid = player->getPlayerInfo()->getId();
- auto sel = player->getGameScene()->selectedItems();
- for (const auto &item : sel) {
- auto *card = static_cast(item);
+ for (auto card : selectedCards) {
if (!card->getPT().isEmpty()) {
oldPT = card->getPT();
}
}
- bool ok;
- player->setDialogSemaphore(true);
- QString pt = getTextWithMax(player->getGame()->getTab(), tr("Change power/toughness"), tr("Change stats to:"),
- QLineEdit::Normal, oldPT, &ok);
- player->setDialogSemaphore(false);
- if (player->clearCardsToDelete() || !ok) {
- return;
- }
- const auto ptList = parsePT(pt);
+ emit requestSetPTDialog(oldPT);
+}
+
+void PlayerActions::actSetPT(QList selectedCards, const QString &pt)
+{
+ int playerid = player->getPlayerInfo()->getId();
+
+ const auto ptList = CardItem::parsePT(pt);
bool empty = ptList.isEmpty();
QList commandList;
- for (const auto &item : sel) {
- auto *card = static_cast(item);
+ for (auto card : selectedCards) {
auto *cmd = new Command_SetCardAttr;
QString newpt = QString();
if (!empty) {
- const auto oldpt = parsePT(card->getPT());
+ const auto oldpt = CardItem::parsePT(card->getPT());
int ptIter = 0;
for (const auto &_item : ptList) {
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
@@ -1425,44 +1382,69 @@ void PlayerActions::actDrawArrow()
}
}
-void PlayerActions::actIncP()
+void PlayerActions::actIncP(QList selectedCards)
{
- actIncPT(1, 0);
+ actIncPT(selectedCards, 1, 0);
}
-void PlayerActions::actDecP()
+void PlayerActions::actDecP(QList selectedCards)
{
- actIncPT(-1, 0);
+ actIncPT(selectedCards, -1, 0);
}
-void PlayerActions::actIncT()
+void PlayerActions::actIncT(QList selectedCards)
{
- actIncPT(0, 1);
+ actIncPT(selectedCards, 0, 1);
}
-void PlayerActions::actDecT()
+void PlayerActions::actDecT(QList selectedCards)
{
- actIncPT(0, -1);
+ actIncPT(selectedCards, 0, -1);
}
-void PlayerActions::actIncPT()
+void PlayerActions::actIncPT(QList selectedCards)
{
- actIncPT(1, 1);
+ actIncPT(selectedCards, 1, 1);
}
-void PlayerActions::actDecPT()
+void PlayerActions::actDecPT(QList selectedCards)
{
- actIncPT(-1, -1);
+ actIncPT(selectedCards, -1, -1);
}
-void PlayerActions::actFlowP()
+void PlayerActions::actFlowP(QList selectedCards)
{
- actIncPT(1, -1);
+ actIncPT(selectedCards, 1, -1);
}
-void PlayerActions::actFlowT()
+void PlayerActions::actFlowT(QList selectedCards)
{
- actIncPT(-1, 1);
+ actIncPT(selectedCards, -1, 1);
+}
+
+void PlayerActions::actReduceLifeByPower(QList selectedCards)
+{
+ // find life counter
+ auto lifeCounter = player->getLifeCounter();
+ if (!lifeCounter) {
+ return;
+ }
+
+ // calculate total power;
+ int total = 0;
+ for (auto card : selectedCards) {
+ QVariantList parsed = CardItem::parsePT(card->getPT());
+ if (!parsed.isEmpty()) {
+ int power = parsed.first().toInt(); // toInt will default to 0 if it's not an int
+ total += qMax(power, 0);
+ }
+ }
+
+ // send cmd
+ Command_IncCounter cmd;
+ cmd.set_counter_id(lifeCounter->getId());
+ cmd.set_delta(-total);
+ sendGameCommand(prepareGameCommand(cmd));
}
void AnnotationDialog::keyPressEvent(QKeyEvent *event)
@@ -1475,33 +1457,22 @@ void AnnotationDialog::keyPressEvent(QKeyEvent *event)
QInputDialog::keyPressEvent(event);
}
-void PlayerActions::actSetAnnotation()
+void PlayerActions::actRequestSetAnnotationDialog(QList selectedCards)
{
QString oldAnnotation;
- auto sel = player->getGameScene()->selectedItems();
- for (const auto &item : sel) {
- auto *card = static_cast(item);
+ for (auto card : selectedCards) {
if (!card->getAnnotation().isEmpty()) {
oldAnnotation = card->getAnnotation();
}
}
- player->setDialogSemaphore(true);
- AnnotationDialog *dialog = new AnnotationDialog(player->getGame()->getTab());
- dialog->setOptions(QInputDialog::UsePlainTextEditForTextInput);
- dialog->setWindowTitle(tr("Set annotation"));
- dialog->setLabelText(tr("Please enter the new annotation:"));
- dialog->setTextValue(oldAnnotation);
- bool ok = dialog->exec();
- player->setDialogSemaphore(false);
- if (player->clearCardsToDelete() || !ok) {
- return;
- }
- QString annotation = dialog->textValue().left(MAX_NAME_LENGTH);
+ emit requestSetAnnotationDialog(oldAnnotation);
+}
+void PlayerActions::actSetAnnotation(QList selectedCards, const QString &annotation)
+{
QList commandList;
- for (const auto &item : sel) {
- auto *card = static_cast(item);
+ for (auto card : selectedCards) {
auto *cmd = new Command_SetCardAttr;
cmd->set_zone(card->getZone()->getName().toStdString());
cmd->set_card_id(card->getId());
@@ -1522,12 +1493,10 @@ void PlayerActions::actAttach()
card->drawAttachArrow();
}
-void PlayerActions::actUnattach()
+void PlayerActions::actUnattach(QList selectedCards)
{
QList commandList;
- for (QGraphicsItem *item : player->getGameScene()->selectedItems()) {
- auto *card = static_cast(item);
-
+ for (auto card : selectedCards) {
if (!card->getAttachedTo()) {
continue;
}
@@ -1540,83 +1509,107 @@ void PlayerActions::actUnattach()
sendGameCommand(prepareGameCommand(commandList));
}
-void PlayerActions::actCardCounterTrigger()
+void PlayerActions::actAddCardCounter(QList selectedCards, int counterId)
+{
+ offsetCardCounter(selectedCards, counterId, 1);
+}
+
+void PlayerActions::actRemoveCardCounter(QList selectedCards, int counterId)
+{
+ offsetCardCounter(selectedCards, counterId, -1);
+}
+
+void PlayerActions::offsetCardCounter(QList selectedCards, int counterId, int offset)
{
- auto *action = static_cast(sender());
- int counterId = action->data().toInt() / 1000;
QList commandList;
- switch (action->data().toInt() % 1000) {
- case 9: { // increment counter
- for (const auto &item : player->getGameScene()->selectedItems()) {
- auto *card = static_cast(item);
- if (card->getCounters().value(counterId, 0) < MAX_COUNTERS_ON_CARD) {
- auto *cmd = new Command_SetCardCounter;
- cmd->set_zone(card->getZone()->getName().toStdString());
- cmd->set_card_id(card->getId());
- cmd->set_counter_id(counterId);
- cmd->set_counter_value(card->getCounters().value(counterId, 0) + 1);
- commandList.append(cmd);
- }
- }
- break;
+ for (auto card : selectedCards) {
+ int oldValue = card->getCounters().value(counterId, 0);
+ int newValue = oldValue + offset;
+
+ // Early exit optimization: server enforces [0, MAX_COUNTERS_ON_CARD].
+ // Compare clamped value to allow recovery from invalid states.
+ int clampedValue = qBound(0, newValue, MAX_COUNTERS_ON_CARD);
+ if (clampedValue != oldValue) {
+ auto *cmd = new Command_SetCardCounter;
+ cmd->set_zone(card->getZone()->getName().toStdString());
+ cmd->set_card_id(card->getId());
+ cmd->set_counter_id(counterId);
+ cmd->set_counter_value(newValue);
+ commandList.append(cmd);
}
- case 10: { // decrement counter
- for (const auto &item : player->getGameScene()->selectedItems()) {
- auto *card = static_cast(item);
- if (card->getCounters().value(counterId, 0)) {
- auto *cmd = new Command_SetCardCounter;
- cmd->set_zone(card->getZone()->getName().toStdString());
- cmd->set_card_id(card->getId());
- cmd->set_counter_id(counterId);
- cmd->set_counter_value(card->getCounters().value(counterId, 0) - 1);
- commandList.append(cmd);
- }
- }
- break;
- }
- case 11: { // set counter with dialog
- player->setDialogSemaphore(true);
-
- // If a single card is selected, we show the old value in the dialog. Otherwise, we show "x"
- QList sel = player->getGameScene()->selectedItems();
- QString oldValueForDlg = "x";
- if (sel.size() == 1) {
- auto *card = dynamic_cast(sel.first());
- oldValueForDlg = QString::number(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();
-
- player->setDialogSemaphore(false);
- if (player->clearCardsToDelete() || !ok) {
- return;
- }
-
- for (const auto &item : sel) {
- auto *card = dynamic_cast(item);
-
- int oldValue = card->getCounters().value(counterId, 0);
- Expression exp(oldValue);
- int number = static_cast(exp.parse(dialog.textValue()));
-
- auto *cmd = new Command_SetCardCounter;
- cmd->set_zone(card->getZone()->getName().toStdString());
- cmd->set_card_id(card->getId());
- cmd->set_counter_id(counterId);
- cmd->set_counter_value(number);
- commandList.append(cmd);
- }
- break;
- }
- default:;
}
+
sendGameCommand(prepareGameCommand(commandList));
}
+void PlayerActions::actRequestSetCardCounterDialog(QList selectedCards, int counterId)
+{
+ // If a single card is selected, we show the old value in the dialog. Otherwise, we show "x"
+ QString oldValueForDlg = "x";
+ if (selectedCards.size() == 1) {
+ auto *card = selectedCards.first();
+ oldValueForDlg = QString::number(card->getCounters().value(counterId, 0));
+ }
+
+ emit requestSetCardCounterDialog(counterId, oldValueForDlg);
+}
+
+void PlayerActions::actSetCardCounter(QList selectedCards, int counterId, const QString &counterValue)
+{
+ QList commandList;
+ for (auto card : selectedCards) {
+ int oldValue = card->getCounters().value(counterId, 0);
+ Expression exp(oldValue);
+ double parsed = exp.parse(counterValue);
+ // Clamp in double precision first to avoid UB, then cast
+ int number = static_cast(qBound(0.0, parsed, static_cast(MAX_COUNTERS_ON_CARD)));
+
+ auto *cmd = new Command_SetCardCounter;
+ cmd->set_zone(card->getZone()->getName().toStdString());
+ cmd->set_card_id(card->getId());
+ cmd->set_counter_id(counterId);
+ cmd->set_counter_value(number);
+ commandList.append(cmd);
+ }
+
+ sendGameCommand(prepareGameCommand(commandList));
+}
+
+void PlayerActions::actIncrementAllCardCounters(QList cardsToUpdate)
+{
+ if (cardsToUpdate.isEmpty()) {
+ // If no cards selected, update all cards on table
+ cardsToUpdate = static_cast>(player->getTableZone()->getCards());
+ }
+
+ QList commandList;
+
+ for (const auto *card : cardsToUpdate) {
+ const auto &cardCounters = card->getCounters();
+
+ QMapIterator counterIterator(cardCounters);
+ while (counterIterator.hasNext()) {
+ counterIterator.next();
+ int counterId = counterIterator.key();
+ int currentValue = counterIterator.value();
+ if (currentValue >= MAX_COUNTERS_ON_CARD) {
+ continue;
+ }
+
+ auto cmd = std::make_unique();
+ cmd->set_zone(card->getZone()->getName().toStdString());
+ cmd->set_card_id(card->getId());
+ cmd->set_counter_id(counterId);
+ cmd->set_counter_value(currentValue + 1);
+ commandList.append(cmd.release());
+ }
+ }
+
+ if (!commandList.isEmpty()) {
+ sendGameCommand(prepareGameCommand(commandList));
+ }
+}
+
/**
* @brief returns true if the zone is a unwritable reveal zone view (eg a card reveal window). Will return false if zone
* is nullptr.
@@ -1629,14 +1622,8 @@ static bool isUnwritableRevealZone(CardZoneLogic *zone)
return false;
}
-void PlayerActions::playSelectedCards(const bool faceDown)
+void PlayerActions::playSelectedCards(QList selectedCards, const bool faceDown)
{
- QList selectedCards;
- for (const auto &item : player->getGameScene()->selectedItems()) {
- auto *card = static_cast(item);
- selectedCards.append(card);
- }
-
// CardIds will get shuffled downwards when cards leave the deck.
// We need to iterate through the cards in reverse order so cardIds don't get changed out from under us as we play
// out the cards one-by-one.
@@ -1650,19 +1637,19 @@ void PlayerActions::playSelectedCards(const bool faceDown)
}
}
-void PlayerActions::actPlay()
+void PlayerActions::actPlay(QList selectedCards)
{
- playSelectedCards(false);
+ playSelectedCards(selectedCards, false);
}
-void PlayerActions::actPlayFacedown()
+void PlayerActions::actPlayFacedown(QList selectedCards)
{
- playSelectedCards(true);
+ playSelectedCards(selectedCards, true);
}
-void PlayerActions::actHide()
+void PlayerActions::actHide(QList selectedCards)
{
- for (const auto &item : player->getGameScene()->selectedItems()) {
+ for (const auto &item : selectedCards) {
auto *card = static_cast(item);
if (card && isUnwritableRevealZone(card->getZone())) {
card->getZone()->removeCard(card);
@@ -1670,7 +1657,7 @@ void PlayerActions::actHide()
}
}
-void PlayerActions::actReveal(QAction *action)
+void PlayerActions::actReveal(QList selectedCards, QAction *action)
{
const int otherPlayerId = action->data().toInt();
@@ -1679,9 +1666,7 @@ void PlayerActions::actReveal(QAction *action)
cmd.set_player_id(otherPlayerId);
}
- QList sel = player->getGameScene()->selectedItems();
- while (!sel.isEmpty()) {
- const auto *card = qgraphicsitem_cast(sel.takeFirst());
+ for (auto card : selectedCards) {
if (!cmd.has_zone_name()) {
cmd.set_zone_name(card->getZone()->getName().toStdString());
}
@@ -1763,19 +1748,14 @@ void PlayerActions::actRevealRandomGraveyardCard(int revealToPlayerId)
sendGameCommand(cmd);
}
-void PlayerActions::cardMenuAction()
+void PlayerActions::cardMenuAction(QList selectedCards, CardMenuActionType type)
{
- auto *a = dynamic_cast(sender());
- QList sel = player->getGameScene()->selectedItems();
- QList cardList;
- while (!sel.isEmpty()) {
- cardList.append(qgraphicsitem_cast(sel.takeFirst()));
- }
+ QList cardList = selectedCards;
QList commandList;
- if (a->data().toInt() <= (int)cmClone) {
+ if (type <= cmClone) {
for (const auto &card : cardList) {
- switch (static_cast(a->data().toInt())) {
+ switch (type) {
// Leaving both for compatibility with server
case cmUntap:
// fallthrough
@@ -1843,7 +1823,7 @@ void PlayerActions::cardMenuAction()
return;
}
- Player *startPlayer = zone->getPlayer();
+ PlayerLogic *startPlayer = zone->getPlayer();
if (!startPlayer) {
return;
}
@@ -1856,7 +1836,7 @@ void PlayerActions::cardMenuAction()
idList.add_card()->set_card_id(i->getId());
}
- switch (static_cast(a->data().toInt())) {
+ switch (type) {
case cmMoveToTopLibrary: {
auto *cmd = new Command_MoveCard;
cmd->set_start_player_id(startPlayerId);
diff --git a/cockatrice/src/game/player/player_actions.h b/cockatrice/src/game/player/player_actions.h
index d4c6daacf..3f1960892 100644
--- a/cockatrice/src/game/player/player_actions.h
+++ b/cockatrice/src/game/player/player_actions.h
@@ -2,21 +2,23 @@
* @file player_actions.h
* @ingroup GameLogicActions
* @ingroup GameLogicPlayers
- * @brief TODO: Document this.
*/
+//! \todo Document this file.
#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 "../../game_graphics/board/card_item.h"
+#include "../../game_graphics/dialogs/dlg_create_token.h"
+#include "../../game_graphics/dialogs/dlg_move_top_cards_until.h"
+#include "../../game_graphics/player/card_menu_action_type.h"
#include "event_processing_options.h"
-#include "player.h"
+#include "player_logic.h"
#include
#include
#include
#include
-#include
namespace google
{
@@ -26,28 +28,21 @@ class Message;
}
} // namespace google
-class CardItem;
class Command_MoveCard;
class GameEventContext;
class PendingCommand;
-class Player;
+class PlayerLogic;
class PlayerActions : public QObject
{
Q_OBJECT
-signals:
- void logSetTapped(Player *player, CardItem *card, bool tapped);
- void logSetAnnotation(Player *player, CardItem *card, QString newAnnotation);
- void logSetDoesntUntap(Player *player, CardItem *card, bool doesntUntap);
- void logSetPT(Player *player, CardItem *card, QString newPT);
-
public:
enum CardsToReveal
{
RANDOM_CARD_FROM_ZONE = -2
};
- explicit PlayerActions(Player *player);
+ explicit PlayerActions(PlayerLogic *player);
void sendGameCommand(PendingCommand *pend);
void sendGameCommand(const google::protobuf::Message &command);
@@ -55,13 +50,6 @@ public:
PendingCommand *prepareGameCommand(const ::google::protobuf::Message &cmd);
PendingCommand *prepareGameCommand(const QList &cmdList);
- void setCardAttrHelper(const GameEventContext &context,
- CardItem *card,
- CardAttribute attribute,
- const QString &avalue,
- bool allCards,
- EventProcessingOptions options);
-
void moveOneCardUntil(CardItem *card);
void stopMoveTopCardsUntil();
@@ -70,29 +58,75 @@ public:
return movingCardsUntil;
}
+signals:
+ void requestViewTopCardsDialog(int defaultNumberTopCards, int deckSize);
+ void requestViewBottomCardsDialog(int defaultNumberBottomCards, int deckSize);
+ void requestShuffleTopDialog(int defaultNumberTopCards, int maxCards);
+ void requestShuffleBottomDialog(int defaultNumberBottomCards, int maxCards);
+ void requestMulliganDialog(int startSize, int handSize, int deckSize);
+ void requestDrawCardsDialog(int defaultNumberTopCards, int deckSize);
+ void requestMoveTopCardsToDialog(int defaultNumberTopCards,
+ int maxCards,
+ const QString &targetZone,
+ const QString &zoneDisplayName,
+ bool faceDown);
+ void requestMoveTopCardsUntilDialog(MoveTopCardsUntilOptions options);
+ void requestMoveBottomCardsToDialog(int defaultNumberBottomCards,
+ int maxCards,
+ const QString &targetZone,
+ const QString &zoneDisplayName,
+ bool faceDown);
+ void requestDrawBottomCardsDialog(int defaultNumberBottomCards, int maxCards);
+ void requestRollDieDialog();
+ void requestCreateTokenDialog(const QStringList &predefinedTokens);
+ void requestCreateRelatedFromRelationDialog(const CardItem *sourceCard, const CardRelation *cardRelation);
+ void requestMoveCardXCardsFromTopDialog(int defaultNumberTopCardsToPlaceBelow, int deckSize);
+ void requestSetPTDialog(const QString &oldPT);
+ void requestSetAnnotationDialog(const QString &oldAnnotation);
+ void requestSetCardCounterDialog(int counterId, const QString &oldValueForDlg);
+ void requestZoneViewToggle(const QString &zoneName, int numberCards, bool isReversed = false);
+ void requestSortHand(const QList &options);
+ void requestEnableAndSetCreateAnotherTokenAction(const QString &lastTokenName);
+ void requestSetLastToken(CardInfoPtr lastToken);
+
public slots:
void setLastToken(CardInfoPtr cardInfo);
+ void setLastTokenInfo(CardInfoPtr cardInfo);
void playCard(CardItem *c, bool faceDown);
void playCardToTable(const CardItem *c, bool faceDown);
void actUntapAll();
- void actRollDie();
- void actCreateToken();
+ void actRequestRollDieDialog();
+ void actRollDie(int sides, int count);
+ void actFlipCoin();
+ void actRequestCreateTokenDialog(const QStringList &predefinedTokens);
+ void actCreateToken(TokenInfo tokenToCreate);
void actCreateAnotherToken();
+ void actRequestCreateRelatedFromRelationDialog(const CardItem *sourceCard, const CardRelation *cardRelation);
+ bool createRelatedFromRelation(const CardItem *sourceCard, const CardRelation *cardRelation, int variableCount);
+ void onRelatedCardCreated(const CardItem *sourceCard, const CardRelation *cardRelation);
+ void setLastRelatedCreationSucceeded(bool succeeded)
+ {
+ lastRelatedCreationSucceeded = succeeded;
+ }
void actShuffle();
- void actShuffleTop();
- void actShuffleBottom();
+ void actRequestShuffleTopDialog();
+ void actShuffleTop(int number);
+ void actRequestShuffleBottomDialog();
+ void actShuffleBottom(int number);
void actDrawCard();
- void actDrawCards();
+ void actRequestDrawCardsDialog();
+ void actDrawCards(int number);
void actUndoDraw();
- void actMulligan();
+ void actRequestMulliganDialog();
+ void actMulligan(int number);
void actMulliganSameSize();
void actMulliganMinusOne();
void doMulligan(int number);
- void actPlay();
- void actPlayFacedown();
- void actHide();
+ void actPlay(QList selectedCards);
+ void actPlayFacedown(QList selectedCards);
+ void actHide(QList selectedCards);
void actMoveTopCardToPlay();
void actMoveTopCardToPlayFaceDown();
@@ -102,10 +136,14 @@ public slots:
void actMoveTopCardsToGraveFaceDown();
void actMoveTopCardsToExile();
void actMoveTopCardsToExileFaceDown();
- void actMoveTopCardsUntil();
+ void actRequestMoveTopCardsUntilDialog();
+ void moveTopCardsUntil(const QString &expr, MoveTopCardsUntilOptions options);
void actMoveTopCardToBottom();
+ void actRequestMoveTopCardsToDialog(const QString &targetZone, const QString &zoneDisplayName, bool faceDown);
+ void moveTopCardsTo(int number, const QString &targetZone, bool faceDown);
void actDrawBottomCard();
- void actDrawBottomCards();
+ void actRequestDrawBottomCardsDialog();
+ void actDrawBottomCards(int number);
void actMoveBottomCardToPlay();
void actMoveBottomCardToPlayFaceDown();
void actMoveBottomCardToGrave();
@@ -115,6 +153,8 @@ public slots:
void actMoveBottomCardsToExile();
void actMoveBottomCardsToExileFaceDown();
void actMoveBottomCardToTop();
+ void actRequestMoveBottomCardsToDialog(const QString &targetZone, const QString &zoneDisplayName, bool faceDown);
+ void moveBottomCardsTo(int number, const QString &targetZone, bool faceDown);
void actSelectAll();
void actSelectRow();
@@ -122,10 +162,12 @@ public slots:
void actViewLibrary();
void actViewHand();
- void actViewTopCards();
- void actViewBottomCards();
- void actAlwaysRevealTopCard();
- void actAlwaysLookAtTopCard();
+ void actRequestViewTopCardsDialog();
+ void actViewTopCards(int number);
+ void actRequestViewBottomCardsDialog();
+ void actViewBottomCards(int number);
+ void actAlwaysRevealTopCard(bool alwaysRevealTopCard);
+ void actAlwaysLookAtTopCard(bool alwaysRevealTopCard);
void actViewGraveyard();
void actLendLibrary(int lendToPlayerId);
void actRevealTopCards(int revealToPlayerId, int amount);
@@ -140,34 +182,44 @@ public slots:
void actCreateRelatedCard();
void actCreateAllRelatedCards();
- void actMoveCardXCardsFromTop();
- void actCardCounterTrigger();
+ void actRequestMoveCardXCardsFromTopDialog();
+ void actMoveCardXCardsFromTop(QList selectedCards, int number);
+ void actRemoveCardCounter(QList selectedCards, int counterId);
+ void actAddCardCounter(QList selectedCards, int counterId);
+ void actRequestSetCardCounterDialog(QList selectedCards, int counterId);
+ void actSetCardCounter(QList selectedCards, int counterId, const QString &counterValue);
+ void actIncrementAllCardCounters(QList cardsToUpdate);
void actAttach();
- void actUnattach();
+ void actUnattach(QList selectedCards);
void actDrawArrow();
- void actIncPT(int deltaP, int deltaT);
- void actResetPT();
- void actSetPT();
- void actIncP();
- void actDecP();
- void actIncT();
- void actDecT();
- void actIncPT();
- void actDecPT();
- void actFlowP();
- void actFlowT();
- void actSetAnnotation();
- void actReveal(QAction *action);
+ void actIncPT(QList selectedCards, int deltaP, int deltaT);
+ void actResetPT(QList selectedCards);
+ void actRequestSetPTDialog(QList selectedCards);
+ void actSetPT(QList selectedCards, const QString &pt);
+ void actIncP(QList selectedCards);
+ void actDecP(QList selectedCards);
+ void actIncT(QList selectedCards);
+ void actDecT(QList selectedCards);
+ void actIncPT(QList selectedCards);
+ void actDecPT(QList selectedCards);
+ void actFlowP(QList selectedCards);
+ void actFlowT(QList selectedCards);
+
+ void actReduceLifeByPower(QList selectedCards);
+
+ void actRequestSetAnnotationDialog(QList selectedCards);
+ void actSetAnnotation(QList selectedCards, const QString &annotation);
+ void actReveal(QList selectedCards, QAction *action);
void actRevealHand(int revealToPlayerId);
void actRevealRandomHandCard(int revealToPlayerId);
void actRevealLibrary(int revealToPlayerId);
void actSortHand();
- void cardMenuAction();
+ void cardMenuAction(QList selectedCards, CardMenuActionType type);
private:
- Player *player;
+ PlayerLogic *player;
int defaultNumberTopCards = 1;
int defaultNumberTopCardsToPlaceBelow = 1;
@@ -183,21 +235,19 @@ private:
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);
+ bool lastRelatedCreationSucceeded = false;
void createCard(const CardItem *sourceCard,
const QString &dbCardName,
CardRelationType attach = CardRelationType::DoesNotAttach,
bool persistent = false);
- bool createRelatedFromRelation(const CardItem *sourceCard, const CardRelation *cardRelation);
- void playSelectedCards(bool faceDown = false);
+ void playSelectedCards(QList selectedCards, bool faceDown = false);
void cmdSetTopCard(Command_MoveCard &cmd);
void cmdSetBottomCard(Command_MoveCard &cmd);
- QVariantList parsePT(const QString &pt);
+ void offsetCardCounter(QList selectedCards, int counterId, int offset);
};
#endif // COCKATRICE_PLAYER_ACTIONS_H
diff --git a/cockatrice/src/game/player/player_event_handler.cpp b/cockatrice/src/game/player/player_event_handler.cpp
index 6fb1ff19a..bc48298f7 100644
--- a/cockatrice/src/game/player/player_event_handler.cpp
+++ b/cockatrice/src/game/player/player_event_handler.cpp
@@ -1,12 +1,13 @@
#include "player_event_handler.h"
+#include "../../game_graphics/board/arrow_item.h"
+#include "../../game_graphics/board/card_item.h"
+#include "../../game_graphics/zones/view_zone.h"
#include "../../interface/widgets/tabs/tab_game.h"
-#include "../board/arrow_item.h"
-#include "../board/card_item.h"
+#include "../board/arrow_data.h"
#include "../board/card_list.h"
-#include "../zones/view_zone.h"
-#include "player.h"
#include "player_actions.h"
+#include "player_logic.h"
#include
#include
@@ -22,6 +23,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -30,10 +32,12 @@
#include
#include
#include